Kaynağa Gözat

Merge pull request #10410 from growilabs/support/156162-172750-app-pages-dir-biome

support: Configure biome for app pages dir
mergify[bot] 5 ay önce
ebeveyn
işleme
d61b637c6e
45 değiştirilmiş dosya ile 2381 ekleme ve 1181 silme
  1. 3 6
      apps/app/.eslintrc.js
  2. 396 181
      apps/app/src/pages/[[...path]].page.tsx
  3. 25 14
      apps/app/src/pages/_app.page.tsx
  4. 47 27
      apps/app/src/pages/_document.page.tsx
  5. 2 4
      apps/app/src/pages/_error.page.tsx
  6. 86 33
      apps/app/src/pages/_private-legacy-pages.page.tsx
  7. 92 46
      apps/app/src/pages/_search.page.tsx
  8. 21 7
      apps/app/src/pages/admin/[...path].page.tsx
  9. 39 14
      apps/app/src/pages/admin/ai-integration.page.tsx
  10. 26 13
      apps/app/src/pages/admin/app.page.tsx
  11. 42 18
      apps/app/src/pages/admin/audit-log.page.tsx
  12. 42 19
      apps/app/src/pages/admin/customize.page.tsx
  13. 35 15
      apps/app/src/pages/admin/data-transfer.page.tsx
  14. 25 12
      apps/app/src/pages/admin/export.page.tsx
  15. 46 26
      apps/app/src/pages/admin/global-notification/[globalNotificationId].page.tsx
  16. 26 13
      apps/app/src/pages/admin/global-notification/new.page.tsx
  17. 25 14
      apps/app/src/pages/admin/importer.page.tsx
  18. 41 22
      apps/app/src/pages/admin/index.page.tsx
  19. 26 13
      apps/app/src/pages/admin/markdown.page.tsx
  20. 25 14
      apps/app/src/pages/admin/notification.page.tsx
  21. 26 14
      apps/app/src/pages/admin/plugins.page.tsx
  22. 35 14
      apps/app/src/pages/admin/search.page.tsx
  23. 58 24
      apps/app/src/pages/admin/security.page.tsx
  24. 30 14
      apps/app/src/pages/admin/slack-integration-legacy.page.tsx
  25. 33 15
      apps/app/src/pages/admin/slack-integration.page.tsx
  26. 41 16
      apps/app/src/pages/admin/user-group-detail/[userGroupId].page.tsx
  27. 34 14
      apps/app/src/pages/admin/user-groups.page.tsx
  28. 27 14
      apps/app/src/pages/admin/users/external-accounts.page.tsx
  29. 34 16
      apps/app/src/pages/admin/users/index.page.tsx
  30. 53 20
      apps/app/src/pages/forgot-password-errors.page.tsx
  31. 36 11
      apps/app/src/pages/forgot-password.page.tsx
  32. 74 24
      apps/app/src/pages/installer.page.tsx
  33. 48 19
      apps/app/src/pages/invited.page.tsx
  34. 72 59
      apps/app/src/pages/login/error/[message].page.tsx
  35. 88 52
      apps/app/src/pages/login/index.page.tsx
  36. 29 10
      apps/app/src/pages/maintenance.page.tsx
  37. 116 61
      apps/app/src/pages/me/[[...path]].page.tsx
  38. 44 15
      apps/app/src/pages/reset-password.page.tsx
  39. 146 76
      apps/app/src/pages/share/[[...path]].page.tsx
  40. 76 61
      apps/app/src/pages/tags.page.tsx
  41. 67 38
      apps/app/src/pages/trash.page.tsx
  42. 42 20
      apps/app/src/pages/user-activation.page.tsx
  43. 98 55
      apps/app/src/pages/utils/commons.ts
  44. 3 3
      apps/app/src/pages/utils/objectid-transformer.ts
  45. 1 5
      biome.json

+ 3 - 6
apps/app/.eslintrc.js

