feat(app): M5.2 — customer-area route shells + role-gated nav
Adds the M5.2 surface set per PLATFORM_ARCHITECTURE.md §5a. Every route is a navigable skeleton with a per-route empty-state pointing at the milestone that ships the real content; the Nav component filters links by session.org_roles so an IT_ADMIN sees settings + api-keys, a CXO sees billing, a USER sees only dashboard + products + support, etc. New surfaces (10): /[slug]/products M10.1 /[slug]/projects M10.1 /[slug]/catalog M11.1 /[slug]/settings M10.1 /[slug]/settings/users M10.1 /[slug]/settings/api-keys M15.1 /[slug]/settings/integrations M15.2 /[slug]/billing M8.3 /[slug]/audit M10.2 /[slug]/support M9.1 Dashboard upgraded: reads session.products, renders one tile per entitled product (real tile content lands in M10.1). Empty-state when the user has no entitlements yet — links into the catalog flow. Backstage stub at /__backstage__ — middleware already rewrites backstage.<apex>/* to this prefix; real RBAC against BREAKPILOT_ADMIN / SUPPORT_ENGINEER / SALES_REP lands in M13.2. Layout enforces tenant-slug match: a session with tenant_slug=A trying to view /B/... gets redirected to /A/dashboard. Prevents JWT-replay across tenants (defence in depth; the real guard is at the API layer, which M4.3 adds in tenant-registry). src/lib/session.ts is the single source of truth for the role matrix + canSee(surface) helper. 13 vitest cases, 100% coverage of src/lib. Refs: M5.2
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
// Reusable empty-state for a customer-area route shell. Every M5.2 route
|
||||
// renders one of these; real content lands in M10.1 / M11.x / M12.x /
|
||||
// M14.x / etc.
|
||||
|
||||
export function ShellEmpty({
|
||||
title,
|
||||
description,
|
||||
milestone,
|
||||
}: {
|
||||
title: string;
|
||||
description: string;
|
||||
milestone: string;
|
||||
}) {
|
||||
return (
|
||||
<section style={{ maxWidth: 720 }}>
|
||||
<h1 style={{ fontSize: 28, marginBottom: 8 }}>{title}</h1>
|
||||
<p style={{ color: "#444", marginBottom: 24 }}>{description}</p>
|
||||
<div
|
||||
style={{
|
||||
padding: 16,
|
||||
border: "1px dashed #ddd",
|
||||
borderRadius: 8,
|
||||
background: "#fafafa",
|
||||
color: "#666",
|
||||
fontSize: 14,
|
||||
}}
|
||||
>
|
||||
This surface is a route shell. Real implementation lands in{" "}
|
||||
<code>{milestone}</code>. See{" "}
|
||||
<a
|
||||
href="https://gitea.meghsakha.com/platform/docs/src/branch/main/PLATFORM_ARCHITECTURE.md"
|
||||
style={{ color: "#0070f3" }}
|
||||
>
|
||||
PLATFORM_ARCHITECTURE.md §5a
|
||||
</a>{" "}
|
||||
for the spec.
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export function NotAuthorized() {
|
||||
return (
|
||||
<section style={{ maxWidth: 720 }}>
|
||||
<h1 style={{ fontSize: 28, marginBottom: 8 }}>403 — Not authorized</h1>
|
||||
<p style={{ color: "#444" }}>
|
||||
This surface requires a role your account doesn't have. If you think
|
||||
that's a mistake, ask an IT_ADMIN on your tenant to invite you with
|
||||
the right role.
|
||||
</p>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user