- {!(hideLogout || webAppAccessMode === AccessMode.EXTERNAL_MEMBERS || webAppAccessMode === AccessMode.PUBLIC) && (
+ {showLogout && (
{t('userProfile.logout', { ns: 'common' })}
diff --git a/web/app/components/share/text-generation/text-generation-sidebar.tsx b/web/app/components/share/text-generation/text-generation-sidebar.tsx
index 70b65f59e9..c07d61faf6 100644
--- a/web/app/components/share/text-generation/text-generation-sidebar.tsx
+++ b/web/app/components/share/text-generation/text-generation-sidebar.tsx
@@ -18,7 +18,7 @@ import RunBatch from './run-batch'
import RunOnce from './run-once'
type TextGenerationSidebarProps = {
- accessMode: AccessMode
+ accessMode: AccessMode | null
allTasksRun: boolean
currentTab: string
customConfig: TextGenerationCustomConfig | null
diff --git a/web/context/__tests__/web-app-context.spec.tsx b/web/context/__tests__/web-app-context.spec.tsx
new file mode 100644
index 0000000000..8dbcb06d25
--- /dev/null
+++ b/web/context/__tests__/web-app-context.spec.tsx
@@ -0,0 +1,108 @@
+import { render, screen, waitFor } from '@testing-library/react'
+import WebAppStoreProvider, { useWebAppStore } from '@/context/web-app-context'
+import { AccessMode } from '@/models/access-control'
+
+let mockPathname = '/share/test-share-code'
+let mockRedirectUrl: string | null = null
+let mockAccessModeResult: { accessMode: AccessMode } | undefined
+
+vi.mock('@/next/navigation', () => ({
+ usePathname: () => mockPathname,
+ useSearchParams: () => ({
+ get: (key: string) => key === 'redirect_url' ? mockRedirectUrl : null,
+ toString: () => {
+ const params = new URLSearchParams()
+ if (mockRedirectUrl)
+ params.set('redirect_url', mockRedirectUrl)
+ return params.toString()
+ },
+ }),
+}))
+
+vi.mock('@/app/components/base/chat/utils', () => ({
+ getProcessedSystemVariablesFromUrlParams: vi.fn().mockResolvedValue({}),
+}))
+
+vi.mock('@/service/use-share', () => ({
+ useGetWebAppAccessModeByCode: vi.fn(() => ({
+ data: mockAccessModeResult,
+ })),
+}))
+
+const StoreSnapshot = () => {
+ const shareCode = useWebAppStore(s => s.shareCode)
+ const accessMode = useWebAppStore(s => s.webAppAccessMode)
+
+ return (
+
+ {shareCode ?? 'none'}
+ {accessMode ?? 'unknown'}
+
+ )
+}
+
+describe('WebAppStoreProvider', () => {
+ beforeEach(() => {
+ vi.clearAllMocks()
+ mockPathname = '/share/test-share-code'
+ mockRedirectUrl = null
+ mockAccessModeResult = undefined
+ useWebAppStore.setState({
+ shareCode: null,
+ appInfo: null,
+ appParams: null,
+ webAppAccessMode: null,
+ appMeta: null,
+ userCanAccessApp: false,
+ embeddedUserId: null,
+ embeddedConversationId: null,
+ })
+ })
+
+ describe('Access Mode State', () => {
+ it('should keep the access mode unknown until the query resolves', async () => {
+ render(
+
+
+ ,
+ )
+
+ await waitFor(() => {
+ expect(screen.getByTestId('share-code')).toHaveTextContent('test-share-code')
+ })
+ expect(screen.getByTestId('access-mode')).toHaveTextContent('unknown')
+ })
+
+ it('should reset the access mode when the share code changes before the next result arrives', async () => {
+ const { rerender } = render(
+
+
+ ,
+ )
+
+ mockAccessModeResult = { accessMode: AccessMode.PUBLIC }
+ rerender(
+
+
+ ,
+ )
+
+ await waitFor(() => {
+ expect(screen.getByTestId('access-mode')).toHaveTextContent(AccessMode.PUBLIC)
+ })
+
+ mockPathname = '/share/next-share-code'
+ mockAccessModeResult = undefined
+ rerender(
+
+
+ ,
+ )
+
+ await waitFor(() => {
+ expect(screen.getByTestId('share-code')).toHaveTextContent('next-share-code')
+ })
+ expect(screen.getByTestId('access-mode')).toHaveTextContent('unknown')
+ })
+ })
+})
diff --git a/web/context/web-app-context.tsx b/web/context/web-app-context.tsx
index e3971c7e08..d8d5619f0d 100644
--- a/web/context/web-app-context.tsx
+++ b/web/context/web-app-context.tsx
@@ -2,11 +2,11 @@
import type { FC, PropsWithChildren } from 'react'
import type { ChatConfig } from '@/app/components/base/chat/types'
+import type { AccessMode } from '@/models/access-control'
import type { AppData, AppMeta } from '@/models/share'
import { useEffect } from 'react'
import { create } from 'zustand'
import { getProcessedSystemVariablesFromUrlParams } from '@/app/components/base/chat/utils'
-import { AccessMode } from '@/models/access-control'
import { usePathname, useSearchParams } from '@/next/navigation'
import { useGetWebAppAccessModeByCode } from '@/service/use-share'
@@ -17,8 +17,8 @@ type WebAppStore = {
updateAppInfo: (appInfo: AppData | null) => void
appParams: ChatConfig | null
updateAppParams: (appParams: ChatConfig | null) => void
- webAppAccessMode: AccessMode
- updateWebAppAccessMode: (accessMode: AccessMode) => void
+ webAppAccessMode: AccessMode | null
+ updateWebAppAccessMode: (accessMode: AccessMode | null) => void
appMeta: AppMeta | null
updateWebAppMeta: (appMeta: AppMeta | null) => void
userCanAccessApp: boolean
@@ -36,8 +36,8 @@ export const useWebAppStore = create
(set => ({
updateAppInfo: (appInfo: AppData | null) => set(() => ({ appInfo })),
appParams: null,
updateAppParams: (appParams: ChatConfig | null) => set(() => ({ appParams })),
- webAppAccessMode: AccessMode.SPECIFIC_GROUPS_MEMBERS,
- updateWebAppAccessMode: (accessMode: AccessMode) => set(() => ({ webAppAccessMode: accessMode })),
+ webAppAccessMode: null,
+ updateWebAppAccessMode: (accessMode: AccessMode | null) => set(() => ({ webAppAccessMode: accessMode })),
appMeta: null,
updateWebAppMeta: (appMeta: AppMeta | null) => set(() => ({ appMeta })),
userCanAccessApp: false,
@@ -78,6 +78,10 @@ const WebAppStoreProvider: FC = ({ children }) => {
updateShareCode(shareCode)
}, [shareCode, updateShareCode])
+ useEffect(() => {
+ updateWebAppAccessMode(null)
+ }, [shareCode, updateWebAppAccessMode])
+
useEffect(() => {
let cancelled = false
const syncEmbeddedUserId = async () => {
@@ -106,7 +110,7 @@ const WebAppStoreProvider: FC = ({ children }) => {
useEffect(() => {
if (accessModeResult?.accessMode)
updateWebAppAccessMode(accessModeResult.accessMode)
- }, [accessModeResult, updateWebAppAccessMode, shareCode])
+ }, [accessModeResult, updateWebAppAccessMode])
return <>{children}>
}