From 81d43e04feacdf48abbca476b8e681bdd4bda0b2 Mon Sep 17 00:00:00 2001 From: Dotta Date: Thu, 21 May 2026 03:31:29 +0000 Subject: [PATCH] Fix mobile company settings navigation --- ui/src/components/Layout.test.tsx | 54 +++++++++++++++++-- ui/src/components/Layout.tsx | 6 +++ .../access/CompanySettingsNav.test.tsx | 14 ++++- .../components/access/CompanySettingsNav.tsx | 10 ++++ ui/src/pages/CompanyExport.tsx | 8 +-- ui/src/pages/CompanyImport.tsx | 10 ++-- ui/src/pages/CompanySettings.tsx | 2 +- 7 files changed, 90 insertions(+), 14 deletions(-) diff --git a/ui/src/components/Layout.test.tsx b/ui/src/components/Layout.test.tsx index 379d6934130..998607145f3 100644 --- a/ui/src/components/Layout.test.tsx +++ b/ui/src/components/Layout.test.tsx @@ -1,7 +1,7 @@ // @vitest-environment jsdom -import { act } from "react"; import { createRoot } from "react-dom/client"; +import { flushSync } from "react-dom"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { Layout } from "./Layout"; @@ -27,6 +27,10 @@ const mockPluginSlots = vi.hoisted(() => ({ })); const mockUsePluginSlots = vi.hoisted(() => vi.fn()); const mockPluginSlotContexts = vi.hoisted(() => [] as Array>); +const mockSidebarState = vi.hoisted(() => ({ + sidebarOpen: true, + isMobile: false, +})); let currentPathname = "/PAP/dashboard"; vi.mock("@/lib/router", () => ({ @@ -161,10 +165,10 @@ vi.mock("../context/CompanyContext", () => ({ vi.mock("../context/SidebarContext", () => ({ useSidebar: () => ({ - sidebarOpen: true, + sidebarOpen: mockSidebarState.sidebarOpen, setSidebarOpen: mockSetSidebarOpen, toggleSidebar: vi.fn(), - isMobile: false, + isMobile: mockSidebarState.isMobile, }), })); @@ -201,6 +205,14 @@ vi.mock("../lib/main-content-focus", () => ({ // eslint-disable-next-line @typescript-eslint/no-explicit-any (globalThis as any).IS_REACT_ACT_ENVIRONMENT = true; +async function act(callback: () => void | Promise) { + let result: void | Promise = undefined; + flushSync(() => { + result = callback(); + }); + await result; +} + async function flushReact() { await act(async () => { await Promise.resolve(); @@ -229,6 +241,8 @@ describe("Layout", () => { }); mockPluginSlots.slots = []; mockPluginSlotContexts.length = 0; + mockSidebarState.sidebarOpen = true; + mockSidebarState.isMobile = false; }); afterEach(() => { @@ -319,6 +333,40 @@ describe("Layout", () => { }); }); + it("renders a mobile company settings selector on company settings routes", async () => { + currentPathname = "/PAP/company/settings/secrets"; + mockSidebarState.isMobile = true; + mockSidebarState.sidebarOpen = false; + const root = createRoot(container); + const queryClient = new QueryClient({ + defaultOptions: { queries: { retry: false } }, + }); + + await act(async () => { + root.render( + + + , + ); + }); + await flushReact(); + await flushReact(); + + const selector = container.querySelector("select"); + expect(selector).not.toBeNull(); + expect(selector?.value).toBe("secrets"); + expect(selector?.textContent).toContain("General"); + expect(selector?.textContent).toContain("Environments"); + expect(selector?.textContent).toContain("Cloud upstream"); + expect(selector?.textContent).toContain("Access"); + expect(selector?.textContent).toContain("Invites"); + expect(selector?.textContent).toContain("Secrets"); + + await act(async () => { + root.unmount(); + }); + }); + it("renders the instance settings sidebar on instance settings routes", async () => { currentPathname = "/instance/settings/general"; const root = createRoot(container); diff --git a/ui/src/components/Layout.tsx b/ui/src/components/Layout.tsx index 5f0567a613e..2b268c076f6 100644 --- a/ui/src/components/Layout.tsx +++ b/ui/src/components/Layout.tsx @@ -4,6 +4,7 @@ import { Outlet, useLocation, useNavigate, useNavigationType, useParams } from " import { Sidebar } from "./Sidebar"; import { InstanceSidebar } from "./InstanceSidebar"; import { CompanySettingsSidebar } from "./CompanySettingsSidebar"; +import { CompanySettingsNav } from "./access/CompanySettingsNav"; import { BreadcrumbBar } from "./BreadcrumbBar"; import { PropertiesPanel } from "./PropertiesPanel"; import { CommandPalette } from "./CommandPalette"; @@ -421,6 +422,11 @@ export function Layout() { )} > + {isMobile && isCompanySettingsRoute ? ( +
+ +
+ ) : null}
({ // eslint-disable-next-line @typescript-eslint/no-explicit-any (globalThis as any).IS_REACT_ACT_ENVIRONMENT = true; +async function act(callback: () => void | Promise) { + let result: void | Promise = undefined; + flushSync(() => { + result = callback(); + }); + await result; +} + describe("CompanySettingsNav", () => { let container: HTMLDivElement; @@ -60,9 +68,11 @@ describe("CompanySettingsNav", () => { expect(getCompanySettingsTab("/PAP/company/settings")).toBe("general"); expect(getCompanySettingsTab("/company/settings/environments")).toBe("environments"); expect(getCompanySettingsTab("/PAP/company/settings/environments")).toBe("environments"); + expect(getCompanySettingsTab("/company/settings/cloud-upstream")).toBe("cloud-upstream"); expect(getCompanySettingsTab("/company/settings/access")).toBe("access"); expect(getCompanySettingsTab("/PAP/company/settings/access")).toBe("access"); expect(getCompanySettingsTab("/company/settings/invites")).toBe("invites"); + expect(getCompanySettingsTab("/PAP/company/settings/secrets")).toBe("secrets"); }); it("renders the active tab and navigates when a different tab is selected", async () => { @@ -80,8 +90,10 @@ describe("CompanySettingsNav", () => { items: [ { value: "general", label: "General" }, { value: "environments", label: "Environments" }, + { value: "cloud-upstream", label: "Cloud upstream" }, { value: "access", label: "Access" }, { value: "invites", label: "Invites" }, + { value: "secrets", label: "Secrets" }, ], }), ); diff --git a/ui/src/components/access/CompanySettingsNav.tsx b/ui/src/components/access/CompanySettingsNav.tsx index 82fb224551e..339b9ed0b94 100644 --- a/ui/src/components/access/CompanySettingsNav.tsx +++ b/ui/src/components/access/CompanySettingsNav.tsx @@ -5,8 +5,10 @@ import { useLocation, useNavigate } from "@/lib/router"; const items = [ { value: "general", label: "General", href: "/company/settings" }, { value: "environments", label: "Environments", href: "/company/settings/environments" }, + { value: "cloud-upstream", label: "Cloud upstream", href: "/company/settings/cloud-upstream" }, { value: "access", label: "Access", href: "/company/settings/access" }, { value: "invites", label: "Invites", href: "/company/settings/invites" }, + { value: "secrets", label: "Secrets", href: "/company/settings/secrets" }, ] as const; type CompanySettingsTab = (typeof items)[number]["value"]; @@ -16,6 +18,10 @@ export function getCompanySettingsTab(pathname: string): CompanySettingsTab { return "environments"; } + if (pathname.includes("/company/settings/cloud-upstream")) { + return "cloud-upstream"; + } + if (pathname.includes("/company/settings/access")) { return "access"; } @@ -24,6 +30,10 @@ export function getCompanySettingsTab(pathname: string): CompanySettingsTab { return "invites"; } + if (pathname.includes("/company/settings/secrets")) { + return "secrets"; + } + return "general"; } diff --git a/ui/src/pages/CompanyExport.tsx b/ui/src/pages/CompanyExport.tsx index a190afbc688..fd1bdabc6dd 100644 --- a/ui/src/pages/CompanyExport.tsx +++ b/ui/src/pages/CompanyExport.tsx @@ -933,7 +933,7 @@ export function CompanyExport() { {/* Sticky top action bar */}
-
+
{selectedCompany?.name ?? "Company"} export @@ -969,8 +969,8 @@ export function CompanyExport() { )} {/* Two-column layout */} -
- -
+
diff --git a/ui/src/pages/CompanyImport.tsx b/ui/src/pages/CompanyImport.tsx index c1be7a28a65..cae6bcf0c04 100644 --- a/ui/src/pages/CompanyImport.tsx +++ b/ui/src/pages/CompanyImport.tsx @@ -1227,7 +1227,7 @@ export function CompanyImport() { -
+