diff --git a/ui/changelog.d/enterprise-billing-navigation.fixed.md b/ui/changelog.d/enterprise-billing-navigation.fixed.md new file mode 100644 index 0000000000..0ea2b4d31e --- /dev/null +++ b/ui/changelog.d/enterprise-billing-navigation.fixed.md @@ -0,0 +1 @@ +Billing navigation is hidden when Cloud billing is disabled, including Enterprise deployments diff --git a/ui/components/layout/app-sidebar/app-sidebar-content.tsx b/ui/components/layout/app-sidebar/app-sidebar-content.tsx index c32a7d5048..ca2b28949d 100644 --- a/ui/components/layout/app-sidebar/app-sidebar-content.tsx +++ b/ui/components/layout/app-sidebar/app-sidebar-content.tsx @@ -24,10 +24,15 @@ interface AppSidebarContentProps { export function AppSidebarContent({ onSelect }: AppSidebarContentProps) { const pathname = usePathname(); const { permissions } = useAuth(); - const { apiDocsUrl } = useRuntimeConfig(); + const { apiDocsUrl, cloudBillingEnabled } = useRuntimeConfig(); const mode = useAppSidebarMode((state) => state.mode); const isCloudEnvironment = isCloud(); - const sections = getNavigationConfig({ pathname, apiDocsUrl, permissions }); + const sections = getNavigationConfig({ + pathname, + apiDocsUrl, + cloudBillingEnabled, + permissions, + }); const showChat = isCloudEnvironment && mode === APP_SIDEBAR_MODE.CHAT; return ( diff --git a/ui/components/layout/app-sidebar/navigation-config.test.ts b/ui/components/layout/app-sidebar/navigation-config.test.ts index 1ad3fc7228..9b3cab3501 100644 --- a/ui/components/layout/app-sidebar/navigation-config.test.ts +++ b/ui/components/layout/app-sidebar/navigation-config.test.ts @@ -157,6 +157,7 @@ describe("getNavigationConfig", () => { const billing = getNavigationConfig({ pathname: "/billing", apiDocsUrl: null, + cloudBillingEnabled: true, permissions, }) .flatMap((section) => section.items) @@ -183,17 +184,28 @@ describe("getNavigationConfig", () => { const cloudItems = getNavigationConfig({ pathname: "/", apiDocsUrl: null, + cloudBillingEnabled: true, permissions, }).flatMap((section) => section.items); + const enterpriseItems = getNavigationConfig({ + pathname: "/", + apiDocsUrl: null, + cloudBillingEnabled: false, + permissions: { ...permissions, manage_billing: true }, + }).flatMap((section) => section.items); vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false"); const localItems = getNavigationConfig({ pathname: "/", apiDocsUrl: null, + cloudBillingEnabled: true, permissions: { ...permissions, manage_billing: true }, }).flatMap((section) => section.items); // Then expect(cloudItems.find((item) => item.label === "Billing")).toBeUndefined(); + expect( + enterpriseItems.find((item) => item.label === "Billing"), + ).toBeUndefined(); expect(localItems.find((item) => item.label === "Billing")).toBeUndefined(); }); diff --git a/ui/components/layout/app-sidebar/navigation-config.ts b/ui/components/layout/app-sidebar/navigation-config.ts index 40e15cb6e7..05ff31abae 100644 --- a/ui/components/layout/app-sidebar/navigation-config.ts +++ b/ui/components/layout/app-sidebar/navigation-config.ts @@ -31,6 +31,7 @@ import { interface NavigationConfigOptions { pathname: string; apiDocsUrl?: string | null; + cloudBillingEnabled?: boolean; permissions?: RolePermissionAttributes; } @@ -106,6 +107,7 @@ export function filterNavigationByPermissions( export function getNavigationConfig({ pathname, apiDocsUrl = null, + cloudBillingEnabled = false, permissions, }: NavigationConfigOptions): NavigationSection[] { const isCloudEnvironment = isCloud(); @@ -265,7 +267,7 @@ export function getNavigationConfig({ }, ], }, - ...(isCloudEnvironment + ...(isCloudEnvironment && cloudBillingEnabled ? [ { kind: NAVIGATION_ITEM_KIND.LINK, diff --git a/ui/proxy.ts b/ui/proxy.ts index d3bb8eb20e..8784fd260f 100644 --- a/ui/proxy.ts +++ b/ui/proxy.ts @@ -2,6 +2,7 @@ import { NextResponse } from "next/server"; import type { NextAuthRequest } from "next-auth"; import { auth } from "@/auth.config"; +import { readEnv } from "@/lib/runtime-env"; const publicRoutes = [ "/sign-in", @@ -23,6 +24,8 @@ export default auth((req: NextAuthRequest) => { const user = req.auth?.user; const sessionError = req.auth?.error; + const cloudBillingEnabled = + (readEnv("CLOUD_BILLING_ENABLED") ?? "false") !== "false"; // If there's a session error (e.g., RefreshAccessTokenError), redirect to login with error info if (sessionError && !isPublicRoute(pathname)) { @@ -38,13 +41,16 @@ export default auth((req: NextAuthRequest) => { return NextResponse.redirect(signInUrl); } + if ( + pathname.startsWith("/billing") && + (!cloudBillingEnabled || user?.permissions?.manage_billing !== true) + ) { + return NextResponse.redirect(new URL("/profile", req.url)); + } + if (user?.permissions) { const permissions = user.permissions; - if (pathname.startsWith("/billing") && !permissions.manage_billing) { - return NextResponse.redirect(new URL("/profile", req.url)); - } - if ( pathname.startsWith("/integrations") && !permissions.manage_integrations