mirror of
https://github.com/langgenius/dify.git
synced 2026-02-09 17:54:01 +00:00
Compare commits
2 Commits
deploy/dev
...
refactor/s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
915a6cc090 | ||
|
|
c63a73ef83 |
@@ -26,37 +26,11 @@ vi.mock('@/app/components/base/chat/utils', () => ({
|
||||
getProcessedSystemVariablesFromUrlParams: (...args: any[]) => mockGetProcessedSystemVariablesFromUrlParams(...args),
|
||||
}))
|
||||
|
||||
// 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', () => ({
|
||||
useSystemFeatures: vi.fn(() => ({})),
|
||||
useIsSystemFeaturesPending: () => false,
|
||||
}))
|
||||
|
||||
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)
|
||||
@@ -91,7 +65,6 @@ const initialWebAppStore = (() => {
|
||||
})()
|
||||
|
||||
beforeEach(() => {
|
||||
mockGlobalStoreState.isGlobalPending = false
|
||||
mockGetProcessedSystemVariablesFromUrlParams.mockReset()
|
||||
useWebAppStore.setState(initialWebAppStore, true)
|
||||
})
|
||||
|
||||
39
web/app/(commonLayout)/layout-client.tsx
Normal file
39
web/app/(commonLayout)/layout-client.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
'use client'
|
||||
|
||||
import type { ReactNode } from 'react'
|
||||
import { AppInitializer } from '@/app/components/app-initializer'
|
||||
import AmplitudeProvider from '@/app/components/base/amplitude'
|
||||
import GotoAnything from '@/app/components/goto-anything'
|
||||
import Header from '@/app/components/header'
|
||||
import HeaderWrapper from '@/app/components/header/header-wrapper'
|
||||
import ReadmePanel from '@/app/components/plugins/readme-panel'
|
||||
import { AppContextProvider } from '@/context/app-context'
|
||||
import { EventEmitterContextProvider } from '@/context/event-emitter'
|
||||
import { ModalContextProvider } from '@/context/modal-context'
|
||||
import { ProviderContextProvider } from '@/context/provider-context'
|
||||
import PartnerStack from '../components/billing/partner-stack'
|
||||
|
||||
export const CommonLayoutClient = ({ children }: { children: ReactNode }) => {
|
||||
return (
|
||||
<>
|
||||
<AmplitudeProvider />
|
||||
<AppInitializer>
|
||||
<AppContextProvider>
|
||||
<EventEmitterContextProvider>
|
||||
<ProviderContextProvider>
|
||||
<ModalContextProvider>
|
||||
<HeaderWrapper>
|
||||
<Header />
|
||||
</HeaderWrapper>
|
||||
{children}
|
||||
<PartnerStack />
|
||||
<ReadmePanel />
|
||||
<GotoAnything />
|
||||
</ModalContextProvider>
|
||||
</ProviderContextProvider>
|
||||
</EventEmitterContextProvider>
|
||||
</AppContextProvider>
|
||||
</AppInitializer>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,45 +1,46 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import * as React from 'react'
|
||||
import { AppInitializer } from '@/app/components/app-initializer'
|
||||
import AmplitudeProvider from '@/app/components/base/amplitude'
|
||||
import { dehydrate, HydrationBoundary } from '@tanstack/react-query'
|
||||
import GA, { GaType } from '@/app/components/base/ga'
|
||||
import Zendesk from '@/app/components/base/zendesk'
|
||||
import GotoAnything from '@/app/components/goto-anything'
|
||||
import Header from '@/app/components/header'
|
||||
import HeaderWrapper from '@/app/components/header/header-wrapper'
|
||||
import ReadmePanel from '@/app/components/plugins/readme-panel'
|
||||
import { AppContextProvider } from '@/context/app-context'
|
||||
import { EventEmitterContextProvider } from '@/context/event-emitter'
|
||||
import { ModalContextProvider } from '@/context/modal-context'
|
||||
import { ProviderContextProvider } from '@/context/provider-context'
|
||||
import PartnerStack from '../components/billing/partner-stack'
|
||||
import Splash from '../components/splash'
|
||||
import { getQueryClientServer } from '@/context/query-client-server'
|
||||
import { serverFetchWithAuth } from '@/utils/ssr-fetch'
|
||||
import { CommonLayoutClient } from './layout-client'
|
||||
|
||||
const IS_DEV = process.env.NODE_ENV === 'development'
|
||||
|
||||
async function fetchUserProfileForSSR() {
|
||||
const { data: profile, headers } = await serverFetchWithAuth('/account/profile')
|
||||
return {
|
||||
profile,
|
||||
meta: {
|
||||
currentVersion: headers.get('x-version'),
|
||||
currentEnv: IS_DEV ? 'DEVELOPMENT' : headers.get('x-env'),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default async function CommonLayout({ children }: { children: ReactNode }) {
|
||||
const queryClient = getQueryClientServer()
|
||||
|
||||
await Promise.all([
|
||||
queryClient.prefetchQuery({
|
||||
queryKey: ['common', 'user-profile'],
|
||||
queryFn: fetchUserProfileForSSR,
|
||||
}),
|
||||
queryClient.prefetchQuery({
|
||||
queryKey: ['common', 'current-workspace'],
|
||||
queryFn: async () => {
|
||||
const { data } = await serverFetchWithAuth('/workspaces/current', 'POST', {})
|
||||
return data
|
||||
},
|
||||
}),
|
||||
])
|
||||
|
||||
const Layout = ({ children }: { children: ReactNode }) => {
|
||||
return (
|
||||
<>
|
||||
<HydrationBoundary state={dehydrate(queryClient)}>
|
||||
<GA gaType={GaType.admin} />
|
||||
<AmplitudeProvider />
|
||||
<AppInitializer>
|
||||
<AppContextProvider>
|
||||
<EventEmitterContextProvider>
|
||||
<ProviderContextProvider>
|
||||
<ModalContextProvider>
|
||||
<HeaderWrapper>
|
||||
<Header />
|
||||
</HeaderWrapper>
|
||||
{children}
|
||||
<PartnerStack />
|
||||
<ReadmePanel />
|
||||
<GotoAnything />
|
||||
<Splash />
|
||||
</ModalContextProvider>
|
||||
</ProviderContextProvider>
|
||||
</EventEmitterContextProvider>
|
||||
</AppContextProvider>
|
||||
<Zendesk />
|
||||
</AppInitializer>
|
||||
</>
|
||||
<CommonLayoutClient>{children}</CommonLayoutClient>
|
||||
<Zendesk />
|
||||
</HydrationBoundary>
|
||||
)
|
||||
}
|
||||
export default Layout
|
||||
|
||||
21
web/app/(commonLayout)/loading.tsx
Normal file
21
web/app/(commonLayout)/loading.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import '@/app/components/base/loading/style.css'
|
||||
|
||||
export default function CommonLayoutLoading() {
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className="spin-animation">
|
||||
<g clipPath="url(#clip0_324_2488)">
|
||||
<path d="M15 0H10C9.44772 0 9 0.447715 9 1V6C9 6.55228 9.44772 7 10 7H15C15.5523 7 16 6.55228 16 6V1C16 0.447715 15.5523 0 15 0Z" fill="#1C64F2" />
|
||||
<path opacity="0.5" d="M15 9H10C9.44772 9 9 9.44772 9 10V15C9 15.5523 9.44772 16 10 16H15C15.5523 16 16 15.5523 16 15V10C16 9.44772 15.5523 9 15 9Z" fill="#1C64F2" />
|
||||
<path opacity="0.1" d="M6 9H1C0.447715 9 0 9.44772 0 10V15C0 15.5523 0.447715 16 1 16H6C6.55228 16 7 15.5523 7 15V10C7 9.44772 6.55228 9 6 9Z" fill="#1C64F2" />
|
||||
<path opacity="0.2" d="M6 0H1C0.447715 0 0 0.447715 0 1V6C0 6.55228 0.447715 7 1 7H6C6.55228 7 7 6.55228 7 6V1C7 0.447715 6.55228 0 6 0Z" fill="#1C64F2" />
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_324_2488">
|
||||
<rect width="16" height="16" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
'use client'
|
||||
import type * as React from 'react'
|
||||
import Header from '@/app/signin/_header'
|
||||
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useSystemFeatures } from '@/hooks/use-global-public'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
export default function SignInLayout({ children }: any) {
|
||||
const { systemFeatures } = useGlobalPublicStore()
|
||||
export default function SignInLayout({ children }: { children: React.ReactNode }) {
|
||||
const systemFeatures = useSystemFeatures()
|
||||
return (
|
||||
<>
|
||||
<div className={cn('flex min-h-screen w-full justify-center bg-background-default-burn p-6')}>
|
||||
|
||||
@@ -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 { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useSystemFeatures } from '@/hooks/use-global-public'
|
||||
import { fetchWebOAuth2SSOUrl, fetchWebOIDCSSOUrl, fetchWebSAMLSSOUrl } from '@/service/share'
|
||||
import { SSOProtocol } from '@/types/feature'
|
||||
|
||||
const ExternalMemberSSOAuth = () => {
|
||||
const systemFeatures = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const systemFeatures = useSystemFeatures()
|
||||
const searchParams = useSearchParams()
|
||||
const router = useRouter()
|
||||
|
||||
|
||||
@@ -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 = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const systemFeatures = useSystemFeatures()
|
||||
useDocumentTitle(t('webapp.login', { ns: 'login' }))
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -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 { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useSystemFeatures } from '@/hooks/use-global-public'
|
||||
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 } = useGlobalPublicStore()
|
||||
const systemFeatures = useSystemFeatures()
|
||||
const [authType, updateAuthType] = useState<'code' | 'password'>('password')
|
||||
const [showORLine, setShowORLine] = useState(false)
|
||||
const [allMethodsAreDisabled, setAllMethodsAreDisabled] = useState(false)
|
||||
|
||||
@@ -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 = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const systemFeatures = useSystemFeatures()
|
||||
const webAppAccessMode = useWebAppStore(s => s.webAppAccessMode)
|
||||
const searchParams = useSearchParams()
|
||||
const router = useRouter()
|
||||
|
||||
@@ -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 } = useGlobalPublicStore()
|
||||
const systemFeatures = useSystemFeatures()
|
||||
const { data: appList } = useAppList({ page: 1, limit: 100, name: '' })
|
||||
const apps = appList?.data || []
|
||||
const { mutateUserProfile, userProfile } = useAppContext()
|
||||
|
||||
@@ -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 { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useSystemFeatures } from '@/hooks/use-global-public'
|
||||
import Avatar from './avatar'
|
||||
|
||||
const Header = () => {
|
||||
const { t } = useTranslation()
|
||||
const router = useRouter()
|
||||
const systemFeatures = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const systemFeatures = useSystemFeatures()
|
||||
|
||||
const goToStudio = useCallback(() => {
|
||||
router.push('/apps')
|
||||
|
||||
@@ -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 } = useGlobalPublicStore()
|
||||
const systemFeatures = useSystemFeatures()
|
||||
useDocumentTitle('')
|
||||
const { isLoading, data: loginData } = useIsLogin()
|
||||
const isLoggedIn = loginData?.logged_in
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
'use client'
|
||||
import * as React from 'react'
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useSystemFeatures } from '@/hooks/use-global-public'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import Header from '../signin/_header'
|
||||
import ActivateForm from './activateForm'
|
||||
|
||||
const Activate = () => {
|
||||
const { systemFeatures } = useGlobalPublicStore()
|
||||
const systemFeatures = useSystemFeatures()
|
||||
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')}>
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
|
||||
import type { ReactNode } from 'react'
|
||||
import Cookies from 'js-cookie'
|
||||
import { usePathname, useRouter, useSearchParams } from 'next/navigation'
|
||||
import { useRouter, useSearchParams } from 'next/navigation'
|
||||
import { parseAsString, useQueryState } from 'nuqs'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useEffect, useReducer, useRef } from 'react'
|
||||
import {
|
||||
EDUCATION_VERIFY_URL_SEARCHPARAMS_ACTION,
|
||||
EDUCATION_VERIFYING_LOCALSTORAGE_ITEM,
|
||||
} from '@/app/education-apply/constants'
|
||||
import { useSetupStatusQuery } from '@/hooks/use-global-public'
|
||||
import { sendGAEvent } from '@/utils/gtag'
|
||||
import { fetchSetupStatusWithCache } from '@/utils/setup-status'
|
||||
import { resolvePostLoginRedirect } from '../signin/utils/post-login-redirect'
|
||||
import { trackEvent } from './base/amplitude'
|
||||
|
||||
@@ -23,80 +23,68 @@ export const AppInitializer = ({
|
||||
}: AppInitializerProps) => {
|
||||
const router = useRouter()
|
||||
const searchParams = useSearchParams()
|
||||
// Tokens are now stored in cookies, no need to check localStorage
|
||||
const pathname = usePathname()
|
||||
const [init, setInit] = useState(false)
|
||||
const [init, markInit] = useReducer(() => true, false)
|
||||
const { data: setupStatus } = useSetupStatusQuery()
|
||||
const [oauthNewUser, setOauthNewUser] = useQueryState(
|
||||
'oauth_new_user',
|
||||
parseAsString.withOptions({ history: 'replace' }),
|
||||
)
|
||||
|
||||
const isSetupFinished = useCallback(async () => {
|
||||
try {
|
||||
const setUpStatus = await fetchSetupStatusWithCache()
|
||||
return setUpStatus.step === 'finished'
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
return false
|
||||
}
|
||||
}, [])
|
||||
const oauthTrackedRef = useRef(false)
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const action = searchParams.get('action')
|
||||
|
||||
if (oauthNewUser === 'true') {
|
||||
let utmInfo = null
|
||||
const utmInfoStr = Cookies.get('utm_info')
|
||||
if (utmInfoStr) {
|
||||
try {
|
||||
utmInfo = JSON.parse(utmInfoStr)
|
||||
}
|
||||
catch (e) {
|
||||
console.error('Failed to parse utm_info cookie:', e)
|
||||
}
|
||||
}
|
||||
|
||||
// Track registration event with UTM params
|
||||
trackEvent(utmInfo ? 'user_registration_success_with_utm' : 'user_registration_success', {
|
||||
method: 'oauth',
|
||||
...utmInfo,
|
||||
})
|
||||
|
||||
sendGAEvent(utmInfo ? 'user_registration_success_with_utm' : 'user_registration_success', {
|
||||
method: 'oauth',
|
||||
...utmInfo,
|
||||
})
|
||||
|
||||
// Clean up: remove utm_info cookie and URL params
|
||||
Cookies.remove('utm_info')
|
||||
setOauthNewUser(null)
|
||||
}
|
||||
|
||||
if (action === EDUCATION_VERIFY_URL_SEARCHPARAMS_ACTION)
|
||||
localStorage.setItem(EDUCATION_VERIFYING_LOCALSTORAGE_ITEM, 'yes')
|
||||
if (oauthNewUser !== 'true' || oauthTrackedRef.current)
|
||||
return
|
||||
oauthTrackedRef.current = true
|
||||
|
||||
let utmInfo = null
|
||||
const utmInfoStr = Cookies.get('utm_info')
|
||||
if (utmInfoStr) {
|
||||
try {
|
||||
const isFinished = await isSetupFinished()
|
||||
if (!isFinished) {
|
||||
router.replace('/install')
|
||||
return
|
||||
}
|
||||
|
||||
const redirectUrl = resolvePostLoginRedirect(searchParams)
|
||||
if (redirectUrl) {
|
||||
location.replace(redirectUrl)
|
||||
return
|
||||
}
|
||||
|
||||
setInit(true)
|
||||
utmInfo = JSON.parse(utmInfoStr)
|
||||
}
|
||||
catch {
|
||||
router.replace('/signin')
|
||||
catch (e) {
|
||||
console.error('Failed to parse utm_info cookie:', e)
|
||||
}
|
||||
})()
|
||||
}, [isSetupFinished, router, pathname, searchParams, oauthNewUser, setOauthNewUser])
|
||||
}
|
||||
|
||||
trackEvent(utmInfo ? 'user_registration_success_with_utm' : 'user_registration_success', {
|
||||
method: 'oauth',
|
||||
...utmInfo,
|
||||
})
|
||||
|
||||
sendGAEvent(utmInfo ? 'user_registration_success_with_utm' : 'user_registration_success', {
|
||||
method: 'oauth',
|
||||
...utmInfo,
|
||||
})
|
||||
|
||||
Cookies.remove('utm_info')
|
||||
// eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect -- setOauthNewUser is from nuqs useQueryState, not useState
|
||||
setOauthNewUser(null)
|
||||
}, [oauthNewUser, setOauthNewUser])
|
||||
|
||||
useEffect(() => {
|
||||
const action = searchParams.get('action')
|
||||
if (action === EDUCATION_VERIFY_URL_SEARCHPARAMS_ACTION)
|
||||
localStorage.setItem(EDUCATION_VERIFYING_LOCALSTORAGE_ITEM, 'yes')
|
||||
}, [searchParams])
|
||||
|
||||
useEffect(() => {
|
||||
if (!setupStatus)
|
||||
return
|
||||
|
||||
if (setupStatus.step !== 'finished') {
|
||||
router.replace('/install')
|
||||
return
|
||||
}
|
||||
|
||||
const redirectUrl = resolvePostLoginRedirect(searchParams)
|
||||
if (redirectUrl) {
|
||||
location.replace(redirectUrl)
|
||||
return
|
||||
}
|
||||
|
||||
markInit()
|
||||
}, [setupStatus, router, searchParams])
|
||||
|
||||
return init ? children : null
|
||||
}
|
||||
|
||||
@@ -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 { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useSystemFeatures } from '@/hooks/use-global-public'
|
||||
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 = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const systemFeatures = useSystemFeatures()
|
||||
const setAppId = useAccessControlStore(s => s.setAppId)
|
||||
const specificGroups = useAccessControlStore(s => s.specificGroups)
|
||||
const specificMembers = useAccessControlStore(s => s.specificMembers)
|
||||
|
||||
@@ -36,9 +36,9 @@ import {
|
||||
import UpgradeBtn from '@/app/components/billing/upgrade-btn'
|
||||
import WorkflowToolConfigureButton from '@/app/components/tools/workflow-tool/configure-button'
|
||||
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'
|
||||
@@ -149,7 +149,7 @@ const AppPublisher = ({
|
||||
|
||||
const appDetail = useAppStore(state => state.appDetail)
|
||||
const setAppDetail = useAppStore(s => s.setAppDetail)
|
||||
const systemFeatures = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const systemFeatures = useSystemFeatures()
|
||||
const { formatTimeFromNow } = useFormatTimeFromNow()
|
||||
const { app_base_url: appBaseURL = '', access_token: accessToken = '' } = appDetail?.site ?? {}
|
||||
|
||||
|
||||
@@ -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 { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useSystemFeatures } from '@/hooks/use-global-public'
|
||||
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 } = useGlobalPublicStore()
|
||||
const systemFeatures = useSystemFeatures()
|
||||
const isTrialApp = app.can_trial && systemFeatures.enable_trial_app
|
||||
const setShowTryAppPanel = useContextSelector(AppListContext, ctx => ctx.setShowTryAppPanel)
|
||||
const showTryAPPPanel = useCallback((appId: string) => {
|
||||
|
||||
@@ -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 = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const systemFeatures = useSystemFeatures()
|
||||
const { data: appAccessSubjects } = useAppWhiteListSubjects(appDetail?.id, systemFeatures.webapp_auth.enabled && appDetail?.access_mode === AccessMode.SPECIFIC_GROUPS_MEMBERS)
|
||||
|
||||
const OPERATIONS_MAP = useMemo(() => {
|
||||
|
||||
@@ -51,11 +51,9 @@ vi.mock('@/context/provider-context', () => ({
|
||||
// Mock global public store - allow dynamic configuration
|
||||
let mockWebappAuthEnabled = false
|
||||
vi.mock('@/context/global-public-context', () => ({
|
||||
useGlobalPublicStore: (selector: (s: any) => any) => selector({
|
||||
systemFeatures: {
|
||||
webapp_auth: { enabled: mockWebappAuthEnabled },
|
||||
branding: { enabled: false },
|
||||
},
|
||||
useSystemFeatures: () => ({
|
||||
webapp_auth: { enabled: mockWebappAuthEnabled },
|
||||
branding: { enabled: false },
|
||||
}),
|
||||
}))
|
||||
|
||||
|
||||
@@ -22,9 +22,9 @@ import Toast, { ToastContext } from '@/app/components/base/toast'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
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, exportAppConfig, updateAppInfo } from '@/service/apps'
|
||||
@@ -64,7 +64,7 @@ export type AppCardProps = {
|
||||
const AppCard = ({ app, onRefresh }: AppCardProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { notify } = useContext(ToastContext)
|
||||
const systemFeatures = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const systemFeatures = useSystemFeatures()
|
||||
const { isCurrentWorkspaceEditor } = useAppContext()
|
||||
const { onPlanInfoChanged } = useProviderContext()
|
||||
const { push } = useRouter()
|
||||
|
||||
@@ -26,10 +26,8 @@ vi.mock('@/context/app-context', () => ({
|
||||
|
||||
// Mock global public store
|
||||
vi.mock('@/context/global-public-context', () => ({
|
||||
useGlobalPublicStore: () => ({
|
||||
systemFeatures: {
|
||||
branding: { enabled: false },
|
||||
},
|
||||
useSystemFeatures: () => ({
|
||||
branding: { enabled: false },
|
||||
}),
|
||||
}))
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ import { useStore as useTagStore } from '@/app/components/base/tag-management/st
|
||||
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 { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useSystemFeatures } from '@/hooks/use-global-public'
|
||||
import { CheckModal } from '@/hooks/use-pay'
|
||||
import { useInfiniteAppList } from '@/service/use-apps'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
@@ -61,7 +61,7 @@ const List: FC<Props> = ({
|
||||
controlRefreshList = 0,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { systemFeatures } = useGlobalPublicStore()
|
||||
const systemFeatures = useSystemFeatures()
|
||||
const router = useRouter()
|
||||
const { isCurrentWorkspaceEditor, isCurrentWorkspaceDatasetOperator, isLoadingCurrentWorkspace } = useAppContext()
|
||||
const showTagManagementModal = useTagStore(s => s.showTagManagementModal)
|
||||
|
||||
@@ -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 { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useSystemFeatures } from '@/hooks/use-global-public'
|
||||
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 = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const systemFeatures = useSystemFeatures()
|
||||
const [showConfirm, setShowConfirm] = useState<ConversationItem | null>(null)
|
||||
const [showRename, setShowRename] = useState<ConversationItem | null>(null)
|
||||
|
||||
|
||||
@@ -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 { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useSystemFeatures } from '@/hooks/use-global-public'
|
||||
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 = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const systemFeatures = useSystemFeatures()
|
||||
|
||||
const handleMessageReceived = useCallback((event: MessageEvent) => {
|
||||
let currentParentOrigin = parentOrigin
|
||||
|
||||
@@ -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 = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const systemFeatures = useSystemFeatures()
|
||||
|
||||
const customConfig = appData?.custom_config
|
||||
const site = appData?.site
|
||||
|
||||
@@ -23,8 +23,8 @@ const usePSInfo = () => {
|
||||
setTrue: setBind,
|
||||
}] = useBoolean(false)
|
||||
const { mutateAsync } = useBindPartnerStackInfo()
|
||||
// Save to top domain. cloud.dify.ai => .dify.ai
|
||||
const domain = globalThis.location.hostname.replace('cloud', '')
|
||||
|
||||
const getDomain = () => globalThis.location?.hostname.replace('cloud', '') ?? ''
|
||||
|
||||
const saveOrUpdate = useCallback(() => {
|
||||
if (!psPartnerKey || !psClickId)
|
||||
@@ -37,7 +37,7 @@ const usePSInfo = () => {
|
||||
}), {
|
||||
expires: PARTNER_STACK_CONFIG.saveCookieDays,
|
||||
path: '/',
|
||||
domain,
|
||||
domain: getDomain(),
|
||||
})
|
||||
}, [psPartnerKey, psClickId, isPSChanged])
|
||||
|
||||
@@ -56,7 +56,7 @@ const usePSInfo = () => {
|
||||
shouldRemoveCookie = true
|
||||
}
|
||||
if (shouldRemoveCookie)
|
||||
Cookies.remove(PARTNER_STACK_CONFIG.cookieName, { path: '/', domain })
|
||||
Cookies.remove(PARTNER_STACK_CONFIG.cookieName, { path: '/', domain: getDomain() })
|
||||
setBind()
|
||||
}
|
||||
}, [psPartnerKey, psClickId, mutateAsync, hasBind, setBind])
|
||||
|
||||
@@ -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', () => ({
|
||||
useGlobalPublicStore: vi.fn(),
|
||||
useSystemFeatures: 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 mockUseGlobalPublicStore = vi.mocked(useGlobalPublicStore)
|
||||
const mockUseSystemFeatures = vi.mocked(useSystemFeatures)
|
||||
const mockImageUpload = vi.mocked(imageUpload)
|
||||
const mockGetImageUploadErrorMessage = vi.mocked(getImageUploadErrorMessage)
|
||||
|
||||
@@ -80,7 +80,7 @@ describe('CustomWebAppBrand', () => {
|
||||
workspace_logo: 'https://example.com/workspace-logo.png',
|
||||
},
|
||||
}
|
||||
mockUseGlobalPublicStore.mockImplementation(selector => selector ? selector({ systemFeatures: systemFeaturesState } as any) : { systemFeatures: systemFeaturesState })
|
||||
mockUseSystemFeatures.mockReturnValue(systemFeaturesState as ReturnType<typeof mockUseSystemFeatures>)
|
||||
mockGetImageUploadErrorMessage.mockReturnValue('upload error')
|
||||
})
|
||||
|
||||
|
||||
@@ -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 = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const systemFeatures = useSystemFeatures()
|
||||
const isSandbox = enableBilling && plan.type === Plan.sandbox
|
||||
const uploading = uploadProgress > 0 && uploadProgress < 100
|
||||
const webappLogo = currentWorkspace.custom_config?.replace_webapp_logo || ''
|
||||
|
||||
@@ -25,10 +25,7 @@ vi.mock('@/context/i18n', () => ({
|
||||
}))
|
||||
|
||||
vi.mock('@/context/global-public-context', () => ({
|
||||
useGlobalPublicStore: vi.fn((selector) => {
|
||||
const state = { systemFeatures: { enable_marketplace: true } }
|
||||
return selector(state)
|
||||
}),
|
||||
useSystemFeatures: vi.fn(() => ({ enable_marketplace: true })),
|
||||
}))
|
||||
|
||||
const mockUsePipelineTemplateList = vi.fn()
|
||||
|
||||
@@ -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 = useGlobalPublicStore(s => s.systemFeatures.enable_marketplace)
|
||||
const enableMarketplace = useSystemFeatures().enable_marketplace
|
||||
const { data: pipelineList, isLoading } = usePipelineTemplateList({ type: 'built-in', language }, enableMarketplace)
|
||||
const list = pipelineList?.pipeline_templates || []
|
||||
|
||||
|
||||
@@ -34,10 +34,8 @@ vi.mock('@/context/app-context', () => ({
|
||||
|
||||
// Mock global public context
|
||||
vi.mock('@/context/global-public-context', () => ({
|
||||
useGlobalPublicStore: () => ({
|
||||
systemFeatures: {
|
||||
branding: { enabled: false },
|
||||
},
|
||||
useSystemFeatures: () => ({
|
||||
branding: { enabled: false },
|
||||
}),
|
||||
}))
|
||||
|
||||
@@ -333,10 +331,8 @@ describe('List', () => {
|
||||
|
||||
it('should not show DatasetFooter when branding is enabled', async () => {
|
||||
vi.doMock('@/context/global-public-context', () => ({
|
||||
useGlobalPublicStore: () => ({
|
||||
systemFeatures: {
|
||||
branding: { enabled: true },
|
||||
},
|
||||
useSystemFeatures: () => ({
|
||||
branding: { enabled: true },
|
||||
}),
|
||||
}))
|
||||
|
||||
|
||||
@@ -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 } = useGlobalPublicStore()
|
||||
const systemFeatures = useSystemFeatures()
|
||||
const router = useRouter()
|
||||
const { currentWorkspace, isCurrentWorkspaceOwner } = useAppContext()
|
||||
const showTagManagementModal = useTagStore(s => s.showTagManagementModal)
|
||||
|
||||
@@ -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 { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useSystemFeatures } from '@/hooks/use-global-public'
|
||||
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 } = useGlobalPublicStore()
|
||||
const systemFeatures = useSystemFeatures()
|
||||
const isTrialApp = app.can_trial && systemFeatures.enable_trial_app
|
||||
const setShowTryAppPanel = useContextSelector(ExploreContext, ctx => ctx.setShowTryAppPanel)
|
||||
const showTryAPPPanel = useCallback((appId: string) => {
|
||||
|
||||
@@ -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 { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useSystemFeatures } from '@/hooks/use-global-public'
|
||||
import { useImportDSL } from '@/hooks/use-import-dsl'
|
||||
import {
|
||||
DSLImportMode,
|
||||
@@ -36,7 +36,7 @@ const Apps = ({
|
||||
onSuccess,
|
||||
}: AppsProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { systemFeatures } = useGlobalPublicStore()
|
||||
const systemFeatures = useSystemFeatures()
|
||||
const { hasEditPermission } = useContext(ExploreContext)
|
||||
const allCategoriesEn = t('apps.allCategories', { ns: 'explore', lng: 'en' })
|
||||
|
||||
|
||||
@@ -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 { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useSystemFeatures } from '@/hooks/use-global-public'
|
||||
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 } = useGlobalPublicStore()
|
||||
const systemFeatures = useSystemFeatures()
|
||||
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)
|
||||
|
||||
@@ -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 { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useSystemFeatures } from '@/hooks/use-global-public'
|
||||
|
||||
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 = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const systemFeatures = useSystemFeatures()
|
||||
|
||||
return (
|
||||
<Modal
|
||||
|
||||
@@ -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 } = useGlobalPublicStore()
|
||||
const systemFeatures = useSystemFeatures()
|
||||
|
||||
const { t } = useTranslation()
|
||||
const docLink = useDocLink()
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { memo } from 'react'
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useSystemFeatures } from '@/hooks/use-global-public'
|
||||
import { useGetDataSourceListAuth } from '@/service/use-datasource'
|
||||
import Card from './card'
|
||||
import InstallFromMarketplace from './install-from-marketplace'
|
||||
|
||||
const DataSourcePage = () => {
|
||||
const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const { enable_marketplace } = useSystemFeatures()
|
||||
const { data } = useGetDataSourceListAuth()
|
||||
|
||||
return (
|
||||
|
||||
@@ -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 = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const systemFeatures = useSystemFeatures()
|
||||
const { formatTimeFromNow } = useFormatTimeFromNow()
|
||||
const [inviteModalVisible, setInviteModalVisible] = useState(false)
|
||||
const [invitationResults, setInvitationResults] = useState<InvitationResult[]>([])
|
||||
|
||||
@@ -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 { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useSystemFeatures } from '@/hooks/use-global-public'
|
||||
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 = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const systemFeatures = useSystemFeatures()
|
||||
const { data: workspacePermissions, isFetching: isFetchingWorkspacePermissions } = useWorkspacePermissions(currentWorkspace!.id, systemFeatures.branding.enabled)
|
||||
if (systemFeatures.branding.enabled) {
|
||||
if (isFetchingWorkspacePermissions) {
|
||||
|
||||
@@ -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 { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useSystemFeatures } from '@/hooks/use-global-public'
|
||||
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 = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const systemFeatures = useSystemFeatures()
|
||||
const { data: workspacePermissions, isFetching: isFetchingWorkspacePermissions } = useWorkspacePermissions(currentWorkspace!.id, systemFeatures.branding.enabled)
|
||||
if (systemFeatures.branding.enabled) {
|
||||
if (isFetchingWorkspacePermissions) {
|
||||
|
||||
@@ -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 } = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const { enable_marketplace } = useSystemFeatures()
|
||||
const isDefaultModelLoading = isTextGenerationDefaultModelLoading
|
||||
|| isEmbeddingsDefaultModelLoading
|
||||
|| isRerankDefaultModelLoading
|
||||
|
||||
@@ -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 { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useSystemFeatures } from '@/hooks/use-global-public'
|
||||
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 } = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const { trial_models } = useSystemFeatures()
|
||||
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]),
|
||||
|
||||
@@ -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 = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const systemFeatures = useSystemFeatures()
|
||||
const isFreePlan = plan.type === Plan.sandbox
|
||||
const isBrandingEnabled = systemFeatures.branding.enabled
|
||||
const handlePlanClick = useCallback(() => {
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
import { RiHourglass2Fill } from '@remixicon/react'
|
||||
import dayjs from 'dayjs'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useSystemFeatures } from '@/hooks/use-global-public'
|
||||
import { LicenseStatus } from '@/types/feature'
|
||||
import PremiumBadge from '../../base/premium-badge'
|
||||
|
||||
const LicenseNav = () => {
|
||||
const { t } = useTranslation()
|
||||
const { systemFeatures } = useGlobalPublicStore()
|
||||
const systemFeatures = useSystemFeatures()
|
||||
|
||||
if (systemFeatures.license?.status === LicenseStatus.EXPIRING) {
|
||||
const expiredAt = systemFeatures.license?.expired_at
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Plugin, PluginManifestInMarket } from '../../types'
|
||||
import type { SystemFeatures } from '@/types/feature'
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useSystemFeatures } from '@/hooks/use-global-public'
|
||||
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 = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const systemFeatures = useSystemFeatures()
|
||||
return pluginInstallLimit(plugin, systemFeatures)
|
||||
}
|
||||
|
||||
@@ -181,7 +181,7 @@ vi.mock('@/context/mitt-context', () => ({
|
||||
|
||||
// Mock global public context
|
||||
vi.mock('@/context/global-public-context', () => ({
|
||||
useGlobalPublicStore: () => ({}),
|
||||
useSystemFeatures: () => ({}),
|
||||
}))
|
||||
|
||||
// Mock useCanInstallPluginFromMarketplace
|
||||
|
||||
@@ -56,9 +56,9 @@ vi.mock('@/app/components/plugins/install-plugin/hooks/use-check-installed', ()
|
||||
}),
|
||||
}))
|
||||
|
||||
// Mock useGlobalPublicStore
|
||||
// Mock useSystemFeatures
|
||||
vi.mock('@/context/global-public-context', () => ({
|
||||
useGlobalPublicStore: () => ({}),
|
||||
useSystemFeatures: () => ({}),
|
||||
}))
|
||||
|
||||
// Mock pluginInstallLimit
|
||||
|
||||
@@ -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 { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useSystemFeatures } from '@/hooks/use-global-public'
|
||||
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 = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const systemFeatures = useSystemFeatures()
|
||||
// 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
|
||||
|
||||
@@ -66,8 +66,7 @@ vi.mock('@/context/i18n', () => ({
|
||||
let mockEnableMarketplace = true
|
||||
|
||||
vi.mock('@/context/global-public-context', () => ({
|
||||
useGlobalPublicStore: (selector: (state: { systemFeatures: { enable_marketplace: boolean } }) => unknown) =>
|
||||
selector({ systemFeatures: { enable_marketplace: mockEnableMarketplace } }),
|
||||
useSystemFeatures: () => ({ enable_marketplace: mockEnableMarketplace }),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/modal-context', () => ({
|
||||
|
||||
@@ -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 } = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const { enable_marketplace } = useSystemFeatures()
|
||||
|
||||
const {
|
||||
id,
|
||||
|
||||
@@ -11,8 +11,7 @@ vi.mock('react-i18next', () => ({
|
||||
}))
|
||||
|
||||
vi.mock('@/context/global-public-context', () => ({
|
||||
useGlobalPublicStore: <T,>(selector: (state: { systemFeatures: { enable_marketplace: boolean } }) => T): T =>
|
||||
selector({ systemFeatures: { enable_marketplace: true } }),
|
||||
useSystemFeatures: () => ({ enable_marketplace: true }),
|
||||
}))
|
||||
|
||||
vi.mock('@/utils/classnames', () => ({
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useSystemFeatures } from '@/hooks/use-global-public'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { PluginSource } from '../types'
|
||||
|
||||
@@ -42,7 +42,7 @@ const OperationDropdown: FC<Props> = ({
|
||||
setOpen(!openRef.current)
|
||||
}, [setOpen])
|
||||
|
||||
const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const { enable_marketplace } = useSystemFeatures()
|
||||
|
||||
return (
|
||||
<PortalToFollowElem
|
||||
|
||||
@@ -68,8 +68,7 @@ vi.mock('@/context/app-context', () => ({
|
||||
// Mock global public store
|
||||
const mockEnableMarketplace = vi.fn(() => true)
|
||||
vi.mock('@/context/global-public-context', () => ({
|
||||
useGlobalPublicStore: (selector: (s: any) => any) =>
|
||||
selector({ systemFeatures: { enable_marketplace: mockEnableMarketplace() } }),
|
||||
useSystemFeatures: () => ({ enable_marketplace: mockEnableMarketplace() }),
|
||||
}))
|
||||
|
||||
// Mock Action component
|
||||
|
||||
@@ -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 { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useSystemFeatures } from '@/hooks/use-global-public'
|
||||
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 } = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const { enable_marketplace } = useSystemFeatures()
|
||||
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}`)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
// Import mocks
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useSystemFeatures } from '@/hooks/use-global-public'
|
||||
|
||||
import { PluginPageContext, PluginPageContextProvider, usePluginPageContext } from './context'
|
||||
|
||||
@@ -11,7 +11,7 @@ vi.mock('nuqs', () => ({
|
||||
}))
|
||||
|
||||
vi.mock('@/context/global-public-context', () => ({
|
||||
useGlobalPublicStore: vi.fn(),
|
||||
useSystemFeatures: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('../hooks', () => ({
|
||||
@@ -25,12 +25,11 @@ vi.mock('../hooks', () => ({
|
||||
],
|
||||
}))
|
||||
|
||||
// Helper function to mock useGlobalPublicStore with marketplace setting
|
||||
// Helper function to mock useSystemFeatures with marketplace setting
|
||||
const mockGlobalPublicStore = (enableMarketplace: boolean) => {
|
||||
vi.mocked(useGlobalPublicStore).mockImplementation((selector) => {
|
||||
const state = { systemFeatures: { enable_marketplace: enableMarketplace } }
|
||||
return selector(state as Parameters<typeof selector>[0])
|
||||
})
|
||||
vi.mocked(useSystemFeatures).mockReturnValue({
|
||||
enable_marketplace: enableMarketplace,
|
||||
} as ReturnType<typeof useSystemFeatures>)
|
||||
}
|
||||
|
||||
// Test component that uses the context
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
createContext,
|
||||
useContextSelector,
|
||||
} from 'use-context-selector'
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useSystemFeatures } from '@/hooks/use-global-public'
|
||||
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 } = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const { enable_marketplace } = useSystemFeatures()
|
||||
const tabs = usePluginPageTabs()
|
||||
const options = useMemo(() => {
|
||||
return enable_marketplace ? tabs : tabs.filter(tab => tab.value !== PLUGIN_PAGE_TABS_MAP.marketplace)
|
||||
|
||||
@@ -56,14 +56,10 @@ vi.mock('../context', () => ({
|
||||
|
||||
// Mock global public store (Zustand store)
|
||||
vi.mock('@/context/global-public-context', () => ({
|
||||
useGlobalPublicStore: (selector: (state: any) => any) => {
|
||||
return selector({
|
||||
systemFeatures: {
|
||||
...defaultSystemFeatures,
|
||||
...mockState.systemFeatures,
|
||||
},
|
||||
})
|
||||
},
|
||||
useSystemFeatures: () => ({
|
||||
...defaultSystemFeatures,
|
||||
...mockState.systemFeatures,
|
||||
}),
|
||||
}))
|
||||
|
||||
// Mock useInstalledPluginList hook
|
||||
|
||||
@@ -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 { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useSystemFeatures } from '@/hooks/use-global-public'
|
||||
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 } = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const { enable_marketplace, plugin_installation_permission } = useSystemFeatures()
|
||||
const setActiveTab = usePluginPageContext(v => v.setActiveTab)
|
||||
|
||||
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
|
||||
@@ -28,14 +28,9 @@ vi.mock('@/context/i18n', () => ({
|
||||
}))
|
||||
|
||||
vi.mock('@/context/global-public-context', () => ({
|
||||
useGlobalPublicStore: vi.fn((selector) => {
|
||||
const state = {
|
||||
systemFeatures: {
|
||||
enable_marketplace: true,
|
||||
},
|
||||
}
|
||||
return selector(state)
|
||||
}),
|
||||
useSystemFeatures: vi.fn(() => ({
|
||||
enable_marketplace: true,
|
||||
})),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
@@ -629,14 +624,9 @@ describe('PluginPage Component', () => {
|
||||
it('should handle marketplace disabled', () => {
|
||||
// Mock marketplace disabled
|
||||
vi.mock('@/context/global-public-context', async () => ({
|
||||
useGlobalPublicStore: vi.fn((selector) => {
|
||||
const state = {
|
||||
systemFeatures: {
|
||||
enable_marketplace: false,
|
||||
},
|
||||
}
|
||||
return selector(state)
|
||||
}),
|
||||
useSystemFeatures: vi.fn(() => ({
|
||||
enable_marketplace: false,
|
||||
})),
|
||||
}))
|
||||
|
||||
vi.mocked(useQueryState).mockReturnValue(['discover', vi.fn()])
|
||||
|
||||
@@ -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 } = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const { enable_marketplace } = useSystemFeatures()
|
||||
|
||||
const isPluginsTab = useMemo(() => activeTab === PLUGIN_PAGE_TABS_MAP.plugins, [activeTab])
|
||||
const isExploringMarketplace = useMemo(() => {
|
||||
|
||||
@@ -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 { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useSystemFeatures } from '@/hooks/use-global-public'
|
||||
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 } = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const { enable_marketplace, plugin_installation_permission } = useSystemFeatures()
|
||||
|
||||
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = event.target.files?.[0]
|
||||
|
||||
@@ -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 { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useSystemFeatures } from '@/hooks/use-global-public'
|
||||
|
||||
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', () => ({
|
||||
useGlobalPublicStore: vi.fn(),
|
||||
useSystemFeatures: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/service/use-plugins', () => ({
|
||||
@@ -309,14 +309,9 @@ describe('useCanInstallPluginFromMarketplace Hook', () => {
|
||||
})
|
||||
|
||||
it('should return true when marketplace is enabled and canManagement is true', () => {
|
||||
vi.mocked(useGlobalPublicStore).mockImplementation((selector) => {
|
||||
const state = {
|
||||
systemFeatures: {
|
||||
enable_marketplace: true,
|
||||
},
|
||||
}
|
||||
return selector(state as Parameters<typeof selector>[0])
|
||||
})
|
||||
vi.mocked(useSystemFeatures).mockReturnValue({
|
||||
enable_marketplace: true,
|
||||
} as ReturnType<typeof useSystemFeatures>)
|
||||
|
||||
const { result } = renderHook(() => useCanInstallPluginFromMarketplace())
|
||||
|
||||
@@ -324,14 +319,9 @@ describe('useCanInstallPluginFromMarketplace Hook', () => {
|
||||
})
|
||||
|
||||
it('should return false when marketplace is disabled', () => {
|
||||
vi.mocked(useGlobalPublicStore).mockImplementation((selector) => {
|
||||
const state = {
|
||||
systemFeatures: {
|
||||
enable_marketplace: false,
|
||||
},
|
||||
}
|
||||
return selector(state as Parameters<typeof selector>[0])
|
||||
})
|
||||
vi.mocked(useSystemFeatures).mockReturnValue({
|
||||
enable_marketplace: false,
|
||||
} as ReturnType<typeof useSystemFeatures>)
|
||||
|
||||
const { result } = renderHook(() => useCanInstallPluginFromMarketplace())
|
||||
|
||||
@@ -339,14 +329,9 @@ describe('useCanInstallPluginFromMarketplace Hook', () => {
|
||||
})
|
||||
|
||||
it('should return false when canManagement is false', () => {
|
||||
vi.mocked(useGlobalPublicStore).mockImplementation((selector) => {
|
||||
const state = {
|
||||
systemFeatures: {
|
||||
enable_marketplace: true,
|
||||
},
|
||||
}
|
||||
return selector(state as Parameters<typeof selector>[0])
|
||||
})
|
||||
vi.mocked(useSystemFeatures).mockReturnValue({
|
||||
enable_marketplace: true,
|
||||
} as ReturnType<typeof useSystemFeatures>)
|
||||
|
||||
vi.mocked(useReferenceSettings).mockReturnValue({
|
||||
data: {
|
||||
@@ -363,14 +348,9 @@ describe('useCanInstallPluginFromMarketplace Hook', () => {
|
||||
})
|
||||
|
||||
it('should return false when both marketplace is disabled and canManagement is false', () => {
|
||||
vi.mocked(useGlobalPublicStore).mockImplementation((selector) => {
|
||||
const state = {
|
||||
systemFeatures: {
|
||||
enable_marketplace: false,
|
||||
},
|
||||
}
|
||||
return selector(state as Parameters<typeof selector>[0])
|
||||
})
|
||||
vi.mocked(useSystemFeatures).mockReturnValue({
|
||||
enable_marketplace: false,
|
||||
} as ReturnType<typeof useSystemFeatures>)
|
||||
|
||||
vi.mocked(useReferenceSettings).mockReturnValue({
|
||||
data: {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useSystemFeatures } from '@/hooks/use-global-public'
|
||||
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 } = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const { enable_marketplace } = useSystemFeatures()
|
||||
const { canManagement } = useReferenceSetting()
|
||||
|
||||
const canInstallPluginFromMarketplace = useMemo(() => {
|
||||
|
||||
@@ -36,9 +36,7 @@ vi.mock('react-i18next', () => ({
|
||||
// Mock global public store
|
||||
const mockSystemFeatures = { enable_marketplace: true }
|
||||
vi.mock('@/context/global-public-context', () => ({
|
||||
useGlobalPublicStore: (selector: (s: { systemFeatures: typeof mockSystemFeatures }) => typeof mockSystemFeatures) => {
|
||||
return selector({ systemFeatures: mockSystemFeatures })
|
||||
},
|
||||
useSystemFeatures: () => mockSystemFeatures,
|
||||
}))
|
||||
|
||||
// Mock Modal component
|
||||
|
||||
@@ -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 { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useSystemFeatures } from '@/hooks/use-global-public'
|
||||
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 } = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const { enable_marketplace } = useSystemFeatures()
|
||||
const handlePrivilegeChange = useCallback((key: string) => {
|
||||
return (value: PermissionType) => {
|
||||
setTempPrivilege({
|
||||
|
||||
@@ -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 = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const systemFeatures = useSystemFeatures()
|
||||
const [appId, setAppId] = useState<string>('')
|
||||
const [siteInfo, setSiteInfo] = useState<SiteInfo | null>(null)
|
||||
const [customConfig, setCustomConfig] = useState<Record<string, any> | null>(null)
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
'use client'
|
||||
import type { FC, PropsWithChildren } from 'react'
|
||||
import * as React from 'react'
|
||||
import { useIsLogin } from '@/service/use-common'
|
||||
import Loading from './base/loading'
|
||||
|
||||
const Splash: FC<PropsWithChildren> = () => {
|
||||
// would auto redirect to signin page if not logged in
|
||||
const { isLoading, data: loginData } = useIsLogin()
|
||||
const isLoggedIn = loginData?.logged_in
|
||||
|
||||
if (isLoading || !isLoggedIn) {
|
||||
return (
|
||||
<div className="fixed inset-0 z-[9999999] flex h-full items-center justify-center bg-background-body">
|
||||
<Loading />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
export default React.memo(Splash)
|
||||
@@ -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 { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useSystemFeatures } from '@/hooks/use-global-public'
|
||||
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 } = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const { enable_marketplace } = useSystemFeatures()
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const [activeTab, setActiveTab] = useQueryState('category', {
|
||||
|
||||
@@ -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 { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useSystemFeatures } from '@/hooks/use-global-public'
|
||||
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 } = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const { enable_marketplace } = useSystemFeatures()
|
||||
const pluginRef = useRef<ListRef>(null)
|
||||
const wrapElemRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
|
||||
@@ -19,8 +19,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'
|
||||
@@ -167,7 +167,7 @@ const AllTools = ({
|
||||
plugins: notInstalledPlugins = [],
|
||||
} = useMarketplacePlugins()
|
||||
|
||||
const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const { enable_marketplace } = useSystemFeatures()
|
||||
|
||||
useEffect(() => {
|
||||
if (!enable_marketplace)
|
||||
|
||||
@@ -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 } = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const { enable_marketplace } = useSystemFeatures()
|
||||
|
||||
const {
|
||||
queryPluginsWithDebounced: fetchPlugins,
|
||||
|
||||
@@ -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 { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useSystemFeatures } from '@/hooks/use-global-public'
|
||||
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 } = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const { enable_marketplace } = useSystemFeatures()
|
||||
const workflowStore = useWorkflowStore()
|
||||
const inRAGPipeline = dataSources.length > 0
|
||||
const {
|
||||
|
||||
@@ -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 { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useSystemFeatures } from '@/hooks/use-global-public'
|
||||
import {
|
||||
createCustomCollection,
|
||||
} from '@/service/tools'
|
||||
@@ -70,7 +70,7 @@ const ToolPicker: FC<Props> = ({
|
||||
const [searchText, setSearchText] = useState('')
|
||||
const [tags, setTags] = useState<string[]>([])
|
||||
|
||||
const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const { enable_marketplace } = useSystemFeatures()
|
||||
const { data: buildInTools } = useAllBuiltInTools()
|
||||
const { data: customTools } = useAllCustomTools()
|
||||
const invalidateCustomTools = useInvalidateAllCustomTools()
|
||||
|
||||
@@ -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 { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useSystemFeatures } from '@/hooks/use-global-public'
|
||||
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 } = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const { enable_marketplace } = useSystemFeatures()
|
||||
|
||||
const { value, onChange } = props
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
@@ -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 } = useGlobalPublicStore()
|
||||
const systemFeatures = useSystemFeatures()
|
||||
|
||||
return (
|
||||
<div className={cn('flex min-h-screen w-full justify-center bg-background-default-burn p-6')}>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
'use client'
|
||||
import * as React from 'react'
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useSystemFeatures } from '@/hooks/use-global-public'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import Header from '../signin/_header'
|
||||
import InstallForm from './installForm'
|
||||
|
||||
const Install = () => {
|
||||
const { systemFeatures } = useGlobalPublicStore()
|
||||
const systemFeatures = useSystemFeatures()
|
||||
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')}>
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import type { Viewport } from 'next'
|
||||
import { dehydrate, HydrationBoundary } from '@tanstack/react-query'
|
||||
import { Provider as JotaiProvider } from 'jotai'
|
||||
import { ThemeProvider } from 'next-themes'
|
||||
import { Instrument_Serif } from 'next/font/google'
|
||||
import { NuqsAdapter } from 'nuqs/adapters/next/app'
|
||||
import GlobalPublicStoreProvider from '@/context/global-public-context'
|
||||
import { TanstackQueryInitializer } from '@/context/query-client'
|
||||
import { getQueryClientServer } from '@/context/query-client-server'
|
||||
import { getLocaleOnServer } from '@/i18n-config/server'
|
||||
import { DatasetAttr } from '@/types/feature'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { serverFetch } from '@/utils/ssr-fetch'
|
||||
import { ToastProvider } from './components/base/toast'
|
||||
import BrowserInitializer from './components/browser-initializer'
|
||||
import { ReactScanLoader } from './components/devtools/react-scan/loader'
|
||||
@@ -39,6 +42,18 @@ const LocaleLayout = async ({
|
||||
children: React.ReactNode
|
||||
}) => {
|
||||
const locale = await getLocaleOnServer()
|
||||
const queryClient = getQueryClientServer()
|
||||
|
||||
await Promise.all([
|
||||
queryClient.prefetchQuery({
|
||||
queryKey: ['systemFeatures'],
|
||||
queryFn: () => serverFetch('/system-features'),
|
||||
}),
|
||||
queryClient.prefetchQuery({
|
||||
queryKey: ['setupStatus'],
|
||||
queryFn: () => serverFetch('/setup'),
|
||||
}),
|
||||
])
|
||||
|
||||
const datasetMap: Record<DatasetAttr, string | undefined> = {
|
||||
[DatasetAttr.DATA_API_PREFIX]: process.env.NEXT_PUBLIC_API_PREFIX,
|
||||
@@ -107,13 +122,15 @@ const LocaleLayout = async ({
|
||||
<BrowserInitializer>
|
||||
<SentryInitializer>
|
||||
<TanstackQueryInitializer>
|
||||
<I18nServerProvider>
|
||||
<ToastProvider>
|
||||
<GlobalPublicStoreProvider>
|
||||
{children}
|
||||
</GlobalPublicStoreProvider>
|
||||
</ToastProvider>
|
||||
</I18nServerProvider>
|
||||
<HydrationBoundary state={dehydrate(queryClient)}>
|
||||
<I18nServerProvider>
|
||||
<ToastProvider>
|
||||
<GlobalPublicStoreProvider>
|
||||
{children}
|
||||
</GlobalPublicStoreProvider>
|
||||
</ToastProvider>
|
||||
</I18nServerProvider>
|
||||
</HydrationBoundary>
|
||||
</TanstackQueryInitializer>
|
||||
</SentryInitializer>
|
||||
</BrowserInitializer>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
'use client'
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useSystemFeatures } from '@/hooks/use-global-public'
|
||||
|
||||
import { cn } from '@/utils/classnames'
|
||||
import Header from '../signin/_header'
|
||||
|
||||
export default function SignInLayout({ children }: any) {
|
||||
const { systemFeatures } = useGlobalPublicStore()
|
||||
const systemFeatures = useSystemFeatures()
|
||||
return (
|
||||
<>
|
||||
<div className={cn('flex min-h-screen w-full justify-center bg-background-default-burn p-6')}>
|
||||
|
||||
@@ -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 = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const systemFeatures = useSystemFeatures()
|
||||
|
||||
return (
|
||||
<div className="flex w-full items-center justify-between p-6">
|
||||
|
||||
@@ -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 { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useSystemFeatures } from '@/hooks/use-global-public'
|
||||
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 = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const systemFeatures = useSystemFeatures()
|
||||
const router = useRouter()
|
||||
const searchParams = useSearchParams()
|
||||
const token = decodeURIComponent(searchParams.get('invite_token') as string)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
'use client'
|
||||
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 './_header'
|
||||
|
||||
export default function SignInLayout({ children }: any) {
|
||||
const { systemFeatures } = useGlobalPublicStore()
|
||||
const systemFeatures = useSystemFeatures()
|
||||
useDocumentTitle('')
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -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 { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useSystemFeatures } from '@/hooks/use-global-public'
|
||||
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 } = useGlobalPublicStore()
|
||||
const systemFeatures = useSystemFeatures()
|
||||
const [authType, updateAuthType] = useState<'code' | 'password'>('password')
|
||||
const [showORLine, setShowORLine] = useState(false)
|
||||
const [allMethodsAreDisabled, setAllMethodsAreDisabled] = useState(false)
|
||||
|
||||
@@ -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', () => ({
|
||||
useGlobalPublicStore: vi.fn(),
|
||||
useSystemFeatures: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/i18n', () => ({
|
||||
@@ -46,7 +46,7 @@ vi.mock('@/service/use-common', () => ({
|
||||
|
||||
type UseSendMailResult = ReturnType<typeof useSendMail>
|
||||
|
||||
const mockUseGlobalPublicStore = useGlobalPublicStore as unknown as MockedFunction<typeof useGlobalPublicStore>
|
||||
const mockUseSystemFeatures = useSystemFeatures as unknown as MockedFunction<typeof useSystemFeatures>
|
||||
const mockUseLocale = useLocale as unknown as MockedFunction<typeof useLocale>
|
||||
const mockUseSendMail = useSendMail as unknown as MockedFunction<typeof useSendMail>
|
||||
|
||||
@@ -57,11 +57,9 @@ const renderForm = ({
|
||||
brandingEnabled?: boolean
|
||||
isPending?: boolean
|
||||
} = {}) => {
|
||||
mockUseGlobalPublicStore.mockReturnValue({
|
||||
systemFeatures: buildSystemFeatures({
|
||||
branding: { enabled: brandingEnabled },
|
||||
}),
|
||||
})
|
||||
mockUseSystemFeatures.mockReturnValue(buildSystemFeatures({
|
||||
branding: { enabled: brandingEnabled },
|
||||
}))
|
||||
mockUseLocale.mockReturnValue('en-US')
|
||||
mockUseSendMail.mockReturnValue({
|
||||
mutateAsync: mockSubmitMail,
|
||||
|
||||
@@ -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 } = useGlobalPublicStore()
|
||||
const systemFeatures = useSystemFeatures()
|
||||
|
||||
const { mutateAsync: submitMail, isPending } = useSendMail()
|
||||
|
||||
|
||||
@@ -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 } = useGlobalPublicStore()
|
||||
const systemFeatures = useSystemFeatures()
|
||||
useDocumentTitle('')
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -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 = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const systemFeatures = useSystemFeatures()
|
||||
const { data: userProfileResp } = useUserProfile()
|
||||
const { data: currentWorkspaceResp, isPending: isLoadingCurrentWorkspace, isFetching: isValidatingCurrentWorkspace } = useCurrentWorkspace()
|
||||
const langGeniusVersionQuery = useLangGeniusVersion(
|
||||
|
||||
@@ -1,61 +1,12 @@
|
||||
'use client'
|
||||
import type { FC, PropsWithChildren } from 'react'
|
||||
import type { SystemFeatures } from '@/types/feature'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { create } from 'zustand'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
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() {
|
||||
const data = await consoleClient.systemFeatures()
|
||||
const { setSystemFeatures } = useGlobalPublicStore.getState()
|
||||
setSystemFeatures({ ...defaultSystemFeatures, ...data })
|
||||
return data
|
||||
}
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
import { useSetupStatusQuery, useSystemFeaturesQuery } from '@/hooks/use-global-public'
|
||||
|
||||
const GlobalPublicStoreProvider: FC<PropsWithChildren> = ({
|
||||
children,
|
||||
}) => {
|
||||
// Fetch systemFeatures and setupStatus in parallel to reduce waterfall.
|
||||
// setupStatus is prefetched here and cached in localStorage for AppInitializer.
|
||||
const { isPending } = useSystemFeaturesQuery()
|
||||
|
||||
// Prefetch setupStatus for AppInitializer (result not needed here)
|
||||
useSetupStatusQuery()
|
||||
|
||||
if (isPending)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { SetupStatusResponse } from '@/models/common'
|
||||
import type { SystemFeatures } from '@/types/feature'
|
||||
import { type } from '@orpc/contract'
|
||||
import { base } from '../base'
|
||||
@@ -9,3 +10,11 @@ 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>())
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { InferContractRouterInputs } from '@orpc/contract'
|
||||
import { bindPartnerStackContract, invoicesContract } from './console/billing'
|
||||
import { systemFeaturesContract } from './console/system'
|
||||
import { setupStatusContract, systemFeaturesContract } from './console/system'
|
||||
import { trialAppDatasetsContract, trialAppInfoContract, trialAppParametersContract, trialAppWorkflowsContract } from './console/try-app'
|
||||
import { collectionPluginsContract, collectionsContract, searchAdvancedContract } from './marketplace'
|
||||
|
||||
@@ -14,6 +14,7 @@ export type MarketPlaceInputs = InferContractRouterInputs<typeof marketplaceRout
|
||||
|
||||
export const consoleRouterContract = {
|
||||
systemFeatures: systemFeaturesContract,
|
||||
setupStatus: setupStatusContract,
|
||||
trialApps: {
|
||||
info: trialAppInfoContract,
|
||||
datasets: trialAppDatasetsContract,
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
},
|
||||
"__tests__/embedded-user-id-store.test.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 3
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"__tests__/goto-anything/command-selector.test.tsx": {
|
||||
@@ -104,11 +104,6 @@
|
||||
"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
|
||||
@@ -656,7 +651,7 @@
|
||||
},
|
||||
"app/components/apps/app-card.spec.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 22
|
||||
"count": 20
|
||||
}
|
||||
},
|
||||
"app/components/apps/app-card.tsx": {
|
||||
@@ -1674,7 +1669,7 @@
|
||||
},
|
||||
"app/components/custom/custom-web-app-brand/index.spec.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 7
|
||||
"count": 6
|
||||
}
|
||||
},
|
||||
"app/components/custom/custom-web-app-brand/index.tsx": {
|
||||
@@ -2543,7 +2538,7 @@
|
||||
},
|
||||
"app/components/plugins/plugin-item/index.spec.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 10
|
||||
"count": 8
|
||||
}
|
||||
},
|
||||
"app/components/plugins/plugin-item/index.tsx": {
|
||||
@@ -2566,7 +2561,7 @@
|
||||
},
|
||||
"app/components/plugins/plugin-page/empty/index.spec.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 7
|
||||
"count": 5
|
||||
}
|
||||
},
|
||||
"app/components/plugins/plugin-page/empty/index.tsx": {
|
||||
@@ -4346,11 +4341,6 @@
|
||||
"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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { act, renderHook } from '@testing-library/react'
|
||||
import { useGlobalPublicStore, useIsSystemFeaturesPending } from '@/context/global-public-context'
|
||||
import { renderHook } from '@testing-library/react'
|
||||
import { useIsSystemFeaturesPending, useSystemFeatures } from '@/hooks/use-global-public'
|
||||
/**
|
||||
* Test suite for useDocumentTitle hook
|
||||
*
|
||||
@@ -15,13 +15,10 @@ import { useGlobalPublicStore, useIsSystemFeaturesPending } from '@/context/glob
|
||||
import { defaultSystemFeatures } from '@/types/feature'
|
||||
import useDocumentTitle from './use-document-title'
|
||||
|
||||
vi.mock('@/context/global-public-context', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@/context/global-public-context')>()
|
||||
return {
|
||||
...actual,
|
||||
useIsSystemFeaturesPending: vi.fn(() => false),
|
||||
}
|
||||
})
|
||||
vi.mock('@/context/global-public-context', () => ({
|
||||
useSystemFeatures: vi.fn(() => ({ ...defaultSystemFeatures })),
|
||||
useIsSystemFeaturesPending: vi.fn(() => false),
|
||||
}))
|
||||
|
||||
/**
|
||||
* Test behavior when system features are still loading
|
||||
@@ -30,11 +27,7 @@ vi.mock('@/context/global-public-context', async (importOriginal) => {
|
||||
describe('title should be empty if systemFeatures is pending', () => {
|
||||
beforeEach(() => {
|
||||
vi.mocked(useIsSystemFeaturesPending).mockReturnValue(true)
|
||||
act(() => {
|
||||
useGlobalPublicStore.setState({
|
||||
systemFeatures: { ...defaultSystemFeatures, branding: { ...defaultSystemFeatures.branding, enabled: false } },
|
||||
})
|
||||
})
|
||||
vi.mocked(useSystemFeatures).mockReturnValue({ ...defaultSystemFeatures, branding: { ...defaultSystemFeatures.branding, enabled: false } })
|
||||
})
|
||||
/**
|
||||
* Test that title stays empty during loading even when a title is provided
|
||||
@@ -59,11 +52,7 @@ describe('title should be empty if systemFeatures is pending', () => {
|
||||
describe('use default branding', () => {
|
||||
beforeEach(() => {
|
||||
vi.mocked(useIsSystemFeaturesPending).mockReturnValue(false)
|
||||
act(() => {
|
||||
useGlobalPublicStore.setState({
|
||||
systemFeatures: { ...defaultSystemFeatures, branding: { ...defaultSystemFeatures.branding, enabled: false } },
|
||||
})
|
||||
})
|
||||
vi.mocked(useSystemFeatures).mockReturnValue({ ...defaultSystemFeatures, branding: { ...defaultSystemFeatures.branding, enabled: false } })
|
||||
})
|
||||
/**
|
||||
* Test title format with page title and default branding
|
||||
@@ -91,11 +80,7 @@ describe('use default branding', () => {
|
||||
describe('use specific branding', () => {
|
||||
beforeEach(() => {
|
||||
vi.mocked(useIsSystemFeaturesPending).mockReturnValue(false)
|
||||
act(() => {
|
||||
useGlobalPublicStore.setState({
|
||||
systemFeatures: { ...defaultSystemFeatures, branding: { ...defaultSystemFeatures.branding, enabled: true, application_title: 'Test' } },
|
||||
})
|
||||
})
|
||||
vi.mocked(useSystemFeatures).mockReturnValue({ ...defaultSystemFeatures, branding: { ...defaultSystemFeatures.branding, enabled: true, application_title: 'Test' } })
|
||||
})
|
||||
/**
|
||||
* Test title format with page title and custom branding
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
'use client'
|
||||
import { useFavicon, useTitle } from 'ahooks'
|
||||
import { useEffect } from 'react'
|
||||
import { useGlobalPublicStore, useIsSystemFeaturesPending } from '@/context/global-public-context'
|
||||
import { useIsSystemFeaturesPending, useSystemFeatures } from '@/hooks/use-global-public'
|
||||
import { basePath } from '@/utils/var'
|
||||
|
||||
export default function useDocumentTitle(title: string) {
|
||||
const isPending = useIsSystemFeaturesPending()
|
||||
const systemFeatures = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const systemFeatures = useSystemFeatures()
|
||||
const prefix = title ? `${title} - ` : ''
|
||||
let titleStr = ''
|
||||
let favicon = ''
|
||||
|
||||
30
web/hooks/use-global-public.ts
Normal file
30
web/hooks/use-global-public.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
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,
|
||||
})
|
||||
}
|
||||
@@ -23,7 +23,7 @@ export type OauthResponse = {
|
||||
|
||||
export type SetupStatusResponse = {
|
||||
step: 'finished' | 'not_started'
|
||||
setup_at?: Date
|
||||
setup_at?: string
|
||||
}
|
||||
|
||||
export type InitValidateStatusResponse = {
|
||||
|
||||
@@ -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 { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useSystemFeatures } from '@/hooks/use-global-public'
|
||||
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 = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const systemFeatures = useSystemFeatures()
|
||||
return useQuery({
|
||||
queryKey: [NAME_SPACE, 'user-can-access-app', appId, systemFeatures.webapp_auth.enabled, isInstalledApp],
|
||||
queryFn: () => {
|
||||
|
||||
@@ -92,8 +92,6 @@ export const useUserProfile = () => {
|
||||
},
|
||||
}
|
||||
},
|
||||
staleTime: 0,
|
||||
gcTime: 0,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { App, AppCategory } from '@/models/explore'
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useSystemFeatures } from '@/hooks/use-global-public'
|
||||
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 = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const systemFeatures = useSystemFeatures()
|
||||
return useQuery({
|
||||
queryKey: [NAME_SPACE, 'appAccessMode', appId, systemFeatures.webapp_auth.enabled],
|
||||
queryFn: () => {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import type { SetupStatusResponse } from '@/models/common'
|
||||
|
||||
import { fetchSetupStatus } from '@/service/common'
|
||||
|
||||
import { consoleClient } from '@/service/client'
|
||||
import { fetchSetupStatusWithCache } from './setup-status'
|
||||
|
||||
vi.mock('@/service/common', () => ({
|
||||
fetchSetupStatus: vi.fn(),
|
||||
vi.mock('@/service/client', () => ({
|
||||
consoleClient: {
|
||||
setupStatus: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
const mockFetchSetupStatus = vi.mocked(fetchSetupStatus)
|
||||
const mockSetupStatus = vi.mocked(consoleClient.setupStatus)
|
||||
|
||||
describe('setup-status utilities', () => {
|
||||
beforeEach(() => {
|
||||
@@ -24,7 +24,7 @@ describe('setup-status utilities', () => {
|
||||
const result = await fetchSetupStatusWithCache()
|
||||
|
||||
expect(result).toEqual({ step: 'finished' })
|
||||
expect(mockFetchSetupStatus).not.toHaveBeenCalled()
|
||||
expect(mockSetupStatus).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not modify localStorage when returning cached value', async () => {
|
||||
@@ -39,22 +39,22 @@ describe('setup-status utilities', () => {
|
||||
describe('when cache does not exist', () => {
|
||||
it('should call API and cache finished status', async () => {
|
||||
const apiResponse: SetupStatusResponse = { step: 'finished' }
|
||||
mockFetchSetupStatus.mockResolvedValue(apiResponse)
|
||||
mockSetupStatus.mockResolvedValue(apiResponse)
|
||||
|
||||
const result = await fetchSetupStatusWithCache()
|
||||
|
||||
expect(mockFetchSetupStatus).toHaveBeenCalledTimes(1)
|
||||
expect(mockSetupStatus).toHaveBeenCalledTimes(1)
|
||||
expect(result).toEqual(apiResponse)
|
||||
expect(localStorage.getItem('setup_status')).toBe('finished')
|
||||
})
|
||||
|
||||
it('should call API and remove cache when not finished', async () => {
|
||||
const apiResponse: SetupStatusResponse = { step: 'not_started' }
|
||||
mockFetchSetupStatus.mockResolvedValue(apiResponse)
|
||||
mockSetupStatus.mockResolvedValue(apiResponse)
|
||||
|
||||
const result = await fetchSetupStatusWithCache()
|
||||
|
||||
expect(mockFetchSetupStatus).toHaveBeenCalledTimes(1)
|
||||
expect(mockSetupStatus).toHaveBeenCalledTimes(1)
|
||||
expect(result).toEqual(apiResponse)
|
||||
expect(localStorage.getItem('setup_status')).toBeNull()
|
||||
})
|
||||
@@ -62,7 +62,7 @@ describe('setup-status utilities', () => {
|
||||
it('should clear stale cache when API returns not_started', async () => {
|
||||
localStorage.setItem('setup_status', 'some_invalid_value')
|
||||
const apiResponse: SetupStatusResponse = { step: 'not_started' }
|
||||
mockFetchSetupStatus.mockResolvedValue(apiResponse)
|
||||
mockSetupStatus.mockResolvedValue(apiResponse)
|
||||
|
||||
const result = await fetchSetupStatusWithCache()
|
||||
|
||||
@@ -75,44 +75,44 @@ describe('setup-status utilities', () => {
|
||||
it('should call API when cache value is empty string', async () => {
|
||||
localStorage.setItem('setup_status', '')
|
||||
const apiResponse: SetupStatusResponse = { step: 'finished' }
|
||||
mockFetchSetupStatus.mockResolvedValue(apiResponse)
|
||||
mockSetupStatus.mockResolvedValue(apiResponse)
|
||||
|
||||
const result = await fetchSetupStatusWithCache()
|
||||
|
||||
expect(mockFetchSetupStatus).toHaveBeenCalledTimes(1)
|
||||
expect(mockSetupStatus).toHaveBeenCalledTimes(1)
|
||||
expect(result).toEqual(apiResponse)
|
||||
})
|
||||
|
||||
it('should call API when cache value is not "finished"', async () => {
|
||||
localStorage.setItem('setup_status', 'not_started')
|
||||
const apiResponse: SetupStatusResponse = { step: 'finished' }
|
||||
mockFetchSetupStatus.mockResolvedValue(apiResponse)
|
||||
mockSetupStatus.mockResolvedValue(apiResponse)
|
||||
|
||||
const result = await fetchSetupStatusWithCache()
|
||||
|
||||
expect(mockFetchSetupStatus).toHaveBeenCalledTimes(1)
|
||||
expect(mockSetupStatus).toHaveBeenCalledTimes(1)
|
||||
expect(result).toEqual(apiResponse)
|
||||
})
|
||||
|
||||
it('should call API when localStorage key does not exist', async () => {
|
||||
const apiResponse: SetupStatusResponse = { step: 'finished' }
|
||||
mockFetchSetupStatus.mockResolvedValue(apiResponse)
|
||||
mockSetupStatus.mockResolvedValue(apiResponse)
|
||||
|
||||
const result = await fetchSetupStatusWithCache()
|
||||
|
||||
expect(mockFetchSetupStatus).toHaveBeenCalledTimes(1)
|
||||
expect(mockSetupStatus).toHaveBeenCalledTimes(1)
|
||||
expect(result).toEqual(apiResponse)
|
||||
})
|
||||
})
|
||||
|
||||
describe('API response handling', () => {
|
||||
it('should preserve setup_at from API response', async () => {
|
||||
const setupDate = new Date('2024-01-01')
|
||||
const setupDate = '2024-01-01T00:00:00.000Z'
|
||||
const apiResponse: SetupStatusResponse = {
|
||||
step: 'finished',
|
||||
setup_at: setupDate,
|
||||
}
|
||||
mockFetchSetupStatus.mockResolvedValue(apiResponse)
|
||||
mockSetupStatus.mockResolvedValue(apiResponse)
|
||||
|
||||
const result = await fetchSetupStatusWithCache()
|
||||
|
||||
@@ -122,13 +122,13 @@ describe('setup-status utilities', () => {
|
||||
|
||||
it('should propagate API errors', async () => {
|
||||
const apiError = new Error('Network error')
|
||||
mockFetchSetupStatus.mockRejectedValue(apiError)
|
||||
mockSetupStatus.mockRejectedValue(apiError)
|
||||
|
||||
await expect(fetchSetupStatusWithCache()).rejects.toThrow('Network error')
|
||||
})
|
||||
|
||||
it('should not update cache when API call fails', async () => {
|
||||
mockFetchSetupStatus.mockRejectedValue(new Error('API error'))
|
||||
mockSetupStatus.mockRejectedValue(new Error('API error'))
|
||||
|
||||
await expect(fetchSetupStatusWithCache()).rejects.toThrow()
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user