diff --git a/ui/src/components/CompanySettingsSidebar.test.tsx b/ui/src/components/CompanySettingsSidebar.test.tsx
index 8b33169357a..fa9a777af76 100644
--- a/ui/src/components/CompanySettingsSidebar.test.tsx
+++ b/ui/src/components/CompanySettingsSidebar.test.tsx
@@ -2,10 +2,14 @@
import { act } from "react";
import { createRoot } from "react-dom/client";
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { CompanySettingsSidebar } from "./CompanySettingsSidebar";
const sidebarNavItemMock = vi.hoisted(() => vi.fn());
+const mockSidebarBadgesApi = vi.hoisted(() => ({
+ get: vi.fn(),
+}));
vi.mock("@/lib/router", () => ({
Link: ({
@@ -25,6 +29,7 @@ vi.mock("@/lib/router", () => ({
vi.mock("@/context/CompanyContext", () => ({
useCompany: () => ({
+ selectedCompanyId: "company-1",
selectedCompany: { id: "company-1", name: "Paperclip" },
}),
}));
@@ -41,21 +46,39 @@ vi.mock("./SidebarNavItem", () => ({
to: string;
label: string;
end?: boolean;
+ badge?: number;
}) => {
sidebarNavItemMock(props);
return
{props.label}
;
},
}));
+vi.mock("@/api/sidebarBadges", () => ({
+ sidebarBadgesApi: mockSidebarBadgesApi,
+}));
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(globalThis as any).IS_REACT_ACT_ENVIRONMENT = true;
+async function flushReact() {
+ await act(async () => {
+ await Promise.resolve();
+ await new Promise((resolve) => window.setTimeout(resolve, 0));
+ });
+}
+
describe("CompanySettingsSidebar", () => {
let container: HTMLDivElement;
beforeEach(() => {
container = document.createElement("div");
document.body.appendChild(container);
+ mockSidebarBadgesApi.get.mockResolvedValue({
+ inbox: 0,
+ approvals: 0,
+ failedRuns: 0,
+ joinRequests: 2,
+ });
});
afterEach(() => {
@@ -66,10 +89,18 @@ describe("CompanySettingsSidebar", () => {
it("renders the company back link and the settings sections in the sidebar", async () => {
const root = createRoot(container);
+ const queryClient = new QueryClient({
+ defaultOptions: { queries: { retry: false } },
+ });
await act(async () => {
- root.render();
+ root.render(
+
+
+ ,
+ );
});
+ await flushReact();
expect(container.textContent).toContain("Paperclip");
expect(container.textContent).toContain("Company Settings");
@@ -87,6 +118,7 @@ describe("CompanySettingsSidebar", () => {
expect.objectContaining({
to: "/company/settings/access",
label: "Access",
+ badge: 2,
end: true,
}),
);
diff --git a/ui/src/components/CompanySettingsSidebar.tsx b/ui/src/components/CompanySettingsSidebar.tsx
index 598b448a659..6339cf1fddc 100644
--- a/ui/src/components/CompanySettingsSidebar.tsx
+++ b/ui/src/components/CompanySettingsSidebar.tsx
@@ -1,12 +1,34 @@
+import { useQuery } from "@tanstack/react-query";
import { ChevronLeft, MailPlus, Settings, Shield, SlidersHorizontal } from "lucide-react";
+import { sidebarBadgesApi } from "@/api/sidebarBadges";
+import { ApiError } from "@/api/client";
import { Link } from "@/lib/router";
+import { queryKeys } from "@/lib/queryKeys";
import { useCompany } from "@/context/CompanyContext";
import { useSidebar } from "@/context/SidebarContext";
import { SidebarNavItem } from "./SidebarNavItem";
export function CompanySettingsSidebar() {
- const { selectedCompany } = useCompany();
+ const { selectedCompany, selectedCompanyId } = useCompany();
const { isMobile, setSidebarOpen } = useSidebar();
+ const { data: badges } = useQuery({
+ queryKey: selectedCompanyId
+ ? queryKeys.sidebarBadges(selectedCompanyId)
+ : ["sidebar-badges", "__disabled__"] as const,
+ queryFn: async () => {
+ try {
+ return await sidebarBadgesApi.get(selectedCompanyId!);
+ } catch (error) {
+ if (error instanceof ApiError && (error.status === 401 || error.status === 403)) {
+ return null;
+ }
+ throw error;
+ }
+ },
+ enabled: !!selectedCompanyId,
+ retry: false,
+ refetchInterval: 15_000,
+ });
return (