import { useMemo, useState } from "react"; import { Outlet, redirect, useLocation, useMatches } from "react-router"; import { useTranslation } from "react-i18next"; import { Route } from "./+types/settings"; import OptionService from "#/api/option-service/option-service.api"; import { queryClient } from "#/query-client-config"; import { SettingsLayout } from "#/components/features/settings"; import { WebClientConfig } from "#/api/option-service/option.types"; import { QUERY_KEYS, CONFIG_CACHE_OPTIONS } from "#/hooks/query/query-keys"; import { Typography } from "#/ui/typography"; import { useBreakpoint } from "#/hooks/use-breakpoint"; import { useSettingsNavItems } from "#/hooks/use-settings-nav-items"; import { OSS_NAV_ITEMS } from "#/constants/settings-nav"; import { getFirstAvailablePath, isSettingsPageHidden, } from "#/utils/settings-utils"; import { SettingsSectionHeaderProvider } from "#/contexts/settings-section-header-context"; export const clientLoader = async ({ request }: Route.ClientLoaderArgs) => { const url = new URL(request.url); const { pathname } = url; const config = await queryClient.fetchQuery({ queryKey: QUERY_KEYS.WEB_CLIENT_CONFIG, queryFn: OptionService.getConfig, ...CONFIG_CACHE_OPTIONS, }); const featureFlags = config?.feature_flags; if (isSettingsPageHidden(pathname, featureFlags)) { const fallbackPath = getFirstAvailablePath(featureFlags); if (fallbackPath && fallbackPath !== pathname) { return redirect(fallbackPath); } } return null; }; function SettingsScreen() { const { t } = useTranslation("openhands"); const location = useLocation(); const matches = useMatches(); const navItems = useSettingsNavItems(); const isMobile = useBreakpoint(768); const [hideSectionHeader, setHideSectionHeader] = useState(false); const { currentSectionTitle, currentSectionSubtitle } = useMemo(() => { // Resolve from the full list, not the listed subset: locked-to-Cloud // unlists most pages but they stay reachable via deep links (OHE-3168). const currentItem = OSS_NAV_ITEMS.find( (item) => item.to === location.pathname, ); if (currentItem) { return { currentSectionTitle: currentItem.text, currentSectionSubtitle: currentItem.subtitle, }; } const firstItem = navItems.find((item) => item.type === "item"); if (firstItem?.type === "item") { return { currentSectionTitle: firstItem.item.text, currentSectionSubtitle: firstItem.item.subtitle, }; } return { currentSectionTitle: "SETTINGS$TITLE", currentSectionSubtitle: null as string | null, }; }, [navItems, location.pathname]); const routeHandle = matches.find((m) => m.pathname === location.pathname) ?.handle as { hideTitle?: boolean } | undefined; const isMobileHub = isMobile && location.pathname === "/settings"; const shouldHideTitle = routeHandle?.hideTitle === true || isMobileHub || hideSectionHeader; return (
{!shouldHideTitle && (
{t(currentSectionTitle)} {currentSectionSubtitle ? (

{t(currentSectionSubtitle)}

) : null}
)}
); } export default SettingsScreen;