@@ -2,12 +2,8 @@
  * @type {import('eslint').Linter.Config}
  */
 module.exports = {
-  extends: [
-    'next/core-web-vitals',
-    'weseek/react',
-  ],
-  plugins: [
-  ],
+  extends: ['next/core-web-vitals', 'weseek/react'],
+  plugins: [],
   ignorePatterns: [
     'dist/**',
     '**/dist/**',
@@ -51,6 +47,7 @@ module.exports = {
     'src/utils/**',
     'src/components/**',
     'src/services/**',
+    'src/pages/**',
   ],
   settings: {
     // resolve path aliases by eslint-import-resolver-typescript

+ 396 - 181
apps/app/src/pages/[[...path]].page.tsx

@@ -1,116 +1,220 @@
-import type { ReactNode, JSX } from 'react';
-import React, { useEffect } from 'react';
-
-import EventEmitter from 'events';
-
-import { isIPageInfo } from '@growi/core';
+import type React from 'react';
+import type { JSX, ReactNode } from 'react';
+import { useEffect } from 'react';
+import type { GetServerSideProps, GetServerSidePropsContext } from 'next';
+import dynamic from 'next/dynamic';
+import Head from 'next/head';
+import { useRouter } from 'next/router';
 import type {
-  IDataWithMeta, IPageInfo, IPagePopulatedToShowRevision,
+  IDataWithMeta,
+  IPageInfo,
+  IPagePopulatedToShowRevision,
 } from '@growi/core';
-import {
-  isClient, pagePathUtils, pathUtils,
-} from '@growi/core/dist/utils';
+import { isIPageInfo } from '@growi/core';
+import { isClient, pagePathUtils, pathUtils } from '@growi/core/dist/utils';
+import EventEmitter from 'events';
 import ExtensibleCustomError from 'extensible-custom-error';
-import type {
-  GetServerSideProps, GetServerSidePropsContext,
-} from 'next';
 import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
-import dynamic from 'next/dynamic';
-import Head from 'next/head';
-import { useRouter } from 'next/router';
 import superjson from 'superjson';
 
 import { BasicLayout } from '~/components/Layout/BasicLayout';
 import { PageView } from '~/components/PageView/PageView';
 import { DrawioViewerScript } from '~/components/Script/DrawioViewerScript';
-import { SupportedAction, type SupportedActionType } from '~/interfaces/activity';
+import {
+  SupportedAction,
+  type SupportedActionType,
+} from '~/interfaces/activity';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
 import { RegistrationMode } from '~/interfaces/registration-mode';
 import type { RendererConfig } from '~/interfaces/services/renderer';
 import type { ISidebarConfig } from '~/interfaces/sidebar-config';
 import type { CurrentPageYjsData } from '~/interfaces/yjs';
-import type { PageModel, PageDocument } from '~/server/models/page';
+import type { PageDocument, PageModel } from '~/server/models/page';
 import type { PageRedirectModel } from '~/server/models/page-redirect';
 import { useEditorModeClassName } from '~/services/layout/use-editor-mode-class-name';
+import { useEditingMarkdown } from '~/stores/editor';
 import {
+  useCurrentPageId,
+  useIsLatestRevision,
+  useIsNotFound,
+  useSWRMUTxCurrentPage,
+  useSWRxCurrentPage,
+  useTemplateBodyData,
+  useTemplateTagData,
+} from '~/stores/page';
+import { useRedirectFrom } from '~/stores/page-redirect';
+import { useRemoteRevisionId } from '~/stores/remote-latest-page';
+import {
+  useSetupGlobalSocket,
+  useSetupGlobalSocketForPage,
+} from '~/stores/websocket';
+import {
+  useCurrentPageYjsData,
+  useSWRMUTxCurrentPageYjsData,
+} from '~/stores/yjs';
+import {
+  useCsrfToken,
+  useCurrentPathname,
   useCurrentUser,
-  useIsForbidden, useIsSharedUser,
-  useIsEnabledStaleNotification, useIsIdenticalPath,
-  useIsSearchServiceConfigured, useIsSearchServiceReachable, useDisableLinkSharing,
-  useDefaultIndentSize, useIsIndentSizeForced,
-  useIsAclEnabled, useIsSearchPage, useIsEnabledAttachTitleHeader,
-  useCsrfToken, useIsSearchScopeChildrenAsDefault, useIsEnabledMarp, useCurrentPathname,
-  useIsSlackConfigured, useRendererConfig, useGrowiCloudUri,
-  useIsAllReplyShown, useShowPageSideAuthors, useIsContainerFluid, useIsNotCreatable,
-  useIsUploadAllFileAllowed, useIsUploadEnabled, useIsBulkExportPagesEnabled,
+  useDefaultIndentSize,
+  useDisableLinkSharing,
   useElasticsearchMaxBodyLengthToIndex,
+  useGrowiCloudUri,
+  useIsAclEnabled,
+  useIsAiEnabled,
+  useIsAllReplyShown,
+  useIsBulkExportPagesEnabled,
+  useIsContainerFluid,
+  useIsEnabledAttachTitleHeader,
+  useIsEnabledMarp,
+  useIsEnabledStaleNotification,
+  useIsForbidden,
+  useIsGuestUser,
+  useIsIdenticalPath,
+  useIsIndentSizeForced,
   useIsLocalAccountRegistrationEnabled,
-  useIsRomUserAllowedToComment,
+  useIsNotCreatable,
   useIsPdfBulkExportEnabled,
-  useIsAiEnabled, useLimitLearnablePageCountPerAssistant, useIsUsersHomepageDeletionEnabled, useIsGuestUser,
+  useIsRomUserAllowedToComment,
+  useIsSearchPage,
+  useIsSearchScopeChildrenAsDefault,
+  useIsSearchServiceConfigured,
+  useIsSearchServiceReachable,
+  useIsSharedUser,
+  useIsSlackConfigured,
+  useIsUploadAllFileAllowed,
+  useIsUploadEnabled,
+  useIsUsersHomepageDeletionEnabled,
+  useLimitLearnablePageCountPerAssistant,
+  useRendererConfig,
+  useShowPageSideAuthors,
 } from '~/stores-universal/context';
-import { useEditingMarkdown } from '~/stores/editor';
-import {
-  useSWRxCurrentPage, useSWRMUTxCurrentPage, useCurrentPageId,
-  useIsNotFound, useIsLatestRevision, useTemplateTagData, useTemplateBodyData,
-} from '~/stores/page';
-import { useRedirectFrom } from '~/stores/page-redirect';
-import { useRemoteRevisionId } from '~/stores/remote-latest-page';
-import { useSetupGlobalSocket, useSetupGlobalSocketForPage } from '~/stores/websocket';
-import { useCurrentPageYjsData, useSWRMUTxCurrentPageYjsData } from '~/stores/yjs';
 import loggerFactory from '~/utils/logger';
 
 import type { NextPageWithLayout } from './_app.page';
 import type { CommonProps } from './utils/commons';
 import {
-  getNextI18NextConfig, getServerSideCommonProps, generateCustomTitleForPage, useInitSidebarConfig, skipSSR, addActivity,
+  addActivity,
+  generateCustomTitleForPage,
+  getNextI18NextConfig,
+  getServerSideCommonProps,
+  skipSSR,
+  useInitSidebarConfig,
 } from './utils/commons';
 
-
 declare global {
   // eslint-disable-next-line vars-on-top, no-var
   var globalEmitter: EventEmitter;
 }
 
+const GrowiContextualSubNavigationSubstance = dynamic(
+  () => import('~/client/components/Navbar/GrowiContextualSubNavigation'),
+  { ssr: false },
+);
 
-const GrowiContextualSubNavigationSubstance = dynamic(() => import('~/client/components/Navbar/GrowiContextualSubNavigation'), { ssr: false });
-
-const GrowiPluginsActivator = dynamic(() => import('~/features/growi-plugin/client/components').then(mod => mod.GrowiPluginsActivator), { ssr: false });
+const GrowiPluginsActivator = dynamic(
+  () =>
+    import('~/features/growi-plugin/client/components').then(
+      (mod) => mod.GrowiPluginsActivator,
+    ),
+  { ssr: false },
+);
 
-const DisplaySwitcher = dynamic(() => import('~/client/components/Page/DisplaySwitcher').then(mod => mod.DisplaySwitcher), { ssr: false });
-const PageStatusAlert = dynamic(() => import('~/client/components/PageStatusAlert').then(mod => mod.PageStatusAlert), { ssr: false });
+const DisplaySwitcher = dynamic(
+  () =>
+    import('~/client/components/Page/DisplaySwitcher').then(
+      (mod) => mod.DisplaySwitcher,
+    ),
+  { ssr: false },
+);
+const PageStatusAlert = dynamic(
+  () =>
+    import('~/client/components/PageStatusAlert').then(
+      (mod) => mod.PageStatusAlert,
+    ),
+  { ssr: false },
+);
 
-const UnsavedAlertDialog = dynamic(() => import('~/client/components/UnsavedAlertDialog'), { ssr: false });
+const UnsavedAlertDialog = dynamic(
+  () => import('~/client/components/UnsavedAlertDialog'),
+  { ssr: false },
+);
 const DescendantsPageListModal = dynamic(
-  () => import('~/client/components/DescendantsPageListModal').then(mod => mod.DescendantsPageListModal),
+  () =>
+    import('~/client/components/DescendantsPageListModal').then(
+      (mod) => mod.DescendantsPageListModal,
+    ),
+  { ssr: false },
+);
+const DrawioModal = dynamic(
+  () =>
+    import('~/client/components/PageEditor/DrawioModal').then(
+      (mod) => mod.DrawioModal,
+    ),
+  { ssr: false },
+);
+const HandsontableModal = dynamic(
+  () =>
+    import('~/client/components/PageEditor/HandsontableModal').then(
+      (mod) => mod.HandsontableModal,
+    ),
+  { ssr: false },
+);
+const TemplateModal = dynamic(
+  () =>
+    import('~/client/components/TemplateModal').then(
+      (mod) => mod.TemplateModal,
+    ),
+  { ssr: false },
+);
+const LinkEditModal = dynamic(
+  () =>
+    import('~/client/components/PageEditor/LinkEditModal').then(
+      (mod) => mod.LinkEditModal,
+    ),
+  { ssr: false },
+);
+const TagEditModal = dynamic(
+  () =>
+    import('~/client/components/PageTags/TagEditModal').then(
+      (mod) => mod.TagEditModal,
+    ),
+  { ssr: false },
+);
+const ConflictDiffModal = dynamic(
+  () =>
+    import('~/client/components/PageEditor/ConflictDiffModal').then(
+      (mod) => mod.ConflictDiffModal,
+    ),
   { ssr: false },
 );
-const DrawioModal = dynamic(() => import('~/client/components/PageEditor/DrawioModal').then(mod => mod.DrawioModal), { ssr: false });
-const HandsontableModal = dynamic(() => import('~/client/components/PageEditor/HandsontableModal').then(mod => mod.HandsontableModal), { ssr: false });
-const TemplateModal = dynamic(() => import('~/client/components/TemplateModal').then(mod => mod.TemplateModal), { ssr: false });
-const LinkEditModal = dynamic(() => import('~/client/components/PageEditor/LinkEditModal').then(mod => mod.LinkEditModal), { ssr: false });
-const TagEditModal = dynamic(() => import('~/client/components/PageTags/TagEditModal').then(mod => mod.TagEditModal), { ssr: false });
-const ConflictDiffModal = dynamic(() => import('~/client/components/PageEditor/ConflictDiffModal').then(mod => mod.ConflictDiffModal), { ssr: false });
-
-const EditablePageEffects = dynamic(() => import('~/client/components/Page/EditablePageEffects').then(mod => mod.EditablePageEffects), { ssr: false });
 
+const EditablePageEffects = dynamic(
+  () =>
+    import('~/client/components/Page/EditablePageEffects').then(
+      (mod) => mod.EditablePageEffects,
+    ),
+  { ssr: false },
+);
 
 const logger = loggerFactory('growi:pages:all');
 
-const {
-  isPermalink: _isPermalink, isCreatablePage,
-} = pagePathUtils;
+const { isPermalink: _isPermalink, isCreatablePage } = pagePathUtils;
 const { removeHeadingSlash } = pathUtils;
 
-type IPageToShowRevisionWithMeta = IDataWithMeta<IPagePopulatedToShowRevision & PageDocument, IPageInfo>;
+type IPageToShowRevisionWithMeta = IDataWithMeta<
+  IPagePopulatedToShowRevision & PageDocument,
+  IPageInfo
+>;
 type IPageToShowRevisionWithMetaSerialized = IDataWithMeta<string, string>;
 
-superjson.registerCustom<IPageToShowRevisionWithMeta, IPageToShowRevisionWithMetaSerialized>(
+superjson.registerCustom<
+  IPageToShowRevisionWithMeta,
+  IPageToShowRevisionWithMetaSerialized
+>(
   {
     isApplicable: (v): v is IPageToShowRevisionWithMeta => {
-      return v?.data != null
-        && v?.data.toObject != null
-        && isIPageInfo(v.meta);
+      return v?.data != null && v?.data.toObject != null && isIPageInfo(v.meta);
     },
     serialize: (v) => {
       return {
@@ -130,76 +234,81 @@ superjson.registerCustom<IPageToShowRevisionWithMeta, IPageToShowRevisionWithMet
 
 // GrowiContextualSubNavigation for NOT shared page
 type GrowiContextualSubNavigationProps = {
-  isLinkSharingDisabled: boolean,
-}
+  isLinkSharingDisabled: boolean;
+};
 
-const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps): JSX.Element => {
+const GrowiContextualSubNavigation = (
+  props: GrowiContextualSubNavigationProps,
+): JSX.Element => {
   const { isLinkSharingDisabled } = props;
   const { data: currentPage } = useSWRxCurrentPage();
   return (
-    <GrowiContextualSubNavigationSubstance currentPage={currentPage} isLinkSharingDisabled={isLinkSharingDisabled} />
+    <GrowiContextualSubNavigationSubstance
+      currentPage={currentPage}
+      isLinkSharingDisabled={isLinkSharingDisabled}
+    />
   );
 };
 
 type Props = CommonProps & {
-  pageWithMeta: IPageToShowRevisionWithMeta | null,
+  pageWithMeta: IPageToShowRevisionWithMeta | null;
   // pageUser?: any,
   redirectFrom?: string;
 
   // shareLinkId?: string;
-  isLatestRevision?: boolean,
+  isLatestRevision?: boolean;
 
-  isIdenticalPathPage?: boolean,
-  isForbidden: boolean,
-  isNotFound: boolean,
-  isNotCreatable: boolean,
+  isIdenticalPathPage?: boolean;
+  isForbidden: boolean;
+  isNotFound: boolean;
+  isNotCreatable: boolean;
   // isAbleToDeleteCompletely: boolean,
 
-  templateTagData?: string[],
-  templateBodyData?: string,
+  templateTagData?: string[];
+  templateBodyData?: string;
 
-  isLocalAccountRegistrationEnabled: boolean,
+  isLocalAccountRegistrationEnabled: boolean;
 
-  isSearchServiceConfigured: boolean,
-  isSearchServiceReachable: boolean,
-  isSearchScopeChildrenAsDefault: boolean,
-  elasticsearchMaxBodyLengthToIndex: number,
-  isEnabledMarp: boolean,
+  isSearchServiceConfigured: boolean;
+  isSearchServiceReachable: boolean;
+  isSearchScopeChildrenAsDefault: boolean;
+  elasticsearchMaxBodyLengthToIndex: number;
+  isEnabledMarp: boolean;
 
-  isRomUserAllowedToComment: boolean,
+  isRomUserAllowedToComment: boolean;
 
-  sidebarConfig: ISidebarConfig,
+  sidebarConfig: ISidebarConfig;
 
-  isSlackConfigured: boolean,
+  isSlackConfigured: boolean;
   // isMailerSetup: boolean,
-  isAclEnabled: boolean,
+  isAclEnabled: boolean;
   // hasSlackConfig: boolean,
-  drawioUri: string | null,
+  drawioUri: string | null;
   // highlightJsStyle: string,
-  isAllReplyShown: boolean,
-  showPageSideAuthors: boolean,
-  isContainerFluid: boolean,
-  isUploadEnabled: boolean,
-  isUploadAllFileAllowed: boolean,
-  isBulkExportPagesEnabled: boolean,
-  isPdfBulkExportEnabled: boolean,
-  isEnabledStaleNotification: boolean,
-  isEnabledAttachTitleHeader: boolean,
+  isAllReplyShown: boolean;
+  showPageSideAuthors: boolean;
+  isContainerFluid: boolean;
+  isUploadEnabled: boolean;
+  isUploadAllFileAllowed: boolean;
+  isBulkExportPagesEnabled: boolean;
+  isPdfBulkExportEnabled: boolean;
+  isEnabledStaleNotification: boolean;
+  isEnabledAttachTitleHeader: boolean;
   // isEnabledLinebreaks: boolean,
   // isEnabledLinebreaksInComments: boolean,
-  adminPreferredIndentSize: number,
-  isIndentSizeForced: boolean,
-  disableLinkSharing: boolean,
-  skipSSR: boolean,
-  ssrMaxRevisionBodyLength: number,
+  adminPreferredIndentSize: number;
+  isIndentSizeForced: boolean;
+  disableLinkSharing: boolean;
+  skipSSR: boolean;
+  ssrMaxRevisionBodyLength: number;
 
-  yjsData: CurrentPageYjsData,
+  yjsData: CurrentPageYjsData;
 
-  rendererConfig: RendererConfig,
+  rendererConfig: RendererConfig;
 
-  aiEnabled: boolean,
-  limitLearnablePageCountPerAssistant: number,
-  isUsersHomepageDeletionEnabled: boolean,
+  aiEnabled: boolean;
+  limitLearnablePageCountPerAssistant: number;
+  isUsersHomepageDeletionEnabled: boolean;
 };
 
 const Page: NextPageWithLayout<Props> = (props: Props) => {
@@ -256,11 +365,12 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
   useIsRomUserAllowedToComment(props.isRomUserAllowedToComment);
 
   useIsAiEnabled(props.aiEnabled);
-  useLimitLearnablePageCountPerAssistant(props.limitLearnablePageCountPerAssistant);
+  useLimitLearnablePageCountPerAssistant(
+    props.limitLearnablePageCountPerAssistant,
+  );
 
   useIsUsersHomepageDeletionEnabled(props.isUsersHomepageDeletionEnabled);
 
-
   const { pageWithMeta } = props;
 
   const pageId = pageWithMeta?.data._id;
@@ -272,10 +382,12 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
   const { data: currentPage } = useSWRxCurrentPage(pageWithMeta?.data ?? null); // store initial data
 
   const { trigger: mutateCurrentPage } = useSWRMUTxCurrentPage();
-  const { trigger: mutateCurrentPageYjsDataFromApi } = useSWRMUTxCurrentPageYjsData();
+  const { trigger: mutateCurrentPageYjsDataFromApi } =
+    useSWRMUTxCurrentPageYjsData();
 
   const { mutate: mutateEditingMarkdown } = useEditingMarkdown();
-  const { data: currentPageId, mutate: mutateCurrentPageId } = useCurrentPageId();
+  const { data: currentPageId, mutate: mutateCurrentPageId } =
+    useCurrentPageId();
   const { data: isGuestUser } = useIsGuestUser();
 
   const { mutate: mutateIsNotFound } = useIsNotFound();
@@ -299,7 +411,7 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
     }
 
     if (currentPageId != null && revisionId != null && !props.isNotFound) {
-      const mutatePageData = async() => {
+      const mutatePageData = async () => {
         const pageData = await mutateCurrentPage();
         mutateEditingMarkdown(pageData?.revision?.body);
       };
@@ -309,24 +421,41 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
       mutatePageData();
     }
   }, [
-    revisionId, currentPageId,
-    mutateCurrentPage, mutateEditingMarkdown,
-    props.isNotFound, props.skipSSR,
+    revisionId,
+    currentPageId,
+    mutateCurrentPage,
+    mutateEditingMarkdown,
+    props.isNotFound,
+    props.skipSSR,
   ]);
 
   // Load current yjs data
   useEffect(() => {
-    if (!isGuestUser && currentPageId != null && revisionId != null && mutateCurrentPageYjsDataFromApi != null && !props.isNotFound) {
+    if (
+      !isGuestUser &&
+      currentPageId != null &&
+      revisionId != null &&
+      mutateCurrentPageYjsDataFromApi != null &&
+      !props.isNotFound
+    ) {
       mutateCurrentPageYjsDataFromApi();
     }
-  }, [isGuestUser, currentPageId, mutateCurrentPageYjsDataFromApi, props.isNotFound, revisionId]);
+  }, [
+    isGuestUser,
+    currentPageId,
+    mutateCurrentPageYjsDataFromApi,
+    props.isNotFound,
+    revisionId,
+  ]);
 
   // sync pathname by Shallow Routing https://nextjs.org/docs/routing/shallow-routing
   useEffect(() => {
     const decodedURI = decodeURI(window.location.pathname);
     if (isClient() && decodedURI !== props.currentPathname) {
       const { search, hash } = window.location;
-      router.replace(`${props.currentPathname}${search}${hash}`, undefined, { shallow: true });
+      router.replace(`${props.currentPathname}${search}${hash}`, undefined, {
+        shallow: true,
+      });
     }
   }, [props.currentPathname, router]);
 
@@ -368,7 +497,8 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
 
   // If the data on the page changes without router.push, pageWithMeta remains old because getServerSideProps() is not executed
   // So preferentially take page data from useSWRxCurrentPage
-  const pagePath = currentPage?.path ?? pageWithMeta?.data.path ?? props.currentPathname;
+  const pagePath =
+    currentPage?.path ?? pageWithMeta?.data.path ?? props.currentPathname;
 
   const title = generateCustomTitleForPage(props, pagePath);
 
@@ -378,8 +508,9 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
         <title>{title}</title>
       </Head>
       <div className="dynamic-layout-root justify-content-between">
-
-        <GrowiContextualSubNavigation isLinkSharingDisabled={props.disableLinkSharing} />
+        <GrowiContextualSubNavigation
+          isLinkSharingDisabled={props.disableLinkSharing}
+        />
 
         <PageView
           className="d-edit-none"
@@ -397,15 +528,18 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
   );
 };
 
-
-const BasicLayoutWithEditor = ({ children }: { children?: ReactNode }): JSX.Element => {
+const BasicLayoutWithEditor = ({
+  children,
+}: {
+  children?: ReactNode;
+}): JSX.Element => {
   const editorModeClassName = useEditorModeClassName();
   return <BasicLayout className={editorModeClassName}>{children}</BasicLayout>;
 };
 
 type LayoutProps = Props & {
-  children?: ReactNode
-}
+  children?: ReactNode;
+};
 
 const Layout = ({ children, ...props }: LayoutProps): JSX.Element => {
   // init sidebar config with UserUISettings and sidebarConfig
@@ -420,9 +554,7 @@ Page.getLayout = function getLayout(page: React.ReactElement<Props>) {
       <GrowiPluginsActivator />
       <DrawioViewerScript drawioUri={page.props.rendererConfig.drawioUri} />
 
-      <Layout {...page.props}>
-        {page}
-      </Layout>
+      <Layout {...page.props}>{page}</Layout>
       <UnsavedAlertDialog />
       <DescendantsPageListModal />
       <DrawioModal />
@@ -435,23 +567,25 @@ Page.getLayout = function getLayout(page: React.ReactElement<Props>) {
   );
 };
 
-
 function getPageIdFromPathname(currentPathname: string): string | null {
-  return _isPermalink(currentPathname) ? removeHeadingSlash(currentPathname) : null;
+  return _isPermalink(currentPathname)
+    ? removeHeadingSlash(currentPathname)
+    : null;
 }
 
 class MultiplePagesHitsError extends ExtensibleCustomError {
-
   pagePath: string;
 
   constructor(pagePath: string) {
     super(`MultiplePagesHitsError occured by '${pagePath}'`);
     this.pagePath = pagePath;
   }
-
 }
 
-async function injectPageData(context: GetServerSidePropsContext, props: Props): Promise<void> {
+async function injectPageData(
+  context: GetServerSidePropsContext,
+  props: Props,
+): Promise<void> {
   const { model: mongooseModel } = await import('mongoose');
 
   const req: CrowiRequest = context.req as CrowiRequest;
@@ -471,7 +605,8 @@ async function injectPageData(context: GetServerSidePropsContext, props: Props):
 
   if (!isPermalink) {
     // check redirects
-    const chains = await PageRedirect.retrievePageRedirectEndpoints(currentPathname);
+    const chains =
+      await PageRedirect.retrievePageRedirectEndpoints(currentPathname);
     if (chains != null) {
       // overwrite currentPathname
       currentPathname = chains.end.toPath;
@@ -481,13 +616,23 @@ async function injectPageData(context: GetServerSidePropsContext, props: Props):
     }
 
     // check whether the specified page path hits to multiple pages
-    const count = await Page.countByPathAndViewer(currentPathname, user, null, true);
+    const count = await Page.countByPathAndViewer(
+      currentPathname,
+      user,
+      null,
+      true,
+    );
     if (count > 1) {
       throw new MultiplePagesHitsError(currentPathname);
     }
   }
 
-  const pageWithMeta = await pageService.findPageAndMetaDataByViewer(pageId, currentPathname, user, true); // includeEmpty = true, isSharedPage = false
+  const pageWithMeta = await pageService.findPageAndMetaDataByViewer(
+    pageId,
+    currentPathname,
+    user,
+    true,
+  ); // includeEmpty = true, isSharedPage = false
   const { data: page, meta } = pageWithMeta ?? {};
 
   // add user to seen users
@@ -501,7 +646,9 @@ async function injectPageData(context: GetServerSidePropsContext, props: Props):
   if (page != null) {
     page.initLatestRevisionField(revisionId);
     props.isLatestRevision = page.isLatestRevision();
-    const ssrMaxRevisionBodyLength = configManager.getConfig('app:ssrMaxRevisionBodyLength');
+    const ssrMaxRevisionBodyLength = configManager.getConfig(
+      'app:ssrMaxRevisionBodyLength',
+    );
     props.skipSSR = await skipSSR(page, ssrMaxRevisionBodyLength);
     const populatedPage = await page.populateDataToShowRevision(props.skipSSR); // shouldExcludeBody = skipSSR
 
@@ -512,7 +659,10 @@ async function injectPageData(context: GetServerSidePropsContext, props: Props):
   }
 }
 
-async function injectRoutingInformation(context: GetServerSidePropsContext, props: Props): Promise<void> {
+async function injectRoutingInformation(
+  context: GetServerSidePropsContext,
+  props: Props,
+): Promise<void> {
   const req: CrowiRequest = context.req as CrowiRequest;
   const { crowi } = req;
   const Page = crowi.model('Page') as PageModel;
@@ -525,15 +675,15 @@ async function injectRoutingInformation(context: GetServerSidePropsContext, prop
 
   if (props.isIdenticalPathPage) {
     props.isNotCreatable = true;
-  }
-  else if (page == null) {
+  } else if (page == null) {
     props.isNotFound = true;
     props.isNotCreatable = !isCreatablePage(currentPathname);
     // check the page is forbidden or just does not exist.
-    const count = isPermalink ? await Page.count({ _id: pageId }) : await Page.count({ path: currentPathname });
+    const count = isPermalink
+      ? await Page.count({ _id: pageId })
+      : await Page.count({ path: currentPathname });
     props.isForbidden = count > 0;
-  }
-  else {
+  } else {
     props.isNotFound = page.isEmpty;
     props.isNotCreatable = false;
     props.isForbidden = false;
@@ -570,23 +720,40 @@ async function injectRoutingInformation(context: GetServerSidePropsContext, prop
 //   }
 // }
 
-function injectServerConfigurations(context: GetServerSidePropsContext, props: Props): void {
+function injectServerConfigurations(
+  context: GetServerSidePropsContext,
+  props: Props,
+): void {
   const req: CrowiRequest = context.req as CrowiRequest;
   const { crowi } = req;
   const {
-    configManager, searchService, aclService, fileUploadService,
-    slackIntegrationService, passportService,
+    configManager,
+    searchService,
+    aclService,
+    fileUploadService,
+    slackIntegrationService,
+    passportService,
   } = crowi;
 
   props.aiEnabled = configManager.getConfig('app:aiEnabled');
-  props.limitLearnablePageCountPerAssistant = configManager.getConfig('openai:limitLearnablePageCountPerAssistant');
-  props.isUsersHomepageDeletionEnabled = configManager.getConfig('security:user-homepage-deletion:isEnabled');
+  props.limitLearnablePageCountPerAssistant = configManager.getConfig(
+    'openai:limitLearnablePageCountPerAssistant',
+  );
+  props.isUsersHomepageDeletionEnabled = configManager.getConfig(
+    'security:user-homepage-deletion:isEnabled',
+  );
   props.isSearchServiceConfigured = searchService.isConfigured;
   props.isSearchServiceReachable = searchService.isReachable;
-  props.isSearchScopeChildrenAsDefault = configManager.getConfig('customize:isSearchScopeChildrenAsDefault');
-  props.elasticsearchMaxBodyLengthToIndex = configManager.getConfig('app:elasticsearchMaxBodyLengthToIndex');
+  props.isSearchScopeChildrenAsDefault = configManager.getConfig(
+    'customize:isSearchScopeChildrenAsDefault',
+  );
+  props.elasticsearchMaxBodyLengthToIndex = configManager.getConfig(
+    'app:elasticsearchMaxBodyLengthToIndex',
+  );
 
-  props.isRomUserAllowedToComment = configManager.getConfig('security:isRomUserAllowedToComment');
+  props.isRomUserAllowedToComment = configManager.getConfig(
+    'security:isRomUserAllowedToComment',
+  );
 
   props.isSlackConfigured = slackIntegrationService.isSlackConfigured;
   // props.isMailerSetup = mailService.isMailerSetup;
@@ -595,50 +762,90 @@ function injectServerConfigurations(context: GetServerSidePropsContext, props: P
   props.drawioUri = configManager.getConfig('app:drawioUri');
   // props.highlightJsStyle = configManager.getConfig('customize:highlightJsStyle');
   props.isAllReplyShown = configManager.getConfig('customize:isAllReplyShown');
-  props.showPageSideAuthors = configManager.getConfig('customize:showPageSideAuthors');
-  props.isContainerFluid = configManager.getConfig('customize:isContainerFluid');
-  props.isEnabledStaleNotification = configManager.getConfig('customize:isEnabledStaleNotification');
-  props.disableLinkSharing = configManager.getConfig('security:disableLinkSharing');
+  props.showPageSideAuthors = configManager.getConfig(
+    'customize:showPageSideAuthors',
+  );
+  props.isContainerFluid = configManager.getConfig(
+    'customize:isContainerFluid',
+  );
+  props.isEnabledStaleNotification = configManager.getConfig(
+    'customize:isEnabledStaleNotification',
+  );
+  props.disableLinkSharing = configManager.getConfig(
+    'security:disableLinkSharing',
+  );
   props.isUploadAllFileAllowed = fileUploadService.getFileUploadEnabled();
   props.isUploadEnabled = fileUploadService.getIsUploadable();
   // TODO: remove growiCloudUri condition when bulk export can be relased for GROWI.cloud (https://redmine.weseek.co.jp/issues/163220)
-  props.isBulkExportPagesEnabled = configManager.getConfig('app:isBulkExportPagesEnabled') && configManager.getConfig('app:growiCloudUri') == null;
-  props.isPdfBulkExportEnabled = configManager.getConfig('app:pageBulkExportPdfConverterUri') != null;
-
-  props.isLocalAccountRegistrationEnabled = passportService.isLocalStrategySetup
-  && configManager.getConfig('security:registrationMode') !== RegistrationMode.CLOSED;
-
-  props.adminPreferredIndentSize = configManager.getConfig('markdown:adminPreferredIndentSize');
-  props.isIndentSizeForced = configManager.getConfig('markdown:isIndentSizeForced');
+  props.isBulkExportPagesEnabled =
+    configManager.getConfig('app:isBulkExportPagesEnabled') &&
+    configManager.getConfig('app:growiCloudUri') == null;
+  props.isPdfBulkExportEnabled =
+    configManager.getConfig('app:pageBulkExportPdfConverterUri') != null;
+
+  props.isLocalAccountRegistrationEnabled =
+    passportService.isLocalStrategySetup &&
+    configManager.getConfig('security:registrationMode') !==
+      RegistrationMode.CLOSED;
+
+  props.adminPreferredIndentSize = configManager.getConfig(
+    'markdown:adminPreferredIndentSize',
+  );
+  props.isIndentSizeForced = configManager.getConfig(
+    'markdown:isIndentSizeForced',
+  );
 
-  props.isEnabledAttachTitleHeader = configManager.getConfig('customize:isEnabledAttachTitleHeader');
+  props.isEnabledAttachTitleHeader = configManager.getConfig(
+    'customize:isEnabledAttachTitleHeader',
+  );
 
   props.sidebarConfig = {
-    isSidebarCollapsedMode: configManager.getConfig('customize:isSidebarCollapsedMode'),
-    isSidebarClosedAtDockMode: configManager.getConfig('customize:isSidebarClosedAtDockMode'),
+    isSidebarCollapsedMode: configManager.getConfig(
+      'customize:isSidebarCollapsedMode',
+    ),
+    isSidebarClosedAtDockMode: configManager.getConfig(
+      'customize:isSidebarClosedAtDockMode',
+    ),
   };
 
   props.rendererConfig = {
-    isEnabledLinebreaks: configManager.getConfig('markdown:isEnabledLinebreaks'),
-    isEnabledLinebreaksInComments: configManager.getConfig('markdown:isEnabledLinebreaksInComments'),
+    isEnabledLinebreaks: configManager.getConfig(
+      'markdown:isEnabledLinebreaks',
+    ),
+    isEnabledLinebreaksInComments: configManager.getConfig(
+      'markdown:isEnabledLinebreaksInComments',
+    ),
     isEnabledMarp: configManager.getConfig('customize:isEnabledMarp'),
-    adminPreferredIndentSize: configManager.getConfig('markdown:adminPreferredIndentSize'),
+    adminPreferredIndentSize: configManager.getConfig(
+      'markdown:adminPreferredIndentSize',
+    ),
     isIndentSizeForced: configManager.getConfig('markdown:isIndentSizeForced'),
 
     drawioUri: configManager.getConfig('app:drawioUri'),
     plantumlUri: configManager.getConfig('app:plantumlUri'),
 
     // XSS Options
-    isEnabledXssPrevention: configManager.getConfig('markdown:rehypeSanitize:isEnabledPrevention'),
+    isEnabledXssPrevention: configManager.getConfig(
+      'markdown:rehypeSanitize:isEnabledPrevention',
+    ),
     sanitizeType: configManager.getConfig('markdown:rehypeSanitize:option'),
-    customTagWhitelist: configManager.getConfig('markdown:rehypeSanitize:tagNames'),
-    customAttrWhitelist: configManager.getConfig('markdown:rehypeSanitize:attributes') != null
-      ? JSON.parse(configManager.getConfig('markdown:rehypeSanitize:attributes'))
-      : undefined,
-    highlightJsStyleBorder: configManager.getConfig('customize:highlightJsStyleBorder'),
+    customTagWhitelist: configManager.getConfig(
+      'markdown:rehypeSanitize:tagNames',
+    ),
+    customAttrWhitelist:
+      configManager.getConfig('markdown:rehypeSanitize:attributes') != null
+        ? JSON.parse(
+            configManager.getConfig('markdown:rehypeSanitize:attributes'),
+          )
+        : undefined,
+    highlightJsStyleBorder: configManager.getConfig(
+      'customize:highlightJsStyleBorder',
+    ),
   };
 
-  props.ssrMaxRevisionBodyLength = configManager.getConfig('app:ssrMaxRevisionBodyLength');
+  props.ssrMaxRevisionBodyLength = configManager.getConfig(
+    'app:ssrMaxRevisionBodyLength',
+  );
 }
 
 /**
@@ -647,8 +854,16 @@ function injectServerConfigurations(context: GetServerSidePropsContext, props: P
  * @param props
  * @param namespacesRequired
  */
-async function injectNextI18NextConfigurations(context: GetServerSidePropsContext, props: Props, namespacesRequired?: string[] | undefined): Promise<void> {
-  const nextI18NextConfig = await getNextI18NextConfig(serverSideTranslations, context, namespacesRequired);
+async function injectNextI18NextConfigurations(
+  context: GetServerSidePropsContext,
+  props: Props,
+  namespacesRequired?: string[] | undefined,
+): Promise<void> {
+  const nextI18NextConfig = await getNextI18NextConfig(
+    serverSideTranslations,
+    context,
+    namespacesRequired,
+  );
   props._nextI18Next = nextI18NextConfig._nextI18Next;
 }
 
@@ -668,7 +883,9 @@ const getAction = (props: Props): SupportedActionType => {
   return SupportedAction.ACTION_PAGE_VIEW;
 };
 
-export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
+export const getServerSideProps: GetServerSideProps = async (
+  context: GetServerSidePropsContext,
+) => {
   const req = context.req as CrowiRequest;
   const { user } = req;
 
@@ -697,12 +914,10 @@ export const getServerSideProps: GetServerSideProps = async(context: GetServerSi
 
   try {
     await injectPageData(context, props);
-  }
-  catch (err) {
+  } catch (err) {
     if (err instanceof MultiplePagesHitsError) {
       props.isIdenticalPathPage = true;
-    }
-    else {
+    } else {
       throw err;
     }
   }

+ 25 - 14
apps/app/src/pages/_app.page.tsx

@@ -1,12 +1,11 @@
-import type { ReactNode, JSX } from 'react';
+import type { JSX, ReactNode } from 'react';
 import React, { useEffect } from 'react';
-
-import type { Locale } from '@growi/core/dist/interfaces';
 import type { NextPage } from 'next';
-import { appWithTranslation } from 'next-i18next';
 import type { AppContext, AppProps } from 'next/app';
 import App from 'next/app';
 import { useRouter } from 'next/router';
+import type { Locale } from '@growi/core/dist/interfaces';
+import { appWithTranslation } from 'next-i18next';
 import { SWRConfig } from 'swr';
 
 import * as nextI18nConfig from '^/config/next-i18next.config';
@@ -14,29 +13,39 @@ import * as nextI18nConfig from '^/config/next-i18next.config';
 import { GlobalFonts } from '~/components/FontFamily/GlobalFonts';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
 import {
-  useAppTitle, useConfidential, useGrowiVersion, useSiteUrl, useIsDefaultLogo, useForcedColorScheme,
+  useAppTitle,
+  useConfidential,
+  useForcedColorScheme,
+  useGrowiVersion,
+  useIsDefaultLogo,
+  useSiteUrl,
 } from '~/stores-universal/context';
 import { swrGlobalConfiguration } from '~/utils/swr-utils';
 
-import { getLocaleAtServerSide, type CommonProps } from './utils/commons';
+import { type CommonProps, getLocaleAtServerSide } from './utils/commons';
 import '~/styles/prebuilt/vendor.css';
 import '~/styles/style-app.scss';
+
 import { registerTransformerForObjectId } from './utils/objectid-transformer';
 
 // eslint-disable-next-line @typescript-eslint/ban-types
 export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
-  getLayout?: (page: JSX.Element) => ReactNode,
-}
+  getLayout?: (page: JSX.Element) => ReactNode;
+};
 
 type GrowiAppProps = AppProps & {
-  Component: NextPageWithLayout,
-  userLocale: Locale,
+  Component: NextPageWithLayout;
+  userLocale: Locale;
 };
 
 // register custom serializer
 registerTransformerForObjectId();
 
-function GrowiApp({ Component, pageProps, userLocale }: GrowiAppProps): JSX.Element {
+function GrowiApp({
+  Component,
+  pageProps,
+  userLocale,
+}: GrowiAppProps): JSX.Element {
   const router = useRouter();
 
   useEffect(() => {
@@ -64,7 +73,7 @@ function GrowiApp({ Component, pageProps, userLocale }: GrowiAppProps): JSX.Elem
   useForcedColorScheme(commonPageProps.forcedColorScheme);
 
   // Use the layout defined at the page level, if available
-  const getLayout = Component.getLayout ?? (page => page);
+  const getLayout = Component.getLayout ?? ((page) => page);
 
   return (
     <>
@@ -76,9 +85,11 @@ function GrowiApp({ Component, pageProps, userLocale }: GrowiAppProps): JSX.Elem
   );
 }
 
-GrowiApp.getInitialProps = async(appContext: AppContext) => {
+GrowiApp.getInitialProps = async (appContext: AppContext) => {
   const appProps = App.getInitialProps(appContext);
-  const userLocale = getLocaleAtServerSide(appContext.ctx.req as unknown as CrowiRequest);
+  const userLocale = getLocaleAtServerSide(
+    appContext.ctx.req as unknown as CrowiRequest,
+  );
 
   return { ...appProps, userLocale };
 };

+ 47 - 27
apps/app/src/pages/_document.page.tsx

@@ -1,11 +1,8 @@
 /* eslint-disable @next/next/google-font-display */
 import React, { type JSX } from 'react';
-
-import type { Locale } from '@growi/core/dist/interfaces';
 import type { DocumentContext, DocumentInitialProps } from 'next/document';
-import Document, {
-  Html, Head, Main, NextScript,
-} from 'next/document';
+import Document, { Head, Html, Main, NextScript } from 'next/document';
+import type { Locale } from '@growi/core/dist/interfaces';
 
 import type { GrowiPluginResourceEntries } from '~/features/growi-plugin/server/services';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
@@ -17,42 +14,50 @@ const logger = loggerFactory('growi:page:_document');
 
 type HeadersForGrowiPluginProps = {
   pluginResourceEntries: GrowiPluginResourceEntries;
-}
-const HeadersForGrowiPlugin = (props: HeadersForGrowiPluginProps): JSX.Element => {
+};
+const HeadersForGrowiPlugin = (
+  props: HeadersForGrowiPluginProps,
+): JSX.Element => {
   const { pluginResourceEntries } = props;
 
   return (
     <>
-      { pluginResourceEntries.map(([installedPath, href]) => {
+      {pluginResourceEntries.map(([installedPath, href]) => {
         if (href.endsWith('.js')) {
           // eslint-disable-next-line @next/next/no-sync-scripts
-          return <script type="module" key={`script_${installedPath}`} src={href} />;
+          return (
+            <script type="module" key={`script_${installedPath}`} src={href} />
+          );
         }
         if (href.endsWith('.css')) {
           // eslint-disable-next-line @next/next/no-sync-scripts
-          return <link rel="stylesheet" key={`link_${installedPath}`} href={href} />;
+          return (
+            <link rel="stylesheet" key={`link_${installedPath}`} href={href} />
+          );
         }
         return <></>;
-      }) }
+      })}
     </>
   );
 };
 
 interface GrowiDocumentProps {
-  themeHref: string,
-  customScript: string | null,
-  customCss: string | null,
-  customNoscript: string | null,
+  themeHref: string;
+  customScript: string | null;
+  customCss: string | null;
+  customNoscript: string | null;
   pluginResourceEntries: GrowiPluginResourceEntries;
   locale: Locale;
 }
-declare type GrowiDocumentInitialProps = DocumentInitialProps & GrowiDocumentProps;
+declare type GrowiDocumentInitialProps = DocumentInitialProps &
+  GrowiDocumentProps;
 
 class GrowiDocument extends Document<GrowiDocumentInitialProps> {
-
-  static override async getInitialProps(ctx: DocumentContext): Promise<GrowiDocumentInitialProps> {
-
-    const initialProps: DocumentInitialProps = await Document.getInitialProps(ctx);
+  static override async getInitialProps(
+    ctx: DocumentContext,
+  ): Promise<GrowiDocumentInitialProps> {
+    const initialProps: DocumentInitialProps =
+      await Document.getInitialProps(ctx);
     const req = ctx.req as CrowiRequest;
     const { crowi } = req;
     const { customizeService } = crowi;
@@ -63,8 +68,11 @@ class GrowiDocument extends Document<GrowiDocumentInitialProps> {
     const customNoscript: string | null = customizeService.getCustomNoscript();
 
     // retrieve plugin manifests
-    const growiPluginService = await import('~/features/growi-plugin/server/services').then(mod => mod.growiPluginService);
-    const pluginResourceEntries = await growiPluginService.retrieveAllPluginResourceEntries();
+    const growiPluginService = await import(
+      '~/features/growi-plugin/server/services'
+    ).then((mod) => mod.growiPluginService);
+    const pluginResourceEntries =
+      await growiPluginService.retrieveAllPluginResourceEntries();
 
     const locale = getLocaleAtServerSide(req);
 
@@ -83,13 +91,20 @@ class GrowiDocument extends Document<GrowiDocumentInitialProps> {
     if (customScript == null || customScript.length === 0) {
       return <></>;
     }
-    return <script id="customScript" dangerouslySetInnerHTML={{ __html: customScript }} />;
+    return (
+      <script
+        id="customScript"
+        // biome-ignore lint/security/noDangerouslySetInnerHtml: ignore
+        dangerouslySetInnerHTML={{ __html: customScript }}
+      />
+    );
   }
 
   renderCustomCss(customCss: string | null): JSX.Element {
     if (customCss == null || customCss.length === 0) {
       return <></>;
     }
+    // biome-ignore lint/security/noDangerouslySetInnerHtml: ignore
     return <style dangerouslySetInnerHTML={{ __html: customCss }} />;
   }
 
@@ -97,13 +112,17 @@ class GrowiDocument extends Document<GrowiDocumentInitialProps> {
     if (customNoscript == null || customNoscript.length === 0) {
       return <></>;
     }
+    // biome-ignore lint/security/noDangerouslySetInnerHtml: ignore
     return <noscript dangerouslySetInnerHTML={{ __html: customNoscript }} />;
   }
 
   override render(): JSX.Element {
     const {
-      customCss, customScript, customNoscript,
-      themeHref, pluginResourceEntries,
+      customCss,
+      customScript,
+      customNoscript,
+      themeHref,
+      pluginResourceEntries,
       locale,
     } = this.props;
 
@@ -114,7 +133,9 @@ class GrowiDocument extends Document<GrowiDocumentInitialProps> {
           <link rel="stylesheet" key="link-theme" href={themeHref} />
           <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
           <link rel="alternate icon" href="/favicon.ico" />
-          <HeadersForGrowiPlugin pluginResourceEntries={pluginResourceEntries} />
+          <HeadersForGrowiPlugin
+            pluginResourceEntries={pluginResourceEntries}
+          />
           {this.renderCustomCss(customCss)}
         </Head>
         <body>
@@ -125,7 +146,6 @@ class GrowiDocument extends Document<GrowiDocumentInitialProps> {
       </Html>
     );
   }
-
 }
 
 export default GrowiDocument;

+ 2 - 4
apps/app/src/pages/_error.page.tsx

@@ -1,12 +1,10 @@
 import type { JSX } from 'react';
-
 import type { NextPageContext } from 'next';
 import type { ErrorProps } from 'next/error';
-import Error from 'next/error';
-
+import NextError from 'next/error';
 
 export default function ErrorPage(props: ErrorProps): JSX.Element {
-  return <Error {...props} />;
+  return <NextError {...props} />;
 }
 
 // add getInitialProps to disable "https://nextjs.org/docs/messages/prerender-error"

+ 86 - 33
apps/app/src/pages/_private-legacy-pages.page.tsx

@@ -1,47 +1,66 @@
-import type { IUser } from '@growi/core';
 import type {
-  NextPage, GetServerSideProps, GetServerSidePropsContext,
+  GetServerSideProps,
+  GetServerSidePropsContext,
+  NextPage,
 } from 'next';
-import { useTranslation } from 'next-i18next';
-import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
 import dynamic from 'next/dynamic';
 import Head from 'next/head';
+import type { IUser } from '@growi/core';
+import { useTranslation } from 'next-i18next';
+import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
 
 import { DrawioViewerScript } from '~/components/Script/DrawioViewerScript';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
 import type { RendererConfig } from '~/interfaces/services/renderer';
 import type { ISidebarConfig } from '~/interfaces/sidebar-config';
+import { useCurrentPageId, useSWRxCurrentPage } from '~/stores/page';
 import {
-  useCsrfToken, useCurrentUser, useIsSearchPage, useIsSearchScopeChildrenAsDefault,
-  useIsSearchServiceConfigured, useIsSearchServiceReachable, useRendererConfig, useGrowiCloudUri, useIsEnabledMarp, useCurrentPathname,
+  useCsrfToken,
+  useCurrentPathname,
+  useCurrentUser,
+  useGrowiCloudUri,
+  useIsEnabledMarp,
+  useIsSearchPage,
+  useIsSearchScopeChildrenAsDefault,
+  useIsSearchServiceConfigured,
+  useIsSearchServiceReachable,
+  useRendererConfig,
 } from '~/stores-universal/context';
-import { useCurrentPageId, useSWRxCurrentPage } from '~/stores/page';
 
 import type { CommonProps } from './utils/commons';
 import {
-  getNextI18NextConfig, getServerSideCommonProps, generateCustomTitle, useInitSidebarConfig,
+  generateCustomTitle,
+  getNextI18NextConfig,
+  getServerSideCommonProps,
+  useInitSidebarConfig,
 } from './utils/commons';
 
-const SearchResultLayout = dynamic(() => import('~/components/Layout/SearchResultLayout'), { ssr: false });
+const SearchResultLayout = dynamic(
+  () => import('~/components/Layout/SearchResultLayout'),
+  { ssr: false },
+);
 
 type Props = CommonProps & {
-  currentUser: IUser,
+  currentUser: IUser;
 
-  isSearchServiceConfigured: boolean,
-  isSearchServiceReachable: boolean,
-  isSearchScopeChildrenAsDefault: boolean,
-  isEnabledMarp: boolean,
+  isSearchServiceConfigured: boolean;
+  isSearchServiceReachable: boolean;
+  isSearchScopeChildrenAsDefault: boolean;
+  isEnabledMarp: boolean;
 
   // Render config
-  rendererConfig: RendererConfig,
+  rendererConfig: RendererConfig;
 
-  sidebarConfig: ISidebarConfig,
+  sidebarConfig: ISidebarConfig;
 };
 
 const PrivateLegacyPage: NextPage<Props> = (props: Props) => {
   const { t } = useTranslation();
 
-  const PrivateLegacyPages = dynamic(() => import('~/client/components/PrivateLegacyPages'), { ssr: false });
+  const PrivateLegacyPages = dynamic(
+    () => import('~/client/components/PrivateLegacyPages'),
+    { ssr: false },
+  );
 
   // commons
   useCsrfToken(props.csrfToken);
@@ -87,39 +106,63 @@ const PrivateLegacyPage: NextPage<Props> = (props: Props) => {
   );
 };
 
-async function injectServerConfigurations(context: GetServerSidePropsContext, props: Props): Promise<void> {
+async function injectServerConfigurations(
+  context: GetServerSidePropsContext,
+  props: Props,
+): Promise<void> {
   const req: CrowiRequest = context.req as CrowiRequest;
   const { crowi } = req;
   const { configManager, searchService } = crowi;
 
   props.isSearchServiceConfigured = searchService.isConfigured;
   props.isSearchServiceReachable = searchService.isReachable;
-  props.isSearchScopeChildrenAsDefault = configManager.getConfig('customize:isSearchScopeChildrenAsDefault');
+  props.isSearchScopeChildrenAsDefault = configManager.getConfig(
+    'customize:isSearchScopeChildrenAsDefault',
+  );
   props.isEnabledMarp = configManager.getConfig('customize:isEnabledMarp');
 
   props.sidebarConfig = {
-    isSidebarCollapsedMode: configManager.getConfig('customize:isSidebarCollapsedMode'),
-    isSidebarClosedAtDockMode: configManager.getConfig('customize:isSidebarClosedAtDockMode'),
+    isSidebarCollapsedMode: configManager.getConfig(
+      'customize:isSidebarCollapsedMode',
+    ),
+    isSidebarClosedAtDockMode: configManager.getConfig(
+      'customize:isSidebarClosedAtDockMode',
+    ),
   };
 
   props.rendererConfig = {
-    isEnabledLinebreaks: configManager.getConfig('markdown:isEnabledLinebreaks'),
-    isEnabledLinebreaksInComments: configManager.getConfig('markdown:isEnabledLinebreaksInComments'),
+    isEnabledLinebreaks: configManager.getConfig(
+      'markdown:isEnabledLinebreaks',
+    ),
+    isEnabledLinebreaksInComments: configManager.getConfig(
+      'markdown:isEnabledLinebreaksInComments',
+    ),
     isEnabledMarp: configManager.getConfig('customize:isEnabledMarp'),
-    adminPreferredIndentSize: configManager.getConfig('markdown:adminPreferredIndentSize'),
+    adminPreferredIndentSize: configManager.getConfig(
+      'markdown:adminPreferredIndentSize',
+    ),
     isIndentSizeForced: configManager.getConfig('markdown:isIndentSizeForced'),
 
     drawioUri: configManager.getConfig('app:drawioUri'),
     plantumlUri: configManager.getConfig('app:plantumlUri'),
 
     // XSS Options
-    isEnabledXssPrevention: configManager.getConfig('markdown:rehypeSanitize:isEnabledPrevention'),
+    isEnabledXssPrevention: configManager.getConfig(
+      'markdown:rehypeSanitize:isEnabledPrevention',
+    ),
     sanitizeType: configManager.getConfig('markdown:rehypeSanitize:option'),
-    customTagWhitelist: crowi.configManager.getConfig('markdown:rehypeSanitize:tagNames'),
-    customAttrWhitelist: configManager.getConfig('markdown:rehypeSanitize:attributes') != null
-      ? JSON.parse(configManager.getConfig('markdown:rehypeSanitize:attributes'))
-      : undefined,
-    highlightJsStyleBorder: crowi.configManager.getConfig('customize:highlightJsStyleBorder'),
+    customTagWhitelist: crowi.configManager.getConfig(
+      'markdown:rehypeSanitize:tagNames',
+    ),
+    customAttrWhitelist:
+      configManager.getConfig('markdown:rehypeSanitize:attributes') != null
+        ? JSON.parse(
+            configManager.getConfig('markdown:rehypeSanitize:attributes'),
+          )
+        : undefined,
+    highlightJsStyleBorder: crowi.configManager.getConfig(
+      'customize:highlightJsStyleBorder',
+    ),
   };
 }
 
@@ -129,12 +172,22 @@ async function injectServerConfigurations(context: GetServerSidePropsContext, pr
  * @param props
  * @param namespacesRequired
  */
-async function injectNextI18NextConfigurations(context: GetServerSidePropsContext, props: Props, namespacesRequired?: string[] | undefined): Promise<void> {
-  const nextI18NextConfig = await getNextI18NextConfig(serverSideTranslations, context, namespacesRequired);
+async function injectNextI18NextConfigurations(
+  context: GetServerSidePropsContext,
+  props: Props,
+  namespacesRequired?: string[] | undefined,
+): Promise<void> {
+  const nextI18NextConfig = await getNextI18NextConfig(
+    serverSideTranslations,
+    context,
+    namespacesRequired,
+  );
   props._nextI18Next = nextI18NextConfig._nextI18Next;
 }
 
-export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
+export const getServerSideProps: GetServerSideProps = async (
+  context: GetServerSidePropsContext,
+) => {
   const req = context.req as CrowiRequest;
   const { user } = req;
 

+ 92 - 46
apps/app/src/pages/_search.page.tsx

@@ -1,49 +1,61 @@
-import type { ReactNode, JSX } from 'react';
-
-import type { IUser } from '@growi/core';
+import type { JSX, ReactNode } from 'react';
 import type { GetServerSideProps, GetServerSidePropsContext } from 'next';
-import { useTranslation } from 'next-i18next';
-import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
 import dynamic from 'next/dynamic';
 import Head from 'next/head';
+import type { IUser } from '@growi/core';
+import { useTranslation } from 'next-i18next';
+import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
 
 import SearchResultLayout from '~/components/Layout/SearchResultLayout';
 import { DrawioViewerScript } from '~/components/Script/DrawioViewerScript';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
 import type { RendererConfig } from '~/interfaces/services/renderer';
 import type { ISidebarConfig } from '~/interfaces/sidebar-config';
+import { useCurrentPageId, useSWRxCurrentPage } from '~/stores/page';
 import {
-  useCsrfToken, useCurrentUser, useIsContainerFluid, useIsSearchPage, useIsSearchScopeChildrenAsDefault,
-  useIsSearchServiceConfigured, useIsSearchServiceReachable, useRendererConfig, useShowPageLimitationL, useGrowiCloudUri, useCurrentPathname,
+  useCsrfToken,
+  useCurrentPathname,
+  useCurrentUser,
+  useGrowiCloudUri,
+  useIsContainerFluid,
+  useIsSearchPage,
+  useIsSearchScopeChildrenAsDefault,
+  useIsSearchServiceConfigured,
+  useIsSearchServiceReachable,
+  useRendererConfig,
+  useShowPageLimitationL,
 } from '~/stores-universal/context';
-import { useCurrentPageId, useSWRxCurrentPage } from '~/stores/page';
 
 import type { NextPageWithLayout } from './_app.page';
 import type { CommonProps } from './utils/commons';
 import {
-  getNextI18NextConfig, getServerSideCommonProps, generateCustomTitle, useInitSidebarConfig,
+  generateCustomTitle,
+  getNextI18NextConfig,
+  getServerSideCommonProps,
+  useInitSidebarConfig,
 } from './utils/commons';
 
-
-const SearchPage = dynamic(() => import('~/client/components/SearchPage').then(mod => mod.SearchPage), { ssr: false });
-
+const SearchPage = dynamic(
+  () => import('~/client/components/SearchPage').then((mod) => mod.SearchPage),
+  { ssr: false },
+);
 
 type Props = CommonProps & {
-  currentUser: IUser,
+  currentUser: IUser;
 
-  isSearchServiceConfigured: boolean,
-  isSearchServiceReachable: boolean,
-  isSearchScopeChildrenAsDefault: boolean,
+  isSearchServiceConfigured: boolean;
+  isSearchServiceReachable: boolean;
+  isSearchScopeChildrenAsDefault: boolean;
 
   // Render config
-  rendererConfig: RendererConfig,
+  rendererConfig: RendererConfig;
 
   // search limit
-  showPageLimitationL: number
+  showPageLimitationL: number;
 
-  isContainerFluid: boolean,
+  isContainerFluid: boolean;
 
-  sidebarConfig: ISidebarConfig,
+  sidebarConfig: ISidebarConfig;
 };
 
 const SearchResultPage: NextPageWithLayout<Props> = (props: Props) => {
@@ -90,19 +102,15 @@ const SearchResultPage: NextPageWithLayout<Props> = (props: Props) => {
 };
 
 type LayoutProps = Props & {
-  sidebarConfig: ISidebarConfig,
-  children?: ReactNode,
-}
+  sidebarConfig: ISidebarConfig;
+  children?: ReactNode;
+};
 
 const Layout = ({ children, ...props }: LayoutProps): JSX.Element => {
   // init sidebar config with UserUISettings and sidebarConfig
   useInitSidebarConfig(props.sidebarConfig, props.userUISettings);
 
-  return (
-    <SearchResultLayout>
-      {children}
-    </SearchResultLayout>
-  );
+  return <SearchResultLayout>{children}</SearchResultLayout>;
 };
 
 SearchResultPage.getLayout = function getLayout(page) {
@@ -114,42 +122,70 @@ SearchResultPage.getLayout = function getLayout(page) {
   );
 };
 
-function injectServerConfigurations(context: GetServerSidePropsContext, props: Props): void {
+function injectServerConfigurations(
+  context: GetServerSidePropsContext,
+  props: Props,
+): void {
   const req: CrowiRequest = context.req as CrowiRequest;
   const { crowi } = req;
   const { configManager, searchService } = crowi;
 
   props.isSearchServiceConfigured = searchService.isConfigured;
   props.isSearchServiceReachable = searchService.isReachable;
-  props.isSearchScopeChildrenAsDefault = configManager.getConfig('customize:isSearchScopeChildrenAsDefault');
-  props.isContainerFluid = configManager.getConfig('customize:isContainerFluid');
+  props.isSearchScopeChildrenAsDefault = configManager.getConfig(
+    'customize:isSearchScopeChildrenAsDefault',
+  );
+  props.isContainerFluid = configManager.getConfig(
+    'customize:isContainerFluid',
+  );
 
   props.sidebarConfig = {
-    isSidebarCollapsedMode: configManager.getConfig('customize:isSidebarCollapsedMode'),
-    isSidebarClosedAtDockMode: configManager.getConfig('customize:isSidebarClosedAtDockMode'),
+    isSidebarCollapsedMode: configManager.getConfig(
+      'customize:isSidebarCollapsedMode',
+    ),
+    isSidebarClosedAtDockMode: configManager.getConfig(
+      'customize:isSidebarClosedAtDockMode',
+    ),
   };
 
   props.rendererConfig = {
-    isEnabledLinebreaks: configManager.getConfig('markdown:isEnabledLinebreaks'),
-    isEnabledLinebreaksInComments: configManager.getConfig('markdown:isEnabledLinebreaksInComments'),
+    isEnabledLinebreaks: configManager.getConfig(
+      'markdown:isEnabledLinebreaks',
+    ),
+    isEnabledLinebreaksInComments: configManager.getConfig(
+      'markdown:isEnabledLinebreaksInComments',
+    ),
     isEnabledMarp: configManager.getConfig('customize:isEnabledMarp'),
-    adminPreferredIndentSize: configManager.getConfig('markdown:adminPreferredIndentSize'),
+    adminPreferredIndentSize: configManager.getConfig(
+      'markdown:adminPreferredIndentSize',
+    ),
     isIndentSizeForced: configManager.getConfig('markdown:isIndentSizeForced'),
 
     drawioUri: configManager.getConfig('app:drawioUri'),
     plantumlUri: configManager.getConfig('app:plantumlUri'),
 
     // XSS Options
-    isEnabledXssPrevention: configManager.getConfig('markdown:rehypeSanitize:isEnabledPrevention'),
+    isEnabledXssPrevention: configManager.getConfig(
+      'markdown:rehypeSanitize:isEnabledPrevention',
+    ),
     sanitizeType: configManager.getConfig('markdown:rehypeSanitize:option'),
-    customTagWhitelist: crowi.configManager.getConfig('markdown:rehypeSanitize:tagNames'),
-    customAttrWhitelist: configManager.getConfig('markdown:rehypeSanitize:attributes') != null
-      ? JSON.parse(configManager.getConfig('markdown:rehypeSanitize:attributes'))
-      : undefined,
-    highlightJsStyleBorder: crowi.configManager.getConfig('customize:highlightJsStyleBorder'),
+    customTagWhitelist: crowi.configManager.getConfig(
+      'markdown:rehypeSanitize:tagNames',
+    ),
+    customAttrWhitelist:
+      configManager.getConfig('markdown:rehypeSanitize:attributes') != null
+        ? JSON.parse(
+            configManager.getConfig('markdown:rehypeSanitize:attributes'),
+          )
+        : undefined,
+    highlightJsStyleBorder: crowi.configManager.getConfig(
+      'customize:highlightJsStyleBorder',
+    ),
   };
 
-  props.showPageLimitationL = configManager.getConfig('customize:showPageLimitationL');
+  props.showPageLimitationL = configManager.getConfig(
+    'customize:showPageLimitationL',
+  );
 }
 
 /**
@@ -158,12 +194,22 @@ function injectServerConfigurations(context: GetServerSidePropsContext, props: P
  * @param props
  * @param namespacesRequired
  */
-async function injectNextI18NextConfigurations(context: GetServerSidePropsContext, props: Props, namespacesRequired?: string[] | undefined): Promise<void> {
-  const nextI18NextConfig = await getNextI18NextConfig(serverSideTranslations, context, namespacesRequired);
+async function injectNextI18NextConfigurations(
+  context: GetServerSidePropsContext,
+  props: Props,
+  namespacesRequired?: string[] | undefined,
+): Promise<void> {
+  const nextI18NextConfig = await getNextI18NextConfig(
+    serverSideTranslations,
+    context,
+    namespacesRequired,
+  );
   props._nextI18Next = nextI18NextConfig._nextI18Next;
 }
 
-export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
+export const getServerSideProps: GetServerSideProps = async (
+  context: GetServerSidePropsContext,
+) => {
   const req = context.req as CrowiRequest;
   const { user } = req;
 

+ 21 - 7
apps/app/src/pages/admin/[...path].page.tsx

@@ -1,18 +1,31 @@
 import type {
-  NextPage, GetServerSideProps, GetServerSidePropsContext,
+  GetServerSideProps,
+  GetServerSidePropsContext,
+  NextPage,
 } from 'next';
 import dynamic from 'next/dynamic';
 
 import AdminLayout from '~/components/Layout/AdminLayout';
 import type { CommonProps } from '~/pages/utils/commons';
-import { useCurrentUser } from '~/stores-universal/context';
 import { useIsMaintenanceMode } from '~/stores/maintenanceMode';
+import { useCurrentUser } from '~/stores-universal/context';
 
 import { retrieveServerSideProps } from '../../utils/admin-page-util';
 
-const AdminNotFoundPage = dynamic(() => import('~/client/components/Admin/NotFoundPage').then(mod => mod.AdminNotFoundPage), { ssr: false });
-const ForbiddenPage = dynamic(() => import('~/client/components/Admin/ForbiddenPage').then(mod => mod.ForbiddenPage), { ssr: false });
-
+const AdminNotFoundPage = dynamic(
+  () =>
+    import('~/client/components/Admin/NotFoundPage').then(
+      (mod) => mod.AdminNotFoundPage,
+    ),
+  { ssr: false },
+);
+const ForbiddenPage = dynamic(
+  () =>
+    import('~/client/components/Admin/ForbiddenPage').then(
+      (mod) => mod.ForbiddenPage,
+    ),
+  { ssr: false },
+);
 
 const AdminAppPage: NextPage<CommonProps> = (props) => {
   useIsMaintenanceMode(props.isMaintenanceMode);
@@ -29,10 +42,11 @@ const AdminAppPage: NextPage<CommonProps> = (props) => {
   );
 };
 
-export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
+export const getServerSideProps: GetServerSideProps = async (
+  context: GetServerSidePropsContext,
+) => {
   const props = await retrieveServerSideProps(context);
   return props;
 };
 
-
 export default AdminAppPage;

+ 39 - 14
apps/app/src/pages/admin/ai-integration.page.tsx

@@ -1,9 +1,11 @@
 import type {
-  NextPage, GetServerSideProps, GetServerSidePropsContext,
+  GetServerSideProps,
+  GetServerSidePropsContext,
+  NextPage,
 } from 'next';
-import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
 import Head from 'next/head';
+import { useTranslation } from 'next-i18next';
 
 import type { CrowiRequest } from '~/interfaces/crowi-request';
 import type { CommonProps } from '~/pages/utils/commons';
@@ -11,15 +13,33 @@ import { generateCustomTitle } from '~/pages/utils/commons';
 
 import { retrieveServerSideProps } from '../../utils/admin-page-util';
 
-const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), { ssr: false });
-const ForbiddenPage = dynamic(() => import('~/client/components/Admin/ForbiddenPage').then(mod => mod.ForbiddenPage), { ssr: false });
-const AiIntegration = dynamic(() => import('~/features/openai/client/components/AiIntegration/AiIntegration').then(mod => mod.AiIntegration), { ssr: false });
+const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), {
+  ssr: false,
+});
+const ForbiddenPage = dynamic(
+  () =>
+    import('~/client/components/Admin/ForbiddenPage').then(
+      (mod) => mod.ForbiddenPage,
+    ),
+  { ssr: false },
+);
+const AiIntegration = dynamic(
+  () =>
+    import(
+      '~/features/openai/client/components/AiIntegration/AiIntegration'
+    ).then((mod) => mod.AiIntegration),
+  { ssr: false },
+);
 const AiIntegrationDisableMode = dynamic(
-  () => import('~/features/openai/client/components/AiIntegration/AiIntegrationDisableMode').then(mod => mod.AiIntegrationDisableMode), { ssr: false },
+  () =>
+    import(
+      '~/features/openai/client/components/AiIntegration/AiIntegrationDisableMode'
+    ).then((mod) => mod.AiIntegrationDisableMode),
+  { ssr: false },
 );
 
 type Props = CommonProps & {
-  aiEnabled: boolean,
+  aiEnabled: boolean;
 };
 
 const AdminAiIntegrationPage: NextPage<Props> = (props: Props) => {
@@ -37,15 +57,15 @@ const AdminAiIntegrationPage: NextPage<Props> = (props: Props) => {
       <Head>
         <title>{headTitle}</title>
       </Head>
-      {props.aiEnabled
-        ? <AiIntegration />
-        : <AiIntegrationDisableMode />
-      }
+      {props.aiEnabled ? <AiIntegration /> : <AiIntegrationDisableMode />}
     </AdminLayout>
   );
 };
 
-const injectServerConfigurations = async(context: GetServerSidePropsContext, props: Props): Promise<void> => {
+const injectServerConfigurations = async (
+  context: GetServerSidePropsContext,
+  props: Props,
+): Promise<void> => {
   const req: CrowiRequest = context.req as CrowiRequest;
   const { crowi } = req;
   const { configManager } = crowi;
@@ -53,8 +73,13 @@ const injectServerConfigurations = async(context: GetServerSidePropsContext, pro
   props.aiEnabled = configManager.getConfig('app:aiEnabled');
 };
 
-export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
-  const props = await retrieveServerSideProps(context, injectServerConfigurations);
+export const getServerSideProps: GetServerSideProps = async (
+  context: GetServerSidePropsContext,
+) => {
+  const props = await retrieveServerSideProps(
+    context,
+    injectServerConfigurations,
+  );
   return props;
 };
 

+ 26 - 13
apps/app/src/pages/admin/app.page.tsx

@@ -1,26 +1,36 @@
 import { useEffect, useMemo } from 'react';
-
 import type {
-  NextPage, GetServerSideProps, GetServerSidePropsContext,
+  GetServerSideProps,
+  GetServerSidePropsContext,
+  NextPage,
 } from 'next';
-import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
 import Head from 'next/head';
+import { useTranslation } from 'next-i18next';
 import type { Container } from 'unstated';
 import { Provider } from 'unstated';
 
 import type { CommonProps } from '~/pages/utils/commons';
 import { generateCustomTitle } from '~/pages/utils/commons';
-import { useCurrentUser } from '~/stores-universal/context';
 import { useIsMaintenanceMode } from '~/stores/maintenanceMode';
+import { useCurrentUser } from '~/stores-universal/context';
 
 import { retrieveServerSideProps } from '../../utils/admin-page-util';
 
-
-const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), { ssr: false });
-const AppSettingsPageContents = dynamic(() => import('~/client/components/Admin/App/AppSettingsPageContents'), { ssr: false });
-const ForbiddenPage = dynamic(() => import('~/client/components/Admin/ForbiddenPage').then(mod => mod.ForbiddenPage), { ssr: false });
-
+const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), {
+  ssr: false,
+});
+const AppSettingsPageContents = dynamic(
+  () => import('~/client/components/Admin/App/AppSettingsPageContents'),
+  { ssr: false },
+);
+const ForbiddenPage = dynamic(
+  () =>
+    import('~/client/components/Admin/ForbiddenPage').then(
+      (mod) => mod.ForbiddenPage,
+    ),
+  { ssr: false },
+);
 
 const AdminAppPage: NextPage<CommonProps> = (props) => {
   const { t } = useTranslation('commons');
@@ -30,8 +40,10 @@ const AdminAppPage: NextPage<CommonProps> = (props) => {
   const injectableContainers: Container<any>[] = useMemo(() => [], []);
 
   useEffect(() => {
-    (async() => {
-      const AdminAppContainer = (await import('~/client/services/AdminAppContainer')).default;
+    (async () => {
+      const AdminAppContainer = (
+        await import('~/client/services/AdminAppContainer')
+      ).default;
       const adminAppContainer = new AdminAppContainer();
       injectableContainers.push(adminAppContainer);
     })();
@@ -55,10 +67,11 @@ const AdminAppPage: NextPage<CommonProps> = (props) => {
   );
 };
 
-export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
+export const getServerSideProps: GetServerSideProps = async (
+  context: GetServerSidePropsContext,
+) => {
   const props = await retrieveServerSideProps(context);
   return props;
 };
 
-
 export default AdminAppPage;

+ 42 - 18
apps/app/src/pages/admin/audit-log.page.tsx

@@ -1,32 +1,49 @@
 import type {
-  NextPage, GetServerSideProps, GetServerSidePropsContext,
+  GetServerSideProps,
+  GetServerSidePropsContext,
+  NextPage,
 } from 'next';
-import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
 import Head from 'next/head';
+import { useTranslation } from 'next-i18next';
 
 import type { SupportedActionType } from '~/interfaces/activity';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
 import type { CommonProps } from '~/pages/utils/commons';
 import { generateCustomTitle } from '~/pages/utils/commons';
 import {
-  useCurrentUser, useAuditLogEnabled, useAuditLogAvailableActions, useActivityExpirationSeconds,
+  useActivityExpirationSeconds,
+  useAuditLogAvailableActions,
+  useAuditLogEnabled,
+  useCurrentUser,
 } from '~/stores-universal/context';
 
 import { retrieveServerSideProps } from '../../utils/admin-page-util';
 
-const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), { ssr: false });
-const AuditLogManagement = dynamic(() => import('~/client/components/Admin/AuditLogManagement').then(mod => mod.AuditLogManagement), { ssr: false });
-const ForbiddenPage = dynamic(() => import('~/client/components/Admin/ForbiddenPage').then(mod => mod.ForbiddenPage), { ssr: false });
-
+const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), {
+  ssr: false,
+});
+const AuditLogManagement = dynamic(
+  () =>
+    import('~/client/components/Admin/AuditLogManagement').then(
+      (mod) => mod.AuditLogManagement,
+    ),
+  { ssr: false },
+);
+const ForbiddenPage = dynamic(
+  () =>
+    import('~/client/components/Admin/ForbiddenPage').then(
+      (mod) => mod.ForbiddenPage,
+    ),
+  { ssr: false },
+);
 
 type Props = CommonProps & {
-  auditLogEnabled: boolean,
-  activityExpirationSeconds: number,
-  auditLogAvailableActions: SupportedActionType[],
+  auditLogEnabled: boolean;
+  activityExpirationSeconds: number;
+  auditLogAvailableActions: SupportedActionType[];
 };
 
-
 const AdminAuditLogPage: NextPage<Props> = (props) => {
   const { t } = useTranslation('admin');
   useAuditLogEnabled(props.auditLogEnabled);
@@ -49,24 +66,31 @@ const AdminAuditLogPage: NextPage<Props> = (props) => {
       <AuditLogManagement />
     </AdminLayout>
   );
-
 };
 
-const injectServerConfigurations = async(context: GetServerSidePropsContext, props: Props): Promise<void> => {
+const injectServerConfigurations = async (
+  context: GetServerSidePropsContext,
+  props: Props,
+): Promise<void> => {
   const req: CrowiRequest = context.req as CrowiRequest;
   const { crowi } = req;
   const { activityService } = crowi;
 
   props.auditLogEnabled = crowi.configManager.getConfig('app:auditLogEnabled');
-  props.activityExpirationSeconds = crowi.configManager.getConfig('app:activityExpirationSeconds');
+  props.activityExpirationSeconds = crowi.configManager.getConfig(
+    'app:activityExpirationSeconds',
+  );
   props.auditLogAvailableActions = activityService.getAvailableActions(false);
 };
 
-
-export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
-  const props = await retrieveServerSideProps(context, injectServerConfigurations);
+export const getServerSideProps: GetServerSideProps = async (
+  context: GetServerSidePropsContext,
+) => {
+  const props = await retrieveServerSideProps(
+    context,
+    injectServerConfigurations,
+  );
   return props;
 };
 
-
 export default AdminAuditLogPage;

+ 42 - 19
apps/app/src/pages/admin/customize.page.tsx

@@ -1,11 +1,12 @@
 import { useEffect, useMemo } from 'react';
-
 import type {
-  NextPage, GetServerSideProps, GetServerSidePropsContext,
+  GetServerSideProps,
+  GetServerSidePropsContext,
+  NextPage,
 } from 'next';
-import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
 import Head from 'next/head';
+import { useTranslation } from 'next-i18next';
 import type { Container } from 'unstated';
 import { Provider } from 'unstated';
 
@@ -13,21 +14,34 @@ import type { CrowiRequest } from '~/interfaces/crowi-request';
 import type { CommonProps } from '~/pages/utils/commons';
 import { generateCustomTitle } from '~/pages/utils/commons';
 import { configManager } from '~/server/service/config-manager';
-import { useCustomizeTitle, useCurrentUser, useIsCustomizedLogoUploaded } from '~/stores-universal/context';
+import {
+  useCurrentUser,
+  useCustomizeTitle,
+  useIsCustomizedLogoUploaded,
+} from '~/stores-universal/context';
 
 import { retrieveServerSideProps } from '../../utils/admin-page-util';
 
-const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), { ssr: false });
-const CustomizeSettingContents = dynamic(() => import('~/client/components/Admin/Customize/Customize'), { ssr: false });
-const ForbiddenPage = dynamic(() => import('~/client/components/Admin/ForbiddenPage').then(mod => mod.ForbiddenPage), { ssr: false });
-
+const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), {
+  ssr: false,
+});
+const CustomizeSettingContents = dynamic(
+  () => import('~/client/components/Admin/Customize/Customize'),
+  { ssr: false },
+);
+const ForbiddenPage = dynamic(
+  () =>
+    import('~/client/components/Admin/ForbiddenPage').then(
+      (mod) => mod.ForbiddenPage,
+    ),
+  { ssr: false },
+);
 
 type Props = CommonProps & {
-  customizeTitle?: string,
-  isCustomizedLogoUploaded: boolean,
+  customizeTitle?: string;
+  isCustomizedLogoUploaded: boolean;
 };
 
-
 const AdminCustomizeSettingsPage: NextPage<Props> = (props) => {
   const { t } = useTranslation('admin');
   useCustomizeTitle(props.customizeTitle);
@@ -39,8 +53,10 @@ const AdminCustomizeSettingsPage: NextPage<Props> = (props) => {
   const injectableContainers: Container<any>[] = useMemo(() => [], []);
 
   useEffect(() => {
-    (async() => {
-      const AdminCustomizeContainer = (await import('~/client/services/AdminCustomizeContainer')).default;
+    (async () => {
+      const AdminCustomizeContainer = (
+        await import('~/client/services/AdminCustomizeContainer')
+      ).default;
       const adminCustomizeContainer = new AdminCustomizeContainer();
       injectableContainers.push(adminCustomizeContainer);
     })();
@@ -62,19 +78,26 @@ const AdminCustomizeSettingsPage: NextPage<Props> = (props) => {
   );
 };
 
-
-const injectServerConfigurations = async(context: GetServerSidePropsContext, props: Props): Promise<void> => {
+const injectServerConfigurations = async (
+  context: GetServerSidePropsContext,
+  props: Props,
+): Promise<void> => {
   const req: CrowiRequest = context.req as CrowiRequest;
   const { crowi } = req;
 
   props.customizeTitle = crowi.configManager.getConfig('customize:title');
-  props.isCustomizedLogoUploaded = await crowi.attachmentService.isBrandLogoExist();
+  props.isCustomizedLogoUploaded =
+    await crowi.attachmentService.isBrandLogoExist();
 };
 
-export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
-  const props = await retrieveServerSideProps(context, injectServerConfigurations);
+export const getServerSideProps: GetServerSideProps = async (
+  context: GetServerSidePropsContext,
+) => {
+  const props = await retrieveServerSideProps(
+    context,
+    injectServerConfigurations,
+  );
   return props;
 };
 
-
 export default AdminCustomizeSettingsPage;

+ 35 - 15
apps/app/src/pages/admin/data-transfer.page.tsx

@@ -1,11 +1,12 @@
 import { useEffect, useMemo } from 'react';
-
 import type {
-  NextPage, GetServerSideProps, GetServerSidePropsContext,
+  GetServerSideProps,
+  GetServerSidePropsContext,
+  NextPage,
 } from 'next';
-import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
 import Head from 'next/head';
+import { useTranslation } from 'next-i18next';
 import type { Container } from 'unstated';
 import { Provider } from 'unstated';
 
@@ -15,14 +16,23 @@ import { useCurrentUser, useGrowiCloudUri } from '~/stores-universal/context';
 
 import { retrieveServerSideProps } from '../../utils/admin-page-util';
 
-const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), { ssr: false });
-const G2GDataTransferPage = dynamic(() => import('~/client/components/Admin/G2GDataTransfer'), { ssr: false });
-const ForbiddenPage = dynamic(() => import('~/client/components/Admin/ForbiddenPage').then(mod => mod.ForbiddenPage), { ssr: false });
-
+const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), {
+  ssr: false,
+});
+const G2GDataTransferPage = dynamic(
+  () => import('~/client/components/Admin/G2GDataTransfer'),
+  { ssr: false },
+);
+const ForbiddenPage = dynamic(
+  () =>
+    import('~/client/components/Admin/ForbiddenPage').then(
+      (mod) => mod.ForbiddenPage,
+    ),
+  { ssr: false },
+);
 
 type Props = CommonProps;
 
-
 const DataTransferPage: NextPage<Props> = (props) => {
   const { t } = useTranslation('commons');
   useCurrentUser(props.currentUser ?? null);
@@ -33,8 +43,10 @@ const DataTransferPage: NextPage<Props> = (props) => {
   const injectableContainers: Container<any>[] = useMemo(() => [], []);
 
   useEffect(() => {
-    (async() => {
-      const AdminAppContainer = (await import('~/client/services/AdminAppContainer')).default;
+    (async () => {
+      const AdminAppContainer = (
+        await import('~/client/services/AdminAppContainer')
+      ).default;
       const adminAppContainer = new AdminAppContainer();
       injectableContainers.push(adminAppContainer);
     })();
@@ -56,17 +68,25 @@ const DataTransferPage: NextPage<Props> = (props) => {
   );
 };
 
-const injectServerConfigurations = async(context: GetServerSidePropsContext, props: Props): Promise<void> => {
+const injectServerConfigurations = async (
+  context: GetServerSidePropsContext,
+  props: Props,
+): Promise<void> => {
   const req: CrowiRequest = context.req as CrowiRequest;
   const { crowi } = req;
 
-  props.growiCloudUri = await crowi.configManager.getConfig('app:growiCloudUri');
+  props.growiCloudUri =
+    await crowi.configManager.getConfig('app:growiCloudUri');
 };
 
-export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
-  const props = await retrieveServerSideProps(context, injectServerConfigurations);
+export const getServerSideProps: GetServerSideProps = async (
+  context: GetServerSidePropsContext,
+) => {
+  const props = await retrieveServerSideProps(
+    context,
+    injectServerConfigurations,
+  );
   return props;
 };
 
-
 export default DataTransferPage;

+ 25 - 12
apps/app/src/pages/admin/export.page.tsx

@@ -1,11 +1,12 @@
 import { useEffect, useMemo } from 'react';
-
 import type {
-  NextPage, GetServerSideProps, GetServerSidePropsContext,
+  GetServerSideProps,
+  GetServerSidePropsContext,
+  NextPage,
 } from 'next';
-import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
 import Head from 'next/head';
+import { useTranslation } from 'next-i18next';
 import type { Container } from 'unstated';
 import { Provider } from 'unstated';
 
@@ -15,10 +16,20 @@ import { useCurrentUser } from '~/stores-universal/context';
 
 import { retrieveServerSideProps } from '../../utils/admin-page-util';
 
-const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), { ssr: false });
-const ExportArchiveDataPage = dynamic(() => import('~/client/components/Admin/ExportArchiveDataPage'), { ssr: false });
-const ForbiddenPage = dynamic(() => import('~/client/components/Admin/ForbiddenPage').then(mod => mod.ForbiddenPage), { ssr: false });
-
+const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), {
+  ssr: false,
+});
+const ExportArchiveDataPage = dynamic(
+  () => import('~/client/components/Admin/ExportArchiveDataPage'),
+  { ssr: false },
+);
+const ForbiddenPage = dynamic(
+  () =>
+    import('~/client/components/Admin/ForbiddenPage').then(
+      (mod) => mod.ForbiddenPage,
+    ),
+  { ssr: false },
+);
 
 const AdminExportDataArchivePage: NextPage<CommonProps> = (props) => {
   const { t } = useTranslation('admin');
@@ -30,8 +41,10 @@ const AdminExportDataArchivePage: NextPage<CommonProps> = (props) => {
   const injectableContainers: Container<any>[] = useMemo(() => [], []);
 
   useEffect(() => {
-    (async() => {
-      const AdminAppContainer = (await import('~/client/services/AdminAppContainer')).default;
+    (async () => {
+      const AdminAppContainer = (
+        await import('~/client/services/AdminAppContainer')
+      ).default;
       const adminAppContainer = new AdminAppContainer();
       injectableContainers.push(adminAppContainer);
     })();
@@ -53,11 +66,11 @@ const AdminExportDataArchivePage: NextPage<CommonProps> = (props) => {
   );
 };
 
-
-export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
+export const getServerSideProps: GetServerSideProps = async (
+  context: GetServerSidePropsContext,
+) => {
   const props = await retrieveServerSideProps(context);
   return props;
 };
 
-
 export default AdminExportDataArchivePage;

+ 46 - 26
apps/app/src/pages/admin/global-notification/[globalNotificationId].page.tsx

@@ -1,13 +1,14 @@
 import { useEffect, useMemo } from 'react';
-
-import { objectIdUtils } from '@growi/core/dist/utils';
 import type {
-  NextPage, GetServerSideProps, GetServerSidePropsContext,
+  GetServerSideProps,
+  GetServerSidePropsContext,
+  NextPage,
 } from 'next';
-import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
 import Head from 'next/head';
 import { useRouter } from 'next/router';
+import { objectIdUtils } from '@growi/core/dist/utils';
+import { useTranslation } from 'next-i18next';
 import type { Container } from 'unstated';
 import { Provider } from 'unstated';
 
@@ -17,44 +18,63 @@ import { useCurrentUser } from '~/stores-universal/context';
 
 import { retrieveServerSideProps } from '../../../utils/admin-page-util';
 
-
-const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), { ssr: false });
-const ManageGlobalNotification = dynamic(() => import('~/client/components/Admin/Notification/ManageGlobalNotification'), { ssr: false });
-const ForbiddenPage = dynamic(() => import('~/client/components/Admin/ForbiddenPage').then(mod => mod.ForbiddenPage), { ssr: false });
-
+const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), {
+  ssr: false,
+});
+const ManageGlobalNotification = dynamic(
+  () =>
+    import('~/client/components/Admin/Notification/ManageGlobalNotification'),
+  { ssr: false },
+);
+const ForbiddenPage = dynamic(
+  () =>
+    import('~/client/components/Admin/ForbiddenPage').then(
+      (mod) => mod.ForbiddenPage,
+    ),
+  { ssr: false },
+);
 
 const AdminGlobalNotificationNewPage: NextPage<CommonProps> = (props) => {
   const { t } = useTranslation('admin');
   useCurrentUser(props.currentUser ?? null);
   const router = useRouter();
   const { globalNotificationId } = router.query;
-  const currentGlobalNotificationId = Array.isArray(globalNotificationId) ? globalNotificationId[0] : globalNotificationId;
-
+  const currentGlobalNotificationId = Array.isArray(globalNotificationId)
+    ? globalNotificationId[0]
+    : globalNotificationId;
 
   useEffect(() => {
-    const toastError = import('~/client/util/toastr').then(mod => mod.toastError);
+    const toastError = import('~/client/util/toastr').then(
+      (mod) => mod.toastError,
+    );
 
     if (globalNotificationId == null) {
       router.push('/admin/notification');
     }
-    if ((currentGlobalNotificationId != null && !objectIdUtils.isValidObjectId(currentGlobalNotificationId))) {
-      (async() => {
-        (await toastError)(t('notification_settings.not_found_global_notification_triggerid'));
+    if (
+      currentGlobalNotificationId != null &&
+      !objectIdUtils.isValidObjectId(currentGlobalNotificationId)
+    ) {
+      (async () => {
+        (await toastError)(
+          t('notification_settings.not_found_global_notification_triggerid'),
+        );
         router.push('/admin/global-notification/new');
       })();
       return;
     }
   }, [currentGlobalNotificationId, globalNotificationId, router, t]);
 
-
   const title = t('external_notification.external_notification');
   const customTitle = generateCustomTitle(props, title);
 
   const injectableContainers: Container<any>[] = useMemo(() => [], []);
 
   useEffect(() => {
-    (async() => {
-      const AdminNotificationContainer = (await import('~/client/services/AdminNotificationContainer')).default;
+    (async () => {
+      const AdminNotificationContainer = (
+        await import('~/client/services/AdminNotificationContainer')
+      ).default;
       const adminNotificationContainer = new AdminNotificationContainer();
       injectableContainers.push(adminNotificationContainer);
     })();
@@ -70,21 +90,21 @@ const AdminGlobalNotificationNewPage: NextPage<CommonProps> = (props) => {
         <Head>
           <title>{customTitle}</title>
         </Head>
-        {
-          currentGlobalNotificationId != null && router.isReady
-      && <ManageGlobalNotification globalNotificationId={currentGlobalNotificationId} />
-        }
+        {currentGlobalNotificationId != null && router.isReady && (
+          <ManageGlobalNotification
+            globalNotificationId={currentGlobalNotificationId}
+          />
+        )}
       </AdminLayout>
     </Provider>
   );
-
 };
 
-
-export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
+export const getServerSideProps: GetServerSideProps = async (
+  context: GetServerSidePropsContext,
+) => {
   const props = await retrieveServerSideProps(context);
   return props;
 };
 
-
 export default AdminGlobalNotificationNewPage;

+ 26 - 13
apps/app/src/pages/admin/global-notification/new.page.tsx

@@ -1,11 +1,12 @@
 import { useEffect, useMemo } from 'react';
-
 import type {
-  NextPage, GetServerSideProps, GetServerSidePropsContext,
+  GetServerSideProps,
+  GetServerSidePropsContext,
+  NextPage,
 } from 'next';
-import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
 import Head from 'next/head';
+import { useTranslation } from 'next-i18next';
 import type { Container } from 'unstated';
 import { Provider } from 'unstated';
 
@@ -14,10 +15,21 @@ import { useCurrentUser } from '~/stores-universal/context';
 
 import { retrieveServerSideProps } from '../../../utils/admin-page-util';
 
-const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), { ssr: false });
-const ManageGlobalNotification = dynamic(() => import('~/client/components/Admin/Notification/ManageGlobalNotification'), { ssr: false });
-const ForbiddenPage = dynamic(() => import('~/client/components/Admin/ForbiddenPage').then(mod => mod.ForbiddenPage), { ssr: false });
-
+const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), {
+  ssr: false,
+});
+const ManageGlobalNotification = dynamic(
+  () =>
+    import('~/client/components/Admin/Notification/ManageGlobalNotification'),
+  { ssr: false },
+);
+const ForbiddenPage = dynamic(
+  () =>
+    import('~/client/components/Admin/ForbiddenPage').then(
+      (mod) => mod.ForbiddenPage,
+    ),
+  { ssr: false },
+);
 
 const AdminGlobalNotificationNewPage: NextPage<CommonProps> = (props) => {
   const { t } = useTranslation('admin');
@@ -28,8 +40,10 @@ const AdminGlobalNotificationNewPage: NextPage<CommonProps> = (props) => {
   const injectableContainers: Container<any>[] = useMemo(() => [], []);
 
   useEffect(() => {
-    (async() => {
-      const AdminNotificationContainer = (await import('~/client/services/AdminNotificationContainer')).default;
+    (async () => {
+      const AdminNotificationContainer = (
+        await import('~/client/services/AdminNotificationContainer')
+      ).default;
       const adminNotificationContainer = new AdminNotificationContainer();
       injectableContainers.push(adminNotificationContainer);
     })();
@@ -49,14 +63,13 @@ const AdminGlobalNotificationNewPage: NextPage<CommonProps> = (props) => {
       </AdminLayout>
     </Provider>
   );
-
 };
 
-
-export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
+export const getServerSideProps: GetServerSideProps = async (
+  context: GetServerSidePropsContext,
+) => {
   const props = await retrieveServerSideProps(context);
   return props;
 };
 
-
 export default AdminGlobalNotificationNewPage;

+ 25 - 14
apps/app/src/pages/admin/importer.page.tsx

@@ -1,11 +1,12 @@
 import { useEffect, useMemo } from 'react';
-
 import type {
-  NextPage, GetServerSideProps, GetServerSidePropsContext,
+  GetServerSideProps,
+  GetServerSidePropsContext,
+  NextPage,
 } from 'next';
-import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
 import Head from 'next/head';
+import { useTranslation } from 'next-i18next';
 import type { Container } from 'unstated';
 import { Provider } from 'unstated';
 
@@ -15,10 +16,20 @@ import { useCurrentUser } from '~/stores-universal/context';
 
 import { retrieveServerSideProps } from '../../utils/admin-page-util';
 
-const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), { ssr: false });
-const DataImportPageContents = dynamic(() => import('~/client/components/Admin/ImportData/ImportDataPageContents'), { ssr: false });
-const ForbiddenPage = dynamic(() => import('~/client/components/Admin/ForbiddenPage').then(mod => mod.ForbiddenPage), { ssr: false });
-
+const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), {
+  ssr: false,
+});
+const DataImportPageContents = dynamic(
+  () => import('~/client/components/Admin/ImportData/ImportDataPageContents'),
+  { ssr: false },
+);
+const ForbiddenPage = dynamic(
+  () =>
+    import('~/client/components/Admin/ForbiddenPage').then(
+      (mod) => mod.ForbiddenPage,
+    ),
+  { ssr: false },
+);
 
 const AdminDataImportPage: NextPage<CommonProps> = (props) => {
   const { t } = useTranslation('admin');
@@ -30,8 +41,10 @@ const AdminDataImportPage: NextPage<CommonProps> = (props) => {
   const injectableContainers: Container<any>[] = useMemo(() => [], []);
 
   useEffect(() => {
-    (async() => {
-      const AdminImportContainer = (await import('~/client/services/AdminImportContainer')).default;
+    (async () => {
+      const AdminImportContainer = (
+        await import('~/client/services/AdminImportContainer')
+      ).default;
       const adminImportContainer = new AdminImportContainer();
       injectableContainers.push(adminImportContainer);
     })();
@@ -41,7 +54,6 @@ const AdminDataImportPage: NextPage<CommonProps> = (props) => {
     return <ForbiddenPage />;
   }
 
-
   return (
     <Provider inject={[...injectableContainers]}>
       <AdminLayout componentTitle={componentTitle}>
@@ -52,14 +64,13 @@ const AdminDataImportPage: NextPage<CommonProps> = (props) => {
       </AdminLayout>
     </Provider>
   );
-
 };
 
-
-export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
+export const getServerSideProps: GetServerSideProps = async (
+  context: GetServerSidePropsContext,
+) => {
   const props = await retrieveServerSideProps(context);
   return props;
 };
 
-
 export default AdminDataImportPage;

+ 41 - 22
apps/app/src/pages/admin/index.page.tsx

@@ -1,11 +1,12 @@
 import { useEffect, useMemo } from 'react';
-
 import type {
-  NextPage, GetServerSideProps, GetServerSidePropsContext,
+  GetServerSideProps,
+  GetServerSidePropsContext,
+  NextPage,
 } from 'next';
-import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
 import Head from 'next/head';
+import { useTranslation } from 'next-i18next';
 import type { Container } from 'unstated';
 import { Provider } from 'unstated';
 
@@ -13,23 +14,33 @@ import type { CrowiRequest } from '~/interfaces/crowi-request';
 import type { CommonProps } from '~/pages/utils/commons';
 import { generateCustomTitle } from '~/pages/utils/commons';
 import {
-  useCurrentUser, useGrowiCloudUri, useGrowiAppIdForGrowiCloud,
+  useCurrentUser,
+  useGrowiAppIdForGrowiCloud,
+  useGrowiCloudUri,
 } from '~/stores-universal/context';
 
-
 import { retrieveServerSideProps } from '../../utils/admin-page-util';
 
-const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), { ssr: false });
-const AdminHome = dynamic(() => import('~/client/components/Admin/AdminHome/AdminHome'), { ssr: false });
-const ForbiddenPage = dynamic(() => import('~/client/components/Admin/ForbiddenPage').then(mod => mod.ForbiddenPage), { ssr: false });
-
+const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), {
+  ssr: false,
+});
+const AdminHome = dynamic(
+  () => import('~/client/components/Admin/AdminHome/AdminHome'),
+  { ssr: false },
+);
+const ForbiddenPage = dynamic(
+  () =>
+    import('~/client/components/Admin/ForbiddenPage').then(
+      (mod) => mod.ForbiddenPage,
+    ),
+  { ssr: false },
+);
 
 type Props = CommonProps & {
-  growiCloudUri?: string,
-  growiAppIdForGrowiCloud?: number,
+  growiCloudUri?: string;
+  growiAppIdForGrowiCloud?: number;
 };
 
-
 const AdminHomepage: NextPage<Props> = (props: Props) => {
   useCurrentUser(props.currentUser ?? null);
   useGrowiCloudUri(props.growiCloudUri);
@@ -42,8 +53,10 @@ const AdminHomepage: NextPage<Props> = (props: Props) => {
   const injectableContainers: Container<any>[] = useMemo(() => [], []);
 
   useEffect(() => {
-    (async() => {
-      const AdminHomeContainer = (await import('~/client/services/AdminHomeContainer')).default;
+    (async () => {
+      const AdminHomeContainer = (
+        await import('~/client/services/AdminHomeContainer')
+      ).default;
       const adminHomeContainer = new AdminHomeContainer();
       injectableContainers.push(adminHomeContainer);
     })();
@@ -53,7 +66,6 @@ const AdminHomepage: NextPage<Props> = (props: Props) => {
     return <ForbiddenPage />;
   }
 
-
   return (
     <Provider inject={[...injectableContainers]}>
       <AdminLayout componentTitle={title}>
@@ -66,20 +78,27 @@ const AdminHomepage: NextPage<Props> = (props: Props) => {
   );
 };
 
-
-const injectServerConfigurations = async(context: GetServerSidePropsContext, props: Props): Promise<void> => {
+const injectServerConfigurations = async (
+  context: GetServerSidePropsContext,
+  props: Props,
+): Promise<void> => {
   const req: CrowiRequest = context.req as CrowiRequest;
   const { crowi } = req;
 
   props.growiCloudUri = crowi.configManager.getConfig('app:growiCloudUri');
-  props.growiAppIdForGrowiCloud = crowi.configManager.getConfig('app:growiAppIdForCloud');
+  props.growiAppIdForGrowiCloud = crowi.configManager.getConfig(
+    'app:growiAppIdForCloud',
+  );
 };
 
-
-export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
-  const props = await retrieveServerSideProps(context, injectServerConfigurations);
+export const getServerSideProps: GetServerSideProps = async (
+  context: GetServerSidePropsContext,
+) => {
+  const props = await retrieveServerSideProps(
+    context,
+    injectServerConfigurations,
+  );
   return props;
 };
 
-
 export default AdminHomepage;

+ 26 - 13
apps/app/src/pages/admin/markdown.page.tsx

@@ -1,11 +1,12 @@
 import { useEffect, useMemo } from 'react';
-
 import type {
-  NextPage, GetServerSideProps, GetServerSidePropsContext,
+  GetServerSideProps,
+  GetServerSidePropsContext,
+  NextPage,
 } from 'next';
-import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
 import Head from 'next/head';
+import { useTranslation } from 'next-i18next';
 import type { Container } from 'unstated';
 import { Provider } from 'unstated';
 
@@ -15,10 +16,21 @@ import { useCurrentUser } from '~/stores-universal/context';
 
 import { retrieveServerSideProps } from '../../utils/admin-page-util';
 
-const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), { ssr: false });
-const MarkDownSettingContents = dynamic(() => import('~/client/components/Admin/MarkdownSetting/MarkDownSettingContents'), { ssr: false });
-const ForbiddenPage = dynamic(() => import('~/client/components/Admin/ForbiddenPage').then(mod => mod.ForbiddenPage), { ssr: false });
-
+const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), {
+  ssr: false,
+});
+const MarkDownSettingContents = dynamic(
+  () =>
+    import('~/client/components/Admin/MarkdownSetting/MarkDownSettingContents'),
+  { ssr: false },
+);
+const ForbiddenPage = dynamic(
+  () =>
+    import('~/client/components/Admin/ForbiddenPage').then(
+      (mod) => mod.ForbiddenPage,
+    ),
+  { ssr: false },
+);
 
 const AdminMarkdownPage: NextPage<CommonProps> = (props) => {
   const { t } = useTranslation('admin');
@@ -30,8 +42,10 @@ const AdminMarkdownPage: NextPage<CommonProps> = (props) => {
   const injectableContainers: Container<any>[] = useMemo(() => [], []);
 
   useEffect(() => {
-    (async() => {
-      const AdminMarkDownContainer = (await import('~/client/services/AdminMarkDownContainer')).default;
+    (async () => {
+      const AdminMarkDownContainer = (
+        await import('~/client/services/AdminMarkDownContainer')
+      ).default;
       const adminMarkDownContainer = new AdminMarkDownContainer();
       injectableContainers.push(adminMarkDownContainer);
     })();
@@ -51,14 +65,13 @@ const AdminMarkdownPage: NextPage<CommonProps> = (props) => {
       </AdminLayout>
     </Provider>
   );
-
 };
 
-
-export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
+export const getServerSideProps: GetServerSideProps = async (
+  context: GetServerSidePropsContext,
+) => {
   const props = await retrieveServerSideProps(context);
   return props;
 };
 
-
 export default AdminMarkdownPage;

+ 25 - 14
apps/app/src/pages/admin/notification.page.tsx

@@ -1,11 +1,12 @@
 import { useEffect, useMemo } from 'react';
-
 import type {
-  NextPage, GetServerSideProps, GetServerSidePropsContext,
+  GetServerSideProps,
+  GetServerSidePropsContext,
+  NextPage,
 } from 'next';
-import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
 import Head from 'next/head';
+import { useTranslation } from 'next-i18next';
 import type { Container } from 'unstated';
 import { Provider } from 'unstated';
 
@@ -15,10 +16,20 @@ import { useCurrentUser } from '~/stores-universal/context';
 
 import { retrieveServerSideProps } from '../../utils/admin-page-util';
 
-const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), { ssr: false });
-const NotificationSetting = dynamic(() => import('~/client/components/Admin/Notification/NotificationSetting'), { ssr: false });
-const ForbiddenPage = dynamic(() => import('~/client/components/Admin/ForbiddenPage').then(mod => mod.ForbiddenPage), { ssr: false });
-
+const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), {
+  ssr: false,
+});
+const NotificationSetting = dynamic(
+  () => import('~/client/components/Admin/Notification/NotificationSetting'),
+  { ssr: false },
+);
+const ForbiddenPage = dynamic(
+  () =>
+    import('~/client/components/Admin/ForbiddenPage').then(
+      (mod) => mod.ForbiddenPage,
+    ),
+  { ssr: false },
+);
 
 const AdminExternalNotificationPage: NextPage<CommonProps> = (props) => {
   const { t } = useTranslation('admin');
@@ -30,8 +41,10 @@ const AdminExternalNotificationPage: NextPage<CommonProps> = (props) => {
   const injectableContainers: Container<any>[] = useMemo(() => [], []);
 
   useEffect(() => {
-    (async() => {
-      const AdminNotificationContainer = (await import('~/client/services/AdminNotificationContainer')).default;
+    (async () => {
+      const AdminNotificationContainer = (
+        await import('~/client/services/AdminNotificationContainer')
+      ).default;
       const adminNotificationContainer = new AdminNotificationContainer();
       injectableContainers.push(adminNotificationContainer);
     })();
@@ -41,7 +54,6 @@ const AdminExternalNotificationPage: NextPage<CommonProps> = (props) => {
     return <ForbiddenPage />;
   }
 
-
   return (
     <Provider inject={[...injectableContainers]}>
       <AdminLayout componentTitle={componentTitle}>
@@ -52,14 +64,13 @@ const AdminExternalNotificationPage: NextPage<CommonProps> = (props) => {
       </AdminLayout>
     </Provider>
   );
-
 };
 
-
-export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
+export const getServerSideProps: GetServerSideProps = async (
+  context: GetServerSidePropsContext,
+) => {
   const props = await retrieveServerSideProps(context);
   return props;
 };
 
-
 export default AdminExternalNotificationPage;

+ 26 - 14
apps/app/src/pages/admin/plugins.page.tsx

@@ -1,30 +1,39 @@
 import { useEffect, useMemo } from 'react';
-
 import type {
-  NextPage, GetServerSideProps, GetServerSidePropsContext,
+  GetServerSideProps,
+  GetServerSidePropsContext,
+  NextPage,
 } from 'next';
-import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
 import Head from 'next/head';
+import { useTranslation } from 'next-i18next';
 import type { Container } from 'unstated';
 import { Provider } from 'unstated';
 
-
 import type { CommonProps } from '~/pages/utils/commons';
 import { generateCustomTitle } from '~/pages/utils/commons';
-import { useCurrentUser } from '~/stores-universal/context';
 import { useIsMaintenanceMode } from '~/stores/maintenanceMode';
+import { useCurrentUser } from '~/stores-universal/context';
 
 import { retrieveServerSideProps } from '../../utils/admin-page-util';
 
-
-const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), { ssr: false });
+const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), {
+  ssr: false,
+});
 const PluginsExtensionPageContents = dynamic(
-  () => import('~/features/growi-plugin/client/components/Admin').then(mod => mod.PluginsExtensionPageContents),
+  () =>
+    import('~/features/growi-plugin/client/components/Admin').then(
+      (mod) => mod.PluginsExtensionPageContents,
+    ),
+  { ssr: false },
+);
+const ForbiddenPage = dynamic(
+  () =>
+    import('~/client/components/Admin/ForbiddenPage').then(
+      (mod) => mod.ForbiddenPage,
+    ),
   { ssr: false },
 );
-const ForbiddenPage = dynamic(() => import('~/client/components/Admin/ForbiddenPage').then(mod => mod.ForbiddenPage), { ssr: false });
-
 
 const AdminAppPage: NextPage<CommonProps> = (props) => {
   const { t } = useTranslation('admin');
@@ -36,8 +45,10 @@ const AdminAppPage: NextPage<CommonProps> = (props) => {
   const injectableContainers: Container<any>[] = useMemo(() => [], []);
 
   useEffect(() => {
-    (async() => {
-      const AdminAppContainer = (await import('~/client/services/AdminAppContainer')).default;
+    (async () => {
+      const AdminAppContainer = (
+        await import('~/client/services/AdminAppContainer')
+      ).default;
       const adminAppContainer = new AdminAppContainer();
       injectableContainers.push(adminAppContainer);
     })();
@@ -59,10 +70,11 @@ const AdminAppPage: NextPage<CommonProps> = (props) => {
   );
 };
 
-export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
+export const getServerSideProps: GetServerSideProps = async (
+  context: GetServerSidePropsContext,
+) => {
   const props = await retrieveServerSideProps(context);
   return props;
 };
 
-
 export default AdminAppPage;

+ 35 - 14
apps/app/src/pages/admin/search.page.tsx

@@ -1,29 +1,44 @@
 import type {
-  NextPage, GetServerSideProps, GetServerSidePropsContext,
+  GetServerSideProps,
+  GetServerSidePropsContext,
+  NextPage,
 } from 'next';
-import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
 import Head from 'next/head';
+import { useTranslation } from 'next-i18next';
 
 import type { CrowiRequest } from '~/interfaces/crowi-request';
 import type { CommonProps } from '~/pages/utils/commons';
 import { generateCustomTitle } from '~/pages/utils/commons';
-import { useIsSearchServiceReachable, useCurrentUser } from '~/stores-universal/context';
+import {
+  useCurrentUser,
+  useIsSearchServiceReachable,
+} from '~/stores-universal/context';
 
 import { retrieveServerSideProps } from '../../utils/admin-page-util';
 
-const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), { ssr: false });
+const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), {
+  ssr: false,
+});
 const FullTextSearchManagement = dynamic(
-  () => import('~/client/components/Admin/FullTextSearchManagement').then(mod => mod.FullTextSearchManagement), { ssr: false },
+  () =>
+    import('~/client/components/Admin/FullTextSearchManagement').then(
+      (mod) => mod.FullTextSearchManagement,
+    ),
+  { ssr: false },
+);
+const ForbiddenPage = dynamic(
+  () =>
+    import('~/client/components/Admin/ForbiddenPage').then(
+      (mod) => mod.ForbiddenPage,
+    ),
+  { ssr: false },
 );
-const ForbiddenPage = dynamic(() => import('~/client/components/Admin/ForbiddenPage').then(mod => mod.ForbiddenPage), { ssr: false });
-
 
 type Props = CommonProps & {
-  isSearchServiceReachable: boolean,
+  isSearchServiceReachable: boolean;
 };
 
-
 const AdminFullTextSearchManagementPage: NextPage<Props> = (props) => {
   const { t } = useTranslation('admin');
   useCurrentUser(props.currentUser ?? null);
@@ -46,7 +61,10 @@ const AdminFullTextSearchManagementPage: NextPage<Props> = (props) => {
   );
 };
 
-const injectServerConfigurations = async(context: GetServerSidePropsContext, props: Props): Promise<void> => {
+const injectServerConfigurations = async (
+  context: GetServerSidePropsContext,
+  props: Props,
+): Promise<void> => {
   const req: CrowiRequest = context.req as CrowiRequest;
   const { crowi } = req;
   const { searchService } = crowi;
@@ -54,11 +72,14 @@ const injectServerConfigurations = async(context: GetServerSidePropsContext, pro
   props.isSearchServiceReachable = searchService.isReachable;
 };
 
-
-export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
-  const props = await retrieveServerSideProps(context, injectServerConfigurations);
+export const getServerSideProps: GetServerSideProps = async (
+  context: GetServerSidePropsContext,
+) => {
+  const props = await retrieveServerSideProps(
+    context,
+    injectServerConfigurations,
+  );
   return props;
 };
 
-
 export default AdminFullTextSearchManagementPage;

+ 58 - 24
apps/app/src/pages/admin/security.page.tsx

@@ -1,32 +1,46 @@
 import { useEffect, useMemo } from 'react';
-
 import type {
-  NextPage, GetServerSideProps, GetServerSidePropsContext,
+  GetServerSideProps,
+  GetServerSidePropsContext,
+  NextPage,
 } from 'next';
-import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
 import Head from 'next/head';
+import { useTranslation } from 'next-i18next';
 import type { Container } from 'unstated';
 import { Provider } from 'unstated';
 
 import type { CrowiRequest } from '~/interfaces/crowi-request';
 import type { CommonProps } from '~/pages/utils/commons';
 import { generateCustomTitle } from '~/pages/utils/commons';
-import { useCurrentUser, useIsMailerSetup, useSiteUrl } from '~/stores-universal/context';
+import {
+  useCurrentUser,
+  useIsMailerSetup,
+  useSiteUrl,
+} from '~/stores-universal/context';
 
 import { retrieveServerSideProps } from '../../utils/admin-page-util';
 
-const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), { ssr: false });
-const SecurityManagement = dynamic(() => import('~/client/components/Admin/Security/SecurityManagement'), { ssr: false });
-const ForbiddenPage = dynamic(() => import('~/client/components/Admin/ForbiddenPage').then(mod => mod.ForbiddenPage), { ssr: false });
-
+const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), {
+  ssr: false,
+});
+const SecurityManagement = dynamic(
+  () => import('~/client/components/Admin/Security/SecurityManagement'),
+  { ssr: false },
+);
+const ForbiddenPage = dynamic(
+  () =>
+    import('~/client/components/Admin/ForbiddenPage').then(
+      (mod) => mod.ForbiddenPage,
+    ),
+  { ssr: false },
+);
 
 type Props = CommonProps & {
-  isMailerSetup: boolean,
-  siteUrl: string,
+  isMailerSetup: boolean;
+  siteUrl: string;
 };
 
-
 const AdminSecuritySettingsPage: NextPage<Props> = (props) => {
   const { t } = useTranslation('admin');
   useCurrentUser(props.currentUser ?? null);
@@ -39,26 +53,40 @@ const AdminSecuritySettingsPage: NextPage<Props> = (props) => {
   const adminSecurityContainers: Container<any>[] = useMemo(() => [], []);
 
   useEffect(() => {
-    (async() => {
-      const AdminGeneralSecurityContainer = (await import('~/client/services/AdminGeneralSecurityContainer')).default;
+    (async () => {
+      const AdminGeneralSecurityContainer = (
+        await import('~/client/services/AdminGeneralSecurityContainer')
+      ).default;
       const adminGeneralSecurityContainer = new AdminGeneralSecurityContainer();
 
-      const AdminLocalSecurityContainer = (await import('~/client/services/AdminLocalSecurityContainer')).default;
+      const AdminLocalSecurityContainer = (
+        await import('~/client/services/AdminLocalSecurityContainer')
+      ).default;
       const adminLocalSecurityContainer = new AdminLocalSecurityContainer();
 
-      const AdminLdapSecurityContainer = (await import('~/client/services/AdminLdapSecurityContainer')).default;
+      const AdminLdapSecurityContainer = (
+        await import('~/client/services/AdminLdapSecurityContainer')
+      ).default;
       const adminLdapSecurityContainer = new AdminLdapSecurityContainer();
 
-      const AdminSamlSecurityContainer = (await import('~/client/services/AdminSamlSecurityContainer')).default;
+      const AdminSamlSecurityContainer = (
+        await import('~/client/services/AdminSamlSecurityContainer')
+      ).default;
       const adminSamlSecurityContainer = new AdminSamlSecurityContainer();
 
-      const AdminOidcSecurityContainer = (await import('~/client/services/AdminOidcSecurityContainer')).default;
+      const AdminOidcSecurityContainer = (
+        await import('~/client/services/AdminOidcSecurityContainer')
+      ).default;
       const adminOidcSecurityContainer = new AdminOidcSecurityContainer();
 
-      const AdminGoogleSecurityContainer = (await import('~/client/services/AdminGoogleSecurityContainer')).default;
+      const AdminGoogleSecurityContainer = (
+        await import('~/client/services/AdminGoogleSecurityContainer')
+      ).default;
       const adminGoogleSecurityContainer = new AdminGoogleSecurityContainer();
 
-      const AdminGitHubSecurityContainer = (await import('~/client/services/AdminGitHubSecurityContainer')).default;
+      const AdminGitHubSecurityContainer = (
+        await import('~/client/services/AdminGitHubSecurityContainer')
+      ).default;
       const adminGitHubSecurityContainer = new AdminGitHubSecurityContainer();
 
       adminSecurityContainers.push(
@@ -89,7 +117,10 @@ const AdminSecuritySettingsPage: NextPage<Props> = (props) => {
   );
 };
 
-const injectServerConfigurations = async(context: GetServerSidePropsContext, props: Props): Promise<void> => {
+const injectServerConfigurations = async (
+  context: GetServerSidePropsContext,
+  props: Props,
+): Promise<void> => {
   const req: CrowiRequest = context.req as CrowiRequest;
   const { crowi } = req;
   const { growiInfoService, mailService } = crowi;
@@ -98,11 +129,14 @@ const injectServerConfigurations = async(context: GetServerSidePropsContext, pro
   props.isMailerSetup = mailService.isMailerSetup;
 };
 
-
-export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
-  const props = await retrieveServerSideProps(context, injectServerConfigurations);
+export const getServerSideProps: GetServerSideProps = async (
+  context: GetServerSidePropsContext,
+) => {
+  const props = await retrieveServerSideProps(
+    context,
+    injectServerConfigurations,
+  );
   return props;
 };
 
-
 export default AdminSecuritySettingsPage;

+ 30 - 14
apps/app/src/pages/admin/slack-integration-legacy.page.tsx

@@ -1,11 +1,12 @@
 import { useEffect, useMemo } from 'react';
-
 import type {
-  NextPage, GetServerSideProps, GetServerSidePropsContext,
+  GetServerSideProps,
+  GetServerSidePropsContext,
+  NextPage,
 } from 'next';
-import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
 import Head from 'next/head';
+import { useTranslation } from 'next-i18next';
 import type { Container } from 'unstated';
 import { Provider } from 'unstated';
 
@@ -15,9 +16,23 @@ import { useCurrentUser } from '~/stores-universal/context';
 
 import { retrieveServerSideProps } from '../../utils/admin-page-util';
 
-const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), { ssr: false });
-const LegacySlackIntegration = dynamic(() => import('~/client/components/Admin/LegacySlackIntegration/LegacySlackIntegration'), { ssr: false });
-const ForbiddenPage = dynamic(() => import('~/client/components/Admin/ForbiddenPage').then(mod => mod.ForbiddenPage), { ssr: false });
+const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), {
+  ssr: false,
+});
+const LegacySlackIntegration = dynamic(
+  () =>
+    import(
+      '~/client/components/Admin/LegacySlackIntegration/LegacySlackIntegration'
+    ),
+  { ssr: false },
+);
+const ForbiddenPage = dynamic(
+  () =>
+    import('~/client/components/Admin/ForbiddenPage').then(
+      (mod) => mod.ForbiddenPage,
+    ),
+  { ssr: false },
+);
 
 const AdminLegacySlackIntegrationPage: NextPage<CommonProps> = (props) => {
   const { t } = useTranslation('admin');
@@ -29,9 +44,12 @@ const AdminLegacySlackIntegrationPage: NextPage<CommonProps> = (props) => {
   const injectableContainers: Container<any>[] = useMemo(() => [], []);
 
   useEffect(() => {
-    (async() => {
-      const AdminSlackIntegrationLegacyContainer = (await import('~/client/services/AdminSlackIntegrationLegacyContainer')).default;
-      const adminSlackIntegrationLegacyContainer = new AdminSlackIntegrationLegacyContainer();
+    (async () => {
+      const AdminSlackIntegrationLegacyContainer = (
+        await import('~/client/services/AdminSlackIntegrationLegacyContainer')
+      ).default;
+      const adminSlackIntegrationLegacyContainer =
+        new AdminSlackIntegrationLegacyContainer();
       injectableContainers.push(adminSlackIntegrationLegacyContainer);
     })();
   }, [injectableContainers]);
@@ -40,7 +58,6 @@ const AdminLegacySlackIntegrationPage: NextPage<CommonProps> = (props) => {
     return <ForbiddenPage />;
   }
 
-
   return (
     <Provider inject={[...injectableContainers]}>
       <AdminLayout componentTitle={title}>
@@ -51,14 +68,13 @@ const AdminLegacySlackIntegrationPage: NextPage<CommonProps> = (props) => {
       </AdminLayout>
     </Provider>
   );
-
 };
 
-
-export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
+export const getServerSideProps: GetServerSideProps = async (
+  context: GetServerSidePropsContext,
+) => {
   const props = await retrieveServerSideProps(context);
   return props;
 };
 
-
 export default AdminLegacySlackIntegrationPage;

+ 33 - 15
apps/app/src/pages/admin/slack-integration.page.tsx

@@ -1,9 +1,11 @@
 import type {
-  NextPage, GetServerSideProps, GetServerSidePropsContext,
+  GetServerSideProps,
+  GetServerSidePropsContext,
+  NextPage,
 } from 'next';
-import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
 import Head from 'next/head';
+import { useTranslation } from 'next-i18next';
 
 import type { CrowiRequest } from '~/interfaces/crowi-request';
 import type { CommonProps } from '~/pages/utils/commons';
@@ -12,17 +14,28 @@ import { useCurrentUser, useSiteUrl } from '~/stores-universal/context';
 
 import { retrieveServerSideProps } from '../../utils/admin-page-util';
 
-
-const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), { ssr: false });
-const SlackIntegration = dynamic(() => import('~/client/components/Admin/SlackIntegration/SlackIntegration').then(mod => mod.SlackIntegration), { ssr: false });
-const ForbiddenPage = dynamic(() => import('~/client/components/Admin/ForbiddenPage').then(mod => mod.ForbiddenPage), { ssr: false });
-
+const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), {
+  ssr: false,
+});
+const SlackIntegration = dynamic(
+  () =>
+    import('~/client/components/Admin/SlackIntegration/SlackIntegration').then(
+      (mod) => mod.SlackIntegration,
+    ),
+  { ssr: false },
+);
+const ForbiddenPage = dynamic(
+  () =>
+    import('~/client/components/Admin/ForbiddenPage').then(
+      (mod) => mod.ForbiddenPage,
+    ),
+  { ssr: false },
+);
 
 type Props = CommonProps & {
-  siteUrl: string
+  siteUrl: string;
 };
 
-
 const AdminSlackIntegrationPage: NextPage<Props> = (props) => {
   const { t } = useTranslation('admin');
   useCurrentUser(props.currentUser ?? null);
@@ -45,8 +58,10 @@ const AdminSlackIntegrationPage: NextPage<Props> = (props) => {
   );
 };
 
-
-const injectServerConfigurations = async(context: GetServerSidePropsContext, props: Props): Promise<void> => {
+const injectServerConfigurations = async (
+  context: GetServerSidePropsContext,
+  props: Props,
+): Promise<void> => {
   const req: CrowiRequest = context.req as CrowiRequest;
   const { crowi } = req;
   const { growiInfoService } = crowi;
@@ -54,11 +69,14 @@ const injectServerConfigurations = async(context: GetServerSidePropsContext, pro
   props.siteUrl = growiInfoService.getSiteUrl();
 };
 
-
-export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
-  const props = await retrieveServerSideProps(context, injectServerConfigurations);
+export const getServerSideProps: GetServerSideProps = async (
+  context: GetServerSidePropsContext,
+) => {
+  const props = await retrieveServerSideProps(
+    context,
+    injectServerConfigurations,
+  );
   return props;
 };
 
-
 export default AdminSlackIntegrationPage;

+ 41 - 16
apps/app/src/pages/admin/user-group-detail/[userGroupId].page.tsx

@@ -1,26 +1,39 @@
 import type {
-  NextPage, GetServerSideProps, GetServerSidePropsContext,
+  GetServerSideProps,
+  GetServerSidePropsContext,
+  NextPage,
 } from 'next';
-import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
 import Head from 'next/head';
 import { useRouter } from 'next/router';
+import { useTranslation } from 'next-i18next';
 
 import type { CrowiRequest } from '~/interfaces/crowi-request';
 import type { CommonProps } from '~/pages/utils/commons';
 import { generateCustomTitle } from '~/pages/utils/commons';
-import { useIsAclEnabled, useCurrentUser } from '~/stores-universal/context';
 import { useIsMaintenanceMode } from '~/stores/maintenanceMode';
+import { useCurrentUser, useIsAclEnabled } from '~/stores-universal/context';
 
 import { retrieveServerSideProps } from '../../../utils/admin-page-util';
 
-const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), { ssr: false });
-const UserGroupDetailPage = dynamic(() => import('~/client/components/Admin/UserGroupDetail/UserGroupDetailPage'), { ssr: false });
-const ForbiddenPage = dynamic(() => import('~/client/components/Admin/ForbiddenPage').then(mod => mod.ForbiddenPage), { ssr: false });
+const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), {
+  ssr: false,
+});
+const UserGroupDetailPage = dynamic(
+  () => import('~/client/components/Admin/UserGroupDetail/UserGroupDetailPage'),
+  { ssr: false },
+);
+const ForbiddenPage = dynamic(
+  () =>
+    import('~/client/components/Admin/ForbiddenPage').then(
+      (mod) => mod.ForbiddenPage,
+    ),
+  { ssr: false },
+);
 
 type Props = CommonProps & {
-  isAclEnabled: boolean
-}
+  isAclEnabled: boolean;
+};
 
 const AdminUserGroupDetailPage: NextPage<Props> = (props: Props) => {
   const { t } = useTranslation('admin');
@@ -32,7 +45,9 @@ const AdminUserGroupDetailPage: NextPage<Props> = (props: Props) => {
   const title = t('user_group_management.user_group_management');
   const customTitle = generateCustomTitle(props, title);
 
-  const currentUserGroupId = Array.isArray(userGroupId) ? userGroupId[0] : userGroupId;
+  const currentUserGroupId = Array.isArray(userGroupId)
+    ? userGroupId[0]
+    : userGroupId;
 
   const isExternalGroupBool = isExternalGroup === 'true';
 
@@ -47,21 +62,31 @@ const AdminUserGroupDetailPage: NextPage<Props> = (props: Props) => {
       <Head>
         <title>{customTitle}</title>
       </Head>
-      {
-        currentUserGroupId != null && router.isReady
-      && <UserGroupDetailPage userGroupId={currentUserGroupId} isExternalGroup={isExternalGroupBool} />
-      }
+      {currentUserGroupId != null && router.isReady && (
+        <UserGroupDetailPage
+          userGroupId={currentUserGroupId}
+          isExternalGroup={isExternalGroupBool}
+        />
+      )}
     </AdminLayout>
   );
 };
 
-const injectServerConfigurations = async(context: GetServerSidePropsContext, props: Props): Promise<void> => {
+const injectServerConfigurations = async (
+  context: GetServerSidePropsContext,
+  props: Props,
+): Promise<void> => {
   const req: CrowiRequest = context.req as CrowiRequest;
   props.isAclEnabled = req.crowi.aclService.isAclEnabled();
 };
 
-export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
-  const props = await retrieveServerSideProps(context, injectServerConfigurations);
+export const getServerSideProps: GetServerSideProps = async (
+  context: GetServerSidePropsContext,
+) => {
+  const props = await retrieveServerSideProps(
+    context,
+    injectServerConfigurations,
+  );
 
   return props;
 };

+ 34 - 14
apps/app/src/pages/admin/user-groups.page.tsx

@@ -1,27 +1,41 @@
 import type {
-  NextPage, GetServerSideProps, GetServerSidePropsContext,
+  GetServerSideProps,
+  GetServerSidePropsContext,
+  NextPage,
 } from 'next';
-import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
 import Head from 'next/head';
+import { useTranslation } from 'next-i18next';
 
 import type { CrowiRequest } from '~/interfaces/crowi-request';
 import type { CommonProps } from '~/pages/utils/commons';
 import { generateCustomTitle } from '~/pages/utils/commons';
-import { useIsAclEnabled, useCurrentUser } from '~/stores-universal/context';
+import { useCurrentUser, useIsAclEnabled } from '~/stores-universal/context';
 
 import { retrieveServerSideProps } from '../../utils/admin-page-util';
 
-const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), { ssr: false });
-const UserGroupPage = dynamic(() => import('~/client/components/Admin/UserGroup/UserGroupPage').then(mod => mod.UserGroupPage), { ssr: false });
-const ForbiddenPage = dynamic(() => import('~/client/components/Admin/ForbiddenPage').then(mod => mod.ForbiddenPage), { ssr: false });
-
+const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), {
+  ssr: false,
+});
+const UserGroupPage = dynamic(
+  () =>
+    import('~/client/components/Admin/UserGroup/UserGroupPage').then(
+      (mod) => mod.UserGroupPage,
+    ),
+  { ssr: false },
+);
+const ForbiddenPage = dynamic(
+  () =>
+    import('~/client/components/Admin/ForbiddenPage').then(
+      (mod) => mod.ForbiddenPage,
+    ),
+  { ssr: false },
+);
 
 type Props = CommonProps & {
-  isAclEnabled: boolean
+  isAclEnabled: boolean;
 };
 
-
 const AdminUserGroupPage: NextPage<Props> = (props) => {
   const { t } = useTranslation('admin');
   useCurrentUser(props.currentUser ?? null);
@@ -44,8 +58,10 @@ const AdminUserGroupPage: NextPage<Props> = (props) => {
   );
 };
 
-
-const injectServerConfigurations = async(context: GetServerSidePropsContext, props: Props): Promise<void> => {
+const injectServerConfigurations = async (
+  context: GetServerSidePropsContext,
+  props: Props,
+): Promise<void> => {
   const req: CrowiRequest = context.req as CrowiRequest;
   const { crowi } = req;
   const { aclService } = crowi;
@@ -53,10 +69,14 @@ const injectServerConfigurations = async(context: GetServerSidePropsContext, pro
   props.isAclEnabled = aclService.isAclEnabled();
 };
 
-export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
-  const props = await retrieveServerSideProps(context, injectServerConfigurations);
+export const getServerSideProps: GetServerSideProps = async (
+  context: GetServerSidePropsContext,
+) => {
+  const props = await retrieveServerSideProps(
+    context,
+    injectServerConfigurations,
+  );
   return props;
 };
 
-
 export default AdminUserGroupPage;

+ 27 - 14
apps/app/src/pages/admin/users/external-accounts.page.tsx

@@ -1,11 +1,12 @@
 import { useEffect, useMemo } from 'react';
-
 import type {
-  NextPage, GetServerSideProps, GetServerSidePropsContext,
+  GetServerSideProps,
+  GetServerSidePropsContext,
+  NextPage,
 } from 'next';
-import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
 import Head from 'next/head';
+import { useTranslation } from 'next-i18next';
 import type { Container } from 'unstated';
 import { Provider } from 'unstated';
 
@@ -14,10 +15,20 @@ import { useCurrentUser } from '~/stores-universal/context';
 
 import { retrieveServerSideProps } from '../../../utils/admin-page-util';
 
-const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), { ssr: false });
-const ManageExternalAccount = dynamic(() => import('~/client/components/Admin/ManageExternalAccount'), { ssr: false });
-const ForbiddenPage = dynamic(() => import('~/client/components/Admin/ForbiddenPage').then(mod => mod.ForbiddenPage), { ssr: false });
-
+const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), {
+  ssr: false,
+});
+const ManageExternalAccount = dynamic(
+  () => import('~/client/components/Admin/ManageExternalAccount'),
+  { ssr: false },
+);
+const ForbiddenPage = dynamic(
+  () =>
+    import('~/client/components/Admin/ForbiddenPage').then(
+      (mod) => mod.ForbiddenPage,
+    ),
+  { ssr: false },
+);
 
 const AdminUserManagementPage: NextPage<CommonProps> = (props) => {
   const { t } = useTranslation('admin');
@@ -28,9 +39,12 @@ const AdminUserManagementPage: NextPage<CommonProps> = (props) => {
   const injectableContainers: Container<any>[] = useMemo(() => [], []);
 
   useEffect(() => {
-    (async() => {
-      const AdminExternalAccountsContainer = (await import('~/client/services/AdminExternalAccountsContainer')).default;
-      const adminExternalAccountsContainer = new AdminExternalAccountsContainer();
+    (async () => {
+      const AdminExternalAccountsContainer = (
+        await import('~/client/services/AdminExternalAccountsContainer')
+      ).default;
+      const adminExternalAccountsContainer =
+        new AdminExternalAccountsContainer();
       injectableContainers.push(adminExternalAccountsContainer);
     })();
   }, [injectableContainers]);
@@ -49,14 +63,13 @@ const AdminUserManagementPage: NextPage<CommonProps> = (props) => {
       </AdminLayout>
     </Provider>
   );
-
 };
 
-
-export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
+export const getServerSideProps: GetServerSideProps = async (
+  context: GetServerSidePropsContext,
+) => {
   const props = await retrieveServerSideProps(context);
   return props;
 };
 
-
 export default AdminUserManagementPage;

+ 34 - 16
apps/app/src/pages/admin/users/index.page.tsx

@@ -1,11 +1,12 @@
 import { useEffect, useMemo } from 'react';
-
 import type {
-  NextPage, GetServerSideProps, GetServerSidePropsContext,
+  GetServerSideProps,
+  GetServerSidePropsContext,
+  NextPage,
 } from 'next';
-import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
 import Head from 'next/head';
+import { useTranslation } from 'next-i18next';
 import type { Container } from 'unstated';
 import { Provider } from 'unstated';
 
@@ -16,16 +17,25 @@ import { useCurrentUser, useIsMailerSetup } from '~/stores-universal/context';
 
 import { retrieveServerSideProps } from '../../../utils/admin-page-util';
 
-const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), { ssr: false });
-const UserManagement = dynamic(() => import('~/client/components/Admin/UserManagement'), { ssr: false });
-const ForbiddenPage = dynamic(() => import('~/client/components/Admin/ForbiddenPage').then(mod => mod.ForbiddenPage), { ssr: false });
-
+const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), {
+  ssr: false,
+});
+const UserManagement = dynamic(
+  () => import('~/client/components/Admin/UserManagement'),
+  { ssr: false },
+);
+const ForbiddenPage = dynamic(
+  () =>
+    import('~/client/components/Admin/ForbiddenPage').then(
+      (mod) => mod.ForbiddenPage,
+    ),
+  { ssr: false },
+);
 
 type Props = CommonProps & {
-  isMailerSetup: boolean,
+  isMailerSetup: boolean;
 };
 
-
 const AdminUserManagementPage: NextPage<Props> = (props) => {
   const { t } = useTranslation('admin');
   useCurrentUser(props.currentUser ?? null);
@@ -37,8 +47,10 @@ const AdminUserManagementPage: NextPage<Props> = (props) => {
   const injectableContainers: Container<any>[] = useMemo(() => [], []);
 
   useEffect(() => {
-    (async() => {
-      const AdminUsersContainer = (await import('~/client/services/AdminUsersContainer')).default;
+    (async () => {
+      const AdminUsersContainer = (
+        await import('~/client/services/AdminUsersContainer')
+      ).default;
       const adminUsersContainer = new AdminUsersContainer();
       injectableContainers.push(adminUsersContainer);
     })();
@@ -58,10 +70,12 @@ const AdminUserManagementPage: NextPage<Props> = (props) => {
       </AdminLayout>
     </Provider>
   );
-
 };
 
-const injectServerConfigurations = async(context: GetServerSidePropsContext, props: Props): Promise<void> => {
+const injectServerConfigurations = async (
+  context: GetServerSidePropsContext,
+  props: Props,
+): Promise<void> => {
   const req: CrowiRequest = context.req as CrowiRequest;
   const { crowi } = req;
   const { mailService } = crowi;
@@ -69,10 +83,14 @@ const injectServerConfigurations = async(context: GetServerSidePropsContext, pro
   props.isMailerSetup = mailService.isMailerSetup;
 };
 
-export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
-  const props = await retrieveServerSideProps(context, injectServerConfigurations);
+export const getServerSideProps: GetServerSideProps = async (
+  context: GetServerSidePropsContext,
+) => {
+  const props = await retrieveServerSideProps(
+    context,
+    injectServerConfigurations,
+  );
   return props;
 };
 
-
 export default AdminUserManagementPage;

+ 53 - 20
apps/app/src/pages/forgot-password-errors.page.tsx

@@ -1,17 +1,23 @@
 import React from 'react';
-
-import type { NextPage, GetServerSideProps, GetServerSidePropsContext } from 'next';
+import type {
+  GetServerSideProps,
+  GetServerSidePropsContext,
+  NextPage,
+} from 'next';
+import Link from 'next/link';
 import { useTranslation } from 'next-i18next';
 import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
-import Link from 'next/link';
 
 import { forgotPasswordErrorCode } from '~/interfaces/errors/forgot-password';
 
 import type { CommonProps } from './utils/commons';
-import { getNextI18NextConfig, getServerSideCommonProps } from './utils/commons';
+import {
+  getNextI18NextConfig,
+  getServerSideCommonProps,
+} from './utils/commons';
 
 type Props = CommonProps & {
-  errorCode?: forgotPasswordErrorCode
+  errorCode?: forgotPasswordErrorCode;
 };
 
 const ForgotPasswordErrorsPage: NextPage<Props> = (props: Props) => {
@@ -25,28 +31,45 @@ const ForgotPasswordErrorsPage: NextPage<Props> = (props: Props) => {
           <div className="row justify-content-md-center">
             <div className="col-md-6 mt-5">
               <div className="text-center">
-                <h1><span className="material-symbols-outlined large">lock_open</span></h1>
-                <h2 className="text-center">{ t('forgot_password.reset_password') }</h2>
-
-                { errorCode == null && (
+                <h1>
+                  <span className="material-symbols-outlined large">
+                    lock_open
+                  </span>
+                </h1>
+                <h2 className="text-center">
+                  {t('forgot_password.reset_password')}
+                </h2>
+
+                {errorCode == null && (
                   <h3 className="text-muted">errorCode Unknown</h3>
                 )}
 
-                { errorCode === forgotPasswordErrorCode.PASSWORD_RESET_IS_UNAVAILABLE && (
-                  <h3 className="text-muted">{ t('forgot_password.feature_is_unavailable') }</h3>
+                {errorCode ===
+                  forgotPasswordErrorCode.PASSWORD_RESET_IS_UNAVAILABLE && (
+                  <h3 className="text-muted">
+                    {t('forgot_password.feature_is_unavailable')}
+                  </h3>
                 )}
 
-                { (errorCode === forgotPasswordErrorCode.PASSWORD_RESET_ORDER_IS_NOT_APPROPRIATE || errorCode === forgotPasswordErrorCode.TOKEN_NOT_FOUND) && (
+                {(errorCode ===
+                  forgotPasswordErrorCode.PASSWORD_RESET_ORDER_IS_NOT_APPROPRIATE ||
+                  errorCode === forgotPasswordErrorCode.TOKEN_NOT_FOUND) && (
                   <div>
                     <div className="alert alert-warning mb-3">
-                      <h2>{ t('forgot_password.incorrect_token_or_expired_url') }</h2>
+                      <h2>
+                        {t('forgot_password.incorrect_token_or_expired_url')}
+                      </h2>
                     </div>
-                    <Link href="/forgot-password" className="link-switch" prefetch={false}>
-                      <span className="material-symbols-outlined">key</span> { t('forgot_password.forgot_password') }
+                    <Link
+                      href="/forgot-password"
+                      className="link-switch"
+                      prefetch={false}
+                    >
+                      <span className="material-symbols-outlined">key</span>{' '}
+                      {t('forgot_password.forgot_password')}
                     </Link>
                   </div>
-                ) }
-
+                )}
               </div>
             </div>
           </div>
@@ -56,12 +79,22 @@ const ForgotPasswordErrorsPage: NextPage<Props> = (props: Props) => {
   );
 };
 
-async function injectNextI18NextConfigurations(context: GetServerSidePropsContext, props: Props, namespacesRequired?: string[] | undefined): Promise<void> {
-  const nextI18NextConfig = await getNextI18NextConfig(serverSideTranslations, context, namespacesRequired);
+async function injectNextI18NextConfigurations(
+  context: GetServerSidePropsContext,
+  props: Props,
+  namespacesRequired?: string[] | undefined,
+): Promise<void> {
+  const nextI18NextConfig = await getNextI18NextConfig(
+    serverSideTranslations,
+    context,
+    namespacesRequired,
+  );
   props._nextI18Next = nextI18NextConfig._nextI18Next;
 }
 
-export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
+export const getServerSideProps: GetServerSideProps = async (
+  context: GetServerSidePropsContext,
+) => {
   const result = await getServerSideCommonProps(context);
 
   // check for presence

+ 36 - 11
apps/app/src/pages/forgot-password.page.tsx

@@ -1,20 +1,29 @@
 import React from 'react';
-
-import type { NextPage, GetServerSideProps, GetServerSidePropsContext } from 'next';
-import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
+import type {
+  GetServerSideProps,
+  GetServerSidePropsContext,
+  NextPage,
+} from 'next';
 import dynamic from 'next/dynamic';
+import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
 
 import { RawLayout } from '~/components/Layout/RawLayout';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
 import { useIsMailerSetup } from '~/stores-universal/context';
 
 import type { CommonProps } from './utils/commons';
-import { getNextI18NextConfig, getServerSideCommonProps } from './utils/commons';
+import {
+  getNextI18NextConfig,
+  getServerSideCommonProps,
+} from './utils/commons';
 
-const PasswordResetRequestForm = dynamic(() => import('~/client/components/PasswordResetRequestForm'), { ssr: false });
+const PasswordResetRequestForm = dynamic(
+  () => import('~/client/components/PasswordResetRequestForm'),
+  { ssr: false },
+);
 
 type Props = CommonProps & {
-  isMailerSetup: boolean,
+  isMailerSetup: boolean;
 };
 
 const ForgotPasswordPage: NextPage<Props> = (props: Props) => {
@@ -40,12 +49,23 @@ const ForgotPasswordPage: NextPage<Props> = (props: Props) => {
 };
 
 // eslint-disable-next-line max-len
-async function injectNextI18NextConfigurations(context: GetServerSidePropsContext, props: Props, namespacesRequired?: string[] | undefined): Promise<void> {
-  const nextI18NextConfig = await getNextI18NextConfig(serverSideTranslations, context, namespacesRequired);
+async function injectNextI18NextConfigurations(
+  context: GetServerSidePropsContext,
+  props: Props,
+  namespacesRequired?: string[] | undefined,
+): Promise<void> {
+  const nextI18NextConfig = await getNextI18NextConfig(
+    serverSideTranslations,
+    context,
+    namespacesRequired,
+  );
   props._nextI18Next = nextI18NextConfig._nextI18Next;
 }
 
-const injectServerConfigurations = async(context: GetServerSidePropsContext, props: Props): Promise<void> => {
+const injectServerConfigurations = async (
+  context: GetServerSidePropsContext,
+  props: Props,
+): Promise<void> => {
   const req: CrowiRequest = context.req as CrowiRequest;
   const { crowi } = req;
   const { mailService } = crowi;
@@ -53,7 +73,9 @@ const injectServerConfigurations = async(context: GetServerSidePropsContext, pro
   props.isMailerSetup = mailService.isMailerSetup;
 };
 
-export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
+export const getServerSideProps: GetServerSideProps = async (
+  context: GetServerSidePropsContext,
+) => {
   const result = await getServerSideCommonProps(context);
 
   // check for presence
@@ -65,7 +87,10 @@ export const getServerSideProps: GetServerSideProps = async(context: GetServerSi
   const props: Props = result.props as Props;
 
   injectServerConfigurations(context, props);
-  await injectNextI18NextConfigurations(context, props, ['translation', 'commons']);
+  await injectNextI18NextConfigurations(context, props, [
+    'translation',
+    'commons',
+  ]);
 
   return {
     props,

+ 74 - 24
apps/app/src/pages/installer.page.tsx

@@ -1,56 +1,95 @@
 import React, { useMemo } from 'react';
-
 import type {
-  NextPage, GetServerSideProps, GetServerSidePropsContext,
+  GetServerSideProps,
+  GetServerSidePropsContext,
+  NextPage,
 } from 'next';
-import { useTranslation } from 'next-i18next';
-import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
 import dynamic from 'next/dynamic';
 import Head from 'next/head';
+import { useTranslation } from 'next-i18next';
+import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
 
 import { NoLoginLayout } from '~/components/Layout/NoLoginLayout';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
 import {
-  useCsrfToken, useAppTitle, useSiteUrl, useConfidential, useGrowiCloudUri,
+  useAppTitle,
+  useConfidential,
+  useCsrfToken,
+  useGrowiCloudUri,
+  useSiteUrl,
 } from '~/stores-universal/context';
 
 import type { CommonProps } from './utils/commons';
-import { getNextI18NextConfig, getServerSideCommonProps, generateCustomTitle } from './utils/commons';
-
-
-const InstallerForm = dynamic(() => import('~/client/components/InstallerForm'), { ssr: false });
-const DataTransferForm = dynamic(() => import('~/client/components/DataTransferForm'), { ssr: false });
-const CustomNavAndContents = dynamic(() => import('~/client/components/CustomNavigation/CustomNavAndContents'), { ssr: false });
-
-async function injectNextI18NextConfigurations(context: GetServerSidePropsContext, props: Props, namespacesRequired?: string[] | undefined): Promise<void> {
-  const nextI18NextConfig = await getNextI18NextConfig(serverSideTranslations, context, namespacesRequired, true);
+import {
+  generateCustomTitle,
+  getNextI18NextConfig,
+  getServerSideCommonProps,
+} from './utils/commons';
+
+const InstallerForm = dynamic(
+  () => import('~/client/components/InstallerForm'),
+  { ssr: false },
+);
+const DataTransferForm = dynamic(
+  () => import('~/client/components/DataTransferForm'),
+  { ssr: false },
+);
+const CustomNavAndContents = dynamic(
+  () => import('~/client/components/CustomNavigation/CustomNavAndContents'),
+  { ssr: false },
+);
+
+async function injectNextI18NextConfigurations(
+  context: GetServerSidePropsContext,
+  props: Props,
+  namespacesRequired?: string[] | undefined,
+): Promise<void> {
+  const nextI18NextConfig = await getNextI18NextConfig(
+    serverSideTranslations,
+    context,
+    namespacesRequired,
+    true,
+  );
   props._nextI18Next = nextI18NextConfig._nextI18Next;
 }
 
 type Props = CommonProps & {
-  minPasswordLength: number,
-  pageWithMetaStr: string,
+  minPasswordLength: number;
+  pageWithMetaStr: string;
 };
 
+const UserInfoIcon = () => (
+  <span className="material-symbols-outlined me-2">person</span>
+);
+
+const ExternalAccountsIcon = () => (
+  <span className="growi-custom-icons me-2">external_link</span>
+);
+
 const InstallerPage: NextPage<Props> = (props: Props) => {
   const { t } = useTranslation();
   const { t: tCommons } = useTranslation('commons');
 
+  const BoundInstallerForm = useMemo(
+    () => () => <InstallerForm minPasswordLength={props.minPasswordLength} />,
+    [props.minPasswordLength],
+  );
+
   const navTabMapping = useMemo(() => {
     return {
       user_infomation: {
-        Icon: () => <span className="material-symbols-outlined me-2">person</span>,
-        Content: () => <InstallerForm minPasswordLength={props.minPasswordLength} />,
+        Icon: UserInfoIcon,
+        Content: BoundInstallerForm,
         i18n: t('installer.tab'),
       },
       external_accounts: {
         // TODO: chack and fix font-size. see: https://redmine.weseek.co.jp/issues/143015
-        Icon: () => <span className="growi-custom-icons me-2">external_link</span>,
+        Icon: ExternalAccountsIcon,
         Content: DataTransferForm,
         i18n: tCommons('g2g_data_transfer.tab'),
       },
     };
-  }, [props.minPasswordLength, t, tCommons]);
+  }, [BoundInstallerForm, t, tCommons]);
 
   // commons
   useAppTitle(props.appTitle);
@@ -67,14 +106,23 @@ const InstallerPage: NextPage<Props> = (props: Props) => {
       <Head>
         <title>{title}</title>
       </Head>
-      <div id="installer-form-container" className="nologin-dialog mx-auto rounded-4 rounded-top-0">
-        <CustomNavAndContents navTabMapping={navTabMapping} tabContentClasses={['p-0']} />
+      <div
+        id="installer-form-container"
+        className="nologin-dialog mx-auto rounded-4 rounded-top-0"
+      >
+        <CustomNavAndContents
+          navTabMapping={navTabMapping}
+          tabContentClasses={['p-0']}
+        />
       </div>
     </NoLoginLayout>
   );
 };
 
-async function injectServerConfigurations(context: GetServerSidePropsContext, props: Props): Promise<void> {
+async function injectServerConfigurations(
+  context: GetServerSidePropsContext,
+  props: Props,
+): Promise<void> {
   const req: CrowiRequest = context.req as CrowiRequest;
   const { crowi } = req;
   const { configManager } = crowi;
@@ -82,7 +130,9 @@ async function injectServerConfigurations(context: GetServerSidePropsContext, pr
   props.minPasswordLength = configManager.getConfig('app:minPasswordLength');
 }
 
-export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
+export const getServerSideProps: GetServerSideProps = async (
+  context: GetServerSidePropsContext,
+) => {
   const result = await getServerSideCommonProps(context);
 
   // check for presence

+ 48 - 19
apps/app/src/pages/invited.page.tsx

@@ -1,27 +1,42 @@
 import React from 'react';
-
+import type {
+  GetServerSideProps,
+  GetServerSidePropsContext,
+  NextPage,
+} from 'next';
+import dynamic from 'next/dynamic';
+import Head from 'next/head';
 import type { IUser } from '@growi/core';
 import { USER_STATUS } from '@growi/core';
-import type { NextPage, GetServerSideProps, GetServerSidePropsContext } from 'next';
 import { useTranslation } from 'next-i18next';
 import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
-import dynamic from 'next/dynamic';
-import Head from 'next/head';
 
 import { NoLoginLayout } from '~/components/Layout/NoLoginLayout';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
-import { useCsrfToken, useCurrentPathname, useCurrentUser } from '~/stores-universal/context';
+import {
+  useCsrfToken,
+  useCurrentPathname,
+  useCurrentUser,
+} from '~/stores-universal/context';
 
 import type { CommonProps } from './utils/commons';
-import { getServerSideCommonProps, generateCustomTitle, getNextI18NextConfig } from './utils/commons';
-
-const InvitedForm = dynamic(() => import('~/client/components/InvitedForm').then(mod => mod.InvitedForm), { ssr: false });
+import {
+  generateCustomTitle,
+  getNextI18NextConfig,
+  getServerSideCommonProps,
+} from './utils/commons';
+
+const InvitedForm = dynamic(
+  () =>
+    import('~/client/components/InvitedForm').then((mod) => mod.InvitedForm),
+  { ssr: false },
+);
 
 type Props = CommonProps & {
-  currentUser: IUser,
-  invitedFormUsername: string,
-  invitedFormName: string,
-}
+  currentUser: IUser;
+  invitedFormUsername: string;
+  invitedFormName: string;
+};
 
 const InvitedPage: NextPage<Props> = (props: Props) => {
   const { t } = useTranslation();
@@ -38,13 +53,18 @@ const InvitedPage: NextPage<Props> = (props: Props) => {
       <Head>
         <title>{title}</title>
       </Head>
-      <InvitedForm invitedFormUsername={props.invitedFormUsername} invitedFormName={props.invitedFormName} />
+      <InvitedForm
+        invitedFormUsername={props.invitedFormUsername}
+        invitedFormName={props.invitedFormName}
+      />
     </NoLoginLayout>
   );
-
 };
 
-async function injectServerConfigurations(context: GetServerSidePropsContext, props: Props): Promise<void> {
+async function injectServerConfigurations(
+  context: GetServerSidePropsContext,
+  props: Props,
+): Promise<void> {
   const req: CrowiRequest = context.req as CrowiRequest;
   const { body: invitedForm } = req;
 
@@ -62,12 +82,22 @@ async function injectServerConfigurations(context: GetServerSidePropsContext, pr
  * @param props
  * @param namespacesRequired
  */
-async function injectNextI18NextConfigurations(context: GetServerSidePropsContext, props: Props, namespacesRequired?: string[] | undefined): Promise<void> {
-  const nextI18NextConfig = await getNextI18NextConfig(serverSideTranslations, context, namespacesRequired);
+async function injectNextI18NextConfigurations(
+  context: GetServerSidePropsContext,
+  props: Props,
+  namespacesRequired?: string[] | undefined,
+): Promise<void> {
+  const nextI18NextConfig = await getNextI18NextConfig(
+    serverSideTranslations,
+    context,
+    namespacesRequired,
+  );
   props._nextI18Next = nextI18NextConfig._nextI18Next;
 }
 
-export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
+export const getServerSideProps: GetServerSideProps = async (
+  context: GetServerSidePropsContext,
+) => {
   const req = context.req as CrowiRequest;
   const { user } = req;
   const result = await getServerSideCommonProps(context);
@@ -91,7 +121,6 @@ export const getServerSideProps: GetServerSideProps = async(context: GetServerSi
         },
       };
     }
-
   }
 
   await injectServerConfigurations(context, props);

+ 72 - 59
apps/app/src/pages/login/error/[message].page.tsx

@@ -1,71 +1,77 @@
 import React from 'react';
-
 import type {
-  NextPage, GetServerSideProps, GetServerSidePropsContext,
+  GetServerSideProps,
+  GetServerSidePropsContext,
+  NextPage,
 } from 'next';
+import { useRouter } from 'next/router';
 import { useTranslation } from 'next-i18next';
 import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
-import { useRouter } from 'next/router';
 
 import { NoLoginLayout } from '~/components/Layout/NoLoginLayout';
 import type { CommonProps } from '~/pages/utils/commons';
-import { getServerSideCommonProps, getNextI18NextConfig } from '~/pages/utils/commons';
-
+import {
+  getNextI18NextConfig,
+  getServerSideCommonProps,
+} from '~/pages/utils/commons';
 
 type Props = CommonProps;
 const classNames: string[] = ['login-page'];
 
-const LoginPage: NextPage<CommonProps> = () => {
-
+const ApprovalPendingUserError = () => {
   const { t } = useTranslation();
-  const router = useRouter();
-  const { message } = router.query;
-
+  return (
+    <>
+      <div className="alert alert-warning">
+        <h2>{t('login.sign_in_error')}</h2>
+      </div>
+      <p>Wait for approved by administrators.</p>
+    </>
+  );
+};
 
-  let loginErrorElm;
+const SuspendedUserError = () => {
+  const { t } = useTranslation();
+  return (
+    <>
+      <div className="alert alert-warning">
+        <h2>{t('login.sign_in_error')}</h2>
+      </div>
+      <p>This account is suspended.</p>
+    </>
+  );
+};
 
-  const ApprovalPendingUserError = () => {
-    return (
-      <>
-        <div className="alert alert-warning">
-          <h2>{ t('login.sign_in_error') }</h2>
-        </div>
-        <p>Wait for approved by administrators.</p>
-      </>
-    );
-  };
+const PasswordResetOrderError = () => {
+  const { t } = useTranslation();
+  return (
+    <>
+      <div className="alert alert-warning mb-3">
+        <h2>{t('forgot_password.incorrect_token_or_expired_url')}</h2>
+      </div>
+      <a href="/forgot-password" className="link-switch">
+        <span className="material-symbols-outlined">key</span>{' '}
+        {t('forgot_password.forgot_password')}
+      </a>
+    </>
+  );
+};
 
-  const SuspendedUserError = () => {
-    return (
-      <>
-        <div className="alert alert-warning">
-          <h2>{ t('login.sign_in_error') }</h2>
-        </div>
-        <p>This account is suspended.</p>
-      </>
-    );
-  };
+const DefaultLoginError = () => {
+  const { t } = useTranslation();
+  return (
+    <div className="alert alert-warning">
+      <h2>{t('login.sign_in_error')}</h2>
+    </div>
+  );
+};
 
-  const PasswordResetOrderError = () => {
-    return (
-      <>
-        <div className="alert alert-warning mb-3">
-          <h2>{ t('forgot_password.incorrect_token_or_expired_url') }</h2>
-        </div>
-        <a href="/forgot-password" className="link-switch">
-          <span className="material-symbols-outlined">key</span> { t('forgot_password.forgot_password') }
-        </a>
-      </>
-    );
-  };
+const LoginPage: NextPage<CommonProps> = () => {
+  const { t } = useTranslation();
+  const router = useRouter();
+  const { message } = router.query;
 
-  const DefaultLoginError = () => {
-    return (
-      <div className="alert alert-warning">
-        <h2>{ t('login.sign_in_error') }</h2>
-      </div>
-    );
-  };
+  let loginErrorElm: JSX.Element;
 
   switch (message) {
     case 'registered':
@@ -81,17 +87,15 @@ const LoginPage: NextPage<CommonProps> = () => {
       loginErrorElm = <DefaultLoginError />;
   }
 
-
   return (
     <NoLoginLayout className={classNames.join(' ')}>
       <div className="mb-4 login-form-errors text-center">
         <div className="nologin-dialog pb-4 mx-auto">
-          <div className="col-12">
-            {loginErrorElm}
-          </div>
+          <div className="col-12">{loginErrorElm}</div>
           {/* If the transition source is "/login", use <a /> tag since the transition will not occur if next/link is used. */}
           <a href="/login">
-            <span className="material-symbols-outlined me-1">login</span>{t('Sign in is here')}
+            <span className="material-symbols-outlined me-1">login</span>
+            {t('Sign in is here')}
           </a>
         </div>
       </div>
@@ -105,13 +109,22 @@ const LoginPage: NextPage<CommonProps> = () => {
  * @param props
  * @param namespacesRequired
  */
-async function injectNextI18NextConfigurations(context: GetServerSidePropsContext, props: Props, namespacesRequired?: string[] | undefined): Promise<void> {
-  const nextI18NextConfig = await getNextI18NextConfig(serverSideTranslations, context, namespacesRequired);
+async function injectNextI18NextConfigurations(
+  context: GetServerSidePropsContext,
+  props: Props,
+  namespacesRequired?: string[] | undefined,
+): Promise<void> {
+  const nextI18NextConfig = await getNextI18NextConfig(
+    serverSideTranslations,
+    context,
+    namespacesRequired,
+  );
   props._nextI18Next = nextI18NextConfig._nextI18Next;
 }
 
-
-export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
+export const getServerSideProps: GetServerSideProps = async (
+  context: GetServerSidePropsContext,
+) => {
   const result = await getServerSideCommonProps(context);
 
   // check for presence

+ 88 - 52
apps/app/src/pages/login/index.page.tsx

@@ -1,13 +1,14 @@
 import React from 'react';
-
-import { pagePathUtils } from '@growi/core/dist/utils';
 import type {
-  NextPage, GetServerSideProps, GetServerSidePropsContext,
+  GetServerSideProps,
+  GetServerSidePropsContext,
+  NextPage,
 } from 'next';
-import { useTranslation } from 'next-i18next';
-import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
 import dynamic from 'next/dynamic';
 import Head from 'next/head';
+import { pagePathUtils } from '@growi/core/dist/utils';
+import { useTranslation } from 'next-i18next';
+import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
 
 import { NoLoginLayout } from '~/components/Layout/NoLoginLayout';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
@@ -16,33 +17,35 @@ import { isExternalAccountLoginError } from '~/interfaces/errors/external-accoun
 import { IExternalAuthProviderType } from '~/interfaces/external-auth-provider';
 import type { RegistrationMode } from '~/interfaces/registration-mode';
 import type { CommonProps } from '~/pages/utils/commons';
-import { getServerSideCommonProps, generateCustomTitle, getNextI18NextConfig } from '~/pages/utils/commons';
 import {
-  useCsrfToken,
-  useCurrentPathname,
-} from '~/stores-universal/context';
+  generateCustomTitle,
+  getNextI18NextConfig,
+  getServerSideCommonProps,
+} from '~/pages/utils/commons';
+import { useCsrfToken, useCurrentPathname } from '~/stores-universal/context';
 
 import styles from './index.module.scss';
 
-
 const { isPermalink, isUserPage, isUsersTopPage } = pagePathUtils;
 
-const LoginForm = dynamic(() => import('~/client/components/LoginForm').then(mod => mod.LoginForm), { ssr: false });
-
+const LoginForm = dynamic(
+  () => import('~/client/components/LoginForm').then((mod) => mod.LoginForm),
+  { ssr: false },
+);
 
 type Props = CommonProps & {
-  registrationMode: RegistrationMode,
-  pageWithMetaStr: string,
-  isMailerSetup: boolean,
-  enabledExternalAuthType: IExternalAuthProviderType[],
-  registrationWhitelist: string[],
-  isLocalStrategySetup: boolean,
-  isLdapStrategySetup: boolean,
-  isLdapSetupFailed: boolean,
-  isPasswordResetEnabled: boolean,
-  isEmailAuthenticationEnabled: boolean,
-  externalAccountLoginError?: IExternalAccountLoginError,
-  minPasswordLength: number,
+  registrationMode: RegistrationMode;
+  pageWithMetaStr: string;
+  isMailerSetup: boolean;
+  enabledExternalAuthType: IExternalAuthProviderType[];
+  registrationWhitelist: string[];
+  isLocalStrategySetup: boolean;
+  isLdapStrategySetup: boolean;
+  isLdapSetupFailed: boolean;
+  isPasswordResetEnabled: boolean;
+  isEmailAuthenticationEnabled: boolean;
+  externalAccountLoginError?: IExternalAccountLoginError;
+  minPasswordLength: number;
 };
 
 const LoginPage: NextPage<Props> = (props: Props) => {
@@ -85,56 +88,86 @@ const LoginPage: NextPage<Props> = (props: Props) => {
  * @param props
  * @param namespacesRequired
  */
-async function injectNextI18NextConfigurations(context: GetServerSidePropsContext, props: Props, namespacesRequired?: string[] | undefined): Promise<void> {
-  const nextI18NextConfig = await getNextI18NextConfig(serverSideTranslations, context, namespacesRequired);
+async function injectNextI18NextConfigurations(
+  context: GetServerSidePropsContext,
+  props: Props,
+  namespacesRequired?: string[] | undefined,
+): Promise<void> {
+  const nextI18NextConfig = await getNextI18NextConfig(
+    serverSideTranslations,
+    context,
+    namespacesRequired,
+  );
   props._nextI18Next = nextI18NextConfig._nextI18Next;
 }
 
-function injectEnabledStrategies(context: GetServerSidePropsContext, props: Props): void {
+function injectEnabledStrategies(
+  context: GetServerSidePropsContext,
+  props: Props,
+): void {
   const req: CrowiRequest = context.req as CrowiRequest;
   const { crowi } = req;
-  const {
-    configManager,
-  } = crowi;
+  const { configManager } = crowi;
 
   props.enabledExternalAuthType = [
-    configManager.getConfig('security:passport-google:isEnabled') === true ? IExternalAuthProviderType.google : undefined,
-    configManager.getConfig('security:passport-github:isEnabled') === true ? IExternalAuthProviderType.github : undefined,
-    configManager.getConfig('security:passport-saml:isEnabled') === true ? IExternalAuthProviderType.saml : undefined,
-    configManager.getConfig('security:passport-oidc:isEnabled') === true ? IExternalAuthProviderType.oidc : undefined,
-
-  ]
-    .filter((authType): authType is Exclude<typeof authType, undefined> => authType != null);
+    configManager.getConfig('security:passport-google:isEnabled') === true
+      ? IExternalAuthProviderType.google
+      : undefined,
+    configManager.getConfig('security:passport-github:isEnabled') === true
+      ? IExternalAuthProviderType.github
+      : undefined,
+    configManager.getConfig('security:passport-saml:isEnabled') === true
+      ? IExternalAuthProviderType.saml
+      : undefined,
+    configManager.getConfig('security:passport-oidc:isEnabled') === true
+      ? IExternalAuthProviderType.oidc
+      : undefined,
+  ].filter(
+    (authType): authType is Exclude<typeof authType, undefined> =>
+      authType != null,
+  );
 }
 
-async function injectServerConfigurations(context: GetServerSidePropsContext, props: Props): Promise<void> {
+async function injectServerConfigurations(
+  context: GetServerSidePropsContext,
+  props: Props,
+): Promise<void> {
   const req: CrowiRequest = context.req as CrowiRequest;
   const { crowi } = req;
-  const {
-    mailService,
-    configManager,
-    passportService,
-  } = crowi;
+  const { mailService, configManager, passportService } = crowi;
 
-  props.isPasswordResetEnabled = configManager.getConfig('security:passport-local:isPasswordResetEnabled');
+  props.isPasswordResetEnabled = configManager.getConfig(
+    'security:passport-local:isPasswordResetEnabled',
+  );
   props.isMailerSetup = mailService.isMailerSetup;
   props.isLocalStrategySetup = passportService.isLocalStrategySetup;
   props.isLdapStrategySetup = passportService.isLdapStrategySetup;
-  props.isLdapSetupFailed = configManager.getConfig('security:passport-ldap:isEnabled') && !props.isLdapStrategySetup;
-  props.registrationWhitelist = configManager.getConfig('security:registrationWhitelist');
-  props.isEmailAuthenticationEnabled = configManager.getConfig('security:passport-local:isEmailAuthenticationEnabled');
+  props.isLdapSetupFailed =
+    configManager.getConfig('security:passport-ldap:isEnabled') &&
+    !props.isLdapStrategySetup;
+  props.registrationWhitelist = configManager.getConfig(
+    'security:registrationWhitelist',
+  );
+  props.isEmailAuthenticationEnabled = configManager.getConfig(
+    'security:passport-local:isEmailAuthenticationEnabled',
+  );
   props.registrationMode = configManager.getConfig('security:registrationMode');
   props.minPasswordLength = configManager.getConfig('app:minPasswordLength');
 }
 
-export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
+export const getServerSideProps: GetServerSideProps = async (
+  context: GetServerSidePropsContext,
+) => {
   const result = await getServerSideCommonProps(context);
 
-
   // redirect to the page the user was on before moving to the Login Page
   if (context.req.headers.referer != null) {
     const urlBeforeLogin = new URL(context.req.headers.referer);
-    if (isPermalink(urlBeforeLogin.pathname) || isUserPage(urlBeforeLogin.pathname) || isUsersTopPage(urlBeforeLogin.pathname)) {
+    if (
+      isPermalink(urlBeforeLogin.pathname) ||
+      isUserPage(urlBeforeLogin.pathname) ||
+      isUsersTopPage(urlBeforeLogin.pathname)
+    ) {
       (context.req as CrowiRequest).session.redirectTo = urlBeforeLogin.href;
     }
   }
@@ -147,12 +180,15 @@ export const getServerSideProps: GetServerSideProps = async(context: GetServerSi
 
   const props: Props = result.props as Props;
 
-  const externalAccountLoginError = (context.req as CrowiRequest).session.externalAccountLoginError;
+  const externalAccountLoginError = (context.req as CrowiRequest).session
+    .externalAccountLoginError;
   if (externalAccountLoginError != null) {
     delete (context.req as CrowiRequest).session.externalAccountLoginError;
     const parsedError = JSON.parse(externalAccountLoginError);
     if (isExternalAccountLoginError(parsedError)) {
-      props.externalAccountLoginError = { ...parsedError as IExternalAccountLoginError };
+      props.externalAccountLoginError = {
+        ...(parsedError as IExternalAccountLoginError),
+      };
     }
   }
 

+ 29 - 10
apps/app/src/pages/maintenance.page.tsx

@@ -1,23 +1,32 @@
+import type {
+  GetServerSideProps,
+  GetServerSidePropsContext,
+  NextPage,
+} from 'next';
+import dynamic from 'next/dynamic';
 import type { IUser } from '@growi/core';
-import type { NextPage, GetServerSideProps, GetServerSidePropsContext } from 'next';
 import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
-import dynamic from 'next/dynamic';
 
 import type { CrowiRequest } from '~/interfaces/crowi-request';
 import { useCurrentUser } from '~/stores-universal/context';
 
 import type { CommonProps } from './utils/commons';
-import { getServerSideCommonProps, getNextI18NextConfig } from './utils/commons';
-
+import {
+  getNextI18NextConfig,
+  getServerSideCommonProps,
+} from './utils/commons';
 
-const Maintenance = dynamic(() => import('~/client/components/Maintenance').then(mod => mod.Maintenance), { ssr: false });
+const Maintenance = dynamic(
+  () =>
+    import('~/client/components/Maintenance').then((mod) => mod.Maintenance),
+  { ssr: false },
+);
 
 type Props = CommonProps & {
-  currentUser: IUser,
+  currentUser: IUser;
 };
 
 const MaintenancePage: NextPage<CommonProps> = (props: Props) => {
-
   useCurrentUser(props.currentUser ?? null);
 
   return (
@@ -33,12 +42,22 @@ const MaintenancePage: NextPage<CommonProps> = (props: Props) => {
   );
 };
 
-async function injectNextI18NextConfigurations(context: GetServerSidePropsContext, props: Props, namespacesRequired?: string[] | undefined): Promise<void> {
-  const nextI18NextConfig = await getNextI18NextConfig(serverSideTranslations, context, namespacesRequired);
+async function injectNextI18NextConfigurations(
+  context: GetServerSidePropsContext,
+  props: Props,
+  namespacesRequired?: string[] | undefined,
+): Promise<void> {
+  const nextI18NextConfig = await getNextI18NextConfig(
+    serverSideTranslations,
+    context,
+    namespacesRequired,
+  );
   props._nextI18Next = nextI18NextConfig._nextI18Next;
 }
 
-export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
+export const getServerSideProps: GetServerSideProps = async (
+  context: GetServerSidePropsContext,
+) => {
   const req = context.req as CrowiRequest;
 
   const result = await getServerSideCommonProps(context);

+ 116 - 61
apps/app/src/pages/me/[[...path]].page.tsx

@@ -1,62 +1,78 @@
-import React, { type ReactNode, useMemo, type JSX } from 'react';
-
-import type {
-  GetServerSideProps, GetServerSidePropsContext,
-} from 'next';
-import { useTranslation } from 'next-i18next';
-import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
+import React, { type JSX, type ReactNode, useMemo } from 'react';
+import type { GetServerSideProps, GetServerSidePropsContext } from 'next';
 import dynamic from 'next/dynamic';
 import Head from 'next/head';
 import { useRouter } from 'next/router';
+import { useTranslation } from 'next-i18next';
+import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
 
 import { BasicLayout } from '~/components/Layout/BasicLayout';
 import { GroundGlassBar } from '~/components/Navbar/GroundGlassBar';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
 import type { RendererConfig } from '~/interfaces/services/renderer';
 import type { ISidebarConfig } from '~/interfaces/sidebar-config';
+import { useCurrentPageId, useSWRxCurrentPage } from '~/stores/page';
 import {
-  useCurrentUser, useIsSearchPage, useGrowiCloudUri,
-  useIsSearchServiceConfigured, useIsSearchServiceReachable,
-  useCsrfToken, useIsSearchScopeChildrenAsDefault,
-  useRegistrationWhitelist, useShowPageLimitationXL, useRendererConfig, useIsEnabledMarp, useCurrentPathname,
+  useCsrfToken,
+  useCurrentPathname,
+  useCurrentUser,
+  useGrowiCloudUri,
+  useIsEnabledMarp,
+  useIsSearchPage,
+  useIsSearchScopeChildrenAsDefault,
+  useIsSearchServiceConfigured,
+  useIsSearchServiceReachable,
+  useRegistrationWhitelist,
+  useRendererConfig,
+  useShowPageLimitationXL,
 } from '~/stores-universal/context';
-import { useCurrentPageId, useSWRxCurrentPage } from '~/stores/page';
 import loggerFactory from '~/utils/logger';
 
 import type { NextPageWithLayout } from '../_app.page';
 import type { CommonProps } from '../utils/commons';
 import {
-  getNextI18NextConfig, getServerSideCommonProps, generateCustomTitle, useInitSidebarConfig,
+  generateCustomTitle,
+  getNextI18NextConfig,
+  getServerSideCommonProps,
+  useInitSidebarConfig,
 } from '../utils/commons';
 
-
 const logger = loggerFactory('growi:pages:me');
 
 type Props = CommonProps & {
-  isSearchServiceConfigured: boolean,
-  isSearchServiceReachable: boolean,
-  isSearchScopeChildrenAsDefault: boolean,
-  isEnabledMarp: boolean,
-  rendererConfig: RendererConfig,
-  showPageLimitationXL: number,
+  isSearchServiceConfigured: boolean;
+  isSearchServiceReachable: boolean;
+  isSearchScopeChildrenAsDefault: boolean;
+  isEnabledMarp: boolean;
+  rendererConfig: RendererConfig;
+  showPageLimitationXL: number;
 
   // config
-  registrationWhitelist: string[],
+  registrationWhitelist: string[];
 
-  sidebarConfig: ISidebarConfig,
+  sidebarConfig: ISidebarConfig;
 };
 
-const PersonalSettings = dynamic(() => import('~/client/components/Me/PersonalSettings'), { ssr: false });
+const PersonalSettings = dynamic(
+  () => import('~/client/components/Me/PersonalSettings'),
+  { ssr: false },
+);
 // const MyDraftList = dynamic(() => import('~/components/MyDraftList/MyDraftList'), { ssr: false });
 const InAppNotificationPage = dynamic(
-  () => import('~/client/components/InAppNotification/InAppNotificationPage').then(mod => mod.InAppNotificationPage), { ssr: false },
+  () =>
+    import('~/client/components/InAppNotification/InAppNotificationPage').then(
+      (mod) => mod.InAppNotificationPage,
+    ),
+  { ssr: false },
 );
 
 const MePage: NextPageWithLayout<Props> = (props: Props) => {
   const router = useRouter();
   const { t } = useTranslation(['translation', 'commons']);
   const { path } = router.query;
-  const pagePathKeys: string[] = Array.isArray(path) ? path : ['personal-settings'];
+  const pagePathKeys: string[] = Array.isArray(path)
+    ? path
+    : ['personal-settings'];
 
   const mePagesMap = useMemo(() => {
     return {
@@ -75,7 +91,10 @@ const MePage: NextPageWithLayout<Props> = (props: Props) => {
     };
   }, [t]);
 
-  const getTargetPageToRender = (pagesMap, keys): {title: string, component: JSX.Element} => {
+  const getTargetPageToRender = (
+    pagesMap,
+    keys,
+  ): { title: string; component: JSX.Element } => {
     return keys.reduce((pagesMap, key) => {
       const page = pagesMap[key];
       if (page == null) {
@@ -131,11 +150,9 @@ const MePage: NextPageWithLayout<Props> = (props: Props) => {
 
         <div className="main ps-sidebar">
           <div className="container-lg wide-gutter-x-lg">
-
-            <h1 className="sticky-top py-2 fs-3">{ targetPage.title }</h1>
+            <h1 className="sticky-top py-2 fs-3">{targetPage.title}</h1>
 
             {targetPage.component}
-
           </div>
         </div>
       </div>
@@ -143,65 +160,85 @@ const MePage: NextPageWithLayout<Props> = (props: Props) => {
   );
 };
 
-
 type LayoutProps = Props & {
-  children?: ReactNode
-}
+  children?: ReactNode;
+};
 
 const Layout = ({ children, ...props }: LayoutProps): JSX.Element => {
   // init sidebar config with UserUISettings and sidebarConfig
   useInitSidebarConfig(props.sidebarConfig, props.userUISettings);
 
-  return (
-    <BasicLayout>
-      {children}
-    </BasicLayout>
-  );
+  return <BasicLayout>{children}</BasicLayout>;
 };
 
 MePage.getLayout = function getLayout(page) {
   return <Layout {...page.props}>{page}</Layout>;
 };
 
-async function injectServerConfigurations(context: GetServerSidePropsContext, props: Props): Promise<void> {
+async function injectServerConfigurations(
+  context: GetServerSidePropsContext,
+  props: Props,
+): Promise<void> {
   const req: CrowiRequest = context.req as CrowiRequest;
   const { crowi } = req;
-  const {
-    searchService,
-    configManager,
-  } = crowi;
+  const { searchService, configManager } = crowi;
 
   props.isSearchServiceConfigured = searchService.isConfigured;
   props.isSearchServiceReachable = searchService.isReachable;
-  props.isSearchScopeChildrenAsDefault = configManager.getConfig('customize:isSearchScopeChildrenAsDefault');
+  props.isSearchScopeChildrenAsDefault = configManager.getConfig(
+    'customize:isSearchScopeChildrenAsDefault',
+  );
 
-  props.registrationWhitelist = configManager.getConfig('security:registrationWhitelist');
+  props.registrationWhitelist = configManager.getConfig(
+    'security:registrationWhitelist',
+  );
 
-  props.showPageLimitationXL = crowi.configManager.getConfig('customize:showPageLimitationXL');
+  props.showPageLimitationXL = crowi.configManager.getConfig(
+    'customize:showPageLimitationXL',
+  );
 
   props.sidebarConfig = {
-    isSidebarCollapsedMode: configManager.getConfig('customize:isSidebarCollapsedMode'),
-    isSidebarClosedAtDockMode: configManager.getConfig('customize:isSidebarClosedAtDockMode'),
+    isSidebarCollapsedMode: configManager.getConfig(
+      'customize:isSidebarCollapsedMode',
+    ),
+    isSidebarClosedAtDockMode: configManager.getConfig(
+      'customize:isSidebarClosedAtDockMode',
+    ),
   };
 
   props.rendererConfig = {
-    isEnabledLinebreaks: configManager.getConfig('markdown:isEnabledLinebreaks'),
-    isEnabledLinebreaksInComments: configManager.getConfig('markdown:isEnabledLinebreaksInComments'),
+    isEnabledLinebreaks: configManager.getConfig(
+      'markdown:isEnabledLinebreaks',
+    ),
+    isEnabledLinebreaksInComments: configManager.getConfig(
+      'markdown:isEnabledLinebreaksInComments',
+    ),
     isEnabledMarp: configManager.getConfig('customize:isEnabledMarp'),
-    adminPreferredIndentSize: configManager.getConfig('markdown:adminPreferredIndentSize'),
+    adminPreferredIndentSize: configManager.getConfig(
+      'markdown:adminPreferredIndentSize',
+    ),
     isIndentSizeForced: configManager.getConfig('markdown:isIndentSizeForced'),
 
     drawioUri: configManager.getConfig('app:drawioUri'),
     plantumlUri: configManager.getConfig('app:plantumlUri'),
 
     // XSS Options
-    isEnabledXssPrevention: configManager.getConfig('markdown:rehypeSanitize:isEnabledPrevention'),
+    isEnabledXssPrevention: configManager.getConfig(
+      'markdown:rehypeSanitize:isEnabledPrevention',
+    ),
     sanitizeType: configManager.getConfig('markdown:rehypeSanitize:option'),
-    customTagWhitelist: crowi.configManager.getConfig('markdown:rehypeSanitize:tagNames'),
-    customAttrWhitelist: configManager.getConfig('markdown:rehypeSanitize:attributes') != null
-      ? JSON.parse(configManager.getConfig('markdown:rehypeSanitize:attributes'))
-      : undefined,
-    highlightJsStyleBorder: crowi.configManager.getConfig('customize:highlightJsStyleBorder'),
+    customTagWhitelist: crowi.configManager.getConfig(
+      'markdown:rehypeSanitize:tagNames',
+    ),
+    customAttrWhitelist:
+      configManager.getConfig('markdown:rehypeSanitize:attributes') != null
+        ? JSON.parse(
+            configManager.getConfig('markdown:rehypeSanitize:attributes'),
+          )
+        : undefined,
+    highlightJsStyleBorder: crowi.configManager.getConfig(
+      'customize:highlightJsStyleBorder',
+    ),
   };
 }
 
@@ -211,13 +248,24 @@ async function injectServerConfigurations(context: GetServerSidePropsContext, pr
 //  * @param props
 //  * @param namespacesRequired
 //  */
-async function injectNextI18NextConfigurations(context: GetServerSidePropsContext, props: Props, namespacesRequired?: string[] | undefined): Promise<void> {
+async function injectNextI18NextConfigurations(
+  context: GetServerSidePropsContext,
+  props: Props,
+  namespacesRequired?: string[] | undefined,
+): Promise<void> {
   // preload all languages because of language lists in user setting
-  const nextI18NextConfig = await getNextI18NextConfig(serverSideTranslations, context, namespacesRequired, true);
+  const nextI18NextConfig = await getNextI18NextConfig(
+    serverSideTranslations,
+    context,
+    namespacesRequired,
+    true,
+  );
   props._nextI18Next = nextI18NextConfig._nextI18Next;
 }
 
-export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
+export const getServerSideProps: GetServerSideProps = async (
+  context: GetServerSidePropsContext,
+) => {
   const req = context.req as CrowiRequest;
   const { user, crowi } = req;
 
@@ -233,12 +281,19 @@ export const getServerSideProps: GetServerSideProps = async(context: GetServerSi
 
   if (user != null) {
     const User = crowi.model('User');
-    const userData = await User.findById(user.id).populate({ path: 'imageAttachment', select: 'filePathProxied' });
+    const userData = await User.findById(user.id).populate({
+      path: 'imageAttachment',
+      select: 'filePathProxied',
+    });
     props.currentUser = userData.toObject();
   }
 
   await injectServerConfigurations(context, props);
-  await injectNextI18NextConfigurations(context, props, ['translation', 'admin', 'commons']);
+  await injectNextI18NextConfigurations(context, props, [
+    'translation',
+    'admin',
+    'commons',
+  ]);
 
   return {
     props,

+ 44 - 15
apps/app/src/pages/reset-password.page.tsx

@@ -1,21 +1,29 @@
 import React from 'react';
-
-import type { NextPage, GetServerSideProps, GetServerSidePropsContext } from 'next';
+import type {
+  GetServerSideProps,
+  GetServerSidePropsContext,
+  NextPage,
+} from 'next';
+import dynamic from 'next/dynamic';
 import { useTranslation } from 'next-i18next';
 import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
-import dynamic from 'next/dynamic';
 
 import { RawLayout } from '~/components/Layout/RawLayout';
 
 import type { CommonProps } from './utils/commons';
-import { getNextI18NextConfig, getServerSideCommonProps } from './utils/commons';
-
+import {
+  getNextI18NextConfig,
+  getServerSideCommonProps,
+} from './utils/commons';
 
 type Props = CommonProps & {
-  email: string
+  email: string;
 };
 
-const PasswordResetExecutionForm = dynamic(() => import('~/client/components/PasswordResetExecutionForm'), { ssr: false });
+const PasswordResetExecutionForm = dynamic(
+  () => import('~/client/components/PasswordResetExecutionForm'),
+  { ssr: false },
+);
 
 const ForgotPasswordPage: NextPage<Props> = (props: Props) => {
   const { t } = useTranslation();
@@ -28,10 +36,18 @@ const ForgotPasswordPage: NextPage<Props> = (props: Props) => {
             <div className="row justify-content-md-center">
               <div className="col-md-6 mt-5">
                 <div className="text-center">
-                  <h1><span className="material-symbols-outlined large">lock_open</span></h1>
-                  <h2 className="text-center">{ t('forgot_password.reset_password') }</h2>
-                  <h5>{ props.email }</h5>
-                  <p className="mt-4">{ t('forgot_password.password_reset_excecution_desc') }</p>
+                  <h1>
+                    <span className="material-symbols-outlined large">
+                      lock_open
+                    </span>
+                  </h1>
+                  <h2 className="text-center">
+                    {t('forgot_password.reset_password')}
+                  </h2>
+                  <h5>{props.email}</h5>
+                  <p className="mt-4">
+                    {t('forgot_password.password_reset_excecution_desc')}
+                  </p>
                   <PasswordResetExecutionForm />
                 </div>
               </div>
@@ -44,12 +60,22 @@ const ForgotPasswordPage: NextPage<Props> = (props: Props) => {
 };
 
 // eslint-disable-next-line max-len
-async function injectNextI18NextConfigurations(context: GetServerSidePropsContext, props: Props, namespacesRequired?: string[] | undefined): Promise<void> {
-  const nextI18NextConfig = await getNextI18NextConfig(serverSideTranslations, context, namespacesRequired);
+async function injectNextI18NextConfigurations(
+  context: GetServerSidePropsContext,
+  props: Props,
+  namespacesRequired?: string[] | undefined,
+): Promise<void> {
+  const nextI18NextConfig = await getNextI18NextConfig(
+    serverSideTranslations,
+    context,
+    namespacesRequired,
+  );
   props._nextI18Next = nextI18NextConfig._nextI18Next;
 }
 
-export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
+export const getServerSideProps: GetServerSideProps = async (
+  context: GetServerSidePropsContext,
+) => {
   const result = await getServerSideCommonProps(context);
 
   // check for presence
@@ -65,7 +91,10 @@ export const getServerSideProps: GetServerSideProps = async(context: GetServerSi
     props.email = email;
   }
 
-  await injectNextI18NextConfigurations(context, props, ['translation', 'commons']);
+  await injectNextI18NextConfigurations(context, props, [
+    'translation',
+    'commons',
+  ]);
 
   return {
     props,

+ 146 - 76
apps/app/src/pages/share/[[...path]].page.tsx

@@ -1,12 +1,9 @@
-import React, { useEffect, type JSX } from 'react';
-
-import { type IPagePopulatedToShowRevision, getIdForRef } from '@growi/core';
-import type {
-  GetServerSideProps, GetServerSidePropsContext,
-} from 'next';
-import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
+import React, { type JSX, useEffect } from 'react';
+import type { GetServerSideProps, GetServerSidePropsContext } from 'next';
 import dynamic from 'next/dynamic';
 import Head from 'next/head';
+import { getIdForRef, type IPagePopulatedToShowRevision } from '@growi/core';
+import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
 import superjson from 'superjson';
 
 import { ShareLinkLayout } from '~/components/Layout/ShareLinkLayout';
@@ -21,41 +18,59 @@ import type { IShareLinkHasId } from '~/interfaces/share-link';
 import type { PageDocument, PageModel } from '~/server/models/page';
 import ShareLink from '~/server/models/share-link';
 import {
-  useCurrentUser, useRendererConfig, useIsSearchPage, useCurrentPathname,
-  useShareLinkId, useIsSearchServiceConfigured, useIsSearchServiceReachable, useIsSearchScopeChildrenAsDefault, useIsContainerFluid, useIsEnabledMarp,
-  useIsLocalAccountRegistrationEnabled, useShowPageSideAuthors,
+  useCurrentPageId,
+  useIsNotFound,
+  useSWRMUTxCurrentPage,
+} from '~/stores/page';
+import {
+  useCurrentPathname,
+  useCurrentUser,
+  useIsContainerFluid,
+  useIsEnabledMarp,
+  useIsLocalAccountRegistrationEnabled,
+  useIsSearchPage,
+  useIsSearchScopeChildrenAsDefault,
+  useIsSearchServiceConfigured,
+  useIsSearchServiceReachable,
+  useRendererConfig,
+  useShareLinkId,
+  useShowPageSideAuthors,
 } from '~/stores-universal/context';
-import { useCurrentPageId, useIsNotFound, useSWRMUTxCurrentPage } from '~/stores/page';
 import loggerFactory from '~/utils/logger';
 
 import type { NextPageWithLayout } from '../_app.page';
 import type { CommonProps } from '../utils/commons';
 import {
-  getServerSideCommonProps, generateCustomTitleForPage, getNextI18NextConfig, skipSSR, addActivity,
+  addActivity,
+  generateCustomTitleForPage,
+  getNextI18NextConfig,
+  getServerSideCommonProps,
+  skipSSR,
 } from '../utils/commons';
 
-
-const GrowiContextualSubNavigationSubstance = dynamic(() => import('~/client/components/Navbar/GrowiContextualSubNavigation'), { ssr: false });
-
+const GrowiContextualSubNavigationSubstance = dynamic(
+  () => import('~/client/components/Navbar/GrowiContextualSubNavigation'),
+  { ssr: false },
+);
 
 const logger = loggerFactory('growi:next-page:share');
 
 type Props = CommonProps & {
-  shareLinkRelatedPage?: IShareLinkRelatedPage,
-  shareLink?: IShareLinkHasId,
-  isNotFound: boolean,
-  isExpired: boolean,
-  disableLinkSharing: boolean,
-  isSearchServiceConfigured: boolean,
-  isSearchServiceReachable: boolean,
-  isSearchScopeChildrenAsDefault: boolean,
-  showPageSideAuthors: boolean,
-  isEnabledMarp: boolean,
-  isLocalAccountRegistrationEnabled: boolean,
-  drawioUri: string | null,
-  rendererConfig: RendererConfig,
-  skipSSR: boolean,
-  ssrMaxRevisionBodyLength: number,
+  shareLinkRelatedPage?: IShareLinkRelatedPage;
+  shareLink?: IShareLinkHasId;
+  isNotFound: boolean;
+  isExpired: boolean;
+  disableLinkSharing: boolean;
+  isSearchServiceConfigured: boolean;
+  isSearchServiceReachable: boolean;
+  isSearchScopeChildrenAsDefault: boolean;
+  showPageSideAuthors: boolean;
+  isEnabledMarp: boolean;
+  isLocalAccountRegistrationEnabled: boolean;
+  drawioUri: string | null;
+  rendererConfig: RendererConfig;
+  skipSSR: boolean;
+  ssrMaxRevisionBodyLength: number;
 };
 
 type IShareLinkRelatedPage = IPagePopulatedToShowRevision & PageDocument;
@@ -63,11 +78,14 @@ type IShareLinkRelatedPage = IPagePopulatedToShowRevision & PageDocument;
 superjson.registerCustom<IShareLinkRelatedPage, string>(
   {
     isApplicable: (v): v is IShareLinkRelatedPage => {
-      return v != null
-        && v.toObject != null;
+      return v != null && v.toObject != null;
+    },
+    serialize: (v) => {
+      return superjson.stringify(v.toObject());
+    },
+    deserialize: (v) => {
+      return superjson.parse(v);
     },
-    serialize: (v) => { return superjson.stringify(v.toObject()) },
-    deserialize: (v) => { return superjson.parse(v) },
   },
   'IShareLinkRelatedPageTransformer',
 );
@@ -75,15 +93,20 @@ superjson.registerCustom<IShareLinkRelatedPage, string>(
 // GrowiContextualSubNavigation for shared page
 // get page info from props not to send request 'GET /page' from client
 type GrowiContextualSubNavigationForSharedPageProps = {
-  page?: IPagePopulatedToShowRevision,
-  isLinkSharingDisabled: boolean,
-}
+  page?: IPagePopulatedToShowRevision;
+  isLinkSharingDisabled: boolean;
+};
 
-const GrowiContextualSubNavigationForSharedPage = (props: GrowiContextualSubNavigationForSharedPageProps): JSX.Element => {
+const GrowiContextualSubNavigationForSharedPage = (
+  props: GrowiContextualSubNavigationForSharedPageProps,
+): JSX.Element => {
   const { page, isLinkSharingDisabled } = props;
 
   return (
-    <GrowiContextualSubNavigationSubstance currentPage={page} isLinkSharingDisabled={isLinkSharingDisabled} />
+    <GrowiContextualSubNavigationSubstance
+      currentPage={page}
+      isLinkSharingDisabled={isLinkSharingDisabled}
+    />
   );
 };
 
@@ -103,7 +126,8 @@ const SharedPage: NextPageWithLayout<Props> = (props: Props) => {
   useShowPageSideAuthors(props.showPageSideAuthors);
   useIsContainerFluid(props.isContainerFluid);
 
-  const { trigger: mutateCurrentPage, data: currentPage } = useSWRMUTxCurrentPage();
+  const { trigger: mutateCurrentPage, data: currentPage } =
+    useSWRMUTxCurrentPage();
 
   useEffect(() => {
     if (!props.skipSSR) {
@@ -113,8 +137,12 @@ const SharedPage: NextPageWithLayout<Props> = (props: Props) => {
     if (props.shareLink?.relatedPage._id != null && !props.isNotFound) {
       mutateCurrentPage();
     }
-  }, [mutateCurrentPage, props.isNotFound, props.shareLink?.relatedPage._id, props.skipSSR]);
-
+  }, [
+    mutateCurrentPage,
+    props.isNotFound,
+    props.shareLink?.relatedPage._id,
+    props.skipSSR,
+  ]);
 
   const pagePath = props.shareLinkRelatedPage?.path ?? '';
 
@@ -127,8 +155,10 @@ const SharedPage: NextPageWithLayout<Props> = (props: Props) => {
       </Head>
 
       <div className="dynamic-layout-root justify-content-between">
-
-        <GrowiContextualSubNavigationForSharedPage page={currentPage ?? props.shareLinkRelatedPage} isLinkSharingDisabled={props.disableLinkSharing} />
+        <GrowiContextualSubNavigationForSharedPage
+          page={currentPage ?? props.shareLinkRelatedPage}
+          isLinkSharingDisabled={props.disableLinkSharing}
+        />
 
         <ShareLinkPageView
           pagePath={pagePath}
@@ -138,7 +168,6 @@ const SharedPage: NextPageWithLayout<Props> = (props: Props) => {
           isExpired={props.isExpired}
           disableLinkSharing={props.disableLinkSharing}
         />
-
       </div>
     </>
   );
@@ -153,50 +182,86 @@ SharedPage.getLayout = function getLayout(page) {
   );
 };
 
-function injectServerConfigurations(context: GetServerSidePropsContext, props: Props): void {
+function injectServerConfigurations(
+  context: GetServerSidePropsContext,
+  props: Props,
+): void {
   const req: CrowiRequest = context.req as CrowiRequest;
   const { crowi } = req;
   const { configManager, searchService } = crowi;
 
-  props.disableLinkSharing = configManager.getConfig('security:disableLinkSharing');
+  props.disableLinkSharing = configManager.getConfig(
+    'security:disableLinkSharing',
+  );
 
   props.isSearchServiceConfigured = searchService.isConfigured;
   props.isSearchServiceReachable = searchService.isReachable;
-  props.isSearchScopeChildrenAsDefault = configManager.getConfig('customize:isSearchScopeChildrenAsDefault');
+  props.isSearchScopeChildrenAsDefault = configManager.getConfig(
+    'customize:isSearchScopeChildrenAsDefault',
+  );
 
   props.drawioUri = configManager.getConfig('app:drawioUri');
 
-  props.showPageSideAuthors = configManager.getConfig('customize:showPageSideAuthors');
+  props.showPageSideAuthors = configManager.getConfig(
+    'customize:showPageSideAuthors',
+  );
 
-  props.isLocalAccountRegistrationEnabled = crowi.passportService.isLocalStrategySetup
-    && configManager.getConfig('security:registrationMode') !== RegistrationMode.CLOSED;
+  props.isLocalAccountRegistrationEnabled =
+    crowi.passportService.isLocalStrategySetup &&
+    configManager.getConfig('security:registrationMode') !==
+      RegistrationMode.CLOSED;
 
   props.rendererConfig = {
     isSharedPage: true,
-    isEnabledLinebreaks: configManager.getConfig('markdown:isEnabledLinebreaks'),
-    isEnabledLinebreaksInComments: configManager.getConfig('markdown:isEnabledLinebreaksInComments'),
+    isEnabledLinebreaks: configManager.getConfig(
+      'markdown:isEnabledLinebreaks',
+    ),
+    isEnabledLinebreaksInComments: configManager.getConfig(
+      'markdown:isEnabledLinebreaksInComments',
+    ),
     isEnabledMarp: configManager.getConfig('customize:isEnabledMarp'),
-    adminPreferredIndentSize: configManager.getConfig('markdown:adminPreferredIndentSize'),
+    adminPreferredIndentSize: configManager.getConfig(
+      'markdown:adminPreferredIndentSize',
+    ),
     isIndentSizeForced: configManager.getConfig('markdown:isIndentSizeForced'),
 
     drawioUri: configManager.getConfig('app:drawioUri'),
     plantumlUri: configManager.getConfig('app:plantumlUri'),
 
     // XSS Options
-    isEnabledXssPrevention: configManager.getConfig('markdown:rehypeSanitize:isEnabledPrevention'),
+    isEnabledXssPrevention: configManager.getConfig(
+      'markdown:rehypeSanitize:isEnabledPrevention',
+    ),
     sanitizeType: configManager.getConfig('markdown:rehypeSanitize:option'),
-    customTagWhitelist: crowi.configManager.getConfig('markdown:rehypeSanitize:tagNames'),
-    customAttrWhitelist: configManager.getConfig('markdown:rehypeSanitize:attributes') != null
-      ? JSON.parse(configManager.getConfig('markdown:rehypeSanitize:attributes'))
-      : undefined,
-    highlightJsStyleBorder: crowi.configManager.getConfig('customize:highlightJsStyleBorder'),
+    customTagWhitelist: crowi.configManager.getConfig(
+      'markdown:rehypeSanitize:tagNames',
+    ),
+    customAttrWhitelist:
+      configManager.getConfig('markdown:rehypeSanitize:attributes') != null
+        ? JSON.parse(
+            configManager.getConfig('markdown:rehypeSanitize:attributes'),
+          )
+        : undefined,
+    highlightJsStyleBorder: crowi.configManager.getConfig(
+      'customize:highlightJsStyleBorder',
+    ),
   };
 
-  props.ssrMaxRevisionBodyLength = configManager.getConfig('app:ssrMaxRevisionBodyLength');
+  props.ssrMaxRevisionBodyLength = configManager.getConfig(
+    'app:ssrMaxRevisionBodyLength',
+  );
 }
 
-async function injectNextI18NextConfigurations(context: GetServerSidePropsContext, props: Props, namespacesRequired?: string[] | undefined): Promise<void> {
-  const nextI18NextConfig = await getNextI18NextConfig(serverSideTranslations, context, namespacesRequired);
+async function injectNextI18NextConfigurations(
+  context: GetServerSidePropsContext,
+  props: Props,
+  namespacesRequired?: string[] | undefined,
+): Promise<void> {
+  const nextI18NextConfig = await getNextI18NextConfig(
+    serverSideTranslations,
+    context,
+    namespacesRequired,
+  );
   props._nextI18Next = nextI18NextConfig._nextI18Next;
 }
 
@@ -204,17 +269,17 @@ function getAction(props: Props): SupportedActionType {
   let action: SupportedActionType;
   if (props.isExpired) {
     action = SupportedAction.ACTION_SHARE_LINK_EXPIRED_PAGE_VIEW;
-  }
-  else if (props.shareLink == null) {
+  } else if (props.shareLink == null) {
     action = SupportedAction.ACTION_SHARE_LINK_NOT_FOUND;
-  }
-  else {
+  } else {
     action = SupportedAction.ACTION_SHARE_LINK_PAGE_VIEW;
   }
 
   return action;
 }
-export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
+export const getServerSideProps: GetServerSideProps = async (
+  context: GetServerSidePropsContext,
+) => {
   const req = context.req as CrowiRequest;
   const { crowi, params } = req;
   const result = await getServerSideCommonProps(context);
@@ -225,29 +290,34 @@ export const getServerSideProps: GetServerSideProps = async(context: GetServerSi
   const props: Props = result.props as Props;
 
   try {
-    const shareLink = await ShareLink.findOne({ _id: params.linkId }).populate('relatedPage');
+    const shareLink = await ShareLink.findOne({ _id: params.linkId }).populate(
+      'relatedPage',
+    );
     if (shareLink == null) {
       props.isNotFound = true;
-    }
-    else {
+    } else {
       props.isNotFound = false;
       props.isExpired = shareLink.isExpired();
       props.shareLink = shareLink.toObject();
 
       // retrieve Page
       const Page = crowi.model('Page') as PageModel;
-      const relatedPage = await Page.findOne({ _id: getIdForRef(shareLink.relatedPage) });
+      const relatedPage = await Page.findOne({
+        _id: getIdForRef(shareLink.relatedPage),
+      });
       // determine whether skip SSR
-      const ssrMaxRevisionBodyLength = crowi.configManager.getConfig('app:ssrMaxRevisionBodyLength');
+      const ssrMaxRevisionBodyLength = crowi.configManager.getConfig(
+        'app:ssrMaxRevisionBodyLength',
+      );
 
       if (relatedPage != null) {
         props.skipSSR = await skipSSR(relatedPage, ssrMaxRevisionBodyLength);
         // populate
-        props.shareLinkRelatedPage = await relatedPage.populateDataToShowRevision(props.skipSSR); // shouldExcludeBody = skipSSR
+        props.shareLinkRelatedPage =
+          await relatedPage.populateDataToShowRevision(props.skipSSR); // shouldExcludeBody = skipSSR
       }
     }
-  }
-  catch (err) {
+  } catch (err) {
     logger.error(err);
   }
 

+ 76 - 61
apps/app/src/pages/tags.page.tsx

@@ -1,13 +1,12 @@
-import type { ReactNode, JSX } from 'react';
-import React, { useState, useCallback } from 'react';
-
+import type { JSX, ReactNode } from 'react';
+import React, { useCallback, useState } from 'react';
+import type { GetServerSideProps, GetServerSidePropsContext } from 'next';
+import dynamic from 'next/dynamic';
+import Head from 'next/head';
 import type { IUser } from '@growi/core';
 import { LoadingSpinner } from '@growi/ui/dist/components';
-import type { GetServerSideProps, GetServerSidePropsContext } from 'next';
 import { useTranslation } from 'next-i18next';
 import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
-import dynamic from 'next/dynamic';
-import Head from 'next/head';
 
 import { BasicLayout } from '~/components/Layout/BasicLayout';
 import { GroundGlassBar } from '~/components/Navbar/GroundGlassBar';
@@ -15,36 +14,46 @@ import type { CrowiRequest } from '~/interfaces/crowi-request';
 import type { RendererConfig } from '~/interfaces/services/renderer';
 import type { ISidebarConfig } from '~/interfaces/sidebar-config';
 import type { IDataTagCount } from '~/interfaces/tag';
-import {
-  useCurrentUser, useIsSearchPage,
-  useIsSearchServiceConfigured, useIsSearchServiceReachable,
-  useIsSearchScopeChildrenAsDefault, useGrowiCloudUri, useCurrentPathname,
-} from '~/stores-universal/context';
 import { useCurrentPageId, useSWRxCurrentPage } from '~/stores/page';
 import { useSWRxTagsList } from '~/stores/tag';
-
+import {
+  useCurrentPathname,
+  useCurrentUser,
+  useGrowiCloudUri,
+  useIsSearchPage,
+  useIsSearchScopeChildrenAsDefault,
+  useIsSearchServiceConfigured,
+  useIsSearchServiceReachable,
+} from '~/stores-universal/context';
 
 import type { NextPageWithLayout } from './_app.page';
 import type { CommonProps } from './utils/commons';
 import {
-  getServerSideCommonProps, getNextI18NextConfig, generateCustomTitle, useInitSidebarConfig,
+  generateCustomTitle,
+  getNextI18NextConfig,
+  getServerSideCommonProps,
+  useInitSidebarConfig,
 } from './utils/commons';
 
 const PAGING_LIMIT = 10;
 
 type Props = CommonProps & {
-  currentUser: IUser,
-  isSearchServiceConfigured: boolean,
-  isSearchServiceReachable: boolean,
-  isSearchScopeChildrenAsDefault: boolean,
+  currentUser: IUser;
+  isSearchServiceConfigured: boolean;
+  isSearchServiceReachable: boolean;
+  isSearchScopeChildrenAsDefault: boolean;
 
-  rendererConfig: RendererConfig,
+  rendererConfig: RendererConfig;
 
-  sidebarConfig: ISidebarConfig,
+  sidebarConfig: ISidebarConfig;
 };
 
-const TagList = dynamic(() => import('~/client/components/TagList'), { ssr: false });
-const TagCloudBox = dynamic(() => import('~/client/components/TagCloudBox'), { ssr: false });
+const TagList = dynamic(() => import('~/client/components/TagList'), {
+  ssr: false,
+});
+const TagCloudBox = dynamic(() => import('~/client/components/TagCloudBox'), {
+  ssr: false,
+});
 
 const TagPage: NextPageWithLayout<CommonProps> = (props: Props) => {
   const [activePage, setActivePage] = useState<number>(1);
@@ -91,32 +100,26 @@ const TagPage: NextPageWithLayout<CommonProps> = (props: Props) => {
 
         <div className="main ps-sidebar" data-testid="tags-page">
           <div className="container-lg wide-gutter-x-lg">
-
-            <h2 className="sticky-top py-1">
-              {`${t('Tags')}(${totalCount})`}
-            </h2>
+            <h2 className="sticky-top py-1">{`${t('Tags')}(${totalCount})`}</h2>
 
             <div className="px-3 mb-5 text-center">
               <TagCloudBox tags={tagData} minSize={20} />
             </div>
-            { isLoading
-              ? (
-                <div className="text-muted text-center">
-                  <LoadingSpinner className="mt-3 fs-3" />
-                </div>
-              )
-              : (
-                <div data-testid="grw-tags-list">
-                  <TagList
-                    tagData={tagData}
-                    totalTags={totalCount}
-                    activePage={activePage}
-                    onChangePage={setOffsetByPageNumber}
-                    pagingLimit={PAGING_LIMIT}
-                  />
-                </div>
-              )
-            }
+            {isLoading ? (
+              <div className="text-muted text-center">
+                <LoadingSpinner className="mt-3 fs-3" />
+              </div>
+            ) : (
+              <div data-testid="grw-tags-list">
+                <TagList
+                  tagData={tagData}
+                  totalTags={totalCount}
+                  activePage={activePage}
+                  onChangePage={setOffsetByPageNumber}
+                  pagingLimit={PAGING_LIMIT}
+                />
+              </div>
+            )}
           </div>
         </div>
       </div>
@@ -125,8 +128,8 @@ const TagPage: NextPageWithLayout<CommonProps> = (props: Props) => {
 };
 
 type LayoutProps = Props & {
-  children?: ReactNode
-}
+  children?: ReactNode;
+};
 
 const Layout = ({ children, ...props }: LayoutProps): JSX.Element => {
   // init sidebar config with UserUISettings and sidebarConfig
@@ -136,29 +139,31 @@ const Layout = ({ children, ...props }: LayoutProps): JSX.Element => {
 };
 
 TagPage.getLayout = function getLayout(page) {
-  return (
-    <Layout {...page.props}>
-      {page}
-    </Layout>
-  );
+  return <Layout {...page.props}>{page}</Layout>;
 };
 
-function injectServerConfigurations(context: GetServerSidePropsContext, props: Props): void {
+function injectServerConfigurations(
+  context: GetServerSidePropsContext,
+  props: Props,
+): void {
   const req: CrowiRequest = context.req as CrowiRequest;
   const { crowi } = req;
-  const {
-    searchService, configManager,
-  } = crowi;
+  const { searchService, configManager } = crowi;
 
   props.isSearchServiceConfigured = searchService.isConfigured;
   props.isSearchServiceReachable = searchService.isReachable;
-  props.isSearchScopeChildrenAsDefault = configManager.getConfig('customize:isSearchScopeChildrenAsDefault');
+  props.isSearchScopeChildrenAsDefault = configManager.getConfig(
+    'customize:isSearchScopeChildrenAsDefault',
+  );
 
   props.sidebarConfig = {
-    isSidebarCollapsedMode: configManager.getConfig('customize:isSidebarCollapsedMode'),
-    isSidebarClosedAtDockMode: configManager.getConfig('customize:isSidebarClosedAtDockMode'),
+    isSidebarCollapsedMode: configManager.getConfig(
+      'customize:isSidebarCollapsedMode',
+    ),
+    isSidebarClosedAtDockMode: configManager.getConfig(
+      'customize:isSidebarClosedAtDockMode',
+    ),
   };
-
 }
 
 /**
@@ -167,12 +172,22 @@ function injectServerConfigurations(context: GetServerSidePropsContext, props: P
  * @param props
  * @param namespacesRequired
  */
-async function injectNextI18NextConfigurations(context: GetServerSidePropsContext, props: Props, namespacesRequired?: string[] | undefined): Promise<void> {
-  const nextI18NextConfig = await getNextI18NextConfig(serverSideTranslations, context, namespacesRequired);
+async function injectNextI18NextConfigurations(
+  context: GetServerSidePropsContext,
+  props: Props,
+  namespacesRequired?: string[] | undefined,
+): Promise<void> {
+  const nextI18NextConfig = await getNextI18NextConfig(
+    serverSideTranslations,
+    context,
+    namespacesRequired,
+  );
   props._nextI18Next = nextI18NextConfig._nextI18Next;
 }
 
-export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
+export const getServerSideProps: GetServerSideProps = async (
+  context: GetServerSidePropsContext,
+) => {
   const req = context.req as CrowiRequest;
   const { user } = req;
   const result = await getServerSideCommonProps(context);

+ 67 - 38
apps/app/src/pages/trash.page.tsx

@@ -1,10 +1,9 @@
-import type { ReactNode, JSX } from 'react';
-
-import type { IUser } from '@growi/core';
+import type { JSX, ReactNode } from 'react';
 import type { GetServerSideProps, GetServerSidePropsContext } from 'next';
-import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
 import dynamic from 'next/dynamic';
 import Head from 'next/head';
+import type { IUser } from '@growi/core';
+import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
 
 import { PagePathNavTitle } from '~/components/Common/PagePathNavTitle';
 import { BasicLayout } from '~/components/Layout/BasicLayout';
@@ -12,35 +11,49 @@ import { GroundGlassBar } from '~/components/Navbar/GroundGlassBar';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
 import type { RendererConfig } from '~/interfaces/services/renderer';
 import type { ISidebarConfig } from '~/interfaces/sidebar-config';
+import { useCurrentPageId, useSWRxCurrentPage } from '~/stores/page';
 import {
-  useCurrentUser, useCurrentPathname, useGrowiCloudUri,
-  useIsSearchServiceConfigured, useIsSearchServiceReachable,
-  useIsSearchScopeChildrenAsDefault, useIsSearchPage, useShowPageLimitationXL,
+  useCurrentPathname,
+  useCurrentUser,
+  useGrowiCloudUri,
+  useIsSearchPage,
+  useIsSearchScopeChildrenAsDefault,
+  useIsSearchServiceConfigured,
+  useIsSearchServiceReachable,
+  useShowPageLimitationXL,
 } from '~/stores-universal/context';
-import { useCurrentPageId, useSWRxCurrentPage } from '~/stores/page';
-
 
 import type { NextPageWithLayout } from './_app.page';
 import type { CommonProps } from './utils/commons';
 import {
-  getServerSideCommonProps, getNextI18NextConfig, generateCustomTitleForPage, useInitSidebarConfig,
+  generateCustomTitleForPage,
+  getNextI18NextConfig,
+  getServerSideCommonProps,
+  useInitSidebarConfig,
 } from './utils/commons';
 
-
-const TrashPageList = dynamic(() => import('~/client/components/TrashPageList').then(mod => mod.TrashPageList), { ssr: false });
-const EmptyTrashModal = dynamic(() => import('~/client/components/EmptyTrashModal'), { ssr: false });
-
+const TrashPageList = dynamic(
+  () =>
+    import('~/client/components/TrashPageList').then(
+      (mod) => mod.TrashPageList,
+    ),
+  { ssr: false },
+);
+const EmptyTrashModal = dynamic(
+  () => import('~/client/components/EmptyTrashModal'),
+  { ssr: false },
+);
 
 type Props = CommonProps & {
-  currentUser: IUser,
-  isSearchServiceConfigured: boolean,
-  isSearchServiceReachable: boolean,
-  isSearchScopeChildrenAsDefault: boolean,
-  showPageLimitationXL: number,
+  currentUser: IUser;
+  isSearchServiceConfigured: boolean;
+  isSearchServiceReachable: boolean;
+  isSearchScopeChildrenAsDefault: boolean;
+  showPageLimitationXL: number;
 
-  rendererConfig: RendererConfig,
+  rendererConfig: RendererConfig;
 
-  sidebarConfig: ISidebarConfig,
+  sidebarConfig: ISidebarConfig;
 };
 
 const TrashPage: NextPageWithLayout<CommonProps> = (props: Props) => {
@@ -87,8 +100,8 @@ const TrashPage: NextPageWithLayout<CommonProps> = (props: Props) => {
 };
 
 type LayoutProps = Props & {
-  children?: ReactNode,
-}
+  children?: ReactNode;
+};
 
 const Layout = ({ children, ...props }: LayoutProps): JSX.Element => {
   // init sidebar config with UserUISettings and sidebarConfig
@@ -100,31 +113,37 @@ const Layout = ({ children, ...props }: LayoutProps): JSX.Element => {
 TrashPage.getLayout = function getLayout(page) {
   return (
     <>
-      <Layout {...page.props}>
-        {page}
-      </Layout>
+      <Layout {...page.props}>{page}</Layout>
       <EmptyTrashModal />
     </>
   );
 };
 
-function injectServerConfigurations(context: GetServerSidePropsContext, props: Props): void {
+function injectServerConfigurations(
+  context: GetServerSidePropsContext,
+  props: Props,
+): void {
   const req: CrowiRequest = context.req as CrowiRequest;
   const { crowi } = req;
-  const {
-    searchService, configManager,
-  } = crowi;
+  const { searchService, configManager } = crowi;
 
   props.isSearchServiceConfigured = searchService.isConfigured;
   props.isSearchServiceReachable = searchService.isReachable;
-  props.isSearchScopeChildrenAsDefault = configManager.getConfig('customize:isSearchScopeChildrenAsDefault');
-  props.showPageLimitationXL = crowi.configManager.getConfig('customize:showPageLimitationXL');
+  props.isSearchScopeChildrenAsDefault = configManager.getConfig(
+    'customize:isSearchScopeChildrenAsDefault',
+  );
+  props.showPageLimitationXL = crowi.configManager.getConfig(
+    'customize:showPageLimitationXL',
+  );
 
   props.sidebarConfig = {
-    isSidebarCollapsedMode: configManager.getConfig('customize:isSidebarCollapsedMode'),
-    isSidebarClosedAtDockMode: configManager.getConfig('customize:isSidebarClosedAtDockMode'),
+    isSidebarCollapsedMode: configManager.getConfig(
+      'customize:isSidebarCollapsedMode',
+    ),
+    isSidebarClosedAtDockMode: configManager.getConfig(
+      'customize:isSidebarClosedAtDockMode',
+    ),
   };
-
 }
 
 /**
@@ -133,12 +152,22 @@ function injectServerConfigurations(context: GetServerSidePropsContext, props: P
  * @param props
  * @param namespacesRequired
  */
-async function injectNextI18NextConfigurations(context: GetServerSidePropsContext, props: Props, namespacesRequired?: string[] | undefined): Promise<void> {
-  const nextI18NextConfig = await getNextI18NextConfig(serverSideTranslations, context, namespacesRequired);
+async function injectNextI18NextConfigurations(
+  context: GetServerSidePropsContext,
+  props: Props,
+  namespacesRequired?: string[] | undefined,
+): Promise<void> {
+  const nextI18NextConfig = await getNextI18NextConfig(
+    serverSideTranslations,
+    context,
+    namespacesRequired,
+  );
   props._nextI18Next = nextI18NextConfig._nextI18Next;
 }
 
-export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
+export const getServerSideProps: GetServerSideProps = async (
+  context: GetServerSidePropsContext,
+) => {
   const req = context.req as CrowiRequest;
   const { user } = req;
   const result = await getServerSideCommonProps(context);

+ 42 - 20
apps/app/src/pages/user-activation.page.tsx

@@ -1,8 +1,12 @@
-import type { NextPage, GetServerSideProps, GetServerSidePropsContext } from 'next';
-import { useTranslation } from 'next-i18next';
-import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
+import type {
+  GetServerSideProps,
+  GetServerSidePropsContext,
+  NextPage,
+} from 'next';
 import dynamic from 'next/dynamic';
 import Head from 'next/head';
+import { useTranslation } from 'next-i18next';
+import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
 
 import { NoLoginLayout } from '~/components/Layout/NoLoginLayout';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
@@ -10,24 +14,28 @@ import type { UserActivationErrorCode } from '~/interfaces/errors/user-activatio
 import type { RegistrationMode } from '~/interfaces/registration-mode';
 import type { ReqWithUserRegistrationOrder } from '~/server/middlewares/inject-user-registration-order-by-token-middleware';
 
-
 import type { CommonProps } from './utils/commons';
 import {
-  getServerSideCommonProps, getNextI18NextConfig, generateCustomTitle,
+  generateCustomTitle,
+  getNextI18NextConfig,
+  getServerSideCommonProps,
 } from './utils/commons';
 
-
-const CompleteUserRegistrationForm = dynamic(() => import('~/client/components/CompleteUserRegistrationForm')
-  .then(mod => mod.CompleteUserRegistrationForm), { ssr: false });
-
+const CompleteUserRegistrationForm = dynamic(
+  () =>
+    import('~/client/components/CompleteUserRegistrationForm').then(
+      (mod) => mod.CompleteUserRegistrationForm,
+    ),
+  { ssr: false },
+);
 
 type Props = CommonProps & {
-  token: string
-  email: string
-  errorCode?: UserActivationErrorCode
-  registrationMode: RegistrationMode
-  isEmailAuthenticationEnabled: boolean
-}
+  token: string;
+  email: string;
+  errorCode?: UserActivationErrorCode;
+  registrationMode: RegistrationMode;
+  isEmailAuthenticationEnabled: boolean;
+};
 
 const UserActivationPage: NextPage<Props> = (props: Props) => {
   const { t } = useTranslation();
@@ -56,12 +64,22 @@ const UserActivationPage: NextPage<Props> = (props: Props) => {
  * @param props
  * @param namespacesRequired
  */
-async function injectNextI18NextConfigurations(context: GetServerSidePropsContext, props: Props, namespacesRequired?: string[] | undefined): Promise<void> {
-  const nextI18NextConfig = await getNextI18NextConfig(serverSideTranslations, context, namespacesRequired);
+async function injectNextI18NextConfigurations(
+  context: GetServerSidePropsContext,
+  props: Props,
+  namespacesRequired?: string[] | undefined,
+): Promise<void> {
+  const nextI18NextConfig = await getNextI18NextConfig(
+    serverSideTranslations,
+    context,
+    namespacesRequired,
+  );
   props._nextI18Next = nextI18NextConfig._nextI18Next;
 }
 
-export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
+export const getServerSideProps: GetServerSideProps = async (
+  context: GetServerSidePropsContext,
+) => {
   const result = await getServerSideCommonProps(context);
   const req = context.req as ReqWithUserRegistrationOrder & CrowiRequest;
 
@@ -81,8 +99,12 @@ export const getServerSideProps: GetServerSideProps = async(context: GetServerSi
   if (typeof context.query.errorCode === 'string') {
     props.errorCode = context.query.errorCode as UserActivationErrorCode;
   }
-  props.registrationMode = req.crowi.configManager.getConfig('security:registrationMode');
-  props.isEmailAuthenticationEnabled = req.crowi.configManager.getConfig('security:passport-local:isEmailAuthenticationEnabled');
+  props.registrationMode = req.crowi.configManager.getConfig(
+    'security:registrationMode',
+  );
+  props.isEmailAuthenticationEnabled = req.crowi.configManager.getConfig(
+    'security:passport-local:isEmailAuthenticationEnabled',
+  );
 
   await injectNextI18NextConfigurations(context, props, ['translation']);
 

+ 98 - 55
apps/app/src/pages/utils/commons.ts

@@ -1,13 +1,13 @@
+import type { GetServerSideProps, GetServerSidePropsContext } from 'next';
 import type { ColorScheme, IUserHasId, Locale } from '@growi/core';
-import { Lang, AllLang } from '@growi/core';
+import { AllLang, Lang } from '@growi/core';
 import { DevidedPagePath } from '@growi/core/dist/models';
 import { isServer } from '@growi/core/dist/utils';
-import type { GetServerSideProps, GetServerSidePropsContext } from 'next';
 import type { SSRConfig, UserConfig } from 'next-i18next';
 
 import * as nextI18NextConfig from '^/config/next-i18next.config';
 
-import { type SupportedActionType } from '~/interfaces/activity';
+import type { SupportedActionType } from '~/interfaces/activity';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
 import type { ISidebarConfig } from '~/interfaces/sidebar-config';
 import type { IUserUISettings } from '~/interfaces/user-ui-settings';
@@ -15,45 +15,51 @@ import type { PageDocument } from '~/server/models/page';
 import type { UserUISettingsDocument } from '~/server/models/user-ui-settings';
 import { detectLocaleFromBrowserAcceptLanguage } from '~/server/util/locale-utils';
 import {
-  useCurrentProductNavWidth, useCurrentSidebarContents, usePreferCollapsedMode,
+  useCurrentProductNavWidth,
+  useCurrentSidebarContents,
+  usePreferCollapsedMode,
 } from '~/stores/ui';
 import { getGrowiVersion } from '~/utils/growi-version';
 
 export type CommonProps = {
-  namespacesRequired: string[], // i18next
-  currentPathname: string,
-  appTitle: string,
-  siteUrl: string | undefined,
-  confidential: string,
-  customTitleTemplate: string,
-  csrfToken: string,
-  isContainerFluid: boolean,
-  growiVersion: string,
-  isMaintenanceMode: boolean,
-  redirectDestination: string | null,
-  isDefaultLogo: boolean,
-  growiCloudUri: string | undefined,
-  isAccessDeniedForNonAdminUser?: boolean,
-  currentUser?: IUserHasId,
-  forcedColorScheme?: ColorScheme,
-  userUISettings?: IUserUISettings
+  namespacesRequired: string[]; // i18next
+  currentPathname: string;
+  appTitle: string;
+  siteUrl: string | undefined;
+  confidential: string;
+  customTitleTemplate: string;
+  csrfToken: string;
+  isContainerFluid: boolean;
+  growiVersion: string;
+  isMaintenanceMode: boolean;
+  redirectDestination: string | null;
+  isDefaultLogo: boolean;
+  growiCloudUri: string | undefined;
+  isAccessDeniedForNonAdminUser?: boolean;
+  currentUser?: IUserHasId;
+  forcedColorScheme?: ColorScheme;
+  userUISettings?: IUserUISettings;
 } & Partial<SSRConfig>;
 
 // eslint-disable-next-line max-len
-export const getServerSideCommonProps: GetServerSideProps<CommonProps> = async(context: GetServerSidePropsContext) => {
-  const getModelSafely = await import('~/server/util/mongoose-utils').then(mod => mod.getModelSafely);
+export const getServerSideCommonProps: GetServerSideProps<CommonProps> = async (
+  context: GetServerSidePropsContext,
+) => {
+  const getModelSafely = await import('~/server/util/mongoose-utils').then(
+    (mod) => mod.getModelSafely,
+  );
 
   const req = context.req as CrowiRequest;
   const { crowi, user } = req;
-  const {
-    appService, configManager, customizeService, attachmentService,
-  } = crowi;
+  const { appService, configManager, customizeService, attachmentService } =
+    crowi;
 
   const url = new URL(context.resolvedUrl, 'http://example.com');
   const currentPathname = decodeURIComponent(url.pathname);
 
   const isMaintenanceMode = appService.isMaintenanceMode();
 
+  // biome-ignore lint/suspicious/noImplicitAnyLet: ignore
   let currentUser;
   if (user != null) {
     currentUser = user.toObject();
@@ -63,26 +69,31 @@ export const getServerSideCommonProps: GetServerSideProps<CommonProps> = async(c
   let redirectDestination: string | null = null;
   if (!crowi.aclService.isGuestAllowedToRead() && currentUser == null) {
     redirectDestination = '/login';
-  }
-  else if (!isMaintenanceMode && currentPathname === '/maintenance') {
+  } else if (!isMaintenanceMode && currentPathname === '/maintenance') {
     redirectDestination = '/';
-  }
-  else if (isMaintenanceMode && !currentPathname.match('/admin/*') && !(currentPathname === '/maintenance')) {
+  } else if (
+    isMaintenanceMode &&
+    !currentPathname.match('/admin/*') &&
+    !(currentPathname === '/maintenance')
+  ) {
     redirectDestination = '/maintenance';
-  }
-  else {
+  } else {
     redirectDestination = null;
   }
 
   const isCustomizedLogoUploaded = await attachmentService.isBrandLogoExist();
-  const isDefaultLogo = crowi.configManager.getConfig('customize:isDefaultLogo') || !isCustomizedLogoUploaded;
+  const isDefaultLogo =
+    crowi.configManager.getConfig('customize:isDefaultLogo') ||
+    !isCustomizedLogoUploaded;
   const forcedColorScheme = crowi.customizeService.forcedColorScheme;
 
   // retrieve UserUISett ings
-  const UserUISettings = getModelSafely<UserUISettingsDocument>('UserUISettings');
-  const userUISettings = user != null && UserUISettings != null
-    ? await UserUISettings.findOne({ user: user._id }).exec()
-    : req.session.uiSettings; // for guests
+  const UserUISettings =
+    getModelSafely<UserUISettingsDocument>('UserUISettings');
+  const userUISettings =
+    user != null && UserUISettings != null
+      ? await UserUISettings.findOne({ user: user._id }).exec()
+      : req.session.uiSettings; // for guests
 
   const props: CommonProps = {
     namespacesRequired: ['translation'],
@@ -92,7 +103,8 @@ export const getServerSideCommonProps: GetServerSideProps<CommonProps> = async(c
     confidential: appService.getAppConfidential() || '',
     customTitleTemplate: customizeService.customTitleTemplate,
     csrfToken: req.csrfToken(),
-    isContainerFluid: configManager.getConfig('customize:isContainerFluid') ?? false,
+    isContainerFluid:
+      configManager.getConfig('customize:isContainerFluid') ?? false,
     growiVersion: getGrowiVersion(),
     isMaintenanceMode,
     redirectDestination,
@@ -123,8 +135,12 @@ export const getLangAtServerSide = (req: CrowiRequest): Lang => {
   const { user, headers } = req;
   const { configManager } = req.crowi;
 
-  return user == null ? detectLocaleFromBrowserAcceptLanguage(headers)
-    : (user.lang ?? configManager.getConfig('app:globalLang') ?? Lang.en_US) ?? Lang.en_US;
+  return user == null
+    ? detectLocaleFromBrowserAcceptLanguage(headers)
+    : (user.lang ??
+        configManager.getConfig('app:globalLang') ??
+        Lang.en_US ??
+        Lang.en_US);
 };
 
 // use this function to get locale for html lang attribute
@@ -132,15 +148,19 @@ export const getLocaleAtServerSide = (req: CrowiRequest): Locale => {
   return langMap[getLangAtServerSide(req)];
 };
 
-export const getNextI18NextConfig = async(
-    // 'serverSideTranslations' method should be given from Next.js Page
-    //  because importing it in this file causes https://github.com/isaachinman/next-i18next/issues/1545
-    serverSideTranslations: (
-      initialLocale: string, namespacesRequired?: string[] | undefined, configOverride?: UserConfig | null, extraLocales?: string[] | false
-    ) => Promise<SSRConfig>,
-    context: GetServerSidePropsContext, namespacesRequired?: string[] | undefined, preloadAllLang = false,
+export const getNextI18NextConfig = async (
+  // 'serverSideTranslations' method should be given from Next.js Page
+  //  because importing it in this file causes https://github.com/isaachinman/next-i18next/issues/1545
+  serverSideTranslations: (
+    initialLocale: string,
+    namespacesRequired?: string[] | undefined,
+    configOverride?: UserConfig | null,
+    extraLocales?: string[] | false,
+  ) => Promise<SSRConfig>,
+  context: GetServerSidePropsContext,
+  namespacesRequired?: string[] | undefined,
+  preloadAllLang = false,
 ): Promise<SSRConfig> => {
-
   // determine language
   const req: CrowiRequest = context.req as CrowiRequest;
   const lang = getLangAtServerSide(req);
@@ -155,7 +175,12 @@ export const getNextI18NextConfig = async(
   }
 
   // The first argument must be a language code with an underscore, such as en_US
-  return serverSideTranslations(lang, namespaces, nextI18NextConfig, preloadAllLang ? AllLang : false);
+  return serverSideTranslations(
+    lang,
+    namespaces,
+    nextI18NextConfig,
+    preloadAllLang ? AllLang : false,
+  );
 };
 
 /**
@@ -163,7 +188,10 @@ export const getNextI18NextConfig = async(
  * @param props
  * @param title
  */
-export const generateCustomTitle = (props: CommonProps, title: string): string => {
+export const generateCustomTitle = (
+  props: CommonProps,
+  title: string,
+): string => {
   return props.customTitleTemplate
     .replace('{{sitename}}', props.appTitle)
     .replace('{{pagepath}}', title)
@@ -175,7 +203,10 @@ export const generateCustomTitle = (props: CommonProps, title: string): string =
  * @param props
  * @param pagePath
  */
-export const generateCustomTitleForPage = (props: CommonProps, pagePath: string): string => {
+export const generateCustomTitleForPage = (
+  props: CommonProps,
+  pagePath: string,
+): string => {
   const dPagePath = new DevidedPagePath(pagePath, true, true);
 
   return props.customTitleTemplate
@@ -184,14 +215,23 @@ export const generateCustomTitleForPage = (props: CommonProps, pagePath: string)
     .replace('{{pagename}}', dPagePath.latter);
 };
 
-export const useInitSidebarConfig = (sidebarConfig: ISidebarConfig, userUISettings?: IUserUISettings): void => {
+export const useInitSidebarConfig = (
+  sidebarConfig: ISidebarConfig,
+  userUISettings?: IUserUISettings,
+): void => {
   // UserUISettings
-  usePreferCollapsedMode(userUISettings?.preferCollapsedModeByUser ?? sidebarConfig.isSidebarCollapsedMode);
+  usePreferCollapsedMode(
+    userUISettings?.preferCollapsedModeByUser ??
+      sidebarConfig.isSidebarCollapsedMode,
+  );
   useCurrentSidebarContents(userUISettings?.currentSidebarContents);
   useCurrentProductNavWidth(userUISettings?.currentProductNavWidth);
 };
 
-export const skipSSR = async(page: PageDocument, ssrMaxRevisionBodyLength: number): Promise<boolean> => {
+export const skipSSR = async (
+  page: PageDocument,
+  ssrMaxRevisionBodyLength: number,
+): Promise<boolean> => {
   if (!isServer()) {
     throw new Error('This method is not available on the client-side');
   }
@@ -205,7 +245,10 @@ export const skipSSR = async(page: PageDocument, ssrMaxRevisionBodyLength: numbe
   return ssrMaxRevisionBodyLength < latestRevisionBodyLength;
 };
 
-export const addActivity = async(context: GetServerSidePropsContext, action: SupportedActionType): Promise<void> => {
+export const addActivity = async (
+  context: GetServerSidePropsContext,
+  action: SupportedActionType,
+): Promise<void> => {
   const req = context.req as CrowiRequest;
 
   const parameters = {

+ 3 - 3
apps/app/src/pages/utils/objectid-transformer.ts

@@ -4,7 +4,7 @@ import type ObjectId from 'bson-objectid';
 import superjson from 'superjson';
 
 export const registerTransformerForObjectId = (): void => {
-  superjson.registerCustom<ObjectId|string, string>(
+  superjson.registerCustom<ObjectId | string, string>(
     {
       isApplicable: (v): v is ObjectId => {
         if (v == null) {
@@ -16,8 +16,8 @@ export const registerTransformerForObjectId = (): void => {
         }
         return objectIdUtils.isValidObjectId(v);
       },
-      serialize: v => (typeof v === 'string' ? v : v.toHexString()),
-      deserialize: v => v,
+      serialize: (v) => (typeof v === 'string' ? v : v.toHexString()),
+      deserialize: (v) => v,
     },
     'ObjectidTransformer',
   );

+ 1 - 5
biome.json

@@ -30,13 +30,9 @@
       "!apps/app/playwright",
       "!apps/app/src/client",
       "!apps/app/src/features/openai",
-      "!apps/app/src/pages",
       "!apps/app/src/server",
       "!apps/app/src/services",
-      "!apps/app/src/stores",
-      "!apps/app/src/styles",
-      "!apps/app/test-with-vite",
-      "!apps/app/tmp"
+      "!apps/app/src/stores"
     ]
   },
   "formatter": {