mirror of
https://github.com/langgenius/dify.git
synced 2026-07-21 10:38:32 +08:00
refactor(web): migrate account settings app context consumers (#38544)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
5ffd4345dc
commit
dce45ef6ae
@ -279,11 +279,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/account/(commonLayout)/delete-account/components/check-email.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/account/(commonLayout)/delete-account/components/verify-email.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
@ -3401,30 +3396,11 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/header/account-setting/members-page/edit-workspace-modal/index.tsx": {
|
||||
"jsx-a11y/no-autofocus": {
|
||||
"count": 1
|
||||
},
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/header/account-setting/members-page/invite-modal/index.tsx": {
|
||||
"jsx-a11y/no-autofocus": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/header/account-setting/members-page/transfer-ownership-modal/index.tsx": {
|
||||
"erasable-syntax-only/enums": {
|
||||
"count": 1
|
||||
},
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/header/account-setting/members-page/transfer-ownership-modal/member-selector.tsx": {
|
||||
"jsx-a11y/click-events-have-key-events": {
|
||||
"count": 1
|
||||
@ -3566,14 +3542,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/header/account-setting/model-provider-page/provider-added-card/model-list.tsx": {
|
||||
"jsx-a11y/click-events-have-key-events": {
|
||||
"count": 1
|
||||
},
|
||||
"jsx-a11y/no-static-element-interactions": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-configs.tsx": {
|
||||
"jsx-a11y/click-events-have-key-events": {
|
||||
"count": 2
|
||||
|
||||
@ -6,11 +6,32 @@ import AccountDropdown from '@/app/components/header/account-dropdown'
|
||||
import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants'
|
||||
|
||||
const {
|
||||
mockAppContextState,
|
||||
mockPush,
|
||||
mockLogout,
|
||||
mockResetUser,
|
||||
mockSetShowAccountSettingModal,
|
||||
} = vi.hoisted(() => ({
|
||||
mockAppContextState: {
|
||||
userProfile: {
|
||||
id: 'user-1',
|
||||
name: 'Ada Lovelace',
|
||||
email: 'ada@example.com',
|
||||
avatar: '',
|
||||
avatar_url: '',
|
||||
is_password_set: true,
|
||||
},
|
||||
langGeniusVersionInfo: {
|
||||
current_env: 'CLOUD',
|
||||
current_version: '1.0.0',
|
||||
latest_version: '1.1.0',
|
||||
release_date: '',
|
||||
release_notes: 'https://example.com/releases/1.1.0',
|
||||
version: '1.0.0',
|
||||
can_auto_update: false,
|
||||
},
|
||||
isCurrentWorkspaceOwner: false,
|
||||
},
|
||||
mockPush: vi.fn(),
|
||||
mockLogout: vi.fn(),
|
||||
mockResetUser: vi.fn(),
|
||||
@ -28,21 +49,19 @@ vi.mock('react-i18next', () => ({
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useAppContext: () => ({
|
||||
userProfile: {
|
||||
name: 'Ada Lovelace',
|
||||
email: 'ada@example.com',
|
||||
avatar_url: '',
|
||||
},
|
||||
langGeniusVersionInfo: {
|
||||
current_version: '1.0.0',
|
||||
latest_version: '1.1.0',
|
||||
release_notes: 'https://example.com/releases/1.1.0',
|
||||
},
|
||||
isCurrentWorkspaceOwner: false,
|
||||
}),
|
||||
useAppContext: () => mockAppContextState,
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => mockAppContextState)
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('@/context/provider-context', () => ({
|
||||
useProviderContext: () => ({
|
||||
isEducationAccount: false,
|
||||
@ -62,7 +81,8 @@ vi.mock('@/context/i18n', () => ({
|
||||
useDocLink: () => (path: string) => `https://docs.example.com${path}`,
|
||||
}))
|
||||
|
||||
vi.mock('@/service/use-common', () => ({
|
||||
vi.mock('@/service/use-common', async importOriginal => ({
|
||||
...await importOriginal<typeof import('@/service/use-common')>(),
|
||||
useLogout: () => ({
|
||||
mutateAsync: mockLogout,
|
||||
}),
|
||||
|
||||
@ -13,6 +13,7 @@ export type AppContextStateMockState = {
|
||||
} | null
|
||||
currentWorkspace?: {
|
||||
id?: string
|
||||
name?: string
|
||||
} | null
|
||||
isCurrentWorkspaceManager?: boolean
|
||||
isCurrentWorkspaceOwner?: boolean
|
||||
@ -22,6 +23,8 @@ export type AppContextStateMockState = {
|
||||
isLoadingWorkspacePermissionKeys?: boolean
|
||||
workspacePermissionKeys?: string[]
|
||||
langGeniusVersionInfo?: LangGeniusVersionResponse
|
||||
refreshUserProfile?: () => void
|
||||
refreshCurrentWorkspace?: () => void
|
||||
}
|
||||
|
||||
type AppContextStateAtomKind
|
||||
@ -32,11 +35,14 @@ type AppContextStateAtomKind
|
||||
| 'currentWorkspaceId'
|
||||
| 'workspaceRoleFlags'
|
||||
| 'isCurrentWorkspaceManager'
|
||||
| 'isCurrentWorkspaceOwner'
|
||||
| 'currentWorkspaceLoading'
|
||||
| 'workspacePermissionKeys'
|
||||
| 'workspacePermissionKeysLoading'
|
||||
| 'langGeniusVersionInfo'
|
||||
| 'langGeniusCurrentVersion'
|
||||
| 'refreshUserProfile'
|
||||
| 'refreshCurrentWorkspace'
|
||||
|
||||
type AppContextStateMockAtom = {
|
||||
[APP_CONTEXT_STATE_ATOM_KIND]: AppContextStateAtomKind
|
||||
@ -57,6 +63,7 @@ const defaultUserProfile = {
|
||||
|
||||
const defaultCurrentWorkspace = {
|
||||
id: 'workspace-1',
|
||||
name: 'Workspace',
|
||||
}
|
||||
|
||||
const defaultLangGeniusVersionInfo = {
|
||||
@ -109,11 +116,14 @@ export const createAppContextStateAtomMock = async (
|
||||
currentWorkspaceIdAtom: createMockAtom('currentWorkspaceId'),
|
||||
workspaceRoleFlagsAtom: createMockAtom('workspaceRoleFlags'),
|
||||
isCurrentWorkspaceManagerAtom: createMockAtom('isCurrentWorkspaceManager'),
|
||||
isCurrentWorkspaceOwnerAtom: createMockAtom('isCurrentWorkspaceOwner'),
|
||||
currentWorkspaceLoadingAtom: createMockAtom('currentWorkspaceLoading'),
|
||||
workspacePermissionKeysAtom: createMockAtom('workspacePermissionKeys'),
|
||||
workspacePermissionKeysLoadingAtom: createMockAtom('workspacePermissionKeysLoading'),
|
||||
langGeniusVersionInfoAtom: createMockAtom('langGeniusVersionInfo'),
|
||||
langGeniusCurrentVersionAtom: createMockAtom('langGeniusCurrentVersion'),
|
||||
refreshUserProfileAtom: createMockAtom('refreshUserProfile'),
|
||||
refreshCurrentWorkspaceAtom: createMockAtom('refreshCurrentWorkspace'),
|
||||
}
|
||||
}
|
||||
|
||||
@ -162,6 +172,9 @@ export const createAppContextStateJotaiMock = async (
|
||||
if (atom[APP_CONTEXT_STATE_ATOM_KIND] === 'isCurrentWorkspaceManager')
|
||||
return state.isCurrentWorkspaceManager ?? false
|
||||
|
||||
if (atom[APP_CONTEXT_STATE_ATOM_KIND] === 'isCurrentWorkspaceOwner')
|
||||
return state.isCurrentWorkspaceOwner ?? false
|
||||
|
||||
if (atom[APP_CONTEXT_STATE_ATOM_KIND] === 'currentWorkspaceLoading')
|
||||
return state.isLoadingCurrentWorkspace ?? false
|
||||
|
||||
@ -179,5 +192,22 @@ export const createAppContextStateJotaiMock = async (
|
||||
|
||||
throw new Error(`Unsupported app context state atom: ${atom[APP_CONTEXT_STATE_ATOM_KIND]}`)
|
||||
},
|
||||
useSetAtom: (atom: unknown) => {
|
||||
if (!isAppContextStateMockAtom(atom))
|
||||
return actual.useSetAtom(atom as Parameters<typeof actual.useSetAtom>[0])
|
||||
|
||||
if (!appContextStateMockRegistry)
|
||||
throw new Error('App context state atom mock is not initialized')
|
||||
|
||||
const state = appContextStateMockRegistry.getState()
|
||||
|
||||
if (atom[APP_CONTEXT_STATE_ATOM_KIND] === 'refreshUserProfile')
|
||||
return state.refreshUserProfile ?? (() => {})
|
||||
|
||||
if (atom[APP_CONTEXT_STATE_ATOM_KIND] === 'refreshCurrentWorkspace')
|
||||
return state.refreshCurrentWorkspace ?? (() => {})
|
||||
|
||||
throw new Error(`Unsupported app context state write atom: ${atom[APP_CONTEXT_STATE_ATOM_KIND]}`)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
'use client'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { Input } from '@langgenius/dify-ui/input'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Input from '@/app/components/base/input'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { userProfileEmailAtom } from '@/context/app-context-state'
|
||||
import Link from '@/next/link'
|
||||
import { useSendDeleteAccountEmail } from '../state'
|
||||
|
||||
@ -14,7 +15,7 @@ type DeleteAccountProps = {
|
||||
|
||||
export default function CheckEmail(props: DeleteAccountProps) {
|
||||
const { t } = useTranslation()
|
||||
const { userProfile } = useAppContext()
|
||||
const userProfileEmail = useAtomValue(userProfileEmailAtom)
|
||||
const [userInputEmail, setUserInputEmail] = useState('')
|
||||
|
||||
const { isPending: isSendingEmail, mutateAsync: getDeleteEmailVerifyCode } = useSendDeleteAccountEmail()
|
||||
@ -45,7 +46,7 @@ export default function CheckEmail(props: DeleteAccountProps) {
|
||||
}}
|
||||
/>
|
||||
<div className="mt-3 flex w-full flex-col gap-2">
|
||||
<Button className="w-full" disabled={userInputEmail !== userProfile.email || isSendingEmail} loading={isSendingEmail} variant="primary" onClick={handleConfirm}>{t('account.sendVerificationButton', { ns: 'common' })}</Button>
|
||||
<Button className="w-full" disabled={userInputEmail !== userProfileEmail || isSendingEmail} loading={isSendingEmail} variant="primary" onClick={handleConfirm}>{t('account.sendVerificationButton', { ns: 'common' })}</Button>
|
||||
<Button className="w-full" onClick={props.onCancel}>{t('operation.cancel', { ns: 'common' })}</Button>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@ -3,9 +3,10 @@ import { Button } from '@langgenius/dify-ui/button'
|
||||
import { Dialog, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||
import { Textarea } from '@langgenius/dify-ui/textarea'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { userProfileEmailAtom } from '@/context/app-context-state'
|
||||
import { useRouter } from '@/next/navigation'
|
||||
import { useLogout } from '@/service/use-common'
|
||||
import { useDeleteAccountFeedback } from '../state'
|
||||
@ -17,7 +18,7 @@ type DeleteAccountProps = {
|
||||
|
||||
export default function FeedBack(props: DeleteAccountProps) {
|
||||
const { t } = useTranslation()
|
||||
const { userProfile } = useAppContext()
|
||||
const userProfileEmail = useAtomValue(userProfileEmailAtom)
|
||||
const router = useRouter()
|
||||
const [userFeedback, setUserFeedback] = useState('')
|
||||
const { isPending, mutateAsync: sendFeedback } = useDeleteAccountFeedback()
|
||||
@ -35,12 +36,12 @@ export default function FeedBack(props: DeleteAccountProps) {
|
||||
|
||||
const handleSubmit = useCallback(async () => {
|
||||
try {
|
||||
await sendFeedback({ feedback: userFeedback, email: userProfile.email })
|
||||
await sendFeedback({ feedback: userFeedback, email: userProfileEmail })
|
||||
props.onConfirm()
|
||||
await handleSuccess()
|
||||
}
|
||||
catch (error) { console.error(error) }
|
||||
}, [handleSuccess, userFeedback, sendFeedback, userProfile, props])
|
||||
}, [handleSuccess, userFeedback, sendFeedback, userProfileEmail, props])
|
||||
|
||||
const handleSkip = useCallback(() => {
|
||||
props.onCancel()
|
||||
|
||||
@ -43,6 +43,9 @@ vi.mock('@/app/components/base/theme-switcher', () => ({
|
||||
const { mockSetTheme } = vi.hoisted(() => ({
|
||||
mockSetTheme: vi.fn(),
|
||||
}))
|
||||
const mockAppContextState = vi.hoisted(() => ({
|
||||
current: undefined as AppContextValue | undefined,
|
||||
}))
|
||||
|
||||
vi.mock('next-themes', () => ({
|
||||
useTheme: () => ({
|
||||
@ -55,6 +58,16 @@ vi.mock('@/context/app-context', () => ({
|
||||
useAppContext: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current ?? {})
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('@/context/provider-context', () => ({
|
||||
useProviderContext: vi.fn(),
|
||||
}))
|
||||
@ -63,7 +76,8 @@ vi.mock('@/context/modal-context', () => ({
|
||||
useModalContext: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/service/use-common', () => ({
|
||||
vi.mock('@/service/use-common', async importOriginal => ({
|
||||
...await importOriginal<typeof import('@/service/use-common')>(),
|
||||
useLogout: vi.fn(),
|
||||
}))
|
||||
|
||||
@ -150,6 +164,11 @@ const baseAppContextValue: AppContextValue = {
|
||||
workspacePermissionKeys: [],
|
||||
}
|
||||
|
||||
const setAppContextValue = (value: AppContextValue) => {
|
||||
mockAppContextState.current = value
|
||||
vi.mocked(useAppContext).mockReturnValue(value)
|
||||
}
|
||||
|
||||
describe('AccountDropdown', () => {
|
||||
const mockPush = vi.fn()
|
||||
const mockLogout = vi.fn()
|
||||
@ -170,7 +189,7 @@ describe('AccountDropdown', () => {
|
||||
mockConfig.IS_CLOUD_EDITION = false
|
||||
mockEnv.env.NEXT_PUBLIC_SITE_ABOUT = 'show'
|
||||
|
||||
vi.mocked(useAppContext).mockReturnValue(baseAppContextValue)
|
||||
setAppContextValue(baseAppContextValue)
|
||||
vi.mocked(useProviderContext).mockReturnValue({
|
||||
isEducationAccount: false,
|
||||
plan: { type: Plan.sandbox },
|
||||
@ -267,7 +286,7 @@ describe('AccountDropdown', () => {
|
||||
it('should show Compliance in Cloud Edition for workspace owner', () => {
|
||||
// Arrange
|
||||
mockConfig.IS_CLOUD_EDITION = true
|
||||
vi.mocked(useAppContext).mockReturnValue({
|
||||
setAppContextValue({
|
||||
...baseAppContextValue,
|
||||
userProfile: { ...baseAppContextValue.userProfile, name: 'User' },
|
||||
isCurrentWorkspaceOwner: true,
|
||||
@ -286,7 +305,7 @@ describe('AccountDropdown', () => {
|
||||
it('should hide Compliance in Cloud Edition when user is not workspace owner', () => {
|
||||
// Arrange
|
||||
mockConfig.IS_CLOUD_EDITION = true
|
||||
vi.mocked(useAppContext).mockReturnValue({
|
||||
setAppContextValue({
|
||||
...baseAppContextValue,
|
||||
isCurrentWorkspaceOwner: false,
|
||||
})
|
||||
@ -382,7 +401,7 @@ describe('AccountDropdown', () => {
|
||||
describe('Version Indicators', () => {
|
||||
it('should show orange indicator when version is not latest', () => {
|
||||
// Arrange
|
||||
vi.mocked(useAppContext).mockReturnValue({
|
||||
setAppContextValue({
|
||||
...baseAppContextValue,
|
||||
userProfile: { ...baseAppContextValue.userProfile, name: 'User' },
|
||||
langGeniusVersionInfo: {
|
||||
@ -402,7 +421,7 @@ describe('AccountDropdown', () => {
|
||||
|
||||
it('should show green indicator when version is latest', () => {
|
||||
// Arrange
|
||||
vi.mocked(useAppContext).mockReturnValue({
|
||||
setAppContextValue({
|
||||
...baseAppContextValue,
|
||||
userProfile: { ...baseAppContextValue.userProfile, name: 'User' },
|
||||
langGeniusVersionInfo: {
|
||||
|
||||
@ -10,12 +10,13 @@ import {
|
||||
} from '@langgenius/dify-ui/dropdown-menu'
|
||||
import { StatusDot } from '@langgenius/dify-ui/status-dot'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import PremiumBadge from '@/app/components/base/premium-badge'
|
||||
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 { isCurrentWorkspaceOwnerAtom, langGeniusVersionInfoAtom, userProfileAtom } from '@/context/app-context-state'
|
||||
import { useDocLink } from '@/context/i18n'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
@ -119,7 +120,9 @@ export function DefaultMenuContent({
|
||||
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
||||
const { t } = useTranslation()
|
||||
const docLink = useDocLink()
|
||||
const { userProfile, langGeniusVersionInfo, isCurrentWorkspaceOwner } = useAppContext()
|
||||
const userProfile = useAtomValue(userProfileAtom)
|
||||
const langGeniusVersionInfo = useAtomValue(langGeniusVersionInfoAtom)
|
||||
const isCurrentWorkspaceOwner = useAtomValue(isCurrentWorkspaceOwnerAtom)
|
||||
const { isEducationAccount } = useProviderContext()
|
||||
const { setShowAccountSettingModal } = useModalContext()
|
||||
|
||||
|
||||
@ -8,11 +8,12 @@ import {
|
||||
DropdownMenuContent,
|
||||
DropdownMenuTrigger,
|
||||
} from '@langgenius/dify-ui/dropdown-menu'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { resetUser } from '@/app/components/base/amplitude/utils'
|
||||
import { useSetEducationExpiredHasNoticed, useSetEducationReverifyHasNoticed, useSetEducationReverifyPrevExpireAt } from '@/app/education-apply/storage'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { langGeniusVersionInfoAtom, userProfileAtom } from '@/context/app-context-state'
|
||||
import { useRouter } from '@/next/navigation'
|
||||
import { useLogout } from '@/service/use-common'
|
||||
import AccountAbout from '../account-about'
|
||||
@ -37,7 +38,8 @@ export default function AppSelector({
|
||||
const [aboutVisible, setAboutVisible] = useState(false)
|
||||
const [isAccountMenuOpen, setIsAccountMenuOpen] = useState(false)
|
||||
const { t } = useTranslation()
|
||||
const { userProfile, langGeniusVersionInfo } = useAppContext()
|
||||
const userProfile = useAtomValue(userProfileAtom)
|
||||
const langGeniusVersionInfo = useAtomValue(langGeniusVersionInfoAtom)
|
||||
const clearEducationReverifyPrevExpireAt = useSetEducationReverifyPrevExpireAt()
|
||||
const clearEducationReverifyHasNoticed = useSetEducationReverifyHasNoticed()
|
||||
const clearEducationExpiredHasNoticed = useSetEducationExpiredHasNoticed()
|
||||
|
||||
@ -16,11 +16,12 @@ import {
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuSubTrigger,
|
||||
} from '@langgenius/dify-ui/dropdown-menu'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import PremiumBadge from '@/app/components/base/premium-badge'
|
||||
import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { userProfileAtom } from '@/context/app-context-state'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import Link from '@/next/link'
|
||||
@ -87,7 +88,7 @@ export function MainNavMenuContent({
|
||||
onLogout,
|
||||
}: MainNavMenuContentProps) {
|
||||
const { t } = useTranslation()
|
||||
const { userProfile } = useAppContext()
|
||||
const userProfile = useAtomValue(userProfileAtom)
|
||||
const { isEducationAccount } = useProviderContext()
|
||||
const { setShowAccountSettingModal } = useModalContext()
|
||||
|
||||
|
||||
@ -15,6 +15,18 @@ vi.mock('@/context/app-context', () => ({
|
||||
})),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
workspacePermissionKeys: mocks.workspacePermissionKeys,
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
const rule: AccessPolicyWithBindings = {
|
||||
policy: {
|
||||
id: 'app-rule-1',
|
||||
|
||||
@ -3,11 +3,12 @@
|
||||
import type { AccessPolicyWithBindings } from '@/models/access-control'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { memo, useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
||||
import { workspacePermissionKeysAtom } from '@/context/app-context-state'
|
||||
import { hasPermission } from '@/utils/permission'
|
||||
import AccessRuleRow from './access-rule-row'
|
||||
|
||||
@ -46,7 +47,7 @@ const AccessRuleSection = ({
|
||||
const [expanded, setExpanded] = useState(defaultExpanded)
|
||||
const listRef = useRef<HTMLDivElement>(null)
|
||||
const anchorRef = useRef<HTMLDivElement>(null)
|
||||
const workspacePermissionKeys = useAppContextWithSelector(s => s.workspacePermissionKeys)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const canManage = hasPermission(workspacePermissionKeys, 'workspace.role.manage')
|
||||
const ruleCount = totalCount ?? rules.length
|
||||
|
||||
|
||||
@ -24,6 +24,18 @@ vi.mock('@/context/app-context', () => ({
|
||||
})),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
workspacePermissionKeys: mockWorkspacePermissionKeys.current,
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('@/service/client', () => ({
|
||||
consoleQuery: {
|
||||
apiBasedExtension: {
|
||||
|
||||
@ -2,11 +2,12 @@ import type { ApiBasedExtensionResponse } from '@dify/contracts/api/console/api-
|
||||
import type { ReactNode } from 'react'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { SearchInput } from '@/app/components/base/search-input'
|
||||
import { SkeletonContainer, SkeletonRectangle, SkeletonRow } from '@/app/components/base/skeleton'
|
||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
||||
import { workspacePermissionKeysAtom } from '@/context/app-context-state'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
import { hasPermission } from '@/utils/permission'
|
||||
import { Empty } from './empty'
|
||||
@ -51,7 +52,7 @@ export function ApiBasedExtensionPage({
|
||||
layout,
|
||||
}: ApiBasedExtensionPageProps = {}) {
|
||||
const { t } = useTranslation()
|
||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const canManage = hasPermission(workspacePermissionKeys, 'api_extension.manage')
|
||||
const { data: apiBasedExtensions = [], isPending: isLoading } = useQuery(consoleQuery.apiBasedExtension.get.queryOptions())
|
||||
const [dialogState, setDialogState] = useState<ApiBasedExtensionDialogState>(null)
|
||||
|
||||
@ -4,6 +4,7 @@ import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { ScrollArea } from '@langgenius/dify-ui/scroll-area'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useCallback, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import BillingPage from '@/app/components/billing/billing-page'
|
||||
@ -12,7 +13,7 @@ import {
|
||||
ACCOUNT_SETTING_TAB,
|
||||
} from '@/app/components/header/account-setting/constants'
|
||||
import MenuDialog from '@/app/components/header/account-setting/menu-dialog'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { workspacePermissionKeysAtom } from '@/context/app-context-state'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
@ -54,7 +55,7 @@ export default function AccountSetting({
|
||||
const { t } = useTranslation()
|
||||
const { enableBilling, enableReplaceWebAppLogo } = useProviderContext()
|
||||
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
||||
const { workspacePermissionKeys } = useAppContext()
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const isRbacEnabled = systemFeatures.rbac_enabled
|
||||
const canManageWorkspaceRoles = isRbacEnabled && hasPermission(workspacePermissionKeys, 'workspace.role.manage')
|
||||
const canViewBilling = enableBilling && hasPermission(workspacePermissionKeys, BillingPermission.View)
|
||||
|
||||
@ -14,7 +14,19 @@ import { useUpdateRolesOfMember } from '@/service/access-control/use-member-role
|
||||
import { useMembers } from '@/service/use-common'
|
||||
import MembersPage from '../index'
|
||||
|
||||
const mockAppContextState = vi.hoisted(() => ({
|
||||
current: {} as Partial<AppContextValue>,
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context')
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current)
|
||||
})
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
vi.mock('@/context/provider-context')
|
||||
vi.mock('@/hooks/use-format-time-from-now')
|
||||
vi.mock('@/service/access-control/use-member-roles')
|
||||
@ -41,6 +53,11 @@ const createRole = (overrides: Partial<Role>): Role => ({
|
||||
...overrides,
|
||||
})
|
||||
|
||||
const setAppContextValue = (value: AppContextValue) => {
|
||||
mockAppContextState.current = value
|
||||
vi.mocked(useAppContext).mockReturnValue(value)
|
||||
}
|
||||
|
||||
vi.mock('../edit-workspace-modal', () => ({
|
||||
default: ({ onCancel }: { onCancel: () => void }) => (
|
||||
<div>
|
||||
@ -181,7 +198,7 @@ describe('MembersPage', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
|
||||
vi.mocked(useAppContext).mockReturnValue({
|
||||
setAppContextValue({
|
||||
userProfile: { email: 'owner@example.com' },
|
||||
currentWorkspace: { name: 'Test Workspace', role: 'owner' } as ICurrentWorkspace,
|
||||
isCurrentWorkspaceOwner: true,
|
||||
@ -289,7 +306,7 @@ describe('MembersPage', () => {
|
||||
})
|
||||
|
||||
it('should hide manager controls for non-owner non-manager users', () => {
|
||||
vi.mocked(useAppContext).mockReturnValue({
|
||||
setAppContextValue({
|
||||
userProfile: { email: 'admin@example.com' },
|
||||
currentWorkspace: { name: 'Test Workspace', role: 'admin' } as ICurrentWorkspace,
|
||||
isCurrentWorkspaceOwner: false,
|
||||
@ -390,7 +407,7 @@ describe('MembersPage', () => {
|
||||
})
|
||||
|
||||
it('should show invite button when user is manager but not owner', () => {
|
||||
vi.mocked(useAppContext).mockReturnValue({
|
||||
setAppContextValue({
|
||||
userProfile: { email: 'admin@example.com' },
|
||||
currentWorkspace: { name: 'Test Workspace', role: 'admin' } as ICurrentWorkspace,
|
||||
isCurrentWorkspaceOwner: false,
|
||||
@ -405,7 +422,7 @@ describe('MembersPage', () => {
|
||||
})
|
||||
|
||||
it('should allow admins to operate other non-owner members only', () => {
|
||||
vi.mocked(useAppContext).mockReturnValue({
|
||||
setAppContextValue({
|
||||
userProfile: { email: 'admin@example.com' },
|
||||
currentWorkspace: { name: 'Test Workspace', role: 'admin' } as ICurrentWorkspace,
|
||||
isCurrentWorkspaceOwner: false,
|
||||
@ -483,7 +500,7 @@ describe('MembersPage', () => {
|
||||
})
|
||||
|
||||
it('should render role badge names from account roles', () => {
|
||||
vi.mocked(useAppContext).mockReturnValue({
|
||||
setAppContextValue({
|
||||
userProfile: { email: 'admin@example.com' },
|
||||
currentWorkspace: { name: 'Test Workspace', role: 'admin' } as ICurrentWorkspace,
|
||||
isCurrentWorkspaceOwner: false,
|
||||
@ -551,7 +568,7 @@ describe('MembersPage', () => {
|
||||
|
||||
it('should not allow assigning roles from member details when target is current user', async () => {
|
||||
const user = userEvent.setup()
|
||||
vi.mocked(useAppContext).mockReturnValue({
|
||||
setAppContextValue({
|
||||
userProfile: { email: 'admin@example.com' },
|
||||
currentWorkspace: { name: 'Test Workspace', role: 'admin' } as ICurrentWorkspace,
|
||||
isCurrentWorkspaceOwner: false,
|
||||
|
||||
@ -8,6 +8,16 @@ import { useWorkspacePermissions } from '@/service/use-workspace'
|
||||
import InviteButton from '../invite-button'
|
||||
|
||||
vi.mock('@/context/app-context')
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
currentWorkspace: { id: 'workspace-id' },
|
||||
}))
|
||||
})
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
vi.mock('@/service/use-workspace')
|
||||
|
||||
describe('InviteButton', () => {
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import type { AppContextValue } from '@/context/app-context'
|
||||
import { render } from '@testing-library/react'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import EditWorkspaceModal from '../index'
|
||||
@ -10,6 +11,9 @@ type DialogProps = {
|
||||
}
|
||||
|
||||
let latestOnOpenChange: DialogProps['onOpenChange']
|
||||
const mockAppContextState = vi.hoisted(() => ({
|
||||
current: {} as Partial<AppContextValue>,
|
||||
}))
|
||||
|
||||
vi.mock('@langgenius/dify-ui/dialog', () => ({
|
||||
Dialog: ({ children, onOpenChange }: DialogProps) => {
|
||||
@ -29,14 +33,26 @@ vi.mock('@/context/app-context', () => ({
|
||||
useAppContext: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current)
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
describe('EditWorkspaceModal dialog lifecycle', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
latestOnOpenChange = undefined
|
||||
vi.mocked(useAppContext).mockReturnValue({
|
||||
const appContextValue = {
|
||||
currentWorkspace: { name: 'Test Workspace' },
|
||||
isCurrentWorkspaceOwner: true,
|
||||
} as never)
|
||||
} as never
|
||||
mockAppContextState.current = appContextValue
|
||||
vi.mocked(useAppContext).mockReturnValue(appContextValue)
|
||||
})
|
||||
|
||||
it('should only call onCancel when the dialog requests closing', () => {
|
||||
|
||||
@ -10,10 +10,21 @@ import EditWorkspaceModal from '../index'
|
||||
const toastMocks = vi.hoisted(() => ({
|
||||
mockNotify: vi.fn(),
|
||||
}))
|
||||
const mockAppContextState = vi.hoisted(() => ({
|
||||
current: {} as Partial<AppContextValue>,
|
||||
}))
|
||||
|
||||
const getSaveButton = () => screen.getByRole('button', { name: /operation\.(save|saving)/i })
|
||||
|
||||
vi.mock('@/context/app-context')
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current)
|
||||
})
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
vi.mock('@/service/common')
|
||||
vi.mock('@langgenius/dify-ui/toast', () => ({
|
||||
default: {
|
||||
@ -34,10 +45,12 @@ describe('EditWorkspaceModal', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
|
||||
vi.mocked(useAppContext).mockReturnValue({
|
||||
const appContextValue = {
|
||||
currentWorkspace: { name: 'Test Workspace' } as ICurrentWorkspace,
|
||||
isCurrentWorkspaceOwner: true,
|
||||
} as unknown as AppContextValue)
|
||||
} as unknown as AppContextValue
|
||||
mockAppContextState.current = appContextValue
|
||||
vi.mocked(useAppContext).mockReturnValue(appContextValue)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@ -2,11 +2,12 @@
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||
import { Input } from '@langgenius/dify-ui/input'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useId, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Input from '@/app/components/base/input'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { currentWorkspaceAtom, isCurrentWorkspaceOwnerAtom } from '@/context/app-context-state'
|
||||
import { updateWorkspaceInfo } from '@/service/common'
|
||||
|
||||
type IEditWorkspaceModalProps = {
|
||||
@ -14,7 +15,8 @@ type IEditWorkspaceModalProps = {
|
||||
}
|
||||
const EditWorkspaceModal = ({ onCancel }: IEditWorkspaceModalProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { currentWorkspace, isCurrentWorkspaceOwner } = useAppContext()
|
||||
const currentWorkspace = useAtomValue(currentWorkspaceAtom)
|
||||
const isCurrentWorkspaceOwner = useAtomValue(isCurrentWorkspaceOwnerAtom)
|
||||
const [name, setName] = useState<string>(currentWorkspace.name)
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
const inputId = useId()
|
||||
@ -82,7 +84,6 @@ const EditWorkspaceModal = ({ onCancel }: IEditWorkspaceModalProps) => {
|
||||
</label>
|
||||
<Input
|
||||
id={inputId}
|
||||
autoFocus
|
||||
value={name}
|
||||
placeholder={t('account.workspaceNamePlaceholder', { ns: 'common' })}
|
||||
onChange={(e) => {
|
||||
|
||||
@ -4,12 +4,13 @@ import type { InvitationResult, Member } from '@/models/common'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
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 { currentWorkspaceAtom, isCurrentWorkspaceOwnerAtom, userProfileEmailAtom, workspacePermissionKeysAtom } from '@/context/app-context-state'
|
||||
import { useLocale } from '@/context/i18n'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
||||
@ -30,7 +31,10 @@ const MembersPage = () => {
|
||||
const locale = useLocale()
|
||||
const language = getAccessControlTemplateLanguage(locale)
|
||||
|
||||
const { userProfile, currentWorkspace, isCurrentWorkspaceOwner, workspacePermissionKeys } = useAppContext()
|
||||
const userProfileEmail = useAtomValue(userProfileEmailAtom)
|
||||
const currentWorkspace = useAtomValue(currentWorkspaceAtom)
|
||||
const isCurrentWorkspaceOwner = useAtomValue(isCurrentWorkspaceOwnerAtom)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const { data, refetch } = useMembers(language)
|
||||
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
||||
const [inviteModalVisible, setInviteModalVisible] = useState(false)
|
||||
@ -162,7 +166,7 @@ const MembersPage = () => {
|
||||
key={account.id}
|
||||
member={account}
|
||||
roles={account.roles}
|
||||
isCurrentUser={userProfile.email === account.email}
|
||||
isCurrentUser={userProfileEmail === account.email}
|
||||
canManage={canManageMembers}
|
||||
canTransferOwnership={isCurrentWorkspaceOwner && isAllowTransferWorkspace}
|
||||
allowMultipleRoles={systemFeatures.rbac_enabled}
|
||||
@ -213,7 +217,7 @@ const MembersPage = () => {
|
||||
canAssignRoles={
|
||||
canManageMembers
|
||||
&& detailsMember.role !== 'owner'
|
||||
&& userProfile.email !== detailsMember.email
|
||||
&& userProfileEmail !== detailsMember.email
|
||||
}
|
||||
allowMultipleRoles={systemFeatures.rbac_enabled}
|
||||
onClose={handleCloseDetails}
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { RiUserAddLine } from '@remixicon/react'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { currentWorkspaceIdAtom } from '@/context/app-context-state'
|
||||
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
||||
import { useWorkspacePermissions } from '@/service/use-workspace'
|
||||
|
||||
@ -14,9 +15,9 @@ type InviteButtonProps = {
|
||||
|
||||
const InviteButton = (props: InviteButtonProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { currentWorkspace } = useAppContext()
|
||||
const currentWorkspaceId = useAtomValue(currentWorkspaceIdAtom)
|
||||
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
||||
const { data: workspacePermissions, isFetching: isFetchingWorkspacePermissions } = useWorkspacePermissions(currentWorkspace!.id, systemFeatures.branding.enabled)
|
||||
const { data: workspacePermissions, isFetching: isFetchingWorkspacePermissions } = useWorkspacePermissions(currentWorkspaceId, systemFeatures.branding.enabled)
|
||||
if (systemFeatures.branding.enabled) {
|
||||
if (isFetchingWorkspacePermissions) {
|
||||
return <Loading />
|
||||
|
||||
@ -11,8 +11,19 @@ import TransferOwnershipModal from '../index'
|
||||
const toastMocks = vi.hoisted(() => ({
|
||||
mockNotify: vi.fn(),
|
||||
}))
|
||||
const mockAppContextState = vi.hoisted(() => ({
|
||||
current: {} as Partial<AppContextValue>,
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context')
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current)
|
||||
})
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
vi.mock('@/service/common')
|
||||
vi.mock('@/service/use-common')
|
||||
vi.mock('@langgenius/dify-ui/toast', () => ({
|
||||
@ -40,10 +51,12 @@ describe('TransferOwnershipModal', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
|
||||
vi.mocked(useAppContext).mockReturnValue({
|
||||
const appContextValue = {
|
||||
currentWorkspace: { name: 'Test Workspace' } as ICurrentWorkspace,
|
||||
userProfile: { email: 'owner@example.com', id: 'owner-id' },
|
||||
} as unknown as AppContextValue)
|
||||
} as unknown as AppContextValue
|
||||
mockAppContextState.current = appContextValue
|
||||
vi.mocked(useAppContext).mockReturnValue(appContextValue)
|
||||
|
||||
vi.mocked(useMembers).mockReturnValue({
|
||||
data: { accounts: [] },
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||
import { Input } from '@langgenius/dify-ui/input'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import Input from '@/app/components/base/input'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { currentWorkspaceAtom, userProfileAtom } from '@/context/app-context-state'
|
||||
import { ownershipTransfer, sendOwnerEmail, verifyOwnerEmail } from '@/service/common'
|
||||
import MemberSelector from './member-selector'
|
||||
|
||||
@ -13,15 +14,22 @@ type Props = Readonly<{
|
||||
show: boolean
|
||||
onClose: () => void
|
||||
}>
|
||||
enum STEP {
|
||||
start = 'start',
|
||||
verify = 'verify',
|
||||
transfer = 'transfer',
|
||||
const STEP = {
|
||||
start: 'start',
|
||||
verify: 'verify',
|
||||
transfer: 'transfer',
|
||||
}
|
||||
type Step = typeof STEP[keyof typeof STEP]
|
||||
|
||||
const getErrorMessage = (error: unknown) => {
|
||||
return error instanceof Error ? error.message : ''
|
||||
}
|
||||
|
||||
const TransferOwnershipModal = ({ onClose, show }: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const { currentWorkspace, userProfile } = useAppContext()
|
||||
const [step, setStep] = useState<STEP>(STEP.start)
|
||||
const currentWorkspace = useAtomValue(currentWorkspaceAtom)
|
||||
const userProfile = useAtomValue(userProfileAtom)
|
||||
const [step, setStep] = useState<Step>(STEP.start)
|
||||
const [code, setCode] = useState<string>('')
|
||||
const [time, setTime] = useState<number>(0)
|
||||
const [stepToken, setStepToken] = useState<string>('')
|
||||
@ -71,7 +79,7 @@ const TransferOwnershipModal = ({ onClose, show }: Props) => {
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
toast.error(`Error verifying email: ${error ? (error as any).message : ''}`)
|
||||
toast.error(`Error verifying email: ${getErrorMessage(error)}`)
|
||||
}
|
||||
}
|
||||
const sendCodeToOriginEmail = async () => {
|
||||
@ -96,7 +104,7 @@ const TransferOwnershipModal = ({ onClose, show }: Props) => {
|
||||
globalThis.location.reload()
|
||||
}
|
||||
catch (error) {
|
||||
toast.error(`Error ownership transfer: ${error ? (error as any).message : ''}`)
|
||||
toast.error(`Error ownership transfer: ${getErrorMessage(error)}`)
|
||||
}
|
||||
finally {
|
||||
setIsTransfer(false)
|
||||
|
||||
@ -22,6 +22,18 @@ vi.mock('@/context/app-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
workspacePermissionKeys: mockWorkspacePermissionKeys,
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('@/context/provider-context', () => ({
|
||||
useProviderContext: () => ({
|
||||
plan: { type: mockPlanType },
|
||||
|
||||
@ -11,6 +11,18 @@ vi.mock('@/context/app-context', () => ({
|
||||
selector({ workspacePermissionKeys: mockWorkspacePermissionKeys }),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
workspacePermissionKeys: mockWorkspacePermissionKeys,
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('@/context/modal-context', () => ({
|
||||
useModalContextSelector: (selector: (state: { setShowModelLoadBalancingModal: typeof mockSetShowModelLoadBalancingModal }) => unknown) =>
|
||||
selector({ setShowModelLoadBalancingModal: mockSetShowModelLoadBalancingModal }),
|
||||
|
||||
@ -7,6 +7,7 @@ import type { PluginDetail } from '@/app/components/plugins/types'
|
||||
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { memo, useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
@ -14,7 +15,7 @@ import {
|
||||
ManageCustomModelCredentials,
|
||||
} from '@/app/components/header/account-setting/model-provider-page/model-auth'
|
||||
import { IS_CE_EDITION } from '@/config'
|
||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
||||
import { workspacePermissionKeysAtom } from '@/context/app-context-state'
|
||||
import { useProviderContextSelector } from '@/context/provider-context'
|
||||
import { useCredentialPermissions } from '@/hooks/use-credential-permissions'
|
||||
import { renderI18nObject } from '@/i18n-config'
|
||||
@ -68,7 +69,7 @@ const ProviderAddedCard: FC<ProviderAddedCardProps> = ({
|
||||
}))
|
||||
const hasModelList = hasFetchedModelList && !!modelList.length
|
||||
const showCollapsedSection = !expanded || !hasFetchedModelList
|
||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const showModelProvider = systemConfig.enabled && MODEL_PROVIDER_QUOTA_GET_PAID.includes(currentProviderName as ModelProviderQuotaGetPaid) && !IS_CE_EDITION
|
||||
const canConfigureModels = hasPermission(workspacePermissionKeys, 'plugin.model_config')
|
||||
const { canUseCredential, canCreateCredential, canManageCredential } = useCredentialPermissions()
|
||||
|
||||
@ -4,12 +4,13 @@ import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/pop
|
||||
import { Switch } from '@langgenius/dify-ui/switch'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { useDebounceFn } from 'ahooks'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { memo, useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Badge from '@/app/components/base/badge'
|
||||
import { Balance } from '@/app/components/base/icons/src/vender/line/financeAndECommerce'
|
||||
import { Plan } from '@/app/components/billing/type'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { workspacePermissionKeysAtom } from '@/context/app-context-state'
|
||||
import { useProviderContext, useProviderContextSelector } from '@/context/provider-context'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
import { disableModel, enableModel } from '@/service/common'
|
||||
@ -32,7 +33,7 @@ const ModelListItem = ({ model, provider, isConfigurable, onChange, onModifyLoad
|
||||
const { t } = useTranslation()
|
||||
const { plan } = useProviderContext()
|
||||
const modelLoadBalancingEnabled = useProviderContextSelector(state => state.modelLoadBalancingEnabled)
|
||||
const { workspacePermissionKeys } = useAppContext()
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const canConfigureModels = hasPermission(workspacePermissionKeys, 'plugin.model_config')
|
||||
const queryClient = useQueryClient()
|
||||
const updateModelList = useUpdateModelList()
|
||||
|
||||
@ -4,13 +4,14 @@ import type {
|
||||
ModelItem,
|
||||
ModelProvider,
|
||||
} from '../declarations'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
AddCustomModel,
|
||||
ManageCustomModelCredentials,
|
||||
} from '@/app/components/header/account-setting/model-provider-page/model-auth'
|
||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
||||
import { workspacePermissionKeysAtom } from '@/context/app-context-state'
|
||||
import { useModalContextSelector } from '@/context/modal-context'
|
||||
import { hasPermission } from '@/utils/permission'
|
||||
import {
|
||||
@ -33,7 +34,7 @@ const ModelList: FC<ModelListProps> = ({
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const configurativeMethods = provider.configurate_methods.filter(method => method !== ConfigurationMethodEnum.fetchFromRemote)
|
||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const canConfigureModels = hasPermission(workspacePermissionKeys, 'plugin.model_config')
|
||||
const isConfigurable = configurativeMethods.includes(ConfigurationMethodEnum.customizableModel)
|
||||
const setShowModelLoadBalancingModal = useModalContextSelector(state => state.setShowModelLoadBalancingModal)
|
||||
@ -58,13 +59,14 @@ const ModelList: FC<ModelListProps> = ({
|
||||
{t('modelProvider.modelsNum', { ns: 'common', num: models.length })}
|
||||
<span className="mr-0.5 i-ri-arrow-right-s-line size-4 rotate-90" />
|
||||
</span>
|
||||
<span
|
||||
className="hidden h-6 cursor-pointer items-center rounded-lg bg-state-base-hover pr-1.5 pl-1 system-xs-medium text-text-tertiary group-hover:inline-flex"
|
||||
<button
|
||||
type="button"
|
||||
className="hidden h-6 cursor-pointer items-center rounded-lg border-none bg-state-base-hover pr-1.5 pl-1 system-xs-medium text-text-tertiary group-hover:inline-flex"
|
||||
onClick={() => onCollapse()}
|
||||
>
|
||||
{t('modelProvider.modelsNum', { ns: 'common', num: models.length })}
|
||||
<span className="mr-0.5 i-ri-arrow-right-s-line size-4 rotate-90" />
|
||||
</span>
|
||||
</button>
|
||||
</span>
|
||||
{
|
||||
isConfigurable && canConfigureModels && (
|
||||
|
||||
@ -40,6 +40,18 @@ vi.mock('@/context/app-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
workspacePermissionKeys: mockWorkspacePermissionKeys,
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('@/context/provider-context', () => ({
|
||||
useProviderContext: () => ({
|
||||
textGenerationModelList: [],
|
||||
|
||||
@ -12,10 +12,11 @@ import {
|
||||
DialogTitle,
|
||||
} from '@langgenius/dify-ui/dialog'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Infotip } from '@/app/components/base/infotip'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { workspacePermissionKeysAtom } from '@/context/app-context-state'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { updateDefaultModel } from '@/service/common'
|
||||
import { hasPermission } from '@/utils/permission'
|
||||
@ -68,7 +69,7 @@ const SystemModel: FC<SystemModelSelectorProps> = ({
|
||||
onOpenMarketplace,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { workspacePermissionKeys } = useAppContext()
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const { textGenerationModelList } = useProviderContext()
|
||||
const canManageSystemDefaultModel = hasPermission(workspacePermissionKeys, 'plugin.model_config')
|
||||
const updateModelList = useUpdateModelList()
|
||||
|
||||
@ -26,6 +26,18 @@ vi.mock('@/context/app-context', () => ({
|
||||
} as AppContextValue)),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
workspacePermissionKeys: mocks.workspacePermissionKeys,
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('@/service/access-control/use-workspace-roles', () => ({
|
||||
useCreateWorkspaceRole: vi.fn(),
|
||||
useUpdateWorkspaceRole: vi.fn(),
|
||||
|
||||
@ -4,9 +4,10 @@ import type { RoleModalMode, submitRoleData } from './role-modal'
|
||||
import type { Role } from '@/models/access-control'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
||||
import { workspacePermissionKeysAtom } from '@/context/app-context-state'
|
||||
import { useLocale } from '@/context/i18n'
|
||||
import { getAccessControlTemplateLanguage } from '@/i18n-config/language'
|
||||
import { useCreateWorkspaceRole, useUpdateWorkspaceRole } from '@/service/access-control/use-workspace-roles'
|
||||
@ -32,7 +33,7 @@ const PermissionsPage = ({ containerRef }: PermissionsPageProps) => {
|
||||
const [modalState, setModalState] = useState<ModalState>(null)
|
||||
const anchorRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const workspacePermissionKeys = useAppContextWithSelector(s => s.workspacePermissionKeys)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
|
||||
const language = useMemo(() => getAccessControlTemplateLanguage(locale), [locale])
|
||||
|
||||
|
||||
@ -36,6 +36,18 @@ vi.mock('@/context/app-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
workspacePermissionKeys: mockWorkspacePermissionKeys.value,
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('@/service/access-control/use-workspace-roles', () => ({
|
||||
useCopyWorkspaceRole: () => ({
|
||||
mutate: mockCopyRole,
|
||||
|
||||
@ -18,10 +18,11 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from '@langgenius/dify-ui/dropdown-menu'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
||||
import { workspacePermissionKeysAtom } from '@/context/app-context-state'
|
||||
import { useCopyWorkspaceRole, useDeleteWorkspaceRole } from '@/service/access-control/use-workspace-roles'
|
||||
import { hasPermission } from '@/utils/permission'
|
||||
import { CopyMembersConfirmDialog } from './copy-members-confirm-dialog'
|
||||
@ -44,7 +45,7 @@ const RowMenu = ({
|
||||
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false)
|
||||
const [showCopyMembersConfirm, setShowCopyMembersConfirm] = useState(false)
|
||||
|
||||
const workspacePermissionKeys = useAppContextWithSelector(s => s.workspacePermissionKeys)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const canManageRoles = hasPermission(workspacePermissionKeys, 'workspace.role.manage')
|
||||
|
||||
const handleView = useCallback(() => onView?.(role), [onView, role])
|
||||
|
||||
@ -75,6 +75,19 @@ vi.mock('@/context/app-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
userProfile: mockUserProfile,
|
||||
refreshUserProfile: mockMutateUserProfile,
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('@/context/i18n', () => ({
|
||||
useLocale: () => mockLocale,
|
||||
}))
|
||||
|
||||
@ -2,10 +2,11 @@
|
||||
import type { Locale } from '@/i18n-config'
|
||||
import { Select, SelectContent, SelectItem, SelectItemIndicator, SelectItemText, SelectTrigger } from '@langgenius/dify-ui/select'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useAtomValue, useSetAtom } from 'jotai'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { refreshUserProfileAtom, userProfileAtom } from '@/context/app-context-state'
|
||||
import { useLocale } from '@/context/i18n'
|
||||
import { setLocaleOnClient } from '@/i18n-config'
|
||||
import { languages } from '@/i18n-config/language'
|
||||
@ -35,7 +36,8 @@ const isThemeOption = (value: string): value is ThemeOption => {
|
||||
|
||||
export default function PreferencePage() {
|
||||
const locale = useLocale()
|
||||
const { userProfile, mutateUserProfile } = useAppContext()
|
||||
const userProfile = useAtomValue(userProfileAtom)
|
||||
const refreshUserProfile = useSetAtom(refreshUserProfileAtom)
|
||||
const [editing, setEditing] = useState(false)
|
||||
const { t } = useTranslation()
|
||||
const router = useRouter()
|
||||
@ -77,7 +79,7 @@ export default function PreferencePage() {
|
||||
try {
|
||||
await updateUserProfile({ url, body: { [bodyKey]: item.value } })
|
||||
toast.success(t('actionMsg.modifiedSuccessfully', { ns: 'common' }))
|
||||
mutateUserProfile()
|
||||
refreshUserProfile()
|
||||
}
|
||||
catch (e) {
|
||||
toast.error((e as Error).message)
|
||||
|
||||
@ -7,12 +7,13 @@ import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Dialog, DialogCloseButton, DialogContent, DialogTitle, DialogTrigger } from '@langgenius/dify-ui/dialog'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { convertTimezoneToOffsetStr } from '@/app/components/base/date-and-time-picker/utils/dayjs'
|
||||
import { AUTO_UPDATE_MODE, AUTO_UPDATE_STRATEGY } from '@/app/components/plugins/reference-setting-modal/auto-update-setting/types'
|
||||
import { convertLocalSecondsToUTCDaySeconds, convertUTCDaySecondsToLocalSeconds, dayjsToTimeOfDay, timeOfDayToDayjs } from '@/app/components/plugins/reference-setting-modal/auto-update-setting/utils'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { userProfileAtom } from '@/context/app-context-state'
|
||||
import { useMutationPluginAutoUpgradeSettings, usePluginAutoUpgradeSettings } from '@/service/use-plugins'
|
||||
import UpdateSettingDialogForm from './update-setting-dialog-form'
|
||||
|
||||
@ -26,7 +27,7 @@ const UpdateSettingDialog = ({
|
||||
disabled = false,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const { userProfile } = useAppContext()
|
||||
const userProfile = useAtomValue(userProfileAtom)
|
||||
const timezone = userProfile.timezone || 'UTC'
|
||||
const {
|
||||
data: autoUpgradeSetting,
|
||||
|
||||
@ -4,10 +4,24 @@ import { vi } from 'vitest'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import EnvNav from '../index'
|
||||
|
||||
const mockAppContextState = vi.hoisted(() => ({
|
||||
current: {} as Partial<AppContextValue>,
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useAppContext: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current)
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
describe('EnvNav', () => {
|
||||
const mockUseAppContext = vi.mocked(useAppContext)
|
||||
|
||||
@ -16,33 +30,39 @@ describe('EnvNav', () => {
|
||||
})
|
||||
|
||||
it('should render null when environment is PRODUCTION', () => {
|
||||
mockUseAppContext.mockReturnValue({
|
||||
const appContextValue = {
|
||||
langGeniusVersionInfo: {
|
||||
current_env: 'PRODUCTION',
|
||||
},
|
||||
} as unknown as AppContextValue)
|
||||
} as unknown as AppContextValue
|
||||
mockAppContextState.current = appContextValue
|
||||
mockUseAppContext.mockReturnValue(appContextValue)
|
||||
|
||||
const { container } = render(<EnvNav />)
|
||||
expect(container.firstChild).toBeNull()
|
||||
})
|
||||
|
||||
it('should render TESTING tag and icon when environment is TESTING', () => {
|
||||
mockUseAppContext.mockReturnValue({
|
||||
const appContextValue = {
|
||||
langGeniusVersionInfo: {
|
||||
current_env: 'TESTING',
|
||||
},
|
||||
} as unknown as AppContextValue)
|
||||
} as unknown as AppContextValue
|
||||
mockAppContextState.current = appContextValue
|
||||
mockUseAppContext.mockReturnValue(appContextValue)
|
||||
|
||||
render(<EnvNav />)
|
||||
expect(screen.getByText('common.environment.testing')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render DEVELOPMENT tag and icon when environment is DEVELOPMENT', () => {
|
||||
mockUseAppContext.mockReturnValue({
|
||||
const appContextValue = {
|
||||
langGeniusVersionInfo: {
|
||||
current_env: 'DEVELOPMENT',
|
||||
},
|
||||
} as unknown as AppContextValue)
|
||||
} as unknown as AppContextValue
|
||||
mockAppContextState.current = appContextValue
|
||||
mockUseAppContext.mockReturnValue(appContextValue)
|
||||
|
||||
render(<EnvNav />)
|
||||
expect(
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
'use client'
|
||||
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { TerminalSquare } from '@/app/components/base/icons/src/vender/solid/development'
|
||||
import { Beaker02 } from '@/app/components/base/icons/src/vender/solid/education'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { langGeniusVersionInfoAtom } from '@/context/app-context-state'
|
||||
|
||||
const headerEnvClassName: { [k: string]: string } = {
|
||||
DEVELOPMENT: 'bg-[#FEC84B] border-[#FDB022] text-[#93370D]',
|
||||
@ -12,7 +13,7 @@ const headerEnvClassName: { [k: string]: string } = {
|
||||
|
||||
const EnvNav = () => {
|
||||
const { t } = useTranslation()
|
||||
const { langGeniusVersionInfo } = useAppContext()
|
||||
const langGeniusVersionInfo = useAtomValue(langGeniusVersionInfoAtom)
|
||||
const showEnvTag = langGeniusVersionInfo.current_env === 'TESTING' || langGeniusVersionInfo.current_env === 'DEVELOPMENT'
|
||||
|
||||
if (!showEnvTag)
|
||||
|
||||
@ -30,6 +30,9 @@ const { mockIsAgentV2Enabled, mockSwitchWorkspace, mockToastSuccess } = vi.hoist
|
||||
mockToastSuccess: vi.fn(),
|
||||
mockIsAgentV2Enabled: vi.fn(() => true),
|
||||
}))
|
||||
const mockAppContextState = vi.hoisted(() => ({
|
||||
current: undefined as AppContextValue | undefined,
|
||||
}))
|
||||
|
||||
vi.mock('@/features/agent-v2/feature-flag', () => ({
|
||||
isAgentV2Enabled: () => mockIsAgentV2Enabled(),
|
||||
@ -40,6 +43,16 @@ vi.mock('@/context/app-context', () => ({
|
||||
useSelector: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current ?? {})
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('@/context/provider-context', () => ({
|
||||
useProviderContext: vi.fn(),
|
||||
}))
|
||||
@ -240,6 +253,7 @@ const renderMainNav = (
|
||||
const queryClient = createTestQueryClient()
|
||||
const getMockAppContext = useAppContext as Mock
|
||||
const currentAppContext = getMockAppContext() as AppContextValue
|
||||
mockAppContextState.current = currentAppContext
|
||||
queryClient.setQueryData(consoleQuery.workspaces.current.post.queryKey(), currentAppContext.currentWorkspace)
|
||||
queryClient.setQueryData(consoleQuery.workspaces.get.queryKey(), { workspaces: mockWorkspaces })
|
||||
const resolvedSystemFeatures = {
|
||||
@ -287,6 +301,7 @@ describe('MainNav', () => {
|
||||
refresh: vi.fn(),
|
||||
})
|
||||
;(useAppContext as Mock).mockReturnValue(appContextValue)
|
||||
mockAppContextState.current = appContextValue
|
||||
;(useAppContextSelector as Mock).mockImplementation((selector: (state: AppContextValue) => unknown) => selector((useAppContext as Mock)() as AppContextValue))
|
||||
;(useProviderContext as Mock).mockReturnValue({
|
||||
enableBilling: true,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user