Compare commits

...
Author SHA1 Message Date
Connor Yoh 6e5267162c Hide Infrastructure tab in portal SaaS build
The Infrastructure view isn't wired up for SaaS yet, so hide it from the
sidebar there until it is. Adds a HIDDEN_NAV_VIEWS flavor seam: core is
empty (self-hosted shows the full nav) and the portal-saas shadow hides
"infrastructure". The Sidebar filters nav entries against it.

Bring it back by emptying the set in portal-saas/components/navVisibility.
2026-07-08 14:49:45 +01:00
4 changed files with 41 additions and 10 deletions
@@ -0,0 +1,10 @@
import { describe, expect, it } from "vitest";
// Resolves to the SaaS override (src/portal-saas/components) via the @portal cascade.
import { HIDDEN_NAV_VIEWS } from "@portal/components/navVisibility";
describe("navVisibility (SaaS) — hidden nav entries", () => {
it("hides the Infrastructure tab (not built for SaaS yet)", () => {
expect(HIDDEN_NAV_VIEWS.has("infrastructure")).toBe(true);
});
});
@@ -0,0 +1,10 @@
import type { ViewId } from "@portal/contexts/ViewContext";
/**
* SaaS flavor: the Infrastructure tab isn't built for SaaS yet, so hide it from
* the sidebar until it is. Drop the entry here (or empty the set) to bring it
* back.
*/
export const HIDDEN_NAV_VIEWS: ReadonlySet<ViewId> = new Set<ViewId>([
"infrastructure",
]);
@@ -7,6 +7,7 @@ import { useTier } from "@portal/contexts/TierContext";
import { useTheme } from "@portal/contexts/ThemeContext";
import { useUI } from "@portal/contexts/UIContext";
import { LinkAccountFooterItem } from "@portal/components/LinkAccountFooterItem";
import { HIDDEN_NAV_VIEWS } from "@portal/components/navVisibility";
import { useAsync } from "@portal/hooks/useAsync";
import { fetchHomeKpis, type KpiEntry } from "@portal/api/home";
import { EDITOR_URL, EDITOR_IS_SAME_APP } from "@portal/auth/editorUrl";
@@ -129,16 +130,18 @@ export function Sidebar() {
// a takeover modal (matching the marketing prototype).
function renderGroup(entries: NavEntry[]) {
return entries.map((entry) => (
<NavItem
key={entry.id}
id={entry.id}
label={t(`portal.nav.${entry.id}`)}
icon={entry.icon}
isActive={activeView === entry.id}
onClick={(id) => setActiveView(id as ViewId)}
/>
));
return entries
.filter((entry) => !HIDDEN_NAV_VIEWS.has(entry.id))
.map((entry) => (
<NavItem
key={entry.id}
id={entry.id}
label={t(`portal.nav.${entry.id}`)}
icon={entry.icon}
isActive={activeView === entry.id}
onClick={(id) => setActiveView(id as ViewId)}
/>
));
}
return (
@@ -0,0 +1,8 @@
import type { ViewId } from "@portal/contexts/ViewContext";
/**
* Nav entries the sidebar should hide for the current build flavor. Self-hosted
* shows the full nav, so this is empty. The SaaS build shadows this file to hide
* views that aren't wired up there yet (see portal-saas/components/navVisibility).
*/
export const HIDDEN_NAV_VIEWS: ReadonlySet<ViewId> = new Set();