From e2913d9ee15a0abdf3fc1c912898a7a74c2a35f4 Mon Sep 17 00:00:00 2001 From: yyh Date: Sun, 1 Feb 2026 19:06:45 +0800 Subject: [PATCH] Revert "refactor!: replace Zustand global store with TanStack Query for systemFeatures" This reverts commit 806ece9a67daf902e8c2a69508d28d88f068d05b. --- web/__tests__/embedded-user-id-store.test.tsx | 33 ++++++++++-- .../webapp-reset-password/layout.tsx | 8 +-- .../components/external-member-sso-auth.tsx | 4 +- .../(shareLayout)/webapp-signin/layout.tsx | 4 +- .../webapp-signin/normalForm.tsx | 4 +- web/app/(shareLayout)/webapp-signin/page.tsx | 4 +- .../(commonLayout)/account-page/index.tsx | 4 +- web/app/account/(commonLayout)/header.tsx | 4 +- web/app/account/oauth/authorize/layout.tsx | 4 +- web/app/activate/page.tsx | 4 +- web/app/components/app-initializer.tsx | 2 +- .../app/app-access-control/index.tsx | 4 +- .../components/app/app-publisher/index.tsx | 4 +- .../app/create-app-dialog/app-card/index.tsx | 4 +- web/app/components/app/overview/app-card.tsx | 4 +- web/app/components/apps/app-card.spec.tsx | 8 +-- web/app/components/apps/app-card.tsx | 4 +- web/app/components/apps/list.spec.tsx | 6 ++- web/app/components/apps/list.tsx | 4 +- .../chat/chat-with-history/sidebar/index.tsx | 4 +- .../chat/embedded-chatbot/header/index.tsx | 4 +- .../base/chat/embedded-chatbot/index.tsx | 4 +- .../custom-web-app-brand/index.spec.tsx | 8 +-- .../custom/custom-web-app-brand/index.tsx | 4 +- .../list/built-in-pipeline-list.spec.tsx | 5 +- .../list/built-in-pipeline-list.tsx | 4 +- .../components/datasets/list/index.spec.tsx | 12 +++-- web/app/components/datasets/list/index.tsx | 4 +- web/app/components/explore/app-card/index.tsx | 4 +- web/app/components/explore/app-list/index.tsx | 4 +- web/app/components/explore/try-app/index.tsx | 4 +- .../components/header/account-about/index.tsx | 4 +- .../header/account-dropdown/index.tsx | 4 +- .../data-source-page-new/index.tsx | 4 +- .../account-setting/members-page/index.tsx | 4 +- .../members-page/invite-button.tsx | 4 +- .../operation/transfer-ownership.tsx | 4 +- .../model-provider-page/index.tsx | 4 +- .../provider-added-card/quota-panel.tsx | 4 +- web/app/components/header/index.tsx | 4 +- .../components/header/license-env/index.tsx | 4 +- .../hooks/use-install-plugin-limit.tsx | 4 +- .../install-bundle/index.spec.tsx | 2 +- .../steps/install-multi.spec.tsx | 4 +- .../install-bundle/steps/install-multi.tsx | 4 +- .../detail-header.spec.tsx | 3 +- .../plugin-detail-panel/detail-header.tsx | 4 +- .../operation-dropdown.spec.tsx | 3 +- .../operation-dropdown.tsx | 4 +- .../plugins/plugin-item/index.spec.tsx | 3 +- .../components/plugins/plugin-item/index.tsx | 4 +- .../plugins/plugin-page/context.spec.tsx | 13 ++--- .../plugins/plugin-page/context.tsx | 4 +- .../plugins/plugin-page/empty/index.spec.tsx | 12 +++-- .../plugins/plugin-page/empty/index.tsx | 4 +- .../plugins/plugin-page/index.spec.tsx | 22 +++++--- .../components/plugins/plugin-page/index.tsx | 4 +- .../plugin-page/install-plugin-dropdown.tsx | 4 +- .../plugin-page/use-reference-setting.spec.ts | 48 ++++++++++++----- .../plugin-page/use-reference-setting.ts | 4 +- .../reference-setting-modal/index.spec.tsx | 4 +- .../plugins/reference-setting-modal/index.tsx | 4 +- .../share/text-generation/index.tsx | 4 +- web/app/components/tools/provider-list.tsx | 4 +- .../hooks/use-nodes-sync-draft.ts | 4 +- .../block-selector/all-start-blocks.tsx | 4 +- .../workflow/block-selector/all-tools.tsx | 4 +- .../workflow/block-selector/data-sources.tsx | 4 +- .../workflow/block-selector/tabs.tsx | 4 +- .../workflow/block-selector/tool-picker.tsx | 4 +- .../collaboration/hooks/use-collaboration.ts | 4 +- .../workflow/hooks/use-leader-restore.ts | 4 +- .../workflow/hooks/use-workflow-comment.ts | 4 +- .../hooks/use-workflow-interactions.ts | 4 +- .../components/agent-strategy-selector.tsx | 4 +- .../workflow/operator/zoom-in-out.tsx | 4 +- .../skill/hooks/use-skill-save-manager.tsx | 4 +- .../hooks/use-skill-tree-collaboration.ts | 6 +-- web/app/forgot-password/page.tsx | 4 +- web/app/install/page.tsx | 4 +- web/app/reset-password/layout.tsx | 4 +- web/app/signin/_header.tsx | 4 +- web/app/signin/invite-settings/page.tsx | 4 +- web/app/signin/layout.tsx | 6 +-- web/app/signin/normal-form.tsx | 4 +- web/app/signup/components/input-mail.spec.tsx | 14 ++--- web/app/signup/components/input-mail.tsx | 4 +- web/app/signup/layout.tsx | 4 +- web/context/app-context.tsx | 4 +- web/context/global-public-context.tsx | 54 ++++++++++++++++++- web/context/web-app-context.tsx | 2 +- web/contract/console/system.ts | 9 ---- web/contract/router.ts | 3 +- web/eslint-suppressions.json | 20 +++++-- web/hooks/use-document-title.spec.ts | 33 ++++++++---- web/hooks/use-document-title.ts | 4 +- web/hooks/use-global-public.ts | 30 ----------- web/models/common.ts | 2 +- web/service/access-control.ts | 4 +- web/service/use-explore.ts | 4 +- web/utils/setup-status.spec.ts | 44 +++++++-------- web/utils/setup-status.ts | 4 +- 102 files changed, 411 insertions(+), 296 deletions(-) delete mode 100644 web/hooks/use-global-public.ts diff --git a/web/__tests__/embedded-user-id-store.test.tsx b/web/__tests__/embedded-user-id-store.test.tsx index aa6bb5e4fb..901218e76b 100644 --- a/web/__tests__/embedded-user-id-store.test.tsx +++ b/web/__tests__/embedded-user-id-store.test.tsx @@ -26,11 +26,37 @@ vi.mock('@/app/components/base/chat/utils', () => ({ getProcessedSystemVariablesFromUrlParams: (...args: any[]) => mockGetProcessedSystemVariablesFromUrlParams(...args), })) -vi.mock('@/context/global-public-context', () => ({ - useSystemFeatures: vi.fn(() => ({})), - useIsSystemFeaturesPending: () => false, +// Use vi.hoisted to define mock state before vi.mock hoisting +const { mockGlobalStoreState } = vi.hoisted(() => ({ + mockGlobalStoreState: { + isGlobalPending: false, + setIsGlobalPending: vi.fn(), + systemFeatures: {}, + setSystemFeatures: vi.fn(), + }, })) +vi.mock('@/context/global-public-context', () => { + const useGlobalPublicStore = Object.assign( + (selector?: (state: typeof mockGlobalStoreState) => any) => + selector ? selector(mockGlobalStoreState) : mockGlobalStoreState, + { + setState: (updater: any) => { + if (typeof updater === 'function') + Object.assign(mockGlobalStoreState, updater(mockGlobalStoreState) ?? {}) + + else + Object.assign(mockGlobalStoreState, updater) + }, + __mockState: mockGlobalStoreState, + }, + ) + return { + useGlobalPublicStore, + useIsSystemFeaturesPending: () => false, + } +}) + const TestConsumer = () => { const embeddedUserId = useWebAppStore(state => state.embeddedUserId) const embeddedConversationId = useWebAppStore(state => state.embeddedConversationId) @@ -65,6 +91,7 @@ const initialWebAppStore = (() => { })() beforeEach(() => { + mockGlobalStoreState.isGlobalPending = false mockGetProcessedSystemVariablesFromUrlParams.mockReset() useWebAppStore.setState(initialWebAppStore, true) }) diff --git a/web/app/(shareLayout)/webapp-reset-password/layout.tsx b/web/app/(shareLayout)/webapp-reset-password/layout.tsx index f73aa17c8b..a71317c8b9 100644 --- a/web/app/(shareLayout)/webapp-reset-password/layout.tsx +++ b/web/app/(shareLayout)/webapp-reset-password/layout.tsx @@ -1,11 +1,11 @@ 'use client' -import type * as React from 'react' import Header from '@/app/signin/_header' -import { useSystemFeatures } from '@/hooks/use-global-public' + +import { useGlobalPublicStore } from '@/context/global-public-context' import { cn } from '@/utils/classnames' -export default function SignInLayout({ children }: { children: React.ReactNode }) { - const systemFeatures = useSystemFeatures() +export default function SignInLayout({ children }: any) { + const { systemFeatures } = useGlobalPublicStore() return ( <>
diff --git a/web/app/(shareLayout)/webapp-signin/components/external-member-sso-auth.tsx b/web/app/(shareLayout)/webapp-signin/components/external-member-sso-auth.tsx index 01539e6ab9..0776df036d 100644 --- a/web/app/(shareLayout)/webapp-signin/components/external-member-sso-auth.tsx +++ b/web/app/(shareLayout)/webapp-signin/components/external-member-sso-auth.tsx @@ -5,12 +5,12 @@ import { useCallback, useEffect } from 'react' import AppUnavailable from '@/app/components/base/app-unavailable' import Loading from '@/app/components/base/loading' import Toast from '@/app/components/base/toast' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import { fetchWebOAuth2SSOUrl, fetchWebOIDCSSOUrl, fetchWebSAMLSSOUrl } from '@/service/share' import { SSOProtocol } from '@/types/feature' const ExternalMemberSSOAuth = () => { - const systemFeatures = useSystemFeatures() + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) const searchParams = useSearchParams() const router = useRouter() diff --git a/web/app/(shareLayout)/webapp-signin/layout.tsx b/web/app/(shareLayout)/webapp-signin/layout.tsx index 6c07bdd8f6..21cb0e1f57 100644 --- a/web/app/(shareLayout)/webapp-signin/layout.tsx +++ b/web/app/(shareLayout)/webapp-signin/layout.tsx @@ -2,13 +2,13 @@ import type { PropsWithChildren } from 'react' import { useTranslation } from 'react-i18next' +import { useGlobalPublicStore } from '@/context/global-public-context' import useDocumentTitle from '@/hooks/use-document-title' -import { useSystemFeatures } from '@/hooks/use-global-public' import { cn } from '@/utils/classnames' export default function SignInLayout({ children }: PropsWithChildren) { const { t } = useTranslation() - const systemFeatures = useSystemFeatures() + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) useDocumentTitle(t('webapp.login', { ns: 'login' })) return ( <> diff --git a/web/app/(shareLayout)/webapp-signin/normalForm.tsx b/web/app/(shareLayout)/webapp-signin/normalForm.tsx index e5cba6c9b7..b15145346f 100644 --- a/web/app/(shareLayout)/webapp-signin/normalForm.tsx +++ b/web/app/(shareLayout)/webapp-signin/normalForm.tsx @@ -6,7 +6,7 @@ import { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import Loading from '@/app/components/base/loading' import { IS_CE_EDITION } from '@/config' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import { LicenseStatus } from '@/types/feature' import { cn } from '@/utils/classnames' import MailAndCodeAuth from './components/mail-and-code-auth' @@ -17,7 +17,7 @@ const NormalForm = () => { const { t } = useTranslation() const [isLoading, setIsLoading] = useState(true) - const systemFeatures = useSystemFeatures() + const { systemFeatures } = useGlobalPublicStore() const [authType, updateAuthType] = useState<'code' | 'password'>('password') const [showORLine, setShowORLine] = useState(false) const [allMethodsAreDisabled, setAllMethodsAreDisabled] = useState(false) diff --git a/web/app/(shareLayout)/webapp-signin/page.tsx b/web/app/(shareLayout)/webapp-signin/page.tsx index c64d6e10c1..b3ad1d48a6 100644 --- a/web/app/(shareLayout)/webapp-signin/page.tsx +++ b/web/app/(shareLayout)/webapp-signin/page.tsx @@ -5,8 +5,8 @@ import * as React from 'react' import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import AppUnavailable from '@/app/components/base/app-unavailable' +import { useGlobalPublicStore } from '@/context/global-public-context' import { useWebAppStore } from '@/context/web-app-context' -import { useSystemFeatures } from '@/hooks/use-global-public' import { AccessMode } from '@/models/access-control' import { webAppLogout } from '@/service/webapp-auth' import ExternalMemberSsoAuth from './components/external-member-sso-auth' @@ -14,7 +14,7 @@ import NormalForm from './normalForm' const WebSSOForm: FC = () => { const { t } = useTranslation() - const systemFeatures = useSystemFeatures() + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) const webAppAccessMode = useWebAppStore(s => s.webAppAccessMode) const searchParams = useSearchParams() const router = useRouter() diff --git a/web/app/account/(commonLayout)/account-page/index.tsx b/web/app/account/(commonLayout)/account-page/index.tsx index 0f6ea06195..f01efc002c 100644 --- a/web/app/account/(commonLayout)/account-page/index.tsx +++ b/web/app/account/(commonLayout)/account-page/index.tsx @@ -16,8 +16,8 @@ import { ToastContext } from '@/app/components/base/toast' import Collapse from '@/app/components/header/account-setting/collapse' import { IS_CE_EDITION, validPassword } from '@/config' import { useAppContext } from '@/context/app-context' +import { useGlobalPublicStore } from '@/context/global-public-context' import { useProviderContext } from '@/context/provider-context' -import { useSystemFeatures } from '@/hooks/use-global-public' import { updateUserProfile } from '@/service/common' import { useAppList } from '@/service/use-apps' import DeleteAccount from '../delete-account' @@ -34,7 +34,7 @@ const descriptionClassName = ` export default function AccountPage() { const { t } = useTranslation() - const systemFeatures = useSystemFeatures() + const { systemFeatures } = useGlobalPublicStore() const { data: appList } = useAppList({ page: 1, limit: 100, name: '' }) const apps = appList?.data || [] const { mutateUserProfile, userProfile } = useAppContext() diff --git a/web/app/account/(commonLayout)/header.tsx b/web/app/account/(commonLayout)/header.tsx index bc75c34334..bb58be87a8 100644 --- a/web/app/account/(commonLayout)/header.tsx +++ b/web/app/account/(commonLayout)/header.tsx @@ -5,13 +5,13 @@ import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import DifyLogo from '@/app/components/base/logo/dify-logo' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import Avatar from './avatar' const Header = () => { const { t } = useTranslation() const router = useRouter() - const systemFeatures = useSystemFeatures() + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) const goToStudio = useCallback(() => { router.push('/apps') diff --git a/web/app/account/oauth/authorize/layout.tsx b/web/app/account/oauth/authorize/layout.tsx index f4332a7c00..189971b16f 100644 --- a/web/app/account/oauth/authorize/layout.tsx +++ b/web/app/account/oauth/authorize/layout.tsx @@ -3,13 +3,13 @@ import Loading from '@/app/components/base/loading' import Header from '@/app/signin/_header' import { AppContextProvider } from '@/context/app-context' +import { useGlobalPublicStore } from '@/context/global-public-context' import useDocumentTitle from '@/hooks/use-document-title' -import { useSystemFeatures } from '@/hooks/use-global-public' import { useIsLogin } from '@/service/use-common' import { cn } from '@/utils/classnames' export default function SignInLayout({ children }: any) { - const systemFeatures = useSystemFeatures() + const { systemFeatures } = useGlobalPublicStore() useDocumentTitle('') const { isLoading, data: loginData } = useIsLogin() const isLoggedIn = loginData?.logged_in diff --git a/web/app/activate/page.tsx b/web/app/activate/page.tsx index ef7f7bbf22..5852ef54e4 100644 --- a/web/app/activate/page.tsx +++ b/web/app/activate/page.tsx @@ -1,12 +1,12 @@ 'use client' import * as React from 'react' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import { cn } from '@/utils/classnames' import Header from '../signin/_header' import ActivateForm from './activateForm' const Activate = () => { - const systemFeatures = useSystemFeatures() + const { systemFeatures } = useGlobalPublicStore() return (
diff --git a/web/app/components/app-initializer.tsx b/web/app/components/app-initializer.tsx index a958e5d87a..fb6ac1c6da 100644 --- a/web/app/components/app-initializer.tsx +++ b/web/app/components/app-initializer.tsx @@ -9,7 +9,7 @@ import { EDUCATION_VERIFY_URL_SEARCHPARAMS_ACTION, EDUCATION_VERIFYING_LOCALSTORAGE_ITEM, } from '@/app/education-apply/constants' -import { useSetupStatusQuery } from '@/hooks/use-global-public' +import { useSetupStatusQuery } from '@/context/global-public-context' import { sendGAEvent } from '@/utils/gtag' import { resolvePostLoginRedirect } from '../signin/utils/post-login-redirect' import { trackEvent } from './base/amplitude' diff --git a/web/app/components/app/app-access-control/index.tsx b/web/app/components/app/app-access-control/index.tsx index d1972114b8..8d46e41a11 100644 --- a/web/app/components/app/app-access-control/index.tsx +++ b/web/app/components/app/app-access-control/index.tsx @@ -5,7 +5,7 @@ import { Description as DialogDescription, DialogTitle } from '@headlessui/react import { RiBuildingLine, RiGlobalLine, RiVerifiedBadgeLine } from '@remixicon/react' import { useCallback, useEffect } from 'react' import { useTranslation } from 'react-i18next' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import { AccessMode, SubjectType } from '@/models/access-control' import { useUpdateAccessMode } from '@/service/access-control' import useAccessControlStore from '../../../../context/access-control-store' @@ -24,7 +24,7 @@ type AccessControlProps = { export default function AccessControl(props: AccessControlProps) { const { app, onClose, onConfirm } = props const { t } = useTranslation() - const systemFeatures = useSystemFeatures() + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) const setAppId = useAccessControlStore(s => s.setAppId) const specificGroups = useAccessControlStore(s => s.specificGroups) const specificMembers = useAccessControlStore(s => s.specificMembers) diff --git a/web/app/components/app/app-publisher/index.tsx b/web/app/components/app/app-publisher/index.tsx index 22c71bdee9..ab7f442ebf 100644 --- a/web/app/components/app/app-publisher/index.tsx +++ b/web/app/components/app/app-publisher/index.tsx @@ -42,9 +42,9 @@ import { collaborationManager } from '@/app/components/workflow/collaboration/co import { webSocketClient } from '@/app/components/workflow/collaboration/core/websocket-manager' import { WorkflowContext } from '@/app/components/workflow/context' import { appDefaultIconBackground } from '@/config' +import { useGlobalPublicStore } from '@/context/global-public-context' import { useAsyncWindowOpen } from '@/hooks/use-async-window-open' import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now' -import { useSystemFeatures } from '@/hooks/use-global-public' import { AccessMode } from '@/models/access-control' import { useAppWhiteListSubjects, useGetUserCanAccessApp } from '@/service/access-control' import { fetchAppDetailDirect } from '@/service/apps' @@ -162,7 +162,7 @@ const AppPublisher = ({ const workflowStore = useContext(WorkflowContext) const appDetail = useAppStore(state => state.appDetail) const setAppDetail = useAppStore(s => s.setAppDetail) - const systemFeatures = useSystemFeatures() + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) const { formatTimeFromNow } = useFormatTimeFromNow() const { app_base_url: appBaseURL = '', access_token: accessToken = '' } = appDetail?.site ?? {} diff --git a/web/app/components/app/create-app-dialog/app-card/index.tsx b/web/app/components/app/create-app-dialog/app-card/index.tsx index 47c2dab7c5..15cfbd5411 100644 --- a/web/app/components/app/create-app-dialog/app-card/index.tsx +++ b/web/app/components/app/create-app-dialog/app-card/index.tsx @@ -8,7 +8,7 @@ import { useContextSelector } from 'use-context-selector' import AppIcon from '@/app/components/base/app-icon' import Button from '@/app/components/base/button' import AppListContext from '@/context/app-list-context' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import { cn } from '@/utils/classnames' import { AppTypeIcon, AppTypeLabel } from '../../type-selector' @@ -25,7 +25,7 @@ const AppCard = ({ }: AppCardProps) => { const { t } = useTranslation() const { app: appBasicInfo } = app - const systemFeatures = useSystemFeatures() + const { systemFeatures } = useGlobalPublicStore() const isTrialApp = app.can_trial && systemFeatures.enable_trial_app const setShowTryAppPanel = useContextSelector(AppListContext, ctx => ctx.setShowTryAppPanel) const showTryAPPPanel = useCallback((appId: string) => { diff --git a/web/app/components/app/overview/app-card.tsx b/web/app/components/app/overview/app-card.tsx index 8a45d124ff..9975c81b3e 100644 --- a/web/app/components/app/overview/app-card.tsx +++ b/web/app/components/app/overview/app-card.tsx @@ -31,8 +31,8 @@ import SecretKeyButton from '@/app/components/develop/secret-key/secret-key-butt import Indicator from '@/app/components/header/indicator' import { BlockEnum } from '@/app/components/workflow/types' import { useAppContext } from '@/context/app-context' +import { useGlobalPublicStore } from '@/context/global-public-context' import { useDocLink } from '@/context/i18n' -import { useSystemFeatures } from '@/hooks/use-global-public' import { AccessMode } from '@/models/access-control' import { useAppWhiteListSubjects } from '@/service/access-control' import { fetchAppDetailDirect } from '@/service/apps' @@ -85,7 +85,7 @@ function AppCard({ const [showConfirmDelete, setShowConfirmDelete] = useState(false) const [showAccessControl, setShowAccessControl] = useState(false) const { t } = useTranslation() - const systemFeatures = useSystemFeatures() + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) const { data: appAccessSubjects } = useAppWhiteListSubjects(appDetail?.id, systemFeatures.webapp_auth.enabled && appDetail?.access_mode === AccessMode.SPECIFIC_GROUPS_MEMBERS) const OPERATIONS_MAP = useMemo(() => { diff --git a/web/app/components/apps/app-card.spec.tsx b/web/app/components/apps/app-card.spec.tsx index 263da61f32..feab513dc1 100644 --- a/web/app/components/apps/app-card.spec.tsx +++ b/web/app/components/apps/app-card.spec.tsx @@ -51,9 +51,11 @@ vi.mock('@/context/provider-context', () => ({ // Mock global public store - allow dynamic configuration let mockWebappAuthEnabled = false vi.mock('@/context/global-public-context', () => ({ - useSystemFeatures: () => ({ - webapp_auth: { enabled: mockWebappAuthEnabled }, - branding: { enabled: false }, + useGlobalPublicStore: (selector: (s: any) => any) => selector({ + systemFeatures: { + webapp_auth: { enabled: mockWebappAuthEnabled }, + branding: { enabled: false }, + }, }), })) diff --git a/web/app/components/apps/app-card.tsx b/web/app/components/apps/app-card.tsx index 0a49f16c19..7415ba6d29 100644 --- a/web/app/components/apps/app-card.tsx +++ b/web/app/components/apps/app-card.tsx @@ -24,9 +24,9 @@ import Tooltip from '@/app/components/base/tooltip' import { UserAvatarList } from '@/app/components/base/user-avatar-list' import { NEED_REFRESH_APP_LIST_KEY } from '@/config' import { useAppContext } from '@/context/app-context' +import { useGlobalPublicStore } from '@/context/global-public-context' import { useProviderContext } from '@/context/provider-context' import { useAsyncWindowOpen } from '@/hooks/use-async-window-open' -import { useSystemFeatures } from '@/hooks/use-global-public' import { AccessMode } from '@/models/access-control' import { useGetUserCanAccessApp } from '@/service/access-control' import { copyApp, deleteApp, exportAppBundle, exportAppConfig, updateAppInfo } from '@/service/apps' @@ -67,7 +67,7 @@ export type AppCardProps = { const AppCard = ({ app, onRefresh, onlineUsers = [] }: AppCardProps) => { const { t } = useTranslation() const { notify } = useContext(ToastContext) - const systemFeatures = useSystemFeatures() + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) const { isCurrentWorkspaceEditor } = useAppContext() const { onPlanInfoChanged } = useProviderContext() const { push } = useRouter() diff --git a/web/app/components/apps/list.spec.tsx b/web/app/components/apps/list.spec.tsx index 5d05da76c2..f086220fc6 100644 --- a/web/app/components/apps/list.spec.tsx +++ b/web/app/components/apps/list.spec.tsx @@ -27,8 +27,10 @@ vi.mock('@/context/app-context', () => ({ // Mock global public store vi.mock('@/context/global-public-context', () => ({ - useSystemFeatures: () => ({ - branding: { enabled: false }, + useGlobalPublicStore: () => ({ + systemFeatures: { + branding: { enabled: false }, + }, }), })) diff --git a/web/app/components/apps/list.tsx b/web/app/components/apps/list.tsx index c6608a9188..d3fc916c10 100644 --- a/web/app/components/apps/list.tsx +++ b/web/app/components/apps/list.tsx @@ -27,7 +27,7 @@ import { ToastContext } from '@/app/components/base/toast' import CheckboxWithLabel from '@/app/components/datasets/create/website/base/checkbox-with-label' import { NEED_REFRESH_APP_LIST_KEY } from '@/config' import { useAppContext } from '@/context/app-context' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import { CheckModal } from '@/hooks/use-pay' import { DSLImportStatus } from '@/models/app' import { fetchWorkflowOnlineUsers, importAppBundle } from '@/service/apps' @@ -68,7 +68,7 @@ const List: FC = ({ }) => { const { t } = useTranslation() const { notify } = useContext(ToastContext) - const systemFeatures = useSystemFeatures() + const { systemFeatures } = useGlobalPublicStore() const router = useRouter() const { push } = useRouter() const { isCurrentWorkspaceEditor, isCurrentWorkspaceDatasetOperator, isLoadingCurrentWorkspace } = useAppContext() diff --git a/web/app/components/base/chat/chat-with-history/sidebar/index.tsx b/web/app/components/base/chat/chat-with-history/sidebar/index.tsx index b0f0ec16b2..73305f86db 100644 --- a/web/app/components/base/chat/chat-with-history/sidebar/index.tsx +++ b/web/app/components/base/chat/chat-with-history/sidebar/index.tsx @@ -17,7 +17,7 @@ import RenameModal from '@/app/components/base/chat/chat-with-history/sidebar/re import Confirm from '@/app/components/base/confirm' import DifyLogo from '@/app/components/base/logo/dify-logo' import MenuDropdown from '@/app/components/share/text-generation/menu-dropdown' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import { cn } from '@/utils/classnames' import { useChatWithHistoryContext } from '../context' @@ -47,7 +47,7 @@ const Sidebar = ({ isPanel, panelVisible }: Props) => { isResponding, } = useChatWithHistoryContext() const isSidebarCollapsed = sidebarCollapseState - const systemFeatures = useSystemFeatures() + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) const [showConfirm, setShowConfirm] = useState(null) const [showRename, setShowRename] = useState(null) diff --git a/web/app/components/base/chat/embedded-chatbot/header/index.tsx b/web/app/components/base/chat/embedded-chatbot/header/index.tsx index f023eb0ec2..fe7afc9e22 100644 --- a/web/app/components/base/chat/embedded-chatbot/header/index.tsx +++ b/web/app/components/base/chat/embedded-chatbot/header/index.tsx @@ -9,7 +9,7 @@ import ViewFormDropdown from '@/app/components/base/chat/embedded-chatbot/inputs import Divider from '@/app/components/base/divider' import DifyLogo from '@/app/components/base/logo/dify-logo' import Tooltip from '@/app/components/base/tooltip' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import { cn } from '@/utils/classnames' import { isClient } from '@/utils/client' import { @@ -45,7 +45,7 @@ const Header: FC = ({ const [parentOrigin, setParentOrigin] = useState('') const [showToggleExpandButton, setShowToggleExpandButton] = useState(false) const [expanded, setExpanded] = useState(false) - const systemFeatures = useSystemFeatures() + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) const handleMessageReceived = useCallback((event: MessageEvent) => { let currentParentOrigin = parentOrigin diff --git a/web/app/components/base/chat/embedded-chatbot/index.tsx b/web/app/components/base/chat/embedded-chatbot/index.tsx index 4573f22c6e..635e8d4aee 100644 --- a/web/app/components/base/chat/embedded-chatbot/index.tsx +++ b/web/app/components/base/chat/embedded-chatbot/index.tsx @@ -9,9 +9,9 @@ import Header from '@/app/components/base/chat/embedded-chatbot/header' import Loading from '@/app/components/base/loading' import DifyLogo from '@/app/components/base/logo/dify-logo' import LogoHeader from '@/app/components/base/logo/logo-embedded-chat-header' +import { useGlobalPublicStore } from '@/context/global-public-context' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import useDocumentTitle from '@/hooks/use-document-title' -import { useSystemFeatures } from '@/hooks/use-global-public' import { AppSourceType } from '@/service/share' import { cn } from '@/utils/classnames' import { @@ -34,7 +34,7 @@ const Chatbot = () => { themeBuilder, } = useEmbeddedChatbotContext() const { t } = useTranslation() - const systemFeatures = useSystemFeatures() + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) const customConfig = appData?.custom_config const site = appData?.site diff --git a/web/app/components/custom/custom-web-app-brand/index.spec.tsx b/web/app/components/custom/custom-web-app-brand/index.spec.tsx index 944d465cf3..e50ca4e9b2 100644 --- a/web/app/components/custom/custom-web-app-brand/index.spec.tsx +++ b/web/app/components/custom/custom-web-app-brand/index.spec.tsx @@ -4,8 +4,8 @@ import { getImageUploadErrorMessage, imageUpload } from '@/app/components/base/i import { useToastContext } from '@/app/components/base/toast' import { Plan } from '@/app/components/billing/type' import { useAppContext } from '@/context/app-context' +import { useGlobalPublicStore } from '@/context/global-public-context' import { useProviderContext } from '@/context/provider-context' -import { useSystemFeatures } from '@/hooks/use-global-public' import { updateCurrentWorkspace } from '@/service/common' import CustomWebAppBrand from './index' @@ -22,7 +22,7 @@ vi.mock('@/context/provider-context', () => ({ useProviderContext: vi.fn(), })) vi.mock('@/context/global-public-context', () => ({ - useSystemFeatures: vi.fn(), + useGlobalPublicStore: vi.fn(), })) vi.mock('@/app/components/base/image-uploader/utils', () => ({ imageUpload: vi.fn(), @@ -34,7 +34,7 @@ const mockUseToastContext = vi.mocked(useToastContext) const mockUpdateCurrentWorkspace = vi.mocked(updateCurrentWorkspace) const mockUseAppContext = vi.mocked(useAppContext) const mockUseProviderContext = vi.mocked(useProviderContext) -const mockUseSystemFeatures = vi.mocked(useSystemFeatures) +const mockUseGlobalPublicStore = vi.mocked(useGlobalPublicStore) const mockImageUpload = vi.mocked(imageUpload) const mockGetImageUploadErrorMessage = vi.mocked(getImageUploadErrorMessage) @@ -80,7 +80,7 @@ describe('CustomWebAppBrand', () => { workspace_logo: 'https://example.com/workspace-logo.png', }, } - mockUseSystemFeatures.mockReturnValue(systemFeaturesState as ReturnType) + mockUseGlobalPublicStore.mockImplementation(selector => selector ? selector({ systemFeatures: systemFeaturesState } as any) : { systemFeatures: systemFeaturesState }) mockGetImageUploadErrorMessage.mockReturnValue('upload error') }) diff --git a/web/app/components/custom/custom-web-app-brand/index.tsx b/web/app/components/custom/custom-web-app-brand/index.tsx index 15d3fa6a7e..d9e80e80d1 100644 --- a/web/app/components/custom/custom-web-app-brand/index.tsx +++ b/web/app/components/custom/custom-web-app-brand/index.tsx @@ -19,8 +19,8 @@ import Switch from '@/app/components/base/switch' import { useToastContext } from '@/app/components/base/toast' import { Plan } from '@/app/components/billing/type' import { useAppContext } from '@/context/app-context' +import { useGlobalPublicStore } from '@/context/global-public-context' import { useProviderContext } from '@/context/provider-context' -import { useSystemFeatures } from '@/hooks/use-global-public' import { updateCurrentWorkspace, } from '@/service/common' @@ -40,7 +40,7 @@ const CustomWebAppBrand = () => { const [fileId, setFileId] = useState('') const [imgKey, setImgKey] = useState(() => Date.now()) const [uploadProgress, setUploadProgress] = useState(0) - const systemFeatures = useSystemFeatures() + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) const isSandbox = enableBilling && plan.type === Plan.sandbox const uploading = uploadProgress > 0 && uploadProgress < 100 const webappLogo = currentWorkspace.custom_config?.replace_webapp_logo || '' diff --git a/web/app/components/datasets/create-from-pipeline/list/built-in-pipeline-list.spec.tsx b/web/app/components/datasets/create-from-pipeline/list/built-in-pipeline-list.spec.tsx index cad2f0fd1e..4650907c94 100644 --- a/web/app/components/datasets/create-from-pipeline/list/built-in-pipeline-list.spec.tsx +++ b/web/app/components/datasets/create-from-pipeline/list/built-in-pipeline-list.spec.tsx @@ -25,7 +25,10 @@ vi.mock('@/context/i18n', () => ({ })) vi.mock('@/context/global-public-context', () => ({ - useSystemFeatures: vi.fn(() => ({ enable_marketplace: true })), + useGlobalPublicStore: vi.fn((selector) => { + const state = { systemFeatures: { enable_marketplace: true } } + return selector(state) + }), })) const mockUsePipelineTemplateList = vi.fn() diff --git a/web/app/components/datasets/create-from-pipeline/list/built-in-pipeline-list.tsx b/web/app/components/datasets/create-from-pipeline/list/built-in-pipeline-list.tsx index 52155e892b..31c62758c1 100644 --- a/web/app/components/datasets/create-from-pipeline/list/built-in-pipeline-list.tsx +++ b/web/app/components/datasets/create-from-pipeline/list/built-in-pipeline-list.tsx @@ -1,6 +1,6 @@ import { useMemo } from 'react' +import { useGlobalPublicStore } from '@/context/global-public-context' import { useLocale } from '@/context/i18n' -import { useSystemFeatures } from '@/hooks/use-global-public' import { LanguagesSupported } from '@/i18n-config/language' import { usePipelineTemplateList } from '@/service/use-pipeline' import CreateCard from './create-card' @@ -13,7 +13,7 @@ const BuiltInPipelineList = () => { return locale return LanguagesSupported[0] }, [locale]) - const enableMarketplace = useSystemFeatures().enable_marketplace + const enableMarketplace = useGlobalPublicStore(s => s.systemFeatures.enable_marketplace) const { data: pipelineList, isLoading } = usePipelineTemplateList({ type: 'built-in', language }, enableMarketplace) const list = pipelineList?.pipeline_templates || [] diff --git a/web/app/components/datasets/list/index.spec.tsx b/web/app/components/datasets/list/index.spec.tsx index 59d8b2a9c0..ff48774c87 100644 --- a/web/app/components/datasets/list/index.spec.tsx +++ b/web/app/components/datasets/list/index.spec.tsx @@ -34,8 +34,10 @@ vi.mock('@/context/app-context', () => ({ // Mock global public context vi.mock('@/context/global-public-context', () => ({ - useSystemFeatures: () => ({ - branding: { enabled: false }, + useGlobalPublicStore: () => ({ + systemFeatures: { + branding: { enabled: false }, + }, }), })) @@ -331,8 +333,10 @@ describe('List', () => { it('should not show DatasetFooter when branding is enabled', async () => { vi.doMock('@/context/global-public-context', () => ({ - useSystemFeatures: () => ({ - branding: { enabled: true }, + useGlobalPublicStore: () => ({ + systemFeatures: { + branding: { enabled: true }, + }, }), })) diff --git a/web/app/components/datasets/list/index.tsx b/web/app/components/datasets/list/index.tsx index 11e3f86d4e..fdbe33986a 100644 --- a/web/app/components/datasets/list/index.tsx +++ b/web/app/components/datasets/list/index.tsx @@ -16,8 +16,8 @@ import { useStore as useTagStore } from '@/app/components/base/tag-management/st import CheckboxWithLabel from '@/app/components/datasets/create/website/base/checkbox-with-label' import { useAppContext, useSelector as useAppContextSelector } from '@/context/app-context' import { useExternalApiPanel } from '@/context/external-api-panel-context' +import { useGlobalPublicStore } from '@/context/global-public-context' import useDocumentTitle from '@/hooks/use-document-title' -import { useSystemFeatures } from '@/hooks/use-global-public' import { useDatasetApiBaseUrl } from '@/service/knowledge/use-dataset' // Components import ExternalAPIPanel from '../external-api/external-api-panel' @@ -27,7 +27,7 @@ import Datasets from './datasets' const List = () => { const { t } = useTranslation() - const systemFeatures = useSystemFeatures() + const { systemFeatures } = useGlobalPublicStore() const router = useRouter() const { currentWorkspace, isCurrentWorkspaceOwner } = useAppContext() const showTagManagementModal = useTagStore(s => s.showTagManagementModal) diff --git a/web/app/components/explore/app-card/index.tsx b/web/app/components/explore/app-card/index.tsx index 09b7691cab..15152e0695 100644 --- a/web/app/components/explore/app-card/index.tsx +++ b/web/app/components/explore/app-card/index.tsx @@ -7,7 +7,7 @@ import { useTranslation } from 'react-i18next' import { useContextSelector } from 'use-context-selector' import AppIcon from '@/app/components/base/app-icon' import ExploreContext from '@/context/explore-context' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import { AppModeEnum } from '@/types/app' import { cn } from '@/utils/classnames' import { AppTypeIcon } from '../../app/type-selector' @@ -28,7 +28,7 @@ const AppCard = ({ }: AppCardProps) => { const { t } = useTranslation() const { app: appBasicInfo } = app - const systemFeatures = useSystemFeatures() + const { systemFeatures } = useGlobalPublicStore() const isTrialApp = app.can_trial && systemFeatures.enable_trial_app const setShowTryAppPanel = useContextSelector(ExploreContext, ctx => ctx.setShowTryAppPanel) const showTryAPPPanel = useCallback((appId: string) => { diff --git a/web/app/components/explore/app-list/index.tsx b/web/app/components/explore/app-list/index.tsx index ec58a31623..04f75107da 100644 --- a/web/app/components/explore/app-list/index.tsx +++ b/web/app/components/explore/app-list/index.tsx @@ -17,7 +17,7 @@ import Banner from '@/app/components/explore/banner/banner' import Category from '@/app/components/explore/category' import CreateAppModal from '@/app/components/explore/create-app-modal' import ExploreContext from '@/context/explore-context' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import { useImportDSL } from '@/hooks/use-import-dsl' import { DSLImportMode, @@ -36,7 +36,7 @@ const Apps = ({ onSuccess, }: AppsProps) => { const { t } = useTranslation() - const systemFeatures = useSystemFeatures() + const { systemFeatures } = useGlobalPublicStore() const { hasEditPermission } = useContext(ExploreContext) const allCategoriesEn = t('apps.allCategories', { ns: 'explore', lng: 'en' }) diff --git a/web/app/components/explore/try-app/index.tsx b/web/app/components/explore/try-app/index.tsx index 510b9326be..c6f00ed08e 100644 --- a/web/app/components/explore/try-app/index.tsx +++ b/web/app/components/explore/try-app/index.tsx @@ -7,7 +7,7 @@ import * as React from 'react' import { useState } from 'react' import Loading from '@/app/components/base/loading' import Modal from '@/app/components/base/modal/index' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import { useGetTryAppInfo } from '@/service/use-try-app' import Button from '../../base/button' import App from './app' @@ -30,7 +30,7 @@ const TryApp: FC = ({ onClose, onCreate, }) => { - const systemFeatures = useSystemFeatures() + const { systemFeatures } = useGlobalPublicStore() const isTrialApp = !!(app && app.can_trial && systemFeatures.enable_trial_app) const [type, setType] = useState(() => (app && !isTrialApp ? TypeEnum.DETAIL : TypeEnum.TRY)) const { data: appDetail, isLoading } = useGetTryAppInfo(appId) diff --git a/web/app/components/header/account-about/index.tsx b/web/app/components/header/account-about/index.tsx index f257098a7e..b80cbb8f03 100644 --- a/web/app/components/header/account-about/index.tsx +++ b/web/app/components/header/account-about/index.tsx @@ -9,7 +9,7 @@ import DifyLogo from '@/app/components/base/logo/dify-logo' import Modal from '@/app/components/base/modal' import { IS_CE_EDITION } from '@/config' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' type IAccountSettingProps = { langGeniusVersionInfo: LangGeniusVersionResponse @@ -22,7 +22,7 @@ export default function AccountAbout({ }: IAccountSettingProps) { const { t } = useTranslation() const isLatest = langGeniusVersionInfo.current_version === langGeniusVersionInfo.latest_version - const systemFeatures = useSystemFeatures() + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) return ( { - const { enable_marketplace } = useSystemFeatures() + const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures) const { data } = useGetDataSourceListAuth() return ( diff --git a/web/app/components/header/account-setting/members-page/index.tsx b/web/app/components/header/account-setting/members-page/index.tsx index 83043858cb..5a8f3aebdb 100644 --- a/web/app/components/header/account-setting/members-page/index.tsx +++ b/web/app/components/header/account-setting/members-page/index.tsx @@ -9,10 +9,10 @@ import { NUM_INFINITE } from '@/app/components/billing/config' import { Plan } from '@/app/components/billing/type' import UpgradeBtn from '@/app/components/billing/upgrade-btn' import { useAppContext } from '@/context/app-context' +import { useGlobalPublicStore } from '@/context/global-public-context' import { useLocale } from '@/context/i18n' import { useProviderContext } from '@/context/provider-context' import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now' -import { useSystemFeatures } from '@/hooks/use-global-public' import { LanguagesSupported } from '@/i18n-config/language' import { useMembers } from '@/service/use-common' import EditWorkspaceModal from './edit-workspace-modal' @@ -36,7 +36,7 @@ const MembersPage = () => { const { userProfile, currentWorkspace, isCurrentWorkspaceOwner, isCurrentWorkspaceManager } = useAppContext() const { data, refetch } = useMembers() - const systemFeatures = useSystemFeatures() + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) const { formatTimeFromNow } = useFormatTimeFromNow() const [inviteModalVisible, setInviteModalVisible] = useState(false) const [invitationResults, setInvitationResults] = useState([]) diff --git a/web/app/components/header/account-setting/members-page/invite-button.tsx b/web/app/components/header/account-setting/members-page/invite-button.tsx index db159dc3e1..fb5b5cdc5e 100644 --- a/web/app/components/header/account-setting/members-page/invite-button.tsx +++ b/web/app/components/header/account-setting/members-page/invite-button.tsx @@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Loading from '@/app/components/base/loading' import { useAppContext } from '@/context/app-context' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import { useWorkspacePermissions } from '@/service/use-workspace' type InviteButtonProps = { @@ -14,7 +14,7 @@ type InviteButtonProps = { const InviteButton = (props: InviteButtonProps) => { const { t } = useTranslation() const { currentWorkspace } = useAppContext() - const systemFeatures = useSystemFeatures() + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) const { data: workspacePermissions, isFetching: isFetchingWorkspacePermissions } = useWorkspacePermissions(currentWorkspace!.id, systemFeatures.branding.enabled) if (systemFeatures.branding.enabled) { if (isFetchingWorkspacePermissions) { diff --git a/web/app/components/header/account-setting/members-page/operation/transfer-ownership.tsx b/web/app/components/header/account-setting/members-page/operation/transfer-ownership.tsx index e3d0020db8..d7d7943c67 100644 --- a/web/app/components/header/account-setting/members-page/operation/transfer-ownership.tsx +++ b/web/app/components/header/account-setting/members-page/operation/transfer-ownership.tsx @@ -7,7 +7,7 @@ import { Fragment } from 'react' import { useTranslation } from 'react-i18next' import Loading from '@/app/components/base/loading' import { useAppContext } from '@/context/app-context' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import { useWorkspacePermissions } from '@/service/use-workspace' import { cn } from '@/utils/classnames' @@ -18,7 +18,7 @@ type Props = { const TransferOwnership = ({ onOperate }: Props) => { const { t } = useTranslation() const { currentWorkspace } = useAppContext() - const systemFeatures = useSystemFeatures() + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) const { data: workspacePermissions, isFetching: isFetchingWorkspacePermissions } = useWorkspacePermissions(currentWorkspace!.id, systemFeatures.branding.enabled) if (systemFeatures.branding.enabled) { if (isFetchingWorkspacePermissions) { diff --git a/web/app/components/header/account-setting/model-provider-page/index.tsx b/web/app/components/header/account-setting/model-provider-page/index.tsx index 006887c6d0..7606bbc04f 100644 --- a/web/app/components/header/account-setting/model-provider-page/index.tsx +++ b/web/app/components/header/account-setting/model-provider-page/index.tsx @@ -10,8 +10,8 @@ import { useEffect, useMemo } from 'react' import { useTranslation } from 'react-i18next' import { IS_CLOUD_EDITION } from '@/config' import { useAppContext } from '@/context/app-context' +import { useGlobalPublicStore } from '@/context/global-public-context' import { useProviderContext } from '@/context/provider-context' -import { useSystemFeatures } from '@/hooks/use-global-public' import { cn } from '@/utils/classnames' import { CustomConfigurationStatusEnum, @@ -41,7 +41,7 @@ const ModelProviderPage = ({ searchText }: Props) => { const { data: speech2textDefaultModel, isLoading: isSpeech2textDefaultModelLoading } = useDefaultModel(ModelTypeEnum.speech2text) const { data: ttsDefaultModel, isLoading: isTTSDefaultModelLoading } = useDefaultModel(ModelTypeEnum.tts) const { modelProviders: providers } = useProviderContext() - const { enable_marketplace } = useSystemFeatures() + const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures) const isDefaultModelLoading = isTextGenerationDefaultModelLoading || isEmbeddingsDefaultModelLoading || isRerankDefaultModelLoading diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/quota-panel.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/quota-panel.tsx index b48509e995..d44c201e0e 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/quota-panel.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/quota-panel.tsx @@ -10,7 +10,7 @@ import Loading from '@/app/components/base/loading' import Tooltip from '@/app/components/base/tooltip' import InstallFromMarketplace from '@/app/components/plugins/install-plugin/install-from-marketplace' import { useAppContext } from '@/context/app-context' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import useTimestamp from '@/hooks/use-timestamp' import { ModelProviderQuotaGetPaid } from '@/types/model-provider' import { cn } from '@/utils/classnames' @@ -56,7 +56,7 @@ const QuotaPanel: FC = ({ }) => { const { t } = useTranslation() const { currentWorkspace } = useAppContext() - const { trial_models } = useSystemFeatures() + const { trial_models } = useGlobalPublicStore(s => s.systemFeatures) const credits = Math.max((currentWorkspace.trial_credits - currentWorkspace.trial_credits_used) || 0, 0) const providerMap = useMemo(() => new Map( providers.map(p => [p.provider, p.preferred_provider_type]), diff --git a/web/app/components/header/index.tsx b/web/app/components/header/index.tsx index 863a50bf2a..210c62b660 100644 --- a/web/app/components/header/index.tsx +++ b/web/app/components/header/index.tsx @@ -5,11 +5,11 @@ import DifyLogo from '@/app/components/base/logo/dify-logo' import WorkplaceSelector from '@/app/components/header/account-dropdown/workplace-selector' import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants' import { useAppContext } from '@/context/app-context' +import { useGlobalPublicStore } from '@/context/global-public-context' import { useModalContext } from '@/context/modal-context' import { useProviderContext } from '@/context/provider-context' import { WorkspaceProvider } from '@/context/workspace-context' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' -import { useSystemFeatures } from '@/hooks/use-global-public' import { Plan } from '../billing/type' import AccountDropdown from './account-dropdown' import AppNav from './app-nav' @@ -33,7 +33,7 @@ const Header = () => { const isMobile = media === MediaType.mobile const { enableBilling, plan } = useProviderContext() const { setShowPricingModal, setShowAccountSettingModal } = useModalContext() - const systemFeatures = useSystemFeatures() + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) const isFreePlan = plan.type === Plan.sandbox const isBrandingEnabled = systemFeatures.branding.enabled const handlePlanClick = useCallback(() => { diff --git a/web/app/components/header/license-env/index.tsx b/web/app/components/header/license-env/index.tsx index 2c89d2a83a..292b3a2d30 100644 --- a/web/app/components/header/license-env/index.tsx +++ b/web/app/components/header/license-env/index.tsx @@ -3,13 +3,13 @@ import { RiHourglass2Fill } from '@remixicon/react' import dayjs from 'dayjs' import { useTranslation } from 'react-i18next' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import { LicenseStatus } from '@/types/feature' import PremiumBadge from '../../base/premium-badge' const LicenseNav = () => { const { t } = useTranslation() - const systemFeatures = useSystemFeatures() + const { systemFeatures } = useGlobalPublicStore() if (systemFeatures.license?.status === LicenseStatus.EXPIRING) { const expiredAt = systemFeatures.license?.expired_at diff --git a/web/app/components/plugins/install-plugin/hooks/use-install-plugin-limit.tsx b/web/app/components/plugins/install-plugin/hooks/use-install-plugin-limit.tsx index 6f6f1c9a82..227c572a77 100644 --- a/web/app/components/plugins/install-plugin/hooks/use-install-plugin-limit.tsx +++ b/web/app/components/plugins/install-plugin/hooks/use-install-plugin-limit.tsx @@ -1,6 +1,6 @@ import type { Plugin, PluginManifestInMarket } from '../../types' import type { SystemFeatures } from '@/types/feature' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import { InstallationScope } from '@/types/feature' type PluginProps = (Plugin | PluginManifestInMarket) & { from: 'github' | 'marketplace' | 'package' } @@ -41,6 +41,6 @@ export function pluginInstallLimit(plugin: PluginProps, systemFeatures: SystemFe } export default function usePluginInstallLimit(plugin: PluginProps) { - const systemFeatures = useSystemFeatures() + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) return pluginInstallLimit(plugin, systemFeatures) } diff --git a/web/app/components/plugins/install-plugin/install-bundle/index.spec.tsx b/web/app/components/plugins/install-plugin/install-bundle/index.spec.tsx index c8ccba4d3f..1b70cfb5c7 100644 --- a/web/app/components/plugins/install-plugin/install-bundle/index.spec.tsx +++ b/web/app/components/plugins/install-plugin/install-bundle/index.spec.tsx @@ -181,7 +181,7 @@ vi.mock('@/context/mitt-context', () => ({ // Mock global public context vi.mock('@/context/global-public-context', () => ({ - useSystemFeatures: () => ({}), + useGlobalPublicStore: () => ({}), })) // Mock useCanInstallPluginFromMarketplace diff --git a/web/app/components/plugins/install-plugin/install-bundle/steps/install-multi.spec.tsx b/web/app/components/plugins/install-plugin/install-bundle/steps/install-multi.spec.tsx index 1d303f389d..48f0703a4b 100644 --- a/web/app/components/plugins/install-plugin/install-bundle/steps/install-multi.spec.tsx +++ b/web/app/components/plugins/install-plugin/install-bundle/steps/install-multi.spec.tsx @@ -56,9 +56,9 @@ vi.mock('@/app/components/plugins/install-plugin/hooks/use-check-installed', () }), })) -// Mock useSystemFeatures +// Mock useGlobalPublicStore vi.mock('@/context/global-public-context', () => ({ - useSystemFeatures: () => ({}), + useGlobalPublicStore: () => ({}), })) // Mock pluginInstallLimit diff --git a/web/app/components/plugins/install-plugin/install-bundle/steps/install-multi.tsx b/web/app/components/plugins/install-plugin/install-bundle/steps/install-multi.tsx index f2c5c50775..1b08ca5a04 100644 --- a/web/app/components/plugins/install-plugin/install-bundle/steps/install-multi.tsx +++ b/web/app/components/plugins/install-plugin/install-bundle/steps/install-multi.tsx @@ -4,7 +4,7 @@ import { produce } from 'immer' import * as React from 'react' import { useCallback, useEffect, useImperativeHandle, useMemo, useState } from 'react' import useCheckInstalled from '@/app/components/plugins/install-plugin/hooks/use-check-installed' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import { useFetchPluginsInMarketPlaceByInfo } from '@/service/use-plugins' import LoadingError from '../../base/loading-error' import { pluginInstallLimit } from '../../hooks/use-install-plugin-limit' @@ -38,7 +38,7 @@ const InstallByDSLList = ({ isFromMarketPlace, ref, }: Props) => { - const systemFeatures = useSystemFeatures() + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) // DSL has id, to get plugin info to show more info const { isLoading: isFetchingMarketplaceDataById, data: infoGetById, error: infoByIdError } = useFetchPluginsInMarketPlaceByInfo(allPlugins.filter(d => d.type === 'marketplace').map((d) => { const dependecy = (d as GitHubItemAndMarketPlaceDependency).value diff --git a/web/app/components/plugins/plugin-detail-panel/detail-header.spec.tsx b/web/app/components/plugins/plugin-detail-panel/detail-header.spec.tsx index 4129cef114..49c3ef1058 100644 --- a/web/app/components/plugins/plugin-detail-panel/detail-header.spec.tsx +++ b/web/app/components/plugins/plugin-detail-panel/detail-header.spec.tsx @@ -66,7 +66,8 @@ vi.mock('@/context/i18n', () => ({ let mockEnableMarketplace = true vi.mock('@/context/global-public-context', () => ({ - useSystemFeatures: () => ({ enable_marketplace: mockEnableMarketplace }), + useGlobalPublicStore: (selector: (state: { systemFeatures: { enable_marketplace: boolean } }) => unknown) => + selector({ systemFeatures: { enable_marketplace: mockEnableMarketplace } }), })) vi.mock('@/context/modal-context', () => ({ diff --git a/web/app/components/plugins/plugin-detail-panel/detail-header.tsx b/web/app/components/plugins/plugin-detail-panel/detail-header.tsx index 00d617493a..7f7e11ad51 100644 --- a/web/app/components/plugins/plugin-detail-panel/detail-header.tsx +++ b/web/app/components/plugins/plugin-detail-panel/detail-header.tsx @@ -25,10 +25,10 @@ import UpdateFromMarketplace from '@/app/components/plugins/update-plugin/from-m import PluginVersionPicker from '@/app/components/plugins/update-plugin/plugin-version-picker' import { API_PREFIX } from '@/config' import { useAppContext } from '@/context/app-context' +import { useGlobalPublicStore } from '@/context/global-public-context' import { useGetLanguage, useLocale } from '@/context/i18n' import { useModalContext } from '@/context/modal-context' import { useProviderContext } from '@/context/provider-context' -import { useSystemFeatures } from '@/hooks/use-global-public' import useTheme from '@/hooks/use-theme' import { uninstallPlugin } from '@/service/plugins' import { useAllToolProviders, useInvalidateAllToolProviders } from '@/service/use-tools' @@ -72,7 +72,7 @@ const DetailHeader = ({ const { setShowUpdatePluginModal } = useModalContext() const { refreshModelProviders } = useProviderContext() const invalidateAllToolProviders = useInvalidateAllToolProviders() - const { enable_marketplace } = useSystemFeatures() + const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures) const { id, diff --git a/web/app/components/plugins/plugin-detail-panel/operation-dropdown.spec.tsx b/web/app/components/plugins/plugin-detail-panel/operation-dropdown.spec.tsx index ddcc0bf58d..5501526b12 100644 --- a/web/app/components/plugins/plugin-detail-panel/operation-dropdown.spec.tsx +++ b/web/app/components/plugins/plugin-detail-panel/operation-dropdown.spec.tsx @@ -11,7 +11,8 @@ vi.mock('react-i18next', () => ({ })) vi.mock('@/context/global-public-context', () => ({ - useSystemFeatures: () => ({ enable_marketplace: true }), + useGlobalPublicStore: (selector: (state: { systemFeatures: { enable_marketplace: boolean } }) => T): T => + selector({ systemFeatures: { enable_marketplace: true } }), })) vi.mock('@/utils/classnames', () => ({ diff --git a/web/app/components/plugins/plugin-detail-panel/operation-dropdown.tsx b/web/app/components/plugins/plugin-detail-panel/operation-dropdown.tsx index aa896175eb..c32dc9ac58 100644 --- a/web/app/components/plugins/plugin-detail-panel/operation-dropdown.tsx +++ b/web/app/components/plugins/plugin-detail-panel/operation-dropdown.tsx @@ -11,7 +11,7 @@ import { PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import { cn } from '@/utils/classnames' import { PluginSource } from '../types' @@ -42,7 +42,7 @@ const OperationDropdown: FC = ({ setOpen(!openRef.current) }, [setOpen]) - const { enable_marketplace } = useSystemFeatures() + const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures) return ( ({ // Mock global public store const mockEnableMarketplace = vi.fn(() => true) vi.mock('@/context/global-public-context', () => ({ - useSystemFeatures: () => ({ enable_marketplace: mockEnableMarketplace() }), + useGlobalPublicStore: (selector: (s: any) => any) => + selector({ systemFeatures: { enable_marketplace: mockEnableMarketplace() } }), })) // Mock Action component diff --git a/web/app/components/plugins/plugin-item/index.tsx b/web/app/components/plugins/plugin-item/index.tsx index 632e9193ed..3f658c63a8 100644 --- a/web/app/components/plugins/plugin-item/index.tsx +++ b/web/app/components/plugins/plugin-item/index.tsx @@ -16,7 +16,7 @@ import Tooltip from '@/app/components/base/tooltip' import useRefreshPluginList from '@/app/components/plugins/install-plugin/hooks/use-refresh-plugin-list' import { API_PREFIX } from '@/config' import { useAppContext } from '@/context/app-context' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import { useRenderI18nObject } from '@/hooks/use-i18n' import useTheme from '@/hooks/use-theme' import { cn } from '@/utils/classnames' @@ -85,7 +85,7 @@ const PluginItem: FC = ({ const getValueFromI18nObject = useRenderI18nObject() const title = getValueFromI18nObject(label) const descriptionText = getValueFromI18nObject(description) - const { enable_marketplace } = useSystemFeatures() + const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures) const iconFileName = theme === 'dark' && icon_dark ? icon_dark : icon const iconSrc = iconFileName ? (iconFileName.startsWith('http') ? iconFileName : `${API_PREFIX}/workspaces/current/plugin/icon?tenant_id=${tenant_id}&filename=${iconFileName}`) diff --git a/web/app/components/plugins/plugin-page/context.spec.tsx b/web/app/components/plugins/plugin-page/context.spec.tsx index 0ce9c1ef33..ea52ae1dbd 100644 --- a/web/app/components/plugins/plugin-page/context.spec.tsx +++ b/web/app/components/plugins/plugin-page/context.spec.tsx @@ -1,7 +1,7 @@ import { render, screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' // Import mocks -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import { PluginPageContext, PluginPageContextProvider, usePluginPageContext } from './context' @@ -11,7 +11,7 @@ vi.mock('nuqs', () => ({ })) vi.mock('@/context/global-public-context', () => ({ - useSystemFeatures: vi.fn(), + useGlobalPublicStore: vi.fn(), })) vi.mock('../hooks', () => ({ @@ -25,11 +25,12 @@ vi.mock('../hooks', () => ({ ], })) -// Helper function to mock useSystemFeatures with marketplace setting +// Helper function to mock useGlobalPublicStore with marketplace setting const mockGlobalPublicStore = (enableMarketplace: boolean) => { - vi.mocked(useSystemFeatures).mockReturnValue({ - enable_marketplace: enableMarketplace, - } as ReturnType) + vi.mocked(useGlobalPublicStore).mockImplementation((selector) => { + const state = { systemFeatures: { enable_marketplace: enableMarketplace } } + return selector(state as Parameters[0]) + }) } // Test component that uses the context diff --git a/web/app/components/plugins/plugin-page/context.tsx b/web/app/components/plugins/plugin-page/context.tsx index 98198d27f1..abc4408d62 100644 --- a/web/app/components/plugins/plugin-page/context.tsx +++ b/web/app/components/plugins/plugin-page/context.tsx @@ -13,7 +13,7 @@ import { createContext, useContextSelector, } from 'use-context-selector' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import { PLUGIN_PAGE_TABS_MAP, usePluginPageTabs } from '../hooks' export type PluginPageContextValue = { @@ -63,7 +63,7 @@ export const PluginPageContextProvider = ({ }) const [currentPluginID, setCurrentPluginID] = useState() - const { enable_marketplace } = useSystemFeatures() + const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures) const tabs = usePluginPageTabs() const options = useMemo(() => { return enable_marketplace ? tabs : tabs.filter(tab => tab.value !== PLUGIN_PAGE_TABS_MAP.marketplace) diff --git a/web/app/components/plugins/plugin-page/empty/index.spec.tsx b/web/app/components/plugins/plugin-page/empty/index.spec.tsx index c6a2603285..51d4af919d 100644 --- a/web/app/components/plugins/plugin-page/empty/index.spec.tsx +++ b/web/app/components/plugins/plugin-page/empty/index.spec.tsx @@ -56,10 +56,14 @@ vi.mock('../context', () => ({ // Mock global public store (Zustand store) vi.mock('@/context/global-public-context', () => ({ - useSystemFeatures: () => ({ - ...defaultSystemFeatures, - ...mockState.systemFeatures, - }), + useGlobalPublicStore: (selector: (state: any) => any) => { + return selector({ + systemFeatures: { + ...defaultSystemFeatures, + ...mockState.systemFeatures, + }, + }) + }, })) // Mock useInstalledPluginList hook diff --git a/web/app/components/plugins/plugin-page/empty/index.tsx b/web/app/components/plugins/plugin-page/empty/index.tsx index cf097c5ad1..7149423d5f 100644 --- a/web/app/components/plugins/plugin-page/empty/index.tsx +++ b/web/app/components/plugins/plugin-page/empty/index.tsx @@ -11,7 +11,7 @@ import { MagicBox } from '@/app/components/base/icons/src/vender/solid/mediaAndD import InstallFromGitHub from '@/app/components/plugins/install-plugin/install-from-github' import InstallFromLocalPackage from '@/app/components/plugins/install-plugin/install-from-local-package' import { SUPPORT_INSTALL_LOCAL_FILE_EXTENSIONS } from '@/config' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import { useInstalledPluginList } from '@/service/use-plugins' import Line from '../../marketplace/empty/line' import { usePluginPageContext } from '../context' @@ -27,7 +27,7 @@ const Empty = () => { const fileInputRef = useRef(null) const [selectedAction, setSelectedAction] = useState(null) const [selectedFile, setSelectedFile] = useState(null) - const { enable_marketplace, plugin_installation_permission } = useSystemFeatures() + const { enable_marketplace, plugin_installation_permission } = useGlobalPublicStore(s => s.systemFeatures) const setActiveTab = usePluginPageContext(v => v.setActiveTab) const handleFileChange = (event: React.ChangeEvent) => { diff --git a/web/app/components/plugins/plugin-page/index.spec.tsx b/web/app/components/plugins/plugin-page/index.spec.tsx index 49aa302503..9b7ada2a87 100644 --- a/web/app/components/plugins/plugin-page/index.spec.tsx +++ b/web/app/components/plugins/plugin-page/index.spec.tsx @@ -28,9 +28,14 @@ vi.mock('@/context/i18n', () => ({ })) vi.mock('@/context/global-public-context', () => ({ - useSystemFeatures: vi.fn(() => ({ - enable_marketplace: true, - })), + useGlobalPublicStore: vi.fn((selector) => { + const state = { + systemFeatures: { + enable_marketplace: true, + }, + } + return selector(state) + }), })) vi.mock('@/context/app-context', () => ({ @@ -624,9 +629,14 @@ describe('PluginPage Component', () => { it('should handle marketplace disabled', () => { // Mock marketplace disabled vi.mock('@/context/global-public-context', async () => ({ - useSystemFeatures: vi.fn(() => ({ - enable_marketplace: false, - })), + useGlobalPublicStore: vi.fn((selector) => { + const state = { + systemFeatures: { + enable_marketplace: false, + }, + } + return selector(state) + }), })) vi.mocked(useQueryState).mockReturnValue(['discover', vi.fn()]) diff --git a/web/app/components/plugins/plugin-page/index.tsx b/web/app/components/plugins/plugin-page/index.tsx index e47c20675e..efb665197a 100644 --- a/web/app/components/plugins/plugin-page/index.tsx +++ b/web/app/components/plugins/plugin-page/index.tsx @@ -16,9 +16,9 @@ import TabSlider from '@/app/components/base/tab-slider' import Tooltip from '@/app/components/base/tooltip' import ReferenceSettingModal from '@/app/components/plugins/reference-setting-modal' import { MARKETPLACE_API_PREFIX, SUPPORT_INSTALL_LOCAL_FILE_EXTENSIONS } from '@/config' +import { useGlobalPublicStore } from '@/context/global-public-context' import { useDocLink } from '@/context/i18n' import useDocumentTitle from '@/hooks/use-document-title' -import { useSystemFeatures } from '@/hooks/use-global-public' import { usePluginInstallation } from '@/hooks/use-query-params' import { fetchBundleInfoFromMarketPlace, fetchManifestFromMarketPlace } from '@/service/plugins' import { sleep } from '@/utils' @@ -112,7 +112,7 @@ const PluginPage = ({ const options = usePluginPageContext(v => v.options) const activeTab = usePluginPageContext(v => v.activeTab) const setActiveTab = usePluginPageContext(v => v.setActiveTab) - const { enable_marketplace } = useSystemFeatures() + const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures) const isPluginsTab = useMemo(() => activeTab === PLUGIN_PAGE_TABS_MAP.plugins, [activeTab]) const isExploringMarketplace = useMemo(() => { diff --git a/web/app/components/plugins/plugin-page/install-plugin-dropdown.tsx b/web/app/components/plugins/plugin-page/install-plugin-dropdown.tsx index d867a7c83f..322591a363 100644 --- a/web/app/components/plugins/plugin-page/install-plugin-dropdown.tsx +++ b/web/app/components/plugins/plugin-page/install-plugin-dropdown.tsx @@ -16,7 +16,7 @@ import { import InstallFromGitHub from '@/app/components/plugins/install-plugin/install-from-github' import InstallFromLocalPackage from '@/app/components/plugins/install-plugin/install-from-local-package' import { SUPPORT_INSTALL_LOCAL_FILE_EXTENSIONS } from '@/config' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import { cn } from '@/utils/classnames' type Props = { @@ -37,7 +37,7 @@ const InstallPluginDropdown = ({ const [isMenuOpen, setIsMenuOpen] = useState(false) const [selectedAction, setSelectedAction] = useState(null) const [selectedFile, setSelectedFile] = useState(null) - const { enable_marketplace, plugin_installation_permission } = useSystemFeatures() + const { enable_marketplace, plugin_installation_permission } = useGlobalPublicStore(s => s.systemFeatures) const handleFileChange = (event: React.ChangeEvent) => { const file = event.target.files?.[0] diff --git a/web/app/components/plugins/plugin-page/use-reference-setting.spec.ts b/web/app/components/plugins/plugin-page/use-reference-setting.spec.ts index c18bd14253..9f64d3fac5 100644 --- a/web/app/components/plugins/plugin-page/use-reference-setting.spec.ts +++ b/web/app/components/plugins/plugin-page/use-reference-setting.spec.ts @@ -2,7 +2,7 @@ import { renderHook, waitFor } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' // Import mocks for assertions import { useAppContext } from '@/context/app-context' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import { useInvalidateReferenceSettings, useMutationReferenceSettings, useReferenceSettings } from '@/service/use-plugins' import Toast from '../../base/toast' @@ -21,7 +21,7 @@ vi.mock('@/context/app-context', () => ({ })) vi.mock('@/context/global-public-context', () => ({ - useSystemFeatures: vi.fn(), + useGlobalPublicStore: vi.fn(), })) vi.mock('@/service/use-plugins', () => ({ @@ -309,9 +309,14 @@ describe('useCanInstallPluginFromMarketplace Hook', () => { }) it('should return true when marketplace is enabled and canManagement is true', () => { - vi.mocked(useSystemFeatures).mockReturnValue({ - enable_marketplace: true, - } as ReturnType) + vi.mocked(useGlobalPublicStore).mockImplementation((selector) => { + const state = { + systemFeatures: { + enable_marketplace: true, + }, + } + return selector(state as Parameters[0]) + }) const { result } = renderHook(() => useCanInstallPluginFromMarketplace()) @@ -319,9 +324,14 @@ describe('useCanInstallPluginFromMarketplace Hook', () => { }) it('should return false when marketplace is disabled', () => { - vi.mocked(useSystemFeatures).mockReturnValue({ - enable_marketplace: false, - } as ReturnType) + vi.mocked(useGlobalPublicStore).mockImplementation((selector) => { + const state = { + systemFeatures: { + enable_marketplace: false, + }, + } + return selector(state as Parameters[0]) + }) const { result } = renderHook(() => useCanInstallPluginFromMarketplace()) @@ -329,9 +339,14 @@ describe('useCanInstallPluginFromMarketplace Hook', () => { }) it('should return false when canManagement is false', () => { - vi.mocked(useSystemFeatures).mockReturnValue({ - enable_marketplace: true, - } as ReturnType) + vi.mocked(useGlobalPublicStore).mockImplementation((selector) => { + const state = { + systemFeatures: { + enable_marketplace: true, + }, + } + return selector(state as Parameters[0]) + }) vi.mocked(useReferenceSettings).mockReturnValue({ data: { @@ -348,9 +363,14 @@ describe('useCanInstallPluginFromMarketplace Hook', () => { }) it('should return false when both marketplace is disabled and canManagement is false', () => { - vi.mocked(useSystemFeatures).mockReturnValue({ - enable_marketplace: false, - } as ReturnType) + vi.mocked(useGlobalPublicStore).mockImplementation((selector) => { + const state = { + systemFeatures: { + enable_marketplace: false, + }, + } + return selector(state as Parameters[0]) + }) vi.mocked(useReferenceSettings).mockReturnValue({ data: { diff --git a/web/app/components/plugins/plugin-page/use-reference-setting.ts b/web/app/components/plugins/plugin-page/use-reference-setting.ts index 37c1833d29..88591b4ced 100644 --- a/web/app/components/plugins/plugin-page/use-reference-setting.ts +++ b/web/app/components/plugins/plugin-page/use-reference-setting.ts @@ -1,7 +1,7 @@ import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import { useAppContext } from '@/context/app-context' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import { useInvalidateReferenceSettings, useMutationReferenceSettings, useReferenceSettings } from '@/service/use-plugins' import Toast from '../../base/toast' import { PermissionType } from '../types' @@ -48,7 +48,7 @@ const useReferenceSetting = () => { } export const useCanInstallPluginFromMarketplace = () => { - const { enable_marketplace } = useSystemFeatures() + const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures) const { canManagement } = useReferenceSetting() const canInstallPluginFromMarketplace = useMemo(() => { diff --git a/web/app/components/plugins/reference-setting-modal/index.spec.tsx b/web/app/components/plugins/reference-setting-modal/index.spec.tsx index 88ff96a3f2..43056b4e86 100644 --- a/web/app/components/plugins/reference-setting-modal/index.spec.tsx +++ b/web/app/components/plugins/reference-setting-modal/index.spec.tsx @@ -36,7 +36,9 @@ vi.mock('react-i18next', () => ({ // Mock global public store const mockSystemFeatures = { enable_marketplace: true } vi.mock('@/context/global-public-context', () => ({ - useSystemFeatures: () => mockSystemFeatures, + useGlobalPublicStore: (selector: (s: { systemFeatures: typeof mockSystemFeatures }) => typeof mockSystemFeatures) => { + return selector({ systemFeatures: mockSystemFeatures }) + }, })) // Mock Modal component diff --git a/web/app/components/plugins/reference-setting-modal/index.tsx b/web/app/components/plugins/reference-setting-modal/index.tsx index 31b1918981..6283c080da 100644 --- a/web/app/components/plugins/reference-setting-modal/index.tsx +++ b/web/app/components/plugins/reference-setting-modal/index.tsx @@ -9,7 +9,7 @@ import Button from '@/app/components/base/button' import Modal from '@/app/components/base/modal' import { PermissionType } from '@/app/components/plugins/types' import OptionCard from '@/app/components/workflow/nodes/_base/components/option-card' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import AutoUpdateSetting from './auto-update-setting' import { defaultValue as autoUpdateDefaultValue } from './auto-update-setting/config' import Label from './label' @@ -30,7 +30,7 @@ const PluginSettingModal: FC = ({ const { auto_upgrade: autoUpdateConfig, permission: privilege } = payload || {} const [tempPrivilege, setTempPrivilege] = useState(privilege) const [tempAutoUpdateConfig, setTempAutoUpdateConfig] = useState(autoUpdateConfig || autoUpdateDefaultValue) - const { enable_marketplace } = useSystemFeatures() + const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures) const handlePrivilegeChange = useCallback((key: string) => { return (value: PermissionType) => { setTempPrivilege({ diff --git a/web/app/components/share/text-generation/index.tsx b/web/app/components/share/text-generation/index.tsx index 967278dff4..90a2fb9277 100644 --- a/web/app/components/share/text-generation/index.tsx +++ b/web/app/components/share/text-generation/index.tsx @@ -27,11 +27,11 @@ import Toast from '@/app/components/base/toast' import Res from '@/app/components/share/text-generation/result' import RunOnce from '@/app/components/share/text-generation/run-once' import { appDefaultIconBackground, BATCH_CONCURRENCY } from '@/config' +import { useGlobalPublicStore } from '@/context/global-public-context' import { useWebAppStore } from '@/context/web-app-context' import { useAppFavicon } from '@/hooks/use-app-favicon' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import useDocumentTitle from '@/hooks/use-document-title' -import { useSystemFeatures } from '@/hooks/use-global-public' import { changeLanguage } from '@/i18n-config/client' import { AccessMode } from '@/models/access-control' import { AppSourceType, fetchSavedMessage as doFetchSavedMessage, removeMessage, saveMessage } from '@/service/share' @@ -91,7 +91,7 @@ const TextGeneration: FC = ({ doSetInputs(newInputs) inputsRef.current = newInputs }, []) - const systemFeatures = useSystemFeatures() + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) const [appId, setAppId] = useState('') const [siteInfo, setSiteInfo] = useState(null) const [customConfig, setCustomConfig] = useState | null>(null) diff --git a/web/app/components/tools/provider-list.tsx b/web/app/components/tools/provider-list.tsx index 7620441828..48fd4ef29d 100644 --- a/web/app/components/tools/provider-list.tsx +++ b/web/app/components/tools/provider-list.tsx @@ -14,7 +14,7 @@ import LabelFilter from '@/app/components/tools/labels/filter' import CustomCreateCard from '@/app/components/tools/provider/custom-create-card' import ProviderDetail from '@/app/components/tools/provider/detail' import WorkflowToolEmpty from '@/app/components/tools/provider/empty' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import { useCheckInstalled, useInvalidateInstalledPluginList } from '@/service/use-plugins' import { useAllToolProviders } from '@/service/use-tools' import { cn } from '@/utils/classnames' @@ -42,7 +42,7 @@ const ProviderList = () => { // searchParams.get('category') === 'workflow' const { t } = useTranslation() const { getTagLabel } = useTags() - const { enable_marketplace } = useSystemFeatures() + const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures) const containerRef = useRef(null) const [activeTab, setActiveTab] = useQueryState('category', { diff --git a/web/app/components/workflow-app/hooks/use-nodes-sync-draft.ts b/web/app/components/workflow-app/hooks/use-nodes-sync-draft.ts index 4864991daf..ffb07e50c3 100644 --- a/web/app/components/workflow-app/hooks/use-nodes-sync-draft.ts +++ b/web/app/components/workflow-app/hooks/use-nodes-sync-draft.ts @@ -9,7 +9,7 @@ import { useSerialAsyncCallback } from '@/app/components/workflow/hooks/use-seri import { useNodesReadOnly } from '@/app/components/workflow/hooks/use-workflow' import { useWorkflowStore } from '@/app/components/workflow/store' import { API_PREFIX } from '@/config' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import { syncWorkflowDraft } from '@/service/workflow' import { useWorkflowRefreshDraft } from '.' @@ -20,7 +20,7 @@ export const useNodesSyncDraft = () => { const { getNodesReadOnly } = useNodesReadOnly() const { handleRefreshWorkflowDraft } = useWorkflowRefreshDraft() const params = useParams() - const isCollaborationEnabled = useSystemFeatures().enable_collaboration_mode + const isCollaborationEnabled = useGlobalPublicStore(s => s.systemFeatures.enable_collaboration_mode) const getPostParams = useCallback(() => { const { diff --git a/web/app/components/workflow/block-selector/all-start-blocks.tsx b/web/app/components/workflow/block-selector/all-start-blocks.tsx index 978e6d1f2c..d122faecf6 100644 --- a/web/app/components/workflow/block-selector/all-start-blocks.tsx +++ b/web/app/components/workflow/block-selector/all-start-blocks.tsx @@ -18,7 +18,7 @@ import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Divider from '@/app/components/base/divider' import { SearchMenu } from '@/app/components/base/icons/src/vender/line/general' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import { useFeaturedTriggersRecommendations } from '@/service/use-plugins' import { useAllTriggerPlugins, useInvalidateAllTriggerPlugins } from '@/service/use-triggers' import { cn } from '@/utils/classnames' @@ -54,7 +54,7 @@ const AllStartBlocks = ({ const { t } = useTranslation() const [hasStartBlocksContent, setHasStartBlocksContent] = useState(false) const [hasPluginContent, setHasPluginContent] = useState(false) - const { enable_marketplace } = useSystemFeatures() + const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures) const pluginRef = useRef(null) const wrapElemRef = useRef(null) diff --git a/web/app/components/workflow/block-selector/all-tools.tsx b/web/app/components/workflow/block-selector/all-tools.tsx index 5a803dd644..29708f6937 100644 --- a/web/app/components/workflow/block-selector/all-tools.tsx +++ b/web/app/components/workflow/block-selector/all-tools.tsx @@ -20,8 +20,8 @@ import Button from '@/app/components/base/button' import Divider from '@/app/components/base/divider' import { SearchMenu } from '@/app/components/base/icons/src/vender/line/general' import PluginList from '@/app/components/workflow/block-selector/market-place-plugin/list' +import { useGlobalPublicStore } from '@/context/global-public-context' import { useGetLanguage } from '@/context/i18n' -import { useSystemFeatures } from '@/hooks/use-global-public' import { cn } from '@/utils/classnames' import { getMarketplaceUrl } from '@/utils/var' import { useMarketplacePlugins } from '../../plugins/marketplace/hooks' @@ -179,7 +179,7 @@ const AllTools = ({ plugins: notInstalledPlugins = [], } = useMarketplacePlugins() - const { enable_marketplace } = useSystemFeatures() + const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures) useEffect(() => { if (!enable_marketplace) diff --git a/web/app/components/workflow/block-selector/data-sources.tsx b/web/app/components/workflow/block-selector/data-sources.tsx index 51358690e6..5204f4e86c 100644 --- a/web/app/components/workflow/block-selector/data-sources.tsx +++ b/web/app/components/workflow/block-selector/data-sources.tsx @@ -11,8 +11,8 @@ import { useRef, } from 'react' import PluginList from '@/app/components/workflow/block-selector/market-place-plugin/list' +import { useGlobalPublicStore } from '@/context/global-public-context' import { useGetLanguage } from '@/context/i18n' -import { useSystemFeatures } from '@/hooks/use-global-public' import { cn } from '@/utils/classnames' import { useMarketplacePlugins } from '../../plugins/marketplace/hooks' import { PluginCategoryEnum } from '../../plugins/types' @@ -76,7 +76,7 @@ const DataSources = ({ onSelect(BlockEnum.DataSource, toolDefaultValue && defaultValue) }, [onSelect]) - const { enable_marketplace } = useSystemFeatures() + const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures) const { queryPluginsWithDebounced: fetchPlugins, diff --git a/web/app/components/workflow/block-selector/tabs.tsx b/web/app/components/workflow/block-selector/tabs.tsx index fbb25fad64..b26980e04b 100644 --- a/web/app/components/workflow/block-selector/tabs.tsx +++ b/web/app/components/workflow/block-selector/tabs.tsx @@ -8,7 +8,7 @@ import type { import { memo, useEffect, useMemo } from 'react' import { useTranslation } from 'react-i18next' import Tooltip from '@/app/components/base/tooltip' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import { useFeaturedToolsRecommendations } from '@/service/use-plugins' import { useAllBuiltInTools, useAllCustomTools, useAllMCPTools, useAllWorkflowTools, useInvalidateAllBuiltInTools } from '@/service/use-tools' import { cn } from '@/utils/classnames' @@ -64,7 +64,7 @@ const Tabs: FC = ({ const { data: workflowTools } = useAllWorkflowTools() const { data: mcpTools } = useAllMCPTools() const invalidateBuiltInTools = useInvalidateAllBuiltInTools() - const { enable_marketplace } = useSystemFeatures() + const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures) const workflowStore = useWorkflowStore() const inRAGPipeline = dataSources.length > 0 const { diff --git a/web/app/components/workflow/block-selector/tool-picker.tsx b/web/app/components/workflow/block-selector/tool-picker.tsx index 6527ba6d02..12502baf0b 100644 --- a/web/app/components/workflow/block-selector/tool-picker.tsx +++ b/web/app/components/workflow/block-selector/tool-picker.tsx @@ -20,7 +20,7 @@ import Toast from '@/app/components/base/toast' import SearchBox from '@/app/components/plugins/marketplace/search-box' import EditCustomToolModal from '@/app/components/tools/edit-custom-collection-modal' import AllTools from '@/app/components/workflow/block-selector/all-tools' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import { createCustomCollection, } from '@/service/tools' @@ -94,7 +94,7 @@ const ToolPicker: FC = ({ } const [tags, setTags] = useState([]) - const { enable_marketplace } = useSystemFeatures() + const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures) const { data: buildInTools } = useAllBuiltInTools() const { data: customTools } = useAllCustomTools() const invalidateCustomTools = useInvalidateAllCustomTools() diff --git a/web/app/components/workflow/collaboration/hooks/use-collaboration.ts b/web/app/components/workflow/collaboration/hooks/use-collaboration.ts index 92b432b3f3..4752a1edf7 100644 --- a/web/app/components/workflow/collaboration/hooks/use-collaboration.ts +++ b/web/app/components/workflow/collaboration/hooks/use-collaboration.ts @@ -7,7 +7,7 @@ import type { } from '../types/collaboration' import { useEffect, useRef, useState } from 'react' import Toast from '@/app/components/base/toast' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import { collaborationManager } from '../core/collaboration-manager' import { CursorService } from '../services/cursor-service' @@ -33,7 +33,7 @@ export function useCollaboration(appId: string, reactFlowStore?: ReactFlowStore) const [state, setState] = useState(initialState) const cursorServiceRef = useRef(null) - const isCollaborationEnabled = useSystemFeatures().enable_collaboration_mode + const isCollaborationEnabled = useGlobalPublicStore(s => s.systemFeatures.enable_collaboration_mode) useEffect(() => { if (!appId || !isCollaborationEnabled) { diff --git a/web/app/components/workflow/hooks/use-leader-restore.ts b/web/app/components/workflow/hooks/use-leader-restore.ts index c91aa8c552..8e73bbf52c 100644 --- a/web/app/components/workflow/hooks/use-leader-restore.ts +++ b/web/app/components/workflow/hooks/use-leader-restore.ts @@ -6,7 +6,7 @@ import { useReactFlow } from 'reactflow' import { useStore as useAppStore } from '@/app/components/app/store' import { useFeaturesStore } from '@/app/components/base/features/hooks' import Toast from '@/app/components/base/toast' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import { collaborationManager } from '../collaboration/core/collaboration-manager' import { useWorkflowStore } from '../store' import { useNodesSyncDraft } from './use-nodes-sync-draft' @@ -123,7 +123,7 @@ export const useLeaderRestore = () => { versionId: string callbacks: RestoreCallbacks | null } | null>(null) - const isCollaborationEnabled = useSystemFeatures().enable_collaboration_mode + const isCollaborationEnabled = useGlobalPublicStore(s => s.systemFeatures.enable_collaboration_mode) const requestRestore = useCallback((data: RestoreRequestData, callbacks?: RestoreCallbacks) => { if (!isCollaborationEnabled || !collaborationManager.isConnected() || collaborationManager.getIsLeader()) { diff --git a/web/app/components/workflow/hooks/use-workflow-comment.ts b/web/app/components/workflow/hooks/use-workflow-comment.ts index 4687521c2b..54aed4217a 100644 --- a/web/app/components/workflow/hooks/use-workflow-comment.ts +++ b/web/app/components/workflow/hooks/use-workflow-comment.ts @@ -4,7 +4,7 @@ import { useCallback, useEffect, useRef } from 'react' import { useReactFlow } from 'reactflow' import { collaborationManager } from '@/app/components/workflow/collaboration' import { useAppContext } from '@/context/app-context' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import { createWorkflowComment, createWorkflowCommentReply, deleteWorkflowComment, deleteWorkflowCommentReply, fetchWorkflowComment, fetchWorkflowComments, resolveWorkflowComment, updateWorkflowComment, updateWorkflowCommentReply } from '@/service/workflow-comment' import { useStore } from '../store' import { ControlMode } from '../types' @@ -50,7 +50,7 @@ export const useWorkflowComment = () => { appId ? state.mentionableUsersCache[appId] ?? EMPTY_USERS : EMPTY_USERS )) const { userProfile } = useAppContext() - const isCollaborationEnabled = useSystemFeatures().enable_collaboration_mode + const isCollaborationEnabled = useGlobalPublicStore(s => s.systemFeatures.enable_collaboration_mode) const commentDetailCacheRef = useRef>(commentDetailCache) const activeCommentIdRef = useRef(null) diff --git a/web/app/components/workflow/hooks/use-workflow-interactions.ts b/web/app/components/workflow/hooks/use-workflow-interactions.ts index 9b39a3dcbe..9fc1c871a4 100644 --- a/web/app/components/workflow/hooks/use-workflow-interactions.ts +++ b/web/app/components/workflow/hooks/use-workflow-interactions.ts @@ -8,7 +8,7 @@ import { useReactFlow } from 'reactflow' import { useStore as useAppStore } from '@/app/components/app/store' import { useCollaborativeWorkflow } from '@/app/components/workflow/hooks/use-collaborative-workflow' import { useEventEmitterContextContext } from '@/context/event-emitter' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import { CUSTOM_NODE, NODE_LAYOUT_HORIZONTAL_PADDING, @@ -73,7 +73,7 @@ export const useWorkflowMoveMode = () => { getNodesReadOnly, } = useNodesReadOnly() const { handleSelectionCancel } = useSelectionInteractions() - const isCollaborationEnabled = useSystemFeatures().enable_collaboration_mode + const isCollaborationEnabled = useGlobalPublicStore(s => s.systemFeatures.enable_collaboration_mode) const appDetail = useAppStore(state => state.appDetail) const isCommentModeAvailable = isCollaborationEnabled && (appDetail?.mode === 'workflow' || appDetail?.mode === 'advanced-chat') diff --git a/web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx b/web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx index 04f7e5d934..4635a5575c 100644 --- a/web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx +++ b/web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx @@ -16,7 +16,7 @@ import { useMarketplacePlugins } from '@/app/components/plugins/marketplace/hook import { PluginCategoryEnum } from '@/app/components/plugins/types' import { CollectionType } from '@/app/components/tools/types' import PluginList from '@/app/components/workflow/block-selector/market-place-plugin/list' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import { useStrategyProviders } from '@/service/use-strategy' import { cn } from '@/utils/classnames' import Tools from '../../../block-selector/tools' @@ -95,7 +95,7 @@ export type AgentStrategySelectorProps = { } export const AgentStrategySelector = memo((props: AgentStrategySelectorProps) => { - const { enable_marketplace } = useSystemFeatures() + const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures) const { value, onChange } = props const [open, setOpen] = useState(false) diff --git a/web/app/components/workflow/operator/zoom-in-out.tsx b/web/app/components/workflow/operator/zoom-in-out.tsx index 42486a03a2..60de39809e 100644 --- a/web/app/components/workflow/operator/zoom-in-out.tsx +++ b/web/app/components/workflow/operator/zoom-in-out.tsx @@ -21,7 +21,7 @@ import { PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import { cn } from '@/utils/classnames' import Divider from '../../base/divider' @@ -79,7 +79,7 @@ const ZoomInOut: FC = ({ workflowReadOnly, getWorkflowReadOnly, } = useWorkflowReadOnly() - const isCollaborationEnabled = useSystemFeatures().enable_collaboration_mode + const isCollaborationEnabled = useGlobalPublicStore(s => s.systemFeatures.enable_collaboration_mode) const ZOOM_IN_OUT_OPTIONS = [ [ diff --git a/web/app/components/workflow/skill/hooks/use-skill-save-manager.tsx b/web/app/components/workflow/skill/hooks/use-skill-save-manager.tsx index 9912c56c8c..9633c5bfec 100644 --- a/web/app/components/workflow/skill/hooks/use-skill-save-manager.tsx +++ b/web/app/components/workflow/skill/hooks/use-skill-save-manager.tsx @@ -7,7 +7,7 @@ import { useTranslation } from 'react-i18next' import Toast from '@/app/components/base/toast' import { useWorkflowStore } from '@/app/components/workflow/store' import { extractToolConfigIds } from '@/app/components/workflow/utils' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import { consoleQuery } from '@/service/client' import { useUpdateAppAssetFileContent } from '@/service/use-app-asset' import { skillCollaborationManager } from '../../collaboration/skills/skill-collaboration-manager' @@ -89,7 +89,7 @@ export const SkillSaveProvider = ({ const storeApi = useWorkflowStore() const queryClient = useQueryClient() const updateContent = useUpdateAppAssetFileContent() - const isCollaborationEnabled = useSystemFeatures().enable_collaboration_mode + const isCollaborationEnabled = useGlobalPublicStore(s => s.systemFeatures.enable_collaboration_mode) const queueRef = useRef>>(new Map()) const fallbackRegistryRef = useRef>(new Map()) diff --git a/web/app/components/workflow/skill/hooks/use-skill-tree-collaboration.ts b/web/app/components/workflow/skill/hooks/use-skill-tree-collaboration.ts index cc019e8836..4f2ab1e096 100644 --- a/web/app/components/workflow/skill/hooks/use-skill-tree-collaboration.ts +++ b/web/app/components/workflow/skill/hooks/use-skill-tree-collaboration.ts @@ -4,13 +4,13 @@ import { useQueryClient } from '@tanstack/react-query' import { useCallback, useEffect } from 'react' import { useStore as useAppStore } from '@/app/components/app/store' import { skillCollaborationManager } from '@/app/components/workflow/collaboration/skills/skill-collaboration-manager' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import { consoleQuery } from '@/service/client' export const useSkillTreeUpdateEmitter = () => { const appDetail = useAppStore(s => s.appDetail) const appId = appDetail?.id || '' - const isCollaborationEnabled = useSystemFeatures().enable_collaboration_mode + const isCollaborationEnabled = useGlobalPublicStore(s => s.systemFeatures.enable_collaboration_mode) return useCallback((payload: Record = {}) => { if (!appId || !isCollaborationEnabled) @@ -22,7 +22,7 @@ export const useSkillTreeUpdateEmitter = () => { export const useSkillTreeCollaboration = () => { const appDetail = useAppStore(s => s.appDetail) const appId = appDetail?.id || '' - const isCollaborationEnabled = useSystemFeatures().enable_collaboration_mode + const isCollaborationEnabled = useGlobalPublicStore(s => s.systemFeatures.enable_collaboration_mode) const queryClient = useQueryClient() useEffect(() => { diff --git a/web/app/forgot-password/page.tsx b/web/app/forgot-password/page.tsx index f15b4037fb..338f4eaf13 100644 --- a/web/app/forgot-password/page.tsx +++ b/web/app/forgot-password/page.tsx @@ -2,8 +2,8 @@ import { useSearchParams } from 'next/navigation' import * as React from 'react' import ChangePasswordForm from '@/app/forgot-password/ChangePasswordForm' +import { useGlobalPublicStore } from '@/context/global-public-context' import useDocumentTitle from '@/hooks/use-document-title' -import { useSystemFeatures } from '@/hooks/use-global-public' import { cn } from '@/utils/classnames' import Header from '../signin/_header' import ForgotPasswordForm from './ForgotPasswordForm' @@ -12,7 +12,7 @@ const ForgotPassword = () => { useDocumentTitle('') const searchParams = useSearchParams() const token = searchParams.get('token') - const systemFeatures = useSystemFeatures() + const { systemFeatures } = useGlobalPublicStore() return (
diff --git a/web/app/install/page.tsx b/web/app/install/page.tsx index 06466d3348..db30d5bc5a 100644 --- a/web/app/install/page.tsx +++ b/web/app/install/page.tsx @@ -1,12 +1,12 @@ 'use client' import * as React from 'react' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import { cn } from '@/utils/classnames' import Header from '../signin/_header' import InstallForm from './installForm' const Install = () => { - const systemFeatures = useSystemFeatures() + const { systemFeatures } = useGlobalPublicStore() return (
diff --git a/web/app/reset-password/layout.tsx b/web/app/reset-password/layout.tsx index f4aaab4fa1..d9b665501d 100644 --- a/web/app/reset-password/layout.tsx +++ b/web/app/reset-password/layout.tsx @@ -1,11 +1,11 @@ 'use client' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import { cn } from '@/utils/classnames' import Header from '../signin/_header' export default function SignInLayout({ children }: any) { - const systemFeatures = useSystemFeatures() + const { systemFeatures } = useGlobalPublicStore() return ( <>
diff --git a/web/app/signin/_header.tsx b/web/app/signin/_header.tsx index 21e9838464..63be6df674 100644 --- a/web/app/signin/_header.tsx +++ b/web/app/signin/_header.tsx @@ -3,8 +3,8 @@ import type { Locale } from '@/i18n-config' import dynamic from 'next/dynamic' import Divider from '@/app/components/base/divider' import LocaleSigninSelect from '@/app/components/base/select/locale-signin' +import { useGlobalPublicStore } from '@/context/global-public-context' import { useLocale } from '@/context/i18n' -import { useSystemFeatures } from '@/hooks/use-global-public' import { setLocaleOnClient } from '@/i18n-config' import { languages } from '@/i18n-config/language' @@ -20,7 +20,7 @@ const ThemeSelector = dynamic(() => import('@/app/components/base/theme-selector const Header = () => { const locale = useLocale() - const systemFeatures = useSystemFeatures() + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) return (
diff --git a/web/app/signin/invite-settings/page.tsx b/web/app/signin/invite-settings/page.tsx index 770b667fed..c16a580b3a 100644 --- a/web/app/signin/invite-settings/page.tsx +++ b/web/app/signin/invite-settings/page.tsx @@ -12,7 +12,7 @@ import Loading from '@/app/components/base/loading' import { SimpleSelect } from '@/app/components/base/select' import Toast from '@/app/components/base/toast' import { LICENSE_LINK } from '@/constants/link' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import { setLocaleOnClient } from '@/i18n-config' import { languages, LanguagesSupported } from '@/i18n-config/language' import { activateMember } from '@/service/common' @@ -22,7 +22,7 @@ import { resolvePostLoginRedirect } from '../utils/post-login-redirect' export default function InviteSettingsPage() { const { t } = useTranslation() - const systemFeatures = useSystemFeatures() + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) const router = useRouter() const searchParams = useSearchParams() const token = decodeURIComponent(searchParams.get('invite_token') as string) diff --git a/web/app/signin/layout.tsx b/web/app/signin/layout.tsx index d1c3d8875e..4a1a2f4f58 100644 --- a/web/app/signin/layout.tsx +++ b/web/app/signin/layout.tsx @@ -1,12 +1,12 @@ 'use client' -import useDocumentTitle from '@/hooks/use-document-title' +import { useGlobalPublicStore } from '@/context/global-public-context' -import { useSystemFeatures } from '@/hooks/use-global-public' +import useDocumentTitle from '@/hooks/use-document-title' import { cn } from '@/utils/classnames' import Header from './_header' export default function SignInLayout({ children }: any) { - const systemFeatures = useSystemFeatures() + const { systemFeatures } = useGlobalPublicStore() useDocumentTitle('') return ( <> diff --git a/web/app/signin/normal-form.tsx b/web/app/signin/normal-form.tsx index 5348dc44b8..be0feea6c1 100644 --- a/web/app/signin/normal-form.tsx +++ b/web/app/signin/normal-form.tsx @@ -6,7 +6,7 @@ import { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import Toast from '@/app/components/base/toast' import { IS_CE_EDITION } from '@/config' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import { invitationCheck } from '@/service/common' import { useIsLogin } from '@/service/use-common' import { LicenseStatus } from '@/types/feature' @@ -30,7 +30,7 @@ const NormalForm = () => { const [isInitCheckLoading, setInitCheckLoading] = useState(true) const [isRedirecting, setIsRedirecting] = useState(false) const isLoading = isCheckLoading || isInitCheckLoading || isRedirecting - const systemFeatures = useSystemFeatures() + const { systemFeatures } = useGlobalPublicStore() const [authType, updateAuthType] = useState<'code' | 'password'>('password') const [showORLine, setShowORLine] = useState(false) const [allMethodsAreDisabled, setAllMethodsAreDisabled] = useState(false) diff --git a/web/app/signup/components/input-mail.spec.tsx b/web/app/signup/components/input-mail.spec.tsx index 044f314d62..d5acc92153 100644 --- a/web/app/signup/components/input-mail.spec.tsx +++ b/web/app/signup/components/input-mail.spec.tsx @@ -2,8 +2,8 @@ import type { MockedFunction } from 'vitest' import type { SystemFeatures } from '@/types/feature' import { fireEvent, render, screen, waitFor } from '@testing-library/react' import * as React from 'react' +import { useGlobalPublicStore } from '@/context/global-public-context' import { useLocale } from '@/context/i18n' -import { useSystemFeatures } from '@/hooks/use-global-public' import { useSendMail } from '@/service/use-common' import { defaultSystemFeatures } from '@/types/feature' import Form from './input-mail' @@ -33,7 +33,7 @@ vi.mock('next/link', () => ({ })) vi.mock('@/context/global-public-context', () => ({ - useSystemFeatures: vi.fn(), + useGlobalPublicStore: vi.fn(), })) vi.mock('@/context/i18n', () => ({ @@ -46,7 +46,7 @@ vi.mock('@/service/use-common', () => ({ type UseSendMailResult = ReturnType -const mockUseSystemFeatures = useSystemFeatures as unknown as MockedFunction +const mockUseGlobalPublicStore = useGlobalPublicStore as unknown as MockedFunction const mockUseLocale = useLocale as unknown as MockedFunction const mockUseSendMail = useSendMail as unknown as MockedFunction @@ -57,9 +57,11 @@ const renderForm = ({ brandingEnabled?: boolean isPending?: boolean } = {}) => { - mockUseSystemFeatures.mockReturnValue(buildSystemFeatures({ - branding: { enabled: brandingEnabled }, - })) + mockUseGlobalPublicStore.mockReturnValue({ + systemFeatures: buildSystemFeatures({ + branding: { enabled: brandingEnabled }, + }), + }) mockUseLocale.mockReturnValue('en-US') mockUseSendMail.mockReturnValue({ mutateAsync: mockSubmitMail, diff --git a/web/app/signup/components/input-mail.tsx b/web/app/signup/components/input-mail.tsx index ac086d1d08..1b88007ce4 100644 --- a/web/app/signup/components/input-mail.tsx +++ b/web/app/signup/components/input-mail.tsx @@ -8,8 +8,8 @@ import Input from '@/app/components/base/input' import Toast from '@/app/components/base/toast' import Split from '@/app/signin/split' import { emailRegex } from '@/config' +import { useGlobalPublicStore } from '@/context/global-public-context' import { useLocale } from '@/context/i18n' -import { useSystemFeatures } from '@/hooks/use-global-public' import { useSendMail } from '@/service/use-common' type Props = { @@ -21,7 +21,7 @@ export default function Form({ const { t } = useTranslation() const [email, setEmail] = useState('') const locale = useLocale() - const systemFeatures = useSystemFeatures() + const { systemFeatures } = useGlobalPublicStore() const { mutateAsync: submitMail, isPending } = useSendMail() diff --git a/web/app/signup/layout.tsx b/web/app/signup/layout.tsx index 535316d8bf..6728b66115 100644 --- a/web/app/signup/layout.tsx +++ b/web/app/signup/layout.tsx @@ -1,12 +1,12 @@ 'use client' import Header from '@/app/signin/_header' +import { useGlobalPublicStore } from '@/context/global-public-context' import useDocumentTitle from '@/hooks/use-document-title' -import { useSystemFeatures } from '@/hooks/use-global-public' import { cn } from '@/utils/classnames' export default function RegisterLayout({ children }: any) { - const systemFeatures = useSystemFeatures() + const { systemFeatures } = useGlobalPublicStore() useDocumentTitle('') return ( <> diff --git a/web/context/app-context.tsx b/web/context/app-context.tsx index ca66e9cbf8..12000044d6 100644 --- a/web/context/app-context.tsx +++ b/web/context/app-context.tsx @@ -10,12 +10,12 @@ import { setUserId, setUserProperties } from '@/app/components/base/amplitude' import { setZendeskConversationFields } from '@/app/components/base/zendesk/utils' import MaintenanceNotice from '@/app/components/header/maintenance-notice' import { ZENDESK_FIELD_IDS } from '@/config' -import { useSystemFeatures } from '@/hooks/use-global-public' import { useCurrentWorkspace, useLangGeniusVersion, useUserProfile, } from '@/service/use-common' +import { useGlobalPublicStore } from './global-public-context' export type AppContextValue = { userProfile: UserProfileResponse @@ -89,7 +89,7 @@ export type AppContextProviderProps = { export const AppContextProvider: FC = ({ children }) => { const queryClient = useQueryClient() - const systemFeatures = useSystemFeatures() + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) const { data: userProfileResp } = useUserProfile() const { data: currentWorkspaceResp, isPending: isLoadingCurrentWorkspace, isFetching: isValidatingCurrentWorkspace } = useCurrentWorkspace() const langGeniusVersionQuery = useLangGeniusVersion( diff --git a/web/context/global-public-context.tsx b/web/context/global-public-context.tsx index 4e5ad2075d..e3d6902fd1 100644 --- a/web/context/global-public-context.tsx +++ b/web/context/global-public-context.tsx @@ -1,14 +1,64 @@ 'use client' import type { FC, PropsWithChildren } from 'react' +import type { SystemFeatures } from '@/types/feature' +import { useQuery } from '@tanstack/react-query' +import { useEffect } from 'react' +import { create } from 'zustand' import Loading from '@/app/components/base/loading' -import { useSetupStatusQuery, useSystemFeaturesQuery } from '@/hooks/use-global-public' +import { consoleClient } from '@/service/client' +import { defaultSystemFeatures } from '@/types/feature' +import { fetchSetupStatusWithCache } from '@/utils/setup-status' + +type GlobalPublicStore = { + systemFeatures: SystemFeatures + setSystemFeatures: (systemFeatures: SystemFeatures) => void +} + +export const useGlobalPublicStore = create(set => ({ + systemFeatures: defaultSystemFeatures, + setSystemFeatures: (systemFeatures: SystemFeatures) => set(() => ({ systemFeatures })), +})) + +const systemFeaturesQueryKey = ['systemFeatures'] as const +const setupStatusQueryKey = ['setupStatus'] as const + +async function fetchSystemFeatures() { + return consoleClient.systemFeatures() +} + +export function useSystemFeaturesQuery() { + return useQuery({ + queryKey: systemFeaturesQueryKey, + queryFn: fetchSystemFeatures, + }) +} + +export function useIsSystemFeaturesPending() { + const { isPending } = useSystemFeaturesQuery() + return isPending +} + +export function useSetupStatusQuery() { + return useQuery({ + queryKey: setupStatusQueryKey, + queryFn: fetchSetupStatusWithCache, + staleTime: Infinity, + }) +} const GlobalPublicStoreProvider: FC = ({ children, }) => { - const { isPending } = useSystemFeaturesQuery() + const { data, isPending } = useSystemFeaturesQuery() useSetupStatusQuery() + useEffect(() => { + if (data) { + const { setSystemFeatures } = useGlobalPublicStore.getState() + setSystemFeatures({ ...defaultSystemFeatures, ...data }) + } + }, [data]) + if (isPending) return
return <>{children} diff --git a/web/context/web-app-context.tsx b/web/context/web-app-context.tsx index ebff2d6ede..c5488a565c 100644 --- a/web/context/web-app-context.tsx +++ b/web/context/web-app-context.tsx @@ -8,9 +8,9 @@ import { useEffect } from 'react' import { create } from 'zustand' import { getProcessedSystemVariablesFromUrlParams } from '@/app/components/base/chat/utils' import Loading from '@/app/components/base/loading' -import { useIsSystemFeaturesPending } from '@/hooks/use-global-public' import { AccessMode } from '@/models/access-control' import { useGetWebAppAccessModeByCode } from '@/service/use-share' +import { useIsSystemFeaturesPending } from './global-public-context' type WebAppStore = { shareCode: string | null diff --git a/web/contract/console/system.ts b/web/contract/console/system.ts index 132018a8d0..bce0a8226e 100644 --- a/web/contract/console/system.ts +++ b/web/contract/console/system.ts @@ -1,4 +1,3 @@ -import type { SetupStatusResponse } from '@/models/common' import type { SystemFeatures } from '@/types/feature' import { type } from '@orpc/contract' import { base } from '../base' @@ -10,11 +9,3 @@ export const systemFeaturesContract = base }) .input(type()) .output(type()) - -export const setupStatusContract = base - .route({ - path: '/setup', - method: 'GET', - }) - .input(type()) - .output(type()) diff --git a/web/contract/router.ts b/web/contract/router.ts index f7262af3ae..85f3ea1c7e 100644 --- a/web/contract/router.ts +++ b/web/contract/router.ts @@ -26,7 +26,7 @@ import { getSandboxProviderListContract, saveSandboxProviderConfigContract, } from './console/sandbox-provider' -import { setupStatusContract, systemFeaturesContract } from './console/system' +import { systemFeaturesContract } from './console/system' import { trialAppDatasetsContract, trialAppInfoContract, trialAppParametersContract, trialAppWorkflowsContract } from './console/try-app' import { workflowDraftEnvironmentVariablesContract, @@ -51,7 +51,6 @@ export const consoleRouterContract = { avatar: accountAvatarContract, }, systemFeatures: systemFeaturesContract, - setupStatus: setupStatusContract, trialApps: { info: trialAppInfoContract, datasets: trialAppDatasetsContract, diff --git a/web/eslint-suppressions.json b/web/eslint-suppressions.json index c1671478be..a0d2e2afab 100644 --- a/web/eslint-suppressions.json +++ b/web/eslint-suppressions.json @@ -24,7 +24,7 @@ }, "__tests__/embedded-user-id-store.test.tsx": { "ts/no-explicit-any": { - "count": 1 + "count": 3 } }, "__tests__/goto-anything/command-selector.test.tsx": { @@ -104,6 +104,11 @@ "count": 1 } }, + "app/(shareLayout)/webapp-reset-password/layout.tsx": { + "ts/no-explicit-any": { + "count": 1 + } + }, "app/(shareLayout)/webapp-signin/components/mail-and-password-auth.tsx": { "ts/no-explicit-any": { "count": 2 @@ -648,7 +653,7 @@ }, "app/components/apps/app-card.spec.tsx": { "ts/no-explicit-any": { - "count": 20 + "count": 22 } }, "app/components/apps/app-card.tsx": { @@ -1666,7 +1671,7 @@ }, "app/components/custom/custom-web-app-brand/index.spec.tsx": { "ts/no-explicit-any": { - "count": 6 + "count": 7 } }, "app/components/custom/custom-web-app-brand/index.tsx": { @@ -2540,7 +2545,7 @@ }, "app/components/plugins/plugin-item/index.spec.tsx": { "ts/no-explicit-any": { - "count": 8 + "count": 10 } }, "app/components/plugins/plugin-item/index.tsx": { @@ -2563,7 +2568,7 @@ }, "app/components/plugins/plugin-page/empty/index.spec.tsx": { "ts/no-explicit-any": { - "count": 5 + "count": 7 } }, "app/components/plugins/plugin-page/empty/index.tsx": { @@ -4300,6 +4305,11 @@ "count": 1 } }, + "context/global-public-context.tsx": { + "react-refresh/only-export-components": { + "count": 4 + } + }, "context/hooks/use-trigger-events-limit-modal.ts": { "react-hooks-extra/no-direct-set-state-in-use-effect": { "count": 3 diff --git a/web/hooks/use-document-title.spec.ts b/web/hooks/use-document-title.spec.ts index fcf98af73f..7ce1e693db 100644 --- a/web/hooks/use-document-title.spec.ts +++ b/web/hooks/use-document-title.spec.ts @@ -1,5 +1,5 @@ -import { renderHook } from '@testing-library/react' -import { useIsSystemFeaturesPending, useSystemFeatures } from '@/hooks/use-global-public' +import { act, renderHook } from '@testing-library/react' +import { useGlobalPublicStore, useIsSystemFeaturesPending } from '@/context/global-public-context' /** * Test suite for useDocumentTitle hook * @@ -15,10 +15,13 @@ import { useIsSystemFeaturesPending, useSystemFeatures } from '@/hooks/use-globa import { defaultSystemFeatures } from '@/types/feature' import useDocumentTitle from './use-document-title' -vi.mock('@/context/global-public-context', () => ({ - useSystemFeatures: vi.fn(() => ({ ...defaultSystemFeatures })), - useIsSystemFeaturesPending: vi.fn(() => false), -})) +vi.mock('@/context/global-public-context', async (importOriginal) => { + const actual = await importOriginal() + return { + ...actual, + useIsSystemFeaturesPending: vi.fn(() => false), + } +}) /** * Test behavior when system features are still loading @@ -27,7 +30,11 @@ vi.mock('@/context/global-public-context', () => ({ describe('title should be empty if systemFeatures is pending', () => { beforeEach(() => { vi.mocked(useIsSystemFeaturesPending).mockReturnValue(true) - vi.mocked(useSystemFeatures).mockReturnValue({ ...defaultSystemFeatures, branding: { ...defaultSystemFeatures.branding, enabled: false } }) + act(() => { + useGlobalPublicStore.setState({ + systemFeatures: { ...defaultSystemFeatures, branding: { ...defaultSystemFeatures.branding, enabled: false } }, + }) + }) }) /** * Test that title stays empty during loading even when a title is provided @@ -52,7 +59,11 @@ describe('title should be empty if systemFeatures is pending', () => { describe('use default branding', () => { beforeEach(() => { vi.mocked(useIsSystemFeaturesPending).mockReturnValue(false) - vi.mocked(useSystemFeatures).mockReturnValue({ ...defaultSystemFeatures, branding: { ...defaultSystemFeatures.branding, enabled: false } }) + act(() => { + useGlobalPublicStore.setState({ + systemFeatures: { ...defaultSystemFeatures, branding: { ...defaultSystemFeatures.branding, enabled: false } }, + }) + }) }) /** * Test title format with page title and default branding @@ -80,7 +91,11 @@ describe('use default branding', () => { describe('use specific branding', () => { beforeEach(() => { vi.mocked(useIsSystemFeaturesPending).mockReturnValue(false) - vi.mocked(useSystemFeatures).mockReturnValue({ ...defaultSystemFeatures, branding: { ...defaultSystemFeatures.branding, enabled: true, application_title: 'Test' } }) + act(() => { + useGlobalPublicStore.setState({ + systemFeatures: { ...defaultSystemFeatures, branding: { ...defaultSystemFeatures.branding, enabled: true, application_title: 'Test' } }, + }) + }) }) /** * Test title format with page title and custom branding diff --git a/web/hooks/use-document-title.ts b/web/hooks/use-document-title.ts index e107ac75da..37b31a7dea 100644 --- a/web/hooks/use-document-title.ts +++ b/web/hooks/use-document-title.ts @@ -1,12 +1,12 @@ 'use client' import { useFavicon, useTitle } from 'ahooks' import { useEffect } from 'react' -import { useIsSystemFeaturesPending, useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore, useIsSystemFeaturesPending } from '@/context/global-public-context' import { basePath } from '@/utils/var' export default function useDocumentTitle(title: string) { const isPending = useIsSystemFeaturesPending() - const systemFeatures = useSystemFeatures() + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) const prefix = title ? `${title} - ` : '' let titleStr = '' let favicon = '' diff --git a/web/hooks/use-global-public.ts b/web/hooks/use-global-public.ts deleted file mode 100644 index 86da36cd34..0000000000 --- a/web/hooks/use-global-public.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { SystemFeatures } from '@/types/feature' -import { useQuery } from '@tanstack/react-query' -import { consoleClient, consoleQuery } from '@/service/client' -import { defaultSystemFeatures } from '@/types/feature' -import { fetchSetupStatusWithCache } from '@/utils/setup-status' - -export function useSystemFeaturesQuery() { - return useQuery({ - queryKey: consoleQuery.systemFeatures.queryKey(), - queryFn: () => consoleClient.systemFeatures(), - }) -} - -export function useSystemFeatures(): SystemFeatures { - const { data } = useSystemFeaturesQuery() - return { ...defaultSystemFeatures, ...data } -} - -export function useIsSystemFeaturesPending() { - const { isPending } = useSystemFeaturesQuery() - return isPending -} - -export function useSetupStatusQuery() { - return useQuery({ - queryKey: consoleQuery.setupStatus.queryKey(), - queryFn: fetchSetupStatusWithCache, - staleTime: Infinity, - }) -} diff --git a/web/models/common.ts b/web/models/common.ts index 00241da1ae..62a543672b 100644 --- a/web/models/common.ts +++ b/web/models/common.ts @@ -23,7 +23,7 @@ export type OauthResponse = { export type SetupStatusResponse = { step: 'finished' | 'not_started' - setup_at?: string + setup_at?: Date } export type InitValidateStatusResponse = { diff --git a/web/service/access-control.ts b/web/service/access-control.ts index 0b46a56af9..c87e01f482 100644 --- a/web/service/access-control.ts +++ b/web/service/access-control.ts @@ -1,7 +1,7 @@ import type { AccessControlAccount, AccessControlGroup, AccessMode, Subject } from '@/models/access-control' import type { App } from '@/types/app' import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from '@tanstack/react-query' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import { get, post } from './base' import { getUserCanAccess } from './share' @@ -71,7 +71,7 @@ export const useUpdateAccessMode = () => { } export const useGetUserCanAccessApp = ({ appId, isInstalledApp = true, enabled }: { appId?: string, isInstalledApp?: boolean, enabled?: boolean }) => { - const systemFeatures = useSystemFeatures() + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) return useQuery({ queryKey: [NAME_SPACE, 'user-can-access-app', appId, systemFeatures.webapp_auth.enabled, isInstalledApp], queryFn: () => { diff --git a/web/service/use-explore.ts b/web/service/use-explore.ts index 7a4fafc98a..3e3b9ff255 100644 --- a/web/service/use-explore.ts +++ b/web/service/use-explore.ts @@ -1,6 +1,6 @@ import type { App, AppCategory } from '@/models/explore' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' -import { useSystemFeatures } from '@/hooks/use-global-public' +import { useGlobalPublicStore } from '@/context/global-public-context' import { AccessMode } from '@/models/access-control' import { fetchAppList, fetchBanners, fetchInstalledAppList, getAppAccessModeByAppId, uninstallApp, updatePinStatus } from './explore' import { AppSourceType, fetchAppMeta, fetchAppParams } from './share' @@ -57,7 +57,7 @@ export const useUpdateAppPinStatus = () => { } export const useGetInstalledAppAccessModeByAppId = (appId: string | null) => { - const systemFeatures = useSystemFeatures() + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) return useQuery({ queryKey: [NAME_SPACE, 'appAccessMode', appId, systemFeatures.webapp_auth.enabled], queryFn: () => { diff --git a/web/utils/setup-status.spec.ts b/web/utils/setup-status.spec.ts index f6dd7c1e76..be96b43eba 100644 --- a/web/utils/setup-status.spec.ts +++ b/web/utils/setup-status.spec.ts @@ -1,14 +1,14 @@ import type { SetupStatusResponse } from '@/models/common' -import { consoleClient } from '@/service/client' + +import { fetchSetupStatus } from '@/service/common' + import { fetchSetupStatusWithCache } from './setup-status' -vi.mock('@/service/client', () => ({ - consoleClient: { - setupStatus: vi.fn(), - }, +vi.mock('@/service/common', () => ({ + fetchSetupStatus: vi.fn(), })) -const mockSetupStatus = vi.mocked(consoleClient.setupStatus) +const mockFetchSetupStatus = vi.mocked(fetchSetupStatus) describe('setup-status utilities', () => { beforeEach(() => { @@ -24,7 +24,7 @@ describe('setup-status utilities', () => { const result = await fetchSetupStatusWithCache() expect(result).toEqual({ step: 'finished' }) - expect(mockSetupStatus).not.toHaveBeenCalled() + expect(mockFetchSetupStatus).not.toHaveBeenCalled() }) it('should not modify localStorage when returning cached value', async () => { @@ -39,22 +39,22 @@ describe('setup-status utilities', () => { describe('when cache does not exist', () => { it('should call API and cache finished status', async () => { const apiResponse: SetupStatusResponse = { step: 'finished' } - mockSetupStatus.mockResolvedValue(apiResponse) + mockFetchSetupStatus.mockResolvedValue(apiResponse) const result = await fetchSetupStatusWithCache() - expect(mockSetupStatus).toHaveBeenCalledTimes(1) + expect(mockFetchSetupStatus).toHaveBeenCalledTimes(1) expect(result).toEqual(apiResponse) expect(localStorage.getItem('setup_status')).toBe('finished') }) it('should call API and remove cache when not finished', async () => { const apiResponse: SetupStatusResponse = { step: 'not_started' } - mockSetupStatus.mockResolvedValue(apiResponse) + mockFetchSetupStatus.mockResolvedValue(apiResponse) const result = await fetchSetupStatusWithCache() - expect(mockSetupStatus).toHaveBeenCalledTimes(1) + expect(mockFetchSetupStatus).toHaveBeenCalledTimes(1) expect(result).toEqual(apiResponse) expect(localStorage.getItem('setup_status')).toBeNull() }) @@ -62,7 +62,7 @@ describe('setup-status utilities', () => { it('should clear stale cache when API returns not_started', async () => { localStorage.setItem('setup_status', 'some_invalid_value') const apiResponse: SetupStatusResponse = { step: 'not_started' } - mockSetupStatus.mockResolvedValue(apiResponse) + mockFetchSetupStatus.mockResolvedValue(apiResponse) const result = await fetchSetupStatusWithCache() @@ -75,44 +75,44 @@ describe('setup-status utilities', () => { it('should call API when cache value is empty string', async () => { localStorage.setItem('setup_status', '') const apiResponse: SetupStatusResponse = { step: 'finished' } - mockSetupStatus.mockResolvedValue(apiResponse) + mockFetchSetupStatus.mockResolvedValue(apiResponse) const result = await fetchSetupStatusWithCache() - expect(mockSetupStatus).toHaveBeenCalledTimes(1) + expect(mockFetchSetupStatus).toHaveBeenCalledTimes(1) expect(result).toEqual(apiResponse) }) it('should call API when cache value is not "finished"', async () => { localStorage.setItem('setup_status', 'not_started') const apiResponse: SetupStatusResponse = { step: 'finished' } - mockSetupStatus.mockResolvedValue(apiResponse) + mockFetchSetupStatus.mockResolvedValue(apiResponse) const result = await fetchSetupStatusWithCache() - expect(mockSetupStatus).toHaveBeenCalledTimes(1) + expect(mockFetchSetupStatus).toHaveBeenCalledTimes(1) expect(result).toEqual(apiResponse) }) it('should call API when localStorage key does not exist', async () => { const apiResponse: SetupStatusResponse = { step: 'finished' } - mockSetupStatus.mockResolvedValue(apiResponse) + mockFetchSetupStatus.mockResolvedValue(apiResponse) const result = await fetchSetupStatusWithCache() - expect(mockSetupStatus).toHaveBeenCalledTimes(1) + expect(mockFetchSetupStatus).toHaveBeenCalledTimes(1) expect(result).toEqual(apiResponse) }) }) describe('API response handling', () => { it('should preserve setup_at from API response', async () => { - const setupDate = '2024-01-01T00:00:00.000Z' + const setupDate = new Date('2024-01-01') const apiResponse: SetupStatusResponse = { step: 'finished', setup_at: setupDate, } - mockSetupStatus.mockResolvedValue(apiResponse) + mockFetchSetupStatus.mockResolvedValue(apiResponse) const result = await fetchSetupStatusWithCache() @@ -122,13 +122,13 @@ describe('setup-status utilities', () => { it('should propagate API errors', async () => { const apiError = new Error('Network error') - mockSetupStatus.mockRejectedValue(apiError) + mockFetchSetupStatus.mockRejectedValue(apiError) await expect(fetchSetupStatusWithCache()).rejects.toThrow('Network error') }) it('should not update cache when API call fails', async () => { - mockSetupStatus.mockRejectedValue(new Error('API error')) + mockFetchSetupStatus.mockRejectedValue(new Error('API error')) await expect(fetchSetupStatusWithCache()).rejects.toThrow() diff --git a/web/utils/setup-status.ts b/web/utils/setup-status.ts index 99443349fc..7a2810bffd 100644 --- a/web/utils/setup-status.ts +++ b/web/utils/setup-status.ts @@ -1,5 +1,5 @@ import type { SetupStatusResponse } from '@/models/common' -import { consoleClient } from '@/service/client' +import { fetchSetupStatus } from '@/service/common' const SETUP_STATUS_KEY = 'setup_status' @@ -10,7 +10,7 @@ export const fetchSetupStatusWithCache = async (): Promise if (isSetupStatusCached()) return { step: 'finished' } - const status = await consoleClient.setupStatus() + const status = await fetchSetupStatus() if (status.step === 'finished') localStorage.setItem(SETUP_STATUS_KEY, 'finished')