From 6917d45d3a023553e09c5a650624308d75b7d1b6 Mon Sep 17 00:00:00 2001 From: Bharath A V <157507162+AVBharath10@users.noreply.github.com> Date: Fri, 12 Dec 2025 18:50:59 +0530 Subject: [PATCH] refactor(frontend): consolidate settings navigation items logic into shared custom hook (#11950) Co-authored-by: amanape <83104063+amanape@users.noreply.github.com> --- .../hooks/use-settings-nav-items.test.tsx | 53 +++++++++++++++++++ .../account-settings-context-menu.tsx | 20 ++----- .../features/settings/settings-layout.tsx | 20 ++----- .../features/settings/settings-navigation.tsx | 10 +--- frontend/src/hooks/use-settings-nav-items.ts | 15 ++++++ frontend/src/routes/settings.tsx | 41 +++----------- 6 files changed, 85 insertions(+), 74 deletions(-) create mode 100644 frontend/__tests__/hooks/use-settings-nav-items.test.tsx create mode 100644 frontend/src/hooks/use-settings-nav-items.ts diff --git a/frontend/__tests__/hooks/use-settings-nav-items.test.tsx b/frontend/__tests__/hooks/use-settings-nav-items.test.tsx new file mode 100644 index 0000000000..64bb675341 --- /dev/null +++ b/frontend/__tests__/hooks/use-settings-nav-items.test.tsx @@ -0,0 +1,53 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { renderHook, waitFor } from "@testing-library/react"; +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { SAAS_NAV_ITEMS, OSS_NAV_ITEMS } from "#/constants/settings-nav"; +import OptionService from "#/api/option-service/option-service.api"; +import { useSettingsNavItems } from "#/hooks/use-settings-nav-items"; + +const queryClient = new QueryClient(); +const wrapper = ({ children }: { children: React.ReactNode }) => ( + {children} +); + +const mockConfig = (appMode: "saas" | "oss", hideLlmSettings = false) => { + vi.spyOn(OptionService, "getConfig").mockResolvedValue({ + APP_MODE: appMode, + FEATURE_FLAGS: { HIDE_LLM_SETTINGS: hideLlmSettings }, + } as Awaited>); +}; + +describe("useSettingsNavItems", () => { + beforeEach(() => { + queryClient.clear(); + }); + + it("should return SAAS_NAV_ITEMS when APP_MODE is 'saas'", async () => { + mockConfig("saas"); + const { result } = renderHook(() => useSettingsNavItems(), { wrapper }); + + await waitFor(() => { + expect(result.current).toEqual(SAAS_NAV_ITEMS); + }); + }); + + it("should return OSS_NAV_ITEMS when APP_MODE is 'oss'", async () => { + mockConfig("oss"); + const { result } = renderHook(() => useSettingsNavItems(), { wrapper }); + + await waitFor(() => { + expect(result.current).toEqual(OSS_NAV_ITEMS); + }); + }); + + it("should filter out '/settings' item when HIDE_LLM_SETTINGS feature flag is enabled", async () => { + mockConfig("saas", true); + const { result } = renderHook(() => useSettingsNavItems(), { wrapper }); + + await waitFor(() => { + expect( + result.current.find((item) => item.to === "/settings"), + ).toBeUndefined(); + }); + }); +}); diff --git a/frontend/src/components/features/context-menu/account-settings-context-menu.tsx b/frontend/src/components/features/context-menu/account-settings-context-menu.tsx index a30fe5f816..0c2541237d 100644 --- a/frontend/src/components/features/context-menu/account-settings-context-menu.tsx +++ b/frontend/src/components/features/context-menu/account-settings-context-menu.tsx @@ -5,11 +5,10 @@ import { ContextMenu } from "#/ui/context-menu"; import { ContextMenuListItem } from "./context-menu-list-item"; import { Divider } from "#/ui/divider"; import { useClickOutsideElement } from "#/hooks/use-click-outside-element"; -import { useConfig } from "#/hooks/query/use-config"; import { I18nKey } from "#/i18n/declaration"; import LogOutIcon from "#/icons/log-out.svg?react"; import DocumentIcon from "#/icons/document.svg?react"; -import { SAAS_NAV_ITEMS, OSS_NAV_ITEMS } from "#/constants/settings-nav"; +import { useSettingsNavItems } from "#/hooks/use-settings-nav-items"; interface AccountSettingsContextMenuProps { onLogout: () => void; @@ -22,15 +21,8 @@ export function AccountSettingsContextMenu({ }: AccountSettingsContextMenuProps) { const ref = useClickOutsideElement(onClose); const { t } = useTranslation(); - const { data: config } = useConfig(); - - const isSaas = config?.APP_MODE === "saas"; - // Get navigation items and filter out LLM settings if the feature flag is enabled - let items = isSaas ? SAAS_NAV_ITEMS : OSS_NAV_ITEMS; - if (config?.FEATURE_FLAGS?.HIDE_LLM_SETTINGS) { - items = items.filter((item) => item.to !== "/settings"); - } + const items = useSettingsNavItems(); const navItems = items.map((item) => ({ ...item, @@ -39,11 +31,7 @@ export function AccountSettingsContextMenu({ height: 16, } as React.SVGProps), })); - - const handleNavigationClick = () => { - onClose(); - // The Link component will handle the actual navigation - }; + const handleNavigationClick = () => onClose(); return ( ( handleNavigationClick()} + onClick={handleNavigationClick} className="flex items-center gap-2 p-2 hover:bg-[#5C5D62] rounded h-[30px]" > {icon} diff --git a/frontend/src/components/features/settings/settings-layout.tsx b/frontend/src/components/features/settings/settings-layout.tsx index 6ac82cf8d0..7d00ab2596 100644 --- a/frontend/src/components/features/settings/settings-layout.tsx +++ b/frontend/src/components/features/settings/settings-layout.tsx @@ -1,16 +1,11 @@ import { useState } from "react"; import { MobileHeader } from "./mobile-header"; import { SettingsNavigation } from "./settings-navigation"; - -interface NavigationItem { - to: string; - icon: React.ReactNode; - text: string; -} +import { SettingsNavItem } from "#/constants/settings-nav"; interface SettingsLayoutProps { children: React.ReactNode; - navigationItems: NavigationItem[]; + navigationItems: SettingsNavItem[]; } export function SettingsLayout({ @@ -19,13 +14,8 @@ export function SettingsLayout({ }: SettingsLayoutProps) { const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); - const toggleMobileMenu = () => { - setIsMobileMenuOpen(!isMobileMenuOpen); - }; - - const closeMobileMenu = () => { - setIsMobileMenuOpen(false); - }; + const toggleMobileMenu = () => setIsMobileMenuOpen(!isMobileMenuOpen); + const closeMobileMenu = () => setIsMobileMenuOpen(false); return (
@@ -34,7 +24,6 @@ export function SettingsLayout({ isMobileMenuOpen={isMobileMenuOpen} onToggleMenu={toggleMobileMenu} /> - {/* Desktop layout with navigation and main content */}
{/* Navigation */} @@ -43,7 +32,6 @@ export function SettingsLayout({ onCloseMobileMenu={closeMobileMenu} navigationItems={navigationItems} /> - {/* Main content */}
{children} diff --git a/frontend/src/components/features/settings/settings-navigation.tsx b/frontend/src/components/features/settings/settings-navigation.tsx index ce9e49aa09..5a35f01495 100644 --- a/frontend/src/components/features/settings/settings-navigation.tsx +++ b/frontend/src/components/features/settings/settings-navigation.tsx @@ -5,17 +5,12 @@ import { Typography } from "#/ui/typography"; import { I18nKey } from "#/i18n/declaration"; import SettingsIcon from "#/icons/settings-gear.svg?react"; import CloseIcon from "#/icons/close.svg?react"; - -interface NavigationItem { - to: string; - icon: React.ReactNode; - text: string; -} +import { SettingsNavItem } from "#/constants/settings-nav"; interface SettingsNavigationProps { isMobileMenuOpen: boolean; onCloseMobileMenu: () => void; - navigationItems: NavigationItem[]; + navigationItems: SettingsNavItem[]; } export function SettingsNavigation({ @@ -34,7 +29,6 @@ export function SettingsNavigation({ onClick={onCloseMobileMenu} /> )} - {/* Navigation sidebar */}