mirror of
https://github.com/langgenius/dify.git
synced 2026-09-08 11:04:27 +08:00
fix(web): show current profile in main navigation (#39172)
This commit is contained in:
parent
61b07ab17d
commit
af7e59de7c
@ -3,14 +3,17 @@ import type { AppContextStateMockState } from '@/__tests__/utils/mock-app-contex
|
|||||||
import type { ModalContextState } from '@/context/modal-context'
|
import type { ModalContextState } from '@/context/modal-context'
|
||||||
import type { ProviderContextState } from '@/context/provider-context'
|
import type { ProviderContextState } from '@/context/provider-context'
|
||||||
import { fireEvent, screen, waitFor } from '@testing-library/react'
|
import { fireEvent, screen, waitFor } from '@testing-library/react'
|
||||||
|
import userEvent from '@testing-library/user-event'
|
||||||
import { renderToString } from 'react-dom/server'
|
import { renderToString } from 'react-dom/server'
|
||||||
import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features'
|
import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features'
|
||||||
import { Plan } from '@/app/components/billing/type'
|
import { Plan } from '@/app/components/billing/type'
|
||||||
import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants'
|
import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants'
|
||||||
|
import AccountSection from '@/app/components/main-nav/components/account-section'
|
||||||
import { useModalContext } from '@/context/modal-context'
|
import { useModalContext } from '@/context/modal-context'
|
||||||
import { useProviderContext } from '@/context/provider-context'
|
import { useProviderContext } from '@/context/provider-context'
|
||||||
import { useRouter } from '@/next/navigation'
|
import { useRouter } from '@/next/navigation'
|
||||||
import { useLogout } from '@/service/use-common'
|
import { useLogout } from '@/service/use-common'
|
||||||
|
import { createAccountProfileQueryClient } from '@/test/account-profile-query'
|
||||||
import AppSelector from '../index'
|
import AppSelector from '../index'
|
||||||
|
|
||||||
type DeepPartial<T> =
|
type DeepPartial<T> =
|
||||||
@ -147,15 +150,17 @@ vi.mock('@/config', async (importOriginal) => {
|
|||||||
})
|
})
|
||||||
vi.mock('@/env', () => mockEnv)
|
vi.mock('@/env', () => mockEnv)
|
||||||
|
|
||||||
|
const baseUserProfile = {
|
||||||
|
id: '1',
|
||||||
|
name: 'Test User',
|
||||||
|
email: 'test@example.com',
|
||||||
|
avatar: '',
|
||||||
|
avatar_url: 'avatar.png',
|
||||||
|
is_password_set: false,
|
||||||
|
}
|
||||||
|
|
||||||
const baseAppContextValue: AppContextStateMockState = {
|
const baseAppContextValue: AppContextStateMockState = {
|
||||||
userProfile: {
|
userProfile: baseUserProfile,
|
||||||
id: '1',
|
|
||||||
name: 'Test User',
|
|
||||||
email: 'test@example.com',
|
|
||||||
avatar: '',
|
|
||||||
avatar_url: 'avatar.png',
|
|
||||||
is_password_set: false,
|
|
||||||
},
|
|
||||||
mutateUserProfile: vi.fn(),
|
mutateUserProfile: vi.fn(),
|
||||||
currentWorkspace: {
|
currentWorkspace: {
|
||||||
id: '1',
|
id: '1',
|
||||||
@ -201,7 +206,12 @@ describe('AccountDropdown', () => {
|
|||||||
ui: React.ReactElement,
|
ui: React.ReactElement,
|
||||||
options: { systemFeatures?: DeepPartial<GetSystemFeaturesResponse> } = {},
|
options: { systemFeatures?: DeepPartial<GetSystemFeaturesResponse> } = {},
|
||||||
) => {
|
) => {
|
||||||
|
const queryClient = createAccountProfileQueryClient({
|
||||||
|
...baseUserProfile,
|
||||||
|
...(mockAppContextState.current?.userProfile ?? {}),
|
||||||
|
})
|
||||||
return renderWithSystemFeatures(ui, {
|
return renderWithSystemFeatures(ui, {
|
||||||
|
queryClient,
|
||||||
systemFeatures: options.systemFeatures ?? { branding: { enabled: false } },
|
systemFeatures: options.systemFeatures ?? { branding: { enabled: false } },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -238,6 +248,29 @@ describe('AccountDropdown', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('Rendering', () => {
|
describe('Rendering', () => {
|
||||||
|
it('should show the signed-in account in the main navigation menu', async () => {
|
||||||
|
const user = userEvent.setup()
|
||||||
|
const queryClient = createAccountProfileQueryClient({
|
||||||
|
id: 'current-user',
|
||||||
|
name: 'Current User',
|
||||||
|
email: 'current@example.com',
|
||||||
|
avatar_url: 'current-avatar.png',
|
||||||
|
})
|
||||||
|
|
||||||
|
renderWithSystemFeatures(<AccountSection />, {
|
||||||
|
queryClient,
|
||||||
|
systemFeatures: { branding: { enabled: false } },
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(screen.getByText('Current User')).toBeInTheDocument()
|
||||||
|
expect(screen.queryByText('Test User')).not.toBeInTheDocument()
|
||||||
|
|
||||||
|
await user.click(screen.getByRole('button', { name: 'common.account.account' }))
|
||||||
|
|
||||||
|
expect(await screen.findByText('current@example.com')).toBeInTheDocument()
|
||||||
|
expect(screen.queryByText('test@example.com')).not.toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
it('should render user profile correctly', () => {
|
it('should render user profile correctly', () => {
|
||||||
// Act
|
// Act
|
||||||
renderWithRouter(<AppSelector />)
|
renderWithRouter(<AppSelector />)
|
||||||
|
|||||||
@ -16,14 +16,14 @@ import {
|
|||||||
DropdownMenuSubContent,
|
DropdownMenuSubContent,
|
||||||
DropdownMenuSubTrigger,
|
DropdownMenuSubTrigger,
|
||||||
} from '@langgenius/dify-ui/dropdown-menu'
|
} from '@langgenius/dify-ui/dropdown-menu'
|
||||||
import { useAtomValue } from 'jotai'
|
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||||
import { useTheme } from 'next-themes'
|
import { useTheme } from 'next-themes'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import PremiumBadge from '@/app/components/base/premium-badge'
|
import PremiumBadge from '@/app/components/base/premium-badge'
|
||||||
import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants'
|
import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants'
|
||||||
import { userProfileAtom } from '@/context/account-state'
|
|
||||||
import { useModalContext } from '@/context/modal-context'
|
import { useModalContext } from '@/context/modal-context'
|
||||||
import { useProviderContext } from '@/context/provider-context'
|
import { useProviderContext } from '@/context/provider-context'
|
||||||
|
import { userProfileQueryOptions } from '@/features/account-profile/client'
|
||||||
import Link from '@/next/link'
|
import Link from '@/next/link'
|
||||||
import { ExternalLinkIndicator, MenuItemContent } from './menu-item-content'
|
import { ExternalLinkIndicator, MenuItemContent } from './menu-item-content'
|
||||||
|
|
||||||
@ -102,7 +102,10 @@ type MainNavMenuContentProps = {
|
|||||||
|
|
||||||
export function MainNavMenuContent({ onLogout }: MainNavMenuContentProps) {
|
export function MainNavMenuContent({ onLogout }: MainNavMenuContentProps) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const userProfile = useAtomValue(userProfileAtom)
|
const { data: userProfile } = useSuspenseQuery({
|
||||||
|
...userProfileQueryOptions(),
|
||||||
|
select: (data) => data.profile,
|
||||||
|
})
|
||||||
const { isEducationAccount } = useProviderContext()
|
const { isEducationAccount } = useProviderContext()
|
||||||
const { setShowAccountSettingModal } = useModalContext()
|
const { setShowAccountSettingModal } = useModalContext()
|
||||||
|
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import { gotoAnythingDialogHandle } from '@/app/components/goto-anything/dialog-
|
|||||||
import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants'
|
import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants'
|
||||||
import { useModalContext } from '@/context/modal-context'
|
import { useModalContext } from '@/context/modal-context'
|
||||||
import { useProviderContext } from '@/context/provider-context'
|
import { useProviderContext } from '@/context/provider-context'
|
||||||
|
import { userProfileQueryOptions } from '@/features/account-profile/client'
|
||||||
import { usePathname, useRouter } from '@/next/navigation'
|
import { usePathname, useRouter } from '@/next/navigation'
|
||||||
import { consoleQuery } from '@/service/client'
|
import { consoleQuery } from '@/service/client'
|
||||||
import { useGetInstalledApps, useUninstallApp, useUpdateAppPinStatus } from '@/service/use-explore'
|
import { useGetInstalledApps, useUninstallApp, useUpdateAppPinStatus } from '@/service/use-explore'
|
||||||
@ -211,15 +212,17 @@ const createInstalledApp = (overrides: Partial<InstalledApp> = {}): InstalledApp
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const mainNavUserProfile = {
|
||||||
|
id: 'user-1',
|
||||||
|
name: 'Evan Z',
|
||||||
|
email: 'evan@example.com',
|
||||||
|
avatar: '',
|
||||||
|
avatar_url: '',
|
||||||
|
is_password_set: true,
|
||||||
|
}
|
||||||
|
|
||||||
const appContextValue: AppContextStateMockState = {
|
const appContextValue: AppContextStateMockState = {
|
||||||
userProfile: {
|
userProfile: mainNavUserProfile,
|
||||||
id: 'user-1',
|
|
||||||
name: 'Evan Z',
|
|
||||||
email: 'evan@example.com',
|
|
||||||
avatar: '',
|
|
||||||
avatar_url: '',
|
|
||||||
is_password_set: true,
|
|
||||||
},
|
|
||||||
mutateUserProfile: vi.fn(),
|
mutateUserProfile: vi.fn(),
|
||||||
currentWorkspace: {
|
currentWorkspace: {
|
||||||
id: 'workspace-1',
|
id: 'workspace-1',
|
||||||
@ -273,6 +276,16 @@ const renderMainNav = (
|
|||||||
consoleQuery.workspaces.current.post.queryKey(),
|
consoleQuery.workspaces.current.post.queryKey(),
|
||||||
currentAppContext.currentWorkspace as ICurrentWorkspace,
|
currentAppContext.currentWorkspace as ICurrentWorkspace,
|
||||||
)
|
)
|
||||||
|
queryClient.setQueryData(userProfileQueryOptions().queryKey, {
|
||||||
|
profile: {
|
||||||
|
...mainNavUserProfile,
|
||||||
|
...(currentAppContext.userProfile ?? {}),
|
||||||
|
},
|
||||||
|
meta: {
|
||||||
|
currentVersion: null,
|
||||||
|
currentEnv: null,
|
||||||
|
},
|
||||||
|
})
|
||||||
queryClient.setQueryData(consoleQuery.workspaces.get.queryKey(), { workspaces: mockWorkspaces })
|
queryClient.setQueryData(consoleQuery.workspaces.get.queryKey(), { workspaces: mockWorkspaces })
|
||||||
const resolvedSystemFeatures = {
|
const resolvedSystemFeatures = {
|
||||||
...defaultMainNavSystemFeatures,
|
...defaultMainNavSystemFeatures,
|
||||||
|
|||||||
@ -2,16 +2,19 @@
|
|||||||
|
|
||||||
import { Avatar } from '@langgenius/dify-ui/avatar'
|
import { Avatar } from '@langgenius/dify-ui/avatar'
|
||||||
import { cn } from '@langgenius/dify-ui/cn'
|
import { cn } from '@langgenius/dify-ui/cn'
|
||||||
import { useAtomValue } from 'jotai'
|
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||||
import AccountDropdown from '@/app/components/header/account-dropdown'
|
import AccountDropdown from '@/app/components/header/account-dropdown'
|
||||||
import { userProfileAtom } from '@/context/account-state'
|
import { userProfileQueryOptions } from '@/features/account-profile/client'
|
||||||
|
|
||||||
type AccountSectionProps = {
|
type AccountSectionProps = {
|
||||||
compact?: boolean
|
compact?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const AccountSection = ({ compact = false }: AccountSectionProps) => {
|
const AccountSection = ({ compact = false }: AccountSectionProps) => {
|
||||||
const userProfile = useAtomValue(userProfileAtom)
|
const { data: userProfile } = useSuspenseQuery({
|
||||||
|
...userProfileQueryOptions(),
|
||||||
|
select: (data) => data.profile,
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AccountDropdown
|
<AccountDropdown
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user