From 9779b0f16dcae8c48213f39b26376601252008e0 Mon Sep 17 00:00:00 2001 From: Dotta Date: Fri, 10 Apr 2026 09:22:20 -0500 Subject: [PATCH] Use shared tabs for company settings --- .../access/CompanySettingsNav.test.tsx | 99 +++++++++++++++++++ .../components/access/CompanySettingsNav.tsx | 46 +++++++++ 2 files changed, 145 insertions(+) create mode 100644 ui/src/components/access/CompanySettingsNav.test.tsx create mode 100644 ui/src/components/access/CompanySettingsNav.tsx diff --git a/ui/src/components/access/CompanySettingsNav.test.tsx b/ui/src/components/access/CompanySettingsNav.test.tsx new file mode 100644 index 00000000000..543735dfec9 --- /dev/null +++ b/ui/src/components/access/CompanySettingsNav.test.tsx @@ -0,0 +1,99 @@ +// @vitest-environment jsdom + +import { act } from "react"; +import { createRoot } from "react-dom/client"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { CompanySettingsNav, getCompanySettingsTab } from "./CompanySettingsNav"; + +let currentPathname = "/company/settings"; +const navigateMock = vi.hoisted(() => vi.fn()); +const pageTabBarMock = vi.hoisted(() => vi.fn()); + +vi.mock("@/lib/router", () => ({ + useLocation: () => ({ pathname: currentPathname, search: "", hash: "" }), + useNavigate: () => navigateMock, +})); + +vi.mock("@/components/ui/tabs", () => ({ + Tabs: ({ children }: { children: React.ReactNode }) =>
{children}
, +})); + +vi.mock("@/components/PageTabBar", () => ({ + PageTabBar: (props: { + items: Array<{ value: string; label: string }>; + value?: string; + onValueChange?: (value: string) => void; + }) => { + pageTabBarMock(props); + + return ( +
+
{props.value}
+ +
+ ); + }, +})); + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +(globalThis as any).IS_REACT_ACT_ENVIRONMENT = true; + +describe("CompanySettingsNav", () => { + let container: HTMLDivElement; + + beforeEach(() => { + container = document.createElement("div"); + document.body.appendChild(container); + currentPathname = "/company/settings"; + }); + + afterEach(() => { + container.remove(); + document.body.innerHTML = ""; + vi.clearAllMocks(); + }); + + it("maps company settings routes to the expected shared tab value", () => { + expect(getCompanySettingsTab("/company/settings")).toBe("general"); + expect(getCompanySettingsTab("/PAP/company/settings")).toBe("general"); + expect(getCompanySettingsTab("/company/settings/access")).toBe("access"); + expect(getCompanySettingsTab("/PAP/company/settings/access")).toBe("access"); + expect(getCompanySettingsTab("/company/settings/invites")).toBe("invites"); + }); + + it("renders the active tab and navigates when a different tab is selected", async () => { + currentPathname = "/PAP/company/settings/access"; + const root = createRoot(container); + + await act(async () => { + root.render(); + }); + + expect(container.textContent).toContain("access"); + expect(pageTabBarMock).toHaveBeenCalledWith( + expect.objectContaining({ + value: "access", + items: [ + { value: "general", label: "General" }, + { value: "access", label: "Access" }, + { value: "invites", label: "Invites" }, + ], + }), + ); + + const button = container.querySelector("button"); + expect(button).not.toBeNull(); + + await act(async () => { + button?.dispatchEvent(new MouseEvent("click", { bubbles: true })); + }); + + expect(navigateMock).toHaveBeenCalledWith("/company/settings/invites"); + + await act(async () => { + root.unmount(); + }); + }); +}); diff --git a/ui/src/components/access/CompanySettingsNav.tsx b/ui/src/components/access/CompanySettingsNav.tsx new file mode 100644 index 00000000000..daa34a86b5b --- /dev/null +++ b/ui/src/components/access/CompanySettingsNav.tsx @@ -0,0 +1,46 @@ +import { PageTabBar } from "@/components/PageTabBar"; +import { Tabs } from "@/components/ui/tabs"; +import { useLocation, useNavigate } from "@/lib/router"; + +const items = [ + { value: "general", label: "General", href: "/company/settings" }, + { value: "access", label: "Access", href: "/company/settings/access" }, + { value: "invites", label: "Invites", href: "/company/settings/invites" }, +] as const; + +type CompanySettingsTab = (typeof items)[number]["value"]; + +export function getCompanySettingsTab(pathname: string): CompanySettingsTab { + if (pathname.includes("/company/settings/access")) { + return "access"; + } + + if (pathname.includes("/company/settings/invites")) { + return "invites"; + } + + return "general"; +} + +export function CompanySettingsNav() { + const location = useLocation(); + const navigate = useNavigate(); + const activeTab = getCompanySettingsTab(location.pathname); + + function handleTabChange(value: string) { + const nextTab = items.find((item) => item.value === value); + if (!nextTab || nextTab.value === activeTab) return; + navigate(nextTab.href); + } + + return ( + + ({ value, label }))} + value={activeTab} + onValueChange={handleTabChange} + align="start" + /> + + ); +}