Split processor sidebar into PDF Processor and PDF Platform sections (#7072)

This commit is contained in:
Anthony Stirling
2026-07-19 18:42:43 +01:00
committed by GitHub
parent 8b179fbc55
commit b509fae0c8
7 changed files with 66 additions and 37 deletions
@@ -7202,6 +7202,10 @@ sources = "Sources"
usage = "Usage & Billing"
users = "Users"
[portal.nav.section]
platform = "PDF Platform"
processor = "PDF Processor"
[portal.notifications]
markAllRead = "Mark all read"
title = "Notifications"
@@ -7144,6 +7144,10 @@ sources = "Sources"
usage = "Usage & Billing"
users = "Users"
[portal.nav.section]
platform = "PDF Platform"
processor = "PDF Processor"
[portal.pipelines]
subtitle = "Every automated document pipeline on the backend: an ordered chain of operations over a set of sources, run on a trigger. Click a row for its steps and sources."
title = "Pipelines"
@@ -1,19 +1,14 @@
import { describe, expect, it } from "vitest";
// Resolves to the SaaS override (src/portal-saas) via the @portal cascade.
import {
GROUP_PRIMARY,
GROUP_OPERATIONAL,
GROUP_PROCESSOR,
GROUP_PLATFORM,
} from "@portal/components/sidebarGroups";
describe("sidebarGroups (SaaS)", () => {
it("drops Components from the operational nav", () => {
expect(GROUP_OPERATIONAL.map((e) => e.id)).not.toContain("components");
});
it("inherits the other operational items from base", () => {
expect(GROUP_OPERATIONAL.map((e) => e.id)).toEqual([
"users",
it("inherits the processor group unchanged from base", () => {
expect(GROUP_PROCESSOR.map((e) => e.id)).toEqual([
"home",
"sources",
"policies",
"pipelines",
@@ -21,9 +16,9 @@ describe("sidebarGroups (SaaS)", () => {
]);
});
it("inherits the primary + platform groups unchanged", () => {
expect(GROUP_PRIMARY.map((e) => e.id)).toEqual(["home"]);
it("inherits the platform group unchanged from base", () => {
expect(GROUP_PLATFORM.map((e) => e.id)).toEqual([
"users",
"infrastructure",
"usage",
"docs",
@@ -1,11 +1,11 @@
import {
GROUP_PRIMARY,
GROUP_OPERATIONAL,
GROUP_PROCESSOR,
GROUP_PLATFORM,
type NavEntry,
type NavGroup,
} from "@portal-proprietary/components/sidebarGroups";
// SaaS shadows the base nav groups so sections not yet shipped there can be
// dropped. Nothing is currently removed, so the base groups pass through as-is.
export { GROUP_PRIMARY, GROUP_OPERATIONAL, GROUP_PLATFORM };
export type { NavEntry };
export { GROUP_PROCESSOR, GROUP_PLATFORM };
export type { NavEntry, NavGroup };
@@ -46,18 +46,32 @@
gap: 0.5rem;
}
/* Each section is a labelled card: a small header above its nav items. */
.portal-sidebar__section {
background: var(--color-surface);
border: 1px solid var(--color-sidebar-divider);
border-radius: 0.625rem;
padding: 0.5rem 0.375rem 0.375rem;
display: flex;
flex-direction: column;
gap: 0.375rem;
}
.portal-sidebar__section-label {
margin: 0;
padding: 0 0.5rem;
font-size: 0.6875rem;
font-weight: 600;
letter-spacing: 0.02em;
color: var(--color-text-3);
}
.portal-sidebar__group {
display: flex;
flex-direction: column;
gap: 0.125rem;
}
.portal-sidebar__divider {
height: 1px;
background: var(--color-sidebar-divider);
margin: 0.25rem 0;
}
/* Footer */
.portal-sidebar__footer {
border-top: 1px solid var(--color-sidebar-divider);
@@ -11,13 +11,18 @@ import wordmarkLight from "@app/assets/brand/modern-logo/StirlingProcessorLogoBl
import wordmarkDark from "@app/assets/brand/modern-logo/StirlingProcessorLogoWhiteText.svg";
import { SettingsIcon } from "@portal/components/icons";
import {
GROUP_PRIMARY,
GROUP_OPERATIONAL,
GROUP_PROCESSOR,
GROUP_PLATFORM,
type NavEntry,
type NavGroup,
} from "@portal/components/sidebarGroups";
import "@portal/components/Sidebar.css";
const NAV_SECTIONS: NavGroup[] = [
{ labelKey: "portal.nav.section.processor", entries: GROUP_PROCESSOR },
{ labelKey: "portal.nav.section.platform", entries: GROUP_PLATFORM },
];
export function Sidebar() {
const { activeView, setActiveView } = useView();
const { theme } = useTheme();
@@ -76,17 +81,16 @@ export function Sidebar() {
</div>
<nav className="portal-sidebar__nav">
<div className="portal-sidebar__group">
{renderGroup(GROUP_PRIMARY)}
</div>
<div className="portal-sidebar__divider" aria-hidden />
<div className="portal-sidebar__group">
{renderGroup(GROUP_OPERATIONAL)}
</div>
<div className="portal-sidebar__divider" aria-hidden />
<div className="portal-sidebar__group">
{renderGroup(GROUP_PLATFORM)}
</div>
{NAV_SECTIONS.map((section) => (
<section key={section.labelKey} className="portal-sidebar__section">
<h2 className="portal-sidebar__section-label">
{t(section.labelKey)}
</h2>
<div className="portal-sidebar__group">
{renderGroup(section.entries)}
</div>
</section>
))}
</nav>
<div className="portal-sidebar__footer">
@@ -19,19 +19,27 @@ export interface NavEntry {
externalUrl?: string;
}
export interface NavGroup {
/** i18n key for the section header shown above the group. */
labelKey: string;
entries: NavEntry[];
}
// Sidebar nav groups. This is a flavor seam: the SaaS build shadows this file to
// drop sections not yet shipped there (see src/portal-saas/components/sidebarGroups).
export const GROUP_PRIMARY: NavEntry[] = [{ id: "home", icon: <HomeIcon /> }];
export const GROUP_OPERATIONAL: NavEntry[] = [
{ id: "users", icon: <UsersIcon /> },
// The processor's own workflow: home plus the pipeline it feeds.
export const GROUP_PROCESSOR: NavEntry[] = [
{ id: "home", icon: <HomeIcon /> },
{ id: "sources", icon: <SourcesIcon /> },
{ id: "policies", icon: <PoliciesIcon /> },
{ id: "pipelines", icon: <PipelinesIcon /> },
{ id: "documents", icon: <DocumentsIcon /> },
];
// The wider platform around the processor: people, infra, billing, docs.
export const GROUP_PLATFORM: NavEntry[] = [
{ id: "users", icon: <UsersIcon /> },
{ id: "infrastructure", icon: <InfrastructureIcon /> },
{ id: "usage", icon: <UsageIcon /> },
{ id: "docs", icon: <DocsIcon /> },