diff --git a/web/app/components/header/account-dropdown/__tests__/index.spec.tsx b/web/app/components/header/account-dropdown/__tests__/index.spec.tsx index cf38adc01b3..30b6f457bd4 100644 --- a/web/app/components/header/account-dropdown/__tests__/index.spec.tsx +++ b/web/app/components/header/account-dropdown/__tests__/index.spec.tsx @@ -3,14 +3,17 @@ import type { AppContextStateMockState } from '@/__tests__/utils/mock-app-contex import type { ModalContextState } from '@/context/modal-context' import type { ProviderContextState } from '@/context/provider-context' import { fireEvent, screen, waitFor } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import { renderToString } from 'react-dom/server' import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import { Plan } from '@/app/components/billing/type' 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 { useProviderContext } from '@/context/provider-context' import { useRouter } from '@/next/navigation' import { useLogout } from '@/service/use-common' +import { createAccountProfileQueryClient } from '@/test/account-profile-query' import AppSelector from '../index' type DeepPartial = @@ -147,15 +150,17 @@ vi.mock('@/config', async (importOriginal) => { }) 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 = { - userProfile: { - id: '1', - name: 'Test User', - email: 'test@example.com', - avatar: '', - avatar_url: 'avatar.png', - is_password_set: false, - }, + userProfile: baseUserProfile, mutateUserProfile: vi.fn(), currentWorkspace: { id: '1', @@ -201,7 +206,12 @@ describe('AccountDropdown', () => { ui: React.ReactElement, options: { systemFeatures?: DeepPartial } = {}, ) => { + const queryClient = createAccountProfileQueryClient({ + ...baseUserProfile, + ...(mockAppContextState.current?.userProfile ?? {}), + }) return renderWithSystemFeatures(ui, { + queryClient, systemFeatures: options.systemFeatures ?? { branding: { enabled: false } }, }) } @@ -238,6 +248,29 @@ describe('AccountDropdown', () => { }) 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(, { + 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', () => { // Act renderWithRouter() diff --git a/web/app/components/header/account-dropdown/main-nav-menu-content.tsx b/web/app/components/header/account-dropdown/main-nav-menu-content.tsx index 0746c49cc3e..ce8b630e144 100644 --- a/web/app/components/header/account-dropdown/main-nav-menu-content.tsx +++ b/web/app/components/header/account-dropdown/main-nav-menu-content.tsx @@ -16,14 +16,14 @@ import { DropdownMenuSubContent, DropdownMenuSubTrigger, } from '@langgenius/dify-ui/dropdown-menu' -import { useAtomValue } from 'jotai' +import { useSuspenseQuery } from '@tanstack/react-query' 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 { userProfileAtom } from '@/context/account-state' import { useModalContext } from '@/context/modal-context' import { useProviderContext } from '@/context/provider-context' +import { userProfileQueryOptions } from '@/features/account-profile/client' import Link from '@/next/link' import { ExternalLinkIndicator, MenuItemContent } from './menu-item-content' @@ -102,7 +102,10 @@ type MainNavMenuContentProps = { export function MainNavMenuContent({ onLogout }: MainNavMenuContentProps) { const { t } = useTranslation() - const userProfile = useAtomValue(userProfileAtom) + const { data: userProfile } = useSuspenseQuery({ + ...userProfileQueryOptions(), + select: (data) => data.profile, + }) const { isEducationAccount } = useProviderContext() const { setShowAccountSettingModal } = useModalContext() diff --git a/web/app/components/main-nav/__tests__/index.spec.tsx b/web/app/components/main-nav/__tests__/index.spec.tsx index df3a60260e8..bf6b3cffc45 100644 --- a/web/app/components/main-nav/__tests__/index.spec.tsx +++ b/web/app/components/main-nav/__tests__/index.spec.tsx @@ -19,6 +19,7 @@ import { gotoAnythingDialogHandle } from '@/app/components/goto-anything/dialog- import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants' import { useModalContext } from '@/context/modal-context' import { useProviderContext } from '@/context/provider-context' +import { userProfileQueryOptions } from '@/features/account-profile/client' import { usePathname, useRouter } from '@/next/navigation' import { consoleQuery } from '@/service/client' import { useGetInstalledApps, useUninstallApp, useUpdateAppPinStatus } from '@/service/use-explore' @@ -211,15 +212,17 @@ const createInstalledApp = (overrides: Partial = {}): InstalledApp }, }) +const mainNavUserProfile = { + id: 'user-1', + name: 'Evan Z', + email: 'evan@example.com', + avatar: '', + avatar_url: '', + is_password_set: true, +} + const appContextValue: AppContextStateMockState = { - userProfile: { - id: 'user-1', - name: 'Evan Z', - email: 'evan@example.com', - avatar: '', - avatar_url: '', - is_password_set: true, - }, + userProfile: mainNavUserProfile, mutateUserProfile: vi.fn(), currentWorkspace: { id: 'workspace-1', @@ -273,6 +276,16 @@ const renderMainNav = ( consoleQuery.workspaces.current.post.queryKey(), 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 }) const resolvedSystemFeatures = { ...defaultMainNavSystemFeatures, diff --git a/web/app/components/main-nav/components/account-section.tsx b/web/app/components/main-nav/components/account-section.tsx index f17cfd05842..a39d741011c 100644 --- a/web/app/components/main-nav/components/account-section.tsx +++ b/web/app/components/main-nav/components/account-section.tsx @@ -2,16 +2,19 @@ import { Avatar } from '@langgenius/dify-ui/avatar' 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 { userProfileAtom } from '@/context/account-state' +import { userProfileQueryOptions } from '@/features/account-profile/client' type AccountSectionProps = { compact?: boolean } const AccountSection = ({ compact = false }: AccountSectionProps) => { - const userProfile = useAtomValue(userProfileAtom) + const { data: userProfile } = useSuspenseQuery({ + ...userProfileQueryOptions(), + select: (data) => data.profile, + }) return (