Revert "refactor!: replace Zustand global store with TanStack Query for systemFeatures"

This reverts commit 806ece9a67.
This commit is contained in:
yyh
2026-02-01 19:06:45 +08:00
parent 806ece9a67
commit e2913d9ee1
102 changed files with 411 additions and 296 deletions

View File

@@ -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)
})

View File

@@ -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 (
<>
<div className={cn('flex min-h-screen w-full justify-center bg-background-default-burn p-6')}>

View File

@@ -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()

View File

@@ -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 (
<>

View File

@@ -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)

View File

@@ -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()

View File

@@ -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()

View File

@@ -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')

View File

@@ -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

View File

@@ -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 (
<div className={cn('flex min-h-screen w-full justify-center bg-background-default-burn p-6')}>
<div className={cn('flex w-full shrink-0 flex-col rounded-2xl border border-effects-highlight bg-background-default-subtle')}>

View File

@@ -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'

View File

@@ -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)

View File

@@ -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 ?? {}

View File

@@ -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) => {

View File

@@ -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<boolean>(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(() => {

View File

@@ -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 },
},
}),
}))

View File

@@ -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()

View File

@@ -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 },
},
}),
}))

View File

@@ -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<Props> = ({
}) => {
const { t } = useTranslation()
const { notify } = useContext(ToastContext)
const systemFeatures = useSystemFeatures()
const { systemFeatures } = useGlobalPublicStore()
const router = useRouter()
const { push } = useRouter()
const { isCurrentWorkspaceEditor, isCurrentWorkspaceDatasetOperator, isLoadingCurrentWorkspace } = useAppContext()

View File

@@ -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<ConversationItem | null>(null)
const [showRename, setShowRename] = useState<ConversationItem | null>(null)

View File

@@ -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<IHeaderProps> = ({
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

View File

@@ -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

View File

@@ -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<typeof mockUseSystemFeatures>)
mockUseGlobalPublicStore.mockImplementation(selector => selector ? selector({ systemFeatures: systemFeaturesState } as any) : { systemFeatures: systemFeaturesState })
mockGetImageUploadErrorMessage.mockReturnValue('upload error')
})

View File

@@ -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 || ''

View File

@@ -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()

View File

@@ -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 || []

View File

@@ -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 },
},
}),
}))

View File

@@ -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)

View File

@@ -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) => {

View File

@@ -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' })

View File

@@ -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<Props> = ({
onClose,
onCreate,
}) => {
const systemFeatures = useSystemFeatures()
const { systemFeatures } = useGlobalPublicStore()
const isTrialApp = !!(app && app.can_trial && systemFeatures.enable_trial_app)
const [type, setType] = useState<TypeEnum>(() => (app && !isTrialApp ? TypeEnum.DETAIL : TypeEnum.TRY))
const { data: appDetail, isLoading } = useGetTryAppInfo(appId)

View File

@@ -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 (
<Modal

View File

@@ -24,10 +24,10 @@ import ThemeSwitcher from '@/app/components/base/theme-switcher'
import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants'
import { IS_CLOUD_EDITION } from '@/config'
import { useAppContext } from '@/context/app-context'
import { useGlobalPublicStore } from '@/context/global-public-context'
import { useDocLink } from '@/context/i18n'
import { useModalContext } from '@/context/modal-context'
import { useProviderContext } from '@/context/provider-context'
import { useSystemFeatures } from '@/hooks/use-global-public'
import { useLogout } from '@/service/use-common'
import { cn } from '@/utils/classnames'
import AccountAbout from '../account-about'
@@ -43,7 +43,7 @@ export default function AppSelector() {
`
const router = useRouter()
const [aboutVisible, setAboutVisible] = useState(false)
const systemFeatures = useSystemFeatures()
const { systemFeatures } = useGlobalPublicStore()
const { t } = useTranslation()
const docLink = useDocLink()

View File

@@ -1,11 +1,11 @@
import { memo } from 'react'
import { useSystemFeatures } from '@/hooks/use-global-public'
import { useGlobalPublicStore } from '@/context/global-public-context'
import { useGetDataSourceListAuth } from '@/service/use-datasource'
import Card from './card'
import InstallFromMarketplace from './install-from-marketplace'
const DataSourcePage = () => {
const { enable_marketplace } = useSystemFeatures()
const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures)
const { data } = useGetDataSourceListAuth()
return (

View File

@@ -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<InvitationResult[]>([])

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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

View File

@@ -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<QuotaPanelProps> = ({
}) => {
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]),

View File

@@ -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(() => {

View File

@@ -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

View File

@@ -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)
}

View File

@@ -181,7 +181,7 @@ vi.mock('@/context/mitt-context', () => ({
// Mock global public context
vi.mock('@/context/global-public-context', () => ({
useSystemFeatures: () => ({}),
useGlobalPublicStore: () => ({}),
}))
// Mock useCanInstallPluginFromMarketplace

View File

@@ -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

View File

@@ -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

View File

@@ -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', () => ({

View File

@@ -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,

View File

@@ -11,7 +11,8 @@ vi.mock('react-i18next', () => ({
}))
vi.mock('@/context/global-public-context', () => ({
useSystemFeatures: () => ({ enable_marketplace: true }),
useGlobalPublicStore: <T,>(selector: (state: { systemFeatures: { enable_marketplace: boolean } }) => T): T =>
selector({ systemFeatures: { enable_marketplace: true } }),
}))
vi.mock('@/utils/classnames', () => ({

View File

@@ -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<Props> = ({
setOpen(!openRef.current)
}, [setOpen])
const { enable_marketplace } = useSystemFeatures()
const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures)
return (
<PortalToFollowElem

View File

@@ -68,7 +68,8 @@ vi.mock('@/context/app-context', () => ({
// 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

View File

@@ -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<Props> = ({
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}`)

View File

@@ -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<typeof useSystemFeatures>)
vi.mocked(useGlobalPublicStore).mockImplementation((selector) => {
const state = { systemFeatures: { enable_marketplace: enableMarketplace } }
return selector(state as Parameters<typeof selector>[0])
})
}
// Test component that uses the context

View File

@@ -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<string | undefined>()
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)

View File

@@ -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

View File

@@ -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<HTMLInputElement>(null)
const [selectedAction, setSelectedAction] = useState<string | null>(null)
const [selectedFile, setSelectedFile] = useState<File | null>(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<HTMLInputElement>) => {

View File

@@ -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()])

View File

@@ -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(() => {

View File

@@ -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<string | null>(null)
const [selectedFile, setSelectedFile] = useState<File | null>(null)
const { enable_marketplace, plugin_installation_permission } = useSystemFeatures()
const { enable_marketplace, plugin_installation_permission } = useGlobalPublicStore(s => s.systemFeatures)
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0]

View File

@@ -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<typeof useSystemFeatures>)
vi.mocked(useGlobalPublicStore).mockImplementation((selector) => {
const state = {
systemFeatures: {
enable_marketplace: true,
},
}
return selector(state as Parameters<typeof selector>[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<typeof useSystemFeatures>)
vi.mocked(useGlobalPublicStore).mockImplementation((selector) => {
const state = {
systemFeatures: {
enable_marketplace: false,
},
}
return selector(state as Parameters<typeof selector>[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<typeof useSystemFeatures>)
vi.mocked(useGlobalPublicStore).mockImplementation((selector) => {
const state = {
systemFeatures: {
enable_marketplace: true,
},
}
return selector(state as Parameters<typeof selector>[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<typeof useSystemFeatures>)
vi.mocked(useGlobalPublicStore).mockImplementation((selector) => {
const state = {
systemFeatures: {
enable_marketplace: false,
},
}
return selector(state as Parameters<typeof selector>[0])
})
vi.mocked(useReferenceSettings).mockReturnValue({
data: {

View File

@@ -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(() => {

View File

@@ -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

View File

@@ -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<Props> = ({
const { auto_upgrade: autoUpdateConfig, permission: privilege } = payload || {}
const [tempPrivilege, setTempPrivilege] = useState<Permissions>(privilege)
const [tempAutoUpdateConfig, setTempAutoUpdateConfig] = useState<AutoUpdateConfig>(autoUpdateConfig || autoUpdateDefaultValue)
const { enable_marketplace } = useSystemFeatures()
const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures)
const handlePrivilegeChange = useCallback((key: string) => {
return (value: PermissionType) => {
setTempPrivilege({

View File

@@ -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<IMainProps> = ({
doSetInputs(newInputs)
inputsRef.current = newInputs
}, [])
const systemFeatures = useSystemFeatures()
const systemFeatures = useGlobalPublicStore(s => s.systemFeatures)
const [appId, setAppId] = useState<string>('')
const [siteInfo, setSiteInfo] = useState<SiteInfo | null>(null)
const [customConfig, setCustomConfig] = useState<Record<string, any> | null>(null)

View File

@@ -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<HTMLDivElement>(null)
const [activeTab, setActiveTab] = useQueryState('category', {

View File

@@ -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 {

View File

@@ -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<ListRef>(null)
const wrapElemRef = useRef<HTMLDivElement>(null)

View File

@@ -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)

View File

@@ -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,

View File

@@ -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<TabsProps> = ({
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 {

View File

@@ -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<Props> = ({
}
const [tags, setTags] = useState<string[]>([])
const { enable_marketplace } = useSystemFeatures()
const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures)
const { data: buildInTools } = useAllBuiltInTools()
const { data: customTools } = useAllCustomTools()
const invalidateCustomTools = useInvalidateAllCustomTools()

View File

@@ -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<CollaborationViewState>(initialState)
const cursorServiceRef = useRef<CursorService | null>(null)
const isCollaborationEnabled = useSystemFeatures().enable_collaboration_mode
const isCollaborationEnabled = useGlobalPublicStore(s => s.systemFeatures.enable_collaboration_mode)
useEffect(() => {
if (!appId || !isCollaborationEnabled) {

View File

@@ -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()) {

View File

@@ -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<Record<string, WorkflowCommentDetail>>(commentDetailCache)
const activeCommentIdRef = useRef<string | null>(null)

View File

@@ -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')

View File

@@ -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)

View File

@@ -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<ZoomInOutProps> = ({
workflowReadOnly,
getWorkflowReadOnly,
} = useWorkflowReadOnly()
const isCollaborationEnabled = useSystemFeatures().enable_collaboration_mode
const isCollaborationEnabled = useGlobalPublicStore(s => s.systemFeatures.enable_collaboration_mode)
const ZOOM_IN_OUT_OPTIONS = [
[

View File

@@ -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<Map<string, Promise<SaveResult>>>(new Map())
const fallbackRegistryRef = useRef<Map<string, FallbackEntry>>(new Map())

View File

@@ -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<string, unknown> = {}) => {
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(() => {

View File

@@ -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 (
<div className={cn('flex min-h-screen w-full justify-center bg-background-default-burn p-6')}>

View File

@@ -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 (
<div className={cn('flex min-h-screen w-full justify-center bg-background-default-burn p-6')}>
<div className={cn('flex w-full shrink-0 flex-col rounded-2xl border border-effects-highlight bg-background-default-subtle')}>

View File

@@ -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 (
<>
<div className={cn('flex min-h-screen w-full justify-center bg-background-default-burn p-6')}>

View File

@@ -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 (
<div className="flex w-full items-center justify-between p-6">

View File

@@ -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)

View File

@@ -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 (
<>

View File

@@ -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)

View File

@@ -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<typeof useSendMail>
const mockUseSystemFeatures = useSystemFeatures as unknown as MockedFunction<typeof useSystemFeatures>
const mockUseGlobalPublicStore = useGlobalPublicStore as unknown as MockedFunction<typeof useGlobalPublicStore>
const mockUseLocale = useLocale as unknown as MockedFunction<typeof useLocale>
const mockUseSendMail = useSendMail as unknown as MockedFunction<typeof useSendMail>
@@ -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,

View File

@@ -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()

View File

@@ -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 (
<>

View File

@@ -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<AppContextProviderProps> = ({ 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(

View File

@@ -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<GlobalPublicStore>(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<PropsWithChildren> = ({
children,
}) => {
const { isPending } = useSystemFeaturesQuery()
const { data, isPending } = useSystemFeaturesQuery()
useSetupStatusQuery()
useEffect(() => {
if (data) {
const { setSystemFeatures } = useGlobalPublicStore.getState()
setSystemFeatures({ ...defaultSystemFeatures, ...data })
}
}, [data])
if (isPending)
return <div className="flex h-screen w-screen items-center justify-center"><Loading /></div>
return <>{children}</>

View File

@@ -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

View File

@@ -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<unknown>())
.output(type<SystemFeatures>())
export const setupStatusContract = base
.route({
path: '/setup',
method: 'GET',
})
.input(type<unknown>())
.output(type<SetupStatusResponse>())

View File

@@ -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,

View File

@@ -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

View File

@@ -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<typeof import('@/context/global-public-context')>()
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

View File

@@ -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 = ''

View File

@@ -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,
})
}

View File

@@ -23,7 +23,7 @@ export type OauthResponse = {
export type SetupStatusResponse = {
step: 'finished' | 'not_started'
setup_at?: string
setup_at?: Date
}
export type InitValidateStatusResponse = {

View File

@@ -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: () => {

View File

@@ -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: () => {

Some files were not shown because too many files have changed in this diff Show More