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

Add UI and logic for disabled user registration

maeshinshin 1 год назад
Родитель
Сommit
f2b9ae5dbe

+ 1 - 0
apps/app/public/static/locales/en_US/translation.json

@@ -765,6 +765,7 @@
     "receive_notifications": "Receive Notifications",
     "receive_notifications": "Receive Notifications",
     "stop_notification": "Stop Notification",
     "stop_notification": "Stop Notification",
     "footprints": "Footprints",
     "footprints": "Footprints",
+    "login_required": "Login required",
     "operation": {
     "operation": {
       "attention": {
       "attention": {
         "rename": "Renaming paths of descendant pages was not successful, please open the menu from the 3-point reader and select 'Path recovery'"
         "rename": "Renaming paths of descendant pages was not successful, please open the menu from the 3-point reader and select 'Path recovery'"

+ 1 - 0
apps/app/public/static/locales/fr_FR/translation.json

@@ -759,6 +759,7 @@
     "receive_notifications": "Recevoir les notifications",
     "receive_notifications": "Recevoir les notifications",
     "stop_notification": "Stopper les notifications",
     "stop_notification": "Stopper les notifications",
     "footprints": "Visiteurs",
     "footprints": "Visiteurs",
+    "login_required": "Connexion requise",
     "operation": {
     "operation": {
       "attention": {
       "attention": {
         "rename": "Échec du renommage du chemin des pages descendantes, ouvrir le menu du lecteur 3-points et sélectionner 'Récupération du chemin'"
         "rename": "Échec du renommage du chemin des pages descendantes, ouvrir le menu du lecteur 3-points et sélectionner 'Récupération du chemin'"

+ 1 - 0
apps/app/public/static/locales/ja_JP/translation.json

@@ -798,6 +798,7 @@
     "receive_notifications": "通知を受け取る",
     "receive_notifications": "通知を受け取る",
     "stop_notification": "通知を止める",
     "stop_notification": "通知を止める",
     "footprints": "足跡",
     "footprints": "足跡",
+    "login_required": "ログインが必要です",
     "operation": {
     "operation": {
       "attention": {
       "attention": {
         "rename": "配下のページパスの更新が正常に行われませんでした。3点リーダーからメニューを開き、「パスを修復」を選択してしてください。"
         "rename": "配下のページパスの更新が正常に行われませんでした。3点リーダーからメニューを開き、「パスを修復」を選択してしてください。"

+ 1 - 0
apps/app/public/static/locales/zh_CN/translation.json

@@ -768,6 +768,7 @@
     "receive_notifications": "接收通知",
     "receive_notifications": "接收通知",
     "stop_notification": "停止通知",
     "stop_notification": "停止通知",
     "footprints": "脚印",
     "footprints": "脚印",
+    "login_required": "需要登录",
     "operation": {
     "operation": {
       "attention": {
       "attention": {
         "rename": "重命名子孙页的路径没有成功,请从三点式阅读器上打开菜单,选择 '路径恢复'。"
         "rename": "重命名子孙页的路径没有成功,请从三点式阅读器上打开菜单,选择 '路径恢复'。"

+ 21 - 5
apps/app/src/client/components/Navbar/GrowiContextualSubNavigation.tsx

@@ -9,12 +9,13 @@ import type {
 import { pagePathUtils } from '@growi/core/dist/utils';
 import { pagePathUtils } from '@growi/core/dist/utils';
 import { GlobalCodeMirrorEditorKey } from '@growi/editor';
 import { GlobalCodeMirrorEditorKey } from '@growi/editor';
 import { useCodeMirrorEditorIsolated } from '@growi/editor/dist/client/stores/codemirror-editor';
 import { useCodeMirrorEditorIsolated } from '@growi/editor/dist/client/stores/codemirror-editor';
+import { auto } from '@popperjs/core';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
 import dynamic from 'next/dynamic';
 import Link from 'next/link';
 import Link from 'next/link';
 import { useRouter } from 'next/router';
 import { useRouter } from 'next/router';
 import Sticky from 'react-stickynode';
 import Sticky from 'react-stickynode';
-import { DropdownItem } from 'reactstrap';
+import { DropdownItem, UncontrolledTooltip } from 'reactstrap';
 
 
 import { exportAsMarkdown, updateContentWidth, syncLatestRevisionBody } from '~/client/services/page-operation';
 import { exportAsMarkdown, updateContentWidth, syncLatestRevisionBody } from '~/client/services/page-operation';
 import { toastSuccess, toastError, toastWarning } from '~/client/util/toastr';
 import { toastSuccess, toastError, toastWarning } from '~/client/util/toastr';
@@ -23,7 +24,7 @@ import type { OnDuplicatedFunction, OnRenamedFunction, OnDeletedFunction } from
 import { useShouldExpandContent } from '~/services/layout/use-should-expand-content';
 import { useShouldExpandContent } from '~/services/layout/use-should-expand-content';
 import {
 import {
   useCurrentPathname,
   useCurrentPathname,
-  useCurrentUser, useIsGuestUser, useIsReadOnlyUser, useIsSharedUser, useShareLinkId,
+  useCurrentUser, useIsGuestUser, useIsReadOnlyUser, useIsLocalAccountRegistrationEnabled, useIsSharedUser, useShareLinkId,
 } from '~/stores-universal/context';
 } from '~/stores-universal/context';
 import { useEditorMode } from '~/stores-universal/ui';
 import { useEditorMode } from '~/stores-universal/ui';
 import {
 import {
@@ -233,6 +234,7 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
   const { data: currentUser } = useCurrentUser();
   const { data: currentUser } = useCurrentUser();
   const { data: isGuestUser } = useIsGuestUser();
   const { data: isGuestUser } = useIsGuestUser();
   const { data: isReadOnlyUser } = useIsReadOnlyUser();
   const { data: isReadOnlyUser } = useIsReadOnlyUser();
+  const { data: isLocalAccountRegistrationEnabled } = useIsLocalAccountRegistrationEnabled();
   const { data: isSharedUser } = useIsSharedUser();
   const { data: isSharedUser } = useIsSharedUser();
 
 
   const shouldExpandContent = useShouldExpandContent(currentPage);
   const shouldExpandContent = useShouldExpandContent(currentPage);
@@ -387,9 +389,23 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
 
 
             { isGuestUser && (
             { isGuestUser && (
               <div className="mt-2">
               <div className="mt-2">
-                <Link href="/login#register" className="btn me-2" prefetch={false}>
-                  <span className="material-symbols-outlined me-1">person_add</span>{t('Sign up')}
-                </Link>
+                <span>
+                  <span className="d-inline-block" id="sign-up-link">
+                    <Link
+                      href={!isLocalAccountRegistrationEnabled ? '#' : '/login#register'}
+                      className={`btn me-2 ${!isLocalAccountRegistrationEnabled ? 'opacity-25' : ''}`}
+                      style={{ pointerEvents: !isLocalAccountRegistrationEnabled ? 'none' : undefined }}
+                      prefetch={false}
+                    >
+                      <span className="material-symbols-outlined me-1">person_add</span>{t('Sign up')}
+                    </Link>
+                  </span>
+                  {!isLocalAccountRegistrationEnabled && (
+                    <UncontrolledTooltip target="sign-up-link" fade={false}>
+                      {t('tooltip.login_required')}
+                    </UncontrolledTooltip>
+                  )}
+                </span>
                 <Link href="/login#login" className="btn btn-primary" prefetch={false}>
                 <Link href="/login#login" className="btn btn-primary" prefetch={false}>
                   <span className="material-symbols-outlined me-1">login</span>{t('Sign in')}
                   <span className="material-symbols-outlined me-1">login</span>{t('Sign in')}
                 </Link>
                 </Link>

+ 9 - 0
apps/app/src/pages/[[...path]].page.tsx

@@ -25,6 +25,7 @@ import { PageView } from '~/components/PageView/PageView';
 import { DrawioViewerScript } from '~/components/Script/DrawioViewerScript';
 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 type { CrowiRequest } from '~/interfaces/crowi-request';
+import { RegistrationMode } from '~/interfaces/registration-mode';
 import type { RendererConfig } from '~/interfaces/services/renderer';
 import type { RendererConfig } from '~/interfaces/services/renderer';
 import type { ISidebarConfig } from '~/interfaces/sidebar-config';
 import type { ISidebarConfig } from '~/interfaces/sidebar-config';
 import type { CurrentPageYjsData } from '~/interfaces/yjs';
 import type { CurrentPageYjsData } from '~/interfaces/yjs';
@@ -43,6 +44,7 @@ import {
   useIsAllReplyShown, useIsContainerFluid, useIsNotCreatable,
   useIsAllReplyShown, useIsContainerFluid, useIsNotCreatable,
   useIsUploadAllFileAllowed, useIsUploadEnabled,
   useIsUploadAllFileAllowed, useIsUploadEnabled,
   useElasticsearchMaxBodyLengthToIndex,
   useElasticsearchMaxBodyLengthToIndex,
+  useIsLocalAccountRegistrationEnabled,
 } from '~/stores-universal/context';
 } from '~/stores-universal/context';
 import { useEditingMarkdown } from '~/stores/editor';
 import { useEditingMarkdown } from '~/stores/editor';
 import {
 import {
@@ -155,6 +157,8 @@ type Props = CommonProps & {
   templateTagData?: string[],
   templateTagData?: string[],
   templateBodyData?: string,
   templateBodyData?: string,
 
 
+  isLocalAccountRegistrationEnabled: boolean,
+
   isSearchServiceConfigured: boolean,
   isSearchServiceConfigured: boolean,
   isSearchServiceReachable: boolean,
   isSearchServiceReachable: boolean,
   isSearchScopeChildrenAsDefault: boolean,
   isSearchScopeChildrenAsDefault: boolean,
@@ -237,6 +241,8 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
   useIsUploadAllFileAllowed(props.isUploadAllFileAllowed);
   useIsUploadAllFileAllowed(props.isUploadAllFileAllowed);
   useIsUploadEnabled(props.isUploadEnabled);
   useIsUploadEnabled(props.isUploadEnabled);
 
 
+  useIsLocalAccountRegistrationEnabled(props.isLocalAccountRegistrationEnabled);
+
   const { pageWithMeta } = props;
   const { pageWithMeta } = props;
 
 
   const pageId = pageWithMeta?.data._id;
   const pageId = pageWithMeta?.data._id;
@@ -556,6 +562,9 @@ function injectServerConfigurations(context: GetServerSidePropsContext, props: P
   props.isUploadAllFileAllowed = crowi.fileUploadService.getFileUploadEnabled();
   props.isUploadAllFileAllowed = crowi.fileUploadService.getFileUploadEnabled();
   props.isUploadEnabled = crowi.fileUploadService.getIsUploadable();
   props.isUploadEnabled = crowi.fileUploadService.getIsUploadable();
 
 
+  props.isLocalAccountRegistrationEnabled = crowi.passportService.isLocalStrategySetup
+  && configManager.getConfig('crowi', 'security:registrationMode') !== RegistrationMode.CLOSED;
+
   props.adminPreferredIndentSize = configManager.getConfig('markdown', 'markdown:adminPreferredIndentSize');
   props.adminPreferredIndentSize = configManager.getConfig('markdown', 'markdown:adminPreferredIndentSize');
   props.isIndentSizeForced = configManager.getConfig('markdown', 'markdown:isIndentSizeForced');
   props.isIndentSizeForced = configManager.getConfig('markdown', 'markdown:isIndentSizeForced');
 
 

+ 7 - 0
apps/app/src/pages/share/[[...path]].page.tsx

@@ -15,6 +15,7 @@ import { ShareLinkPageView } from '~/components/ShareLinkPageView';
 import type { SupportedActionType } from '~/interfaces/activity';
 import type { SupportedActionType } from '~/interfaces/activity';
 import { SupportedAction } from '~/interfaces/activity';
 import { SupportedAction } from '~/interfaces/activity';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
+import { RegistrationMode } from '~/interfaces/registration-mode';
 import type { RendererConfig } from '~/interfaces/services/renderer';
 import type { RendererConfig } from '~/interfaces/services/renderer';
 import type { IShareLinkHasId } from '~/interfaces/share-link';
 import type { IShareLinkHasId } from '~/interfaces/share-link';
 import type { PageDocument } from '~/server/models/page';
 import type { PageDocument } from '~/server/models/page';
@@ -22,6 +23,7 @@ import ShareLink from '~/server/models/share-link';
 import {
 import {
   useCurrentUser, useRendererConfig, useIsSearchPage, useCurrentPathname,
   useCurrentUser, useRendererConfig, useIsSearchPage, useCurrentPathname,
   useShareLinkId, useIsSearchServiceConfigured, useIsSearchServiceReachable, useIsSearchScopeChildrenAsDefault, useIsContainerFluid, useIsEnabledMarp,
   useShareLinkId, useIsSearchServiceConfigured, useIsSearchServiceReachable, useIsSearchScopeChildrenAsDefault, useIsContainerFluid, useIsEnabledMarp,
+  useIsLocalAccountRegistrationEnabled,
 } from '~/stores-universal/context';
 } from '~/stores-universal/context';
 import { useCurrentPageId, useIsNotFound, useSWRMUTxCurrentPage } from '~/stores/page';
 import { useCurrentPageId, useIsNotFound, useSWRMUTxCurrentPage } from '~/stores/page';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
@@ -48,6 +50,7 @@ type Props = CommonProps & {
   isSearchServiceReachable: boolean,
   isSearchServiceReachable: boolean,
   isSearchScopeChildrenAsDefault: boolean,
   isSearchScopeChildrenAsDefault: boolean,
   isEnabledMarp: boolean,
   isEnabledMarp: boolean,
+  isLocalAccountRegistrationEnabled: boolean,
   drawioUri: string | null,
   drawioUri: string | null,
   rendererConfig: RendererConfig,
   rendererConfig: RendererConfig,
   skipSSR: boolean,
   skipSSR: boolean,
@@ -98,6 +101,7 @@ const SharedPage: NextPageWithLayout<Props> = (props: Props) => {
   useIsSearchServiceReachable(props.isSearchServiceReachable);
   useIsSearchServiceReachable(props.isSearchServiceReachable);
   useIsSearchScopeChildrenAsDefault(props.isSearchScopeChildrenAsDefault);
   useIsSearchScopeChildrenAsDefault(props.isSearchScopeChildrenAsDefault);
   useIsEnabledMarp(props.rendererConfig.isEnabledMarp);
   useIsEnabledMarp(props.rendererConfig.isEnabledMarp);
+  useIsLocalAccountRegistrationEnabled(props.isLocalAccountRegistrationEnabled);
   useIsContainerFluid(props.isContainerFluid);
   useIsContainerFluid(props.isContainerFluid);
 
 
   const { trigger: mutateCurrentPage, data: currentPage } = useSWRMUTxCurrentPage();
   const { trigger: mutateCurrentPage, data: currentPage } = useSWRMUTxCurrentPage();
@@ -163,6 +167,9 @@ function injectServerConfigurations(context: GetServerSidePropsContext, props: P
 
 
   props.drawioUri = configManager.getConfig('crowi', 'app:drawioUri');
   props.drawioUri = configManager.getConfig('crowi', 'app:drawioUri');
 
 
+  props.isLocalAccountRegistrationEnabled = crowi.passportService.isLocalStrategySetup
+    && configManager.getConfig('crowi', 'security:registrationMode') !== RegistrationMode.CLOSED;
+
   props.rendererConfig = {
   props.rendererConfig = {
     isSharedPage: true,
     isSharedPage: true,
     isEnabledLinebreaks: configManager.getConfig('markdown', 'markdown:isEnabledLinebreaks'),
     isEnabledLinebreaks: configManager.getConfig('markdown', 'markdown:isEnabledLinebreaks'),

+ 4 - 0
apps/app/src/stores-universal/context.tsx

@@ -204,6 +204,10 @@ export const useIsContainerFluid = (initialData?: boolean): SWRResponse<boolean,
   return useContextSWR('isContainerFluid', initialData);
   return useContextSWR('isContainerFluid', initialData);
 };
 };
 
 
+export const useIsLocalAccountRegistrationEnabled = (initialData?: boolean): SWRResponse<boolean, Error> => {
+  return useContextSWR('isLocalAccountRegistrationEnabled', initialData);
+};
+
 /** **********************************************************
 /** **********************************************************
  *                     Computed contexts
  *                     Computed contexts
  *********************************************************** */
  *********************************************************** */