-
-
Partner Portal
-
Co-sell deals, technical onboarding, and solution registration
-
+
+
+
+
Partner Portal
+
Co-sell deals, technical onboarding, and solution registration
+
-
- {partnerSections.map((section) => {
- const Icon = section.icon;
- return (
-
-
-
-
- {section.title}
-
- {section.description}
-
-
-
- {section.features.map((feature) => (
- -
- •
- {feature}
-
- ))}
-
-
- Access
-
-
-
- );
- })}
+
+ {partnerSections.map((section) => {
+ const Icon = section.icon;
+ return (
+
+
+
+
+ {section.title}
+
+ {section.description}
+
+
+
+ {section.features.map((feature) => (
+ -
+ •
+ {feature}
+
+ ))}
+
+
+ Access
+
+
+
+ );
+ })}
+
-
+
);
}
-
diff --git a/portal/src/app/security/page.tsx b/portal/src/app/security/page.tsx
new file mode 100644
index 0000000..51c9f4e
--- /dev/null
+++ b/portal/src/app/security/page.tsx
@@ -0,0 +1,19 @@
+import { FeaturePreviewPage } from '@/components/preview/FeaturePreviewPage';
+
+export default function SecurityPage() {
+ return (
+
+ );
+}
diff --git a/portal/src/app/users/page.tsx b/portal/src/app/users/page.tsx
new file mode 100644
index 0000000..3a9a45c
--- /dev/null
+++ b/portal/src/app/users/page.tsx
@@ -0,0 +1,19 @@
+import { FeaturePreviewPage } from '@/components/preview/FeaturePreviewPage';
+
+export default function UsersPage() {
+ return (
+
+ );
+}
diff --git a/portal/src/components/auth/PortalSignInCard.tsx b/portal/src/components/auth/PortalSignInCard.tsx
new file mode 100644
index 0000000..2f284f7
--- /dev/null
+++ b/portal/src/components/auth/PortalSignInCard.tsx
@@ -0,0 +1,61 @@
+'use client';
+
+import { signIn } from 'next-auth/react';
+import { useCallback, useState, type ReactNode } from 'react';
+
+import { CloudflareTurnstile } from '@/components/security/CloudflareTurnstile';
+
+const TURNSTILE_SITE_KEY = process.env.NEXT_PUBLIC_CLOUDFLARE_TURNSTILE_SITE_KEY;
+
+export interface PortalSignInCardProps {
+ badge?: string;
+ title: string;
+ subtitle: string;
+ callbackUrl?: string;
+ /** Extra hint under the sign-in button (e.g. dev IdP note) */
+ footer?: ReactNode;
+}
+
+export function PortalSignInCard({
+ badge = 'Sankofa Phoenix',
+ title,
+ subtitle,
+ callbackUrl = '/',
+ footer,
+}: PortalSignInCardProps) {
+ const [turnstileToken, setTurnstileToken] = useState
(() =>
+ TURNSTILE_SITE_KEY ? null : ''
+ );
+
+ const onTurnstileToken = useCallback((token: string | null) => {
+ setTurnstileToken(token);
+ }, []);
+
+ const canSignIn = !TURNSTILE_SITE_KEY || Boolean(turnstileToken);
+
+ return (
+
+
{badge}
+
{title}
+
{subtitle}
+
+ {TURNSTILE_SITE_KEY ? (
+
+ ) : null}
+
+
+
+ {footer}
+
+ );
+}
diff --git a/portal/src/components/corporate/CorporateFooter.tsx b/portal/src/components/corporate/CorporateFooter.tsx
new file mode 100644
index 0000000..27e9e07
--- /dev/null
+++ b/portal/src/components/corporate/CorporateFooter.tsx
@@ -0,0 +1,94 @@
+import Link from 'next/link';
+
+import { ECOSYSTEM_SIGN_IN_PATH } from '@/lib/corporate-site-data';
+
+function isExternalHref(href: string) {
+ return href.startsWith('http://') || href.startsWith('https://');
+}
+
+const footerColumns = [
+ {
+ title: 'Products',
+ links: [
+ { label: 'Phoenix Cloud', href: 'https://phoenix.sankofa.nexus' },
+ { label: 'Complete Credential', href: 'https://cc.sankofa.nexus' },
+ { label: 'Sankofa Studio', href: 'https://studio.sankofa.nexus/studio/' },
+ { label: 'Client Portal', href: 'https://portal.sankofa.nexus' },
+ ],
+ },
+ {
+ title: 'Platform',
+ links: [
+ { label: 'Identity (Keycloak)', href: 'https://keycloak.sankofa.nexus' },
+ { label: 'Blockchain Explorer', href: 'https://explorer.d-bis.org' },
+ { label: 'Documentation', href: 'https://docs.d-bis.org' },
+ { label: 'Marketplace', href: 'https://portal.sankofa.nexus' },
+ ],
+ },
+ {
+ title: 'Company',
+ links: [
+ { label: 'About Sankofa', href: '#resources' },
+ { label: 'Institutional registry', href: 'https://d-bis.org/cb/dbis' },
+ { label: 'Partner program', href: '/partner' },
+ { label: 'Contact', href: 'https://portal.sankofa.nexus' },
+ ],
+ },
+ {
+ title: 'Access',
+ links: [
+ { label: 'Sign in to ecosystem', href: ECOSYSTEM_SIGN_IN_PATH },
+ { label: 'Phoenix console', href: 'https://phoenix.sankofa.nexus' },
+ { label: 'Operator dashboard', href: 'https://dash.sankofa.nexus' },
+ { label: 'Admin console', href: 'https://admin.sankofa.nexus' },
+ ],
+ },
+];
+
+export function CorporateFooter() {
+ return (
+
+ );
+}
diff --git a/portal/src/components/corporate/CorporateHeader.tsx b/portal/src/components/corporate/CorporateHeader.tsx
new file mode 100644
index 0000000..46aae65
--- /dev/null
+++ b/portal/src/components/corporate/CorporateHeader.tsx
@@ -0,0 +1,122 @@
+'use client';
+
+import { ChevronDown, LogIn, Menu, X } from 'lucide-react';
+import Link from 'next/link';
+import { useState } from 'react';
+
+import { corporateNav, ECOSYSTEM_SIGN_IN_PATH } from '@/lib/corporate-site-data';
+import { cn } from '@/lib/utils';
+
+export function CorporateHeader() {
+ const [mobileOpen, setMobileOpen] = useState(false);
+
+ return (
+
+
+ {/* Top-left: brand + ecosystem sign-in (AWS / Microsoft pattern) */}
+
+
+
+ S
+
+
+ Sankofa
+
+
+
+
+
+
+
+ Sign in
+
+
+
+ {/* Desktop nav */}
+
+
+ {/* Desktop CTAs */}
+
+
+ Contact sales
+
+
+ Get started
+
+
+
+ {/* Mobile menu toggle */}
+
+
+
+ {/* Mobile drawer */}
+
+
+
+
+ );
+}
diff --git a/portal/src/components/corporate/CorporateLandingPage.tsx b/portal/src/components/corporate/CorporateLandingPage.tsx
new file mode 100644
index 0000000..02694b1
--- /dev/null
+++ b/portal/src/components/corporate/CorporateLandingPage.tsx
@@ -0,0 +1,270 @@
+'use client';
+
+import { ArrowRight, ExternalLink } from 'lucide-react';
+import Link from 'next/link';
+
+import { CorporateFooter } from '@/components/corporate/CorporateFooter';
+import { CorporateHeader } from '@/components/corporate/CorporateHeader';
+import { InstitutionalGradeSection } from '@/components/corporate/InstitutionalGradeSection';
+import {
+ ECOSYSTEM_SIGN_IN_PATH,
+ philosophySteps,
+ platformServices,
+ productDivisions,
+ solutionVerticals,
+} from '@/lib/corporate-site-data';
+
+export function CorporateLandingPage() {
+ return (
+
+
+
+
+ {/* Hero */}
+
+
+
+
+ Sovereign Technologies
+
+
+ Build on infrastructure you{' '}
+
+ own and control
+
+
+
+ Sankofa delivers sovereign cloud, identity, financial rails, and credential infrastructure —
+ the complete ecosystem for institutions that cannot depend on hyperscaler public cloud.
+
+
+
+ Explore Phoenix Cloud
+
+
+
+ Sign in to ecosystem
+
+
+
+
+
+ {/* Philosophy */}
+
+
+
+ {philosophySteps.map((step, index) => (
+
+
+ {String(index + 1).padStart(2, '0')}
+
+
{step.verb}
+
{step.detail}
+
+ ))}
+
+
+
+
+ {/* Product divisions */}
+
+
+
+
Products
+
+ From sovereign cloud to verifiable credentials — integrated products under one ecosystem,
+ modeled for institutional scale.
+
+
+
+
+ {productDivisions.map((product) => {
+ const Icon = product.icon;
+ return (
+
+
+
+
+
+ {product.external ? (
+
+ ) : null}
+
+
+ {product.tagline}
+
+ {product.name}
+ {product.description}
+
+ {product.highlights.map((item) => (
+ -
+
+ {item}
+
+ ))}
+
+
+ Learn more
+
+
+
+ );
+ })}
+
+
+
+
+ {/* Platform services */}
+
+
+
+
Platform services
+
+ Owned core primitives — ledger, identity, wallet, orchestration — available through the
+ Phoenix marketplace without third-party platform lock-in.
+
+
+
+
+ {platformServices.map((service) => {
+ const Icon = service.icon;
+ return (
+
+
+
+
+
+
+
{service.category}
+
{service.name}
+
+
+
{service.description}
+
+ );
+ })}
+
+
+
+
+ Browse full marketplace catalog
+
+
+
+
+
+
+ {/* Solutions */}
+
+
+
+
Industry solutions
+
+ Purpose-built stacks for regulated industries — public sector, finance, healthcare, and telecom.
+
+
+
+
+ {solutionVerticals.map((vertical) => {
+ const Icon = vertical.icon;
+ return (
+
+
+
+
+
+
{vertical.name}
+
{vertical.description}
+
+
+ );
+ })}
+
+
+
+
+
+
+ {/* Resources / trust */}
+
+
+
+
+
Built for institutional trust
+
+ Sankofa Phoenix operates under sovereign governance with SOC 2, GDPR, and sector-specific
+ compliance pathways. Identity, ledger, and credential services are designed for operators
+ who need auditability without outsourcing control.
+
+
+ -
+ ✓ Sovereign Keycloak identity — no Azure AD dependency
+
+ -
+ ✓ Multi-tenant Proxmox orchestration with GitOps
+
+ -
+ ✓ Chain 138 DeFi Oracle Meta Mainnet & cross-chain mesh
+
+ -
+ ✓ Complete Credential eIDAS-aligned issuance
+
+
+
+
+
+
Get started
+
Join the Sankofa ecosystem
+
+ Sign in to access Phoenix Cloud, marketplace subscriptions, partner tools, and client
+ workspaces — one identity across the platform.
+
+
+
+ Sign in to ecosystem
+
+
+ Phoenix Cloud
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/portal/src/components/corporate/InstitutionalGradeSection.tsx b/portal/src/components/corporate/InstitutionalGradeSection.tsx
new file mode 100644
index 0000000..0f17422
--- /dev/null
+++ b/portal/src/components/corporate/InstitutionalGradeSection.tsx
@@ -0,0 +1,186 @@
+import { ArrowRight, ShieldCheck } from 'lucide-react';
+import Link from 'next/link';
+
+import {
+ gradeBadgeClass,
+ institutionalGradeRubric,
+ scoreBarClass,
+ sovereignInstitutionGrades,
+} from '@/lib/corporate-site-data';
+
+const audienceLabels = {
+ sovereign: 'Sovereign & public sector',
+ regulated: 'Regulated counterparties',
+ platform: 'Platform & L1',
+} as const;
+
+export function InstitutionalGradeSection() {
+ const sovereignRows = sovereignInstitutionGrades.filter((e) => e.audience === 'sovereign');
+ const otherRows = sovereignInstitutionGrades.filter((e) => e.audience !== 'sovereign');
+
+ return (
+
+
+
+
+
+
+ Institutional readiness
+
+
+ Grade & score for sovereign institutions
+
+
+ Transparent engineering scorecards aligned with the DBIS institutional rubric — the same
+ framework used for ecosystem readiness, jurisdiction matrices, and settlement evidence.
+
+
+
+ Informational only — not legal advice, a regulatory determination, or an audit certificate.
+ Counsel owns statutory interpretation. Scores reflect repo artifacts and live probes as of each
+ assessment date.
+
+
+
+ {/* Rubric legend */}
+
+
+
+
+ | Grade |
+ Score |
+ Institutional meaning |
+
+
+
+ {institutionalGradeRubric.map((row) => (
+
+ |
+
+ {row.grade}
+
+ |
+ {row.range} |
+ {row.meaning} |
+
+ ))}
+
+
+
+
+ {/* Sovereign institution scorecards */}
+
+
{audienceLabels.sovereign}
+
+ {sovereignRows.map((entry) => (
+
+ ))}
+
+
+
+
+
Related institutional assessments
+
+ {otherRows.map((entry) => (
+
+ ))}
+
+
+
+
+
+ DBIS institutional registry
+
+
+
+ ·
+
+
+ Compliance matrices & onboarding charter
+
+
+
+
+
+ );
+}
+
+function GradeCard({
+ entry,
+ compact = false,
+}: {
+ entry: (typeof sovereignInstitutionGrades)[number];
+ compact?: boolean;
+}) {
+ const pct = Math.round((entry.score / entry.maxScore) * 100);
+ const inner = (
+ <>
+
+
+
{entry.name}
+ {!compact ? (
+
Assessed {entry.assessmentDate}
+ ) : null}
+
+
+ {entry.letterGrade}
+
+
+
+
+ {entry.score}
+ / {entry.maxScore}
+
+
+
+
+
+ {entry.tier}
+
+
+ {compact ? (
+ {entry.assessmentDate}
+ ) : null}
+ >
+ );
+
+ const className = compact
+ ? 'rounded-xl border border-gray-800 bg-gray-900/30 p-4 transition hover:border-gray-700'
+ : 'rounded-2xl border border-gray-800 bg-gray-900/40 p-6 transition hover:border-orange-500/30';
+
+ if (entry.href) {
+ return (
+
+ {inner}
+ {!compact ? (
+
+ View assessment
+
+
+ ) : null}
+
+ );
+ }
+
+ return {inner};
+}
diff --git a/portal/src/components/layout/AppShell.tsx b/portal/src/components/layout/AppShell.tsx
new file mode 100644
index 0000000..6886b91
--- /dev/null
+++ b/portal/src/components/layout/AppShell.tsx
@@ -0,0 +1,35 @@
+'use client';
+
+import { useSession } from 'next-auth/react';
+
+import { KeyboardShortcutsProvider } from '@/components/KeyboardShortcutsProvider';
+import { MobileNavigation } from '@/components/layout/MobileNavigation';
+import { PortalBreadcrumbs } from '@/components/layout/PortalBreadcrumbs';
+import { PortalHeader } from '@/components/layout/PortalHeader';
+import { PortalSidebar } from '@/components/layout/PortalSidebar';
+
+/**
+ * Full console chrome only after sign-in. Unauthenticated routes render children alone
+ * so the landing / sign-in view is not squeezed beside sidebar + mega-header.
+ */
+export function AppShell({ children }: { children: React.ReactNode }) {
+ const { status } = useSession();
+
+ if (status !== 'authenticated') {
+ return <>{children}>;
+ }
+
+ return (
+
+
+
+ );
+}
diff --git a/portal/src/components/layout/PortalHeader.tsx b/portal/src/components/layout/PortalHeader.tsx
index f8c5986..49babb5 100644
--- a/portal/src/components/layout/PortalHeader.tsx
+++ b/portal/src/components/layout/PortalHeader.tsx
@@ -10,26 +10,32 @@ export function PortalHeader() {
return (
-
-
-
+
+
+
Nexus Console
-
- {/* Search Bar - Enterprise-class search-first UX */}
-
+
+
-
@@ -137,4 +154,3 @@ export function OnboardingWizard({ steps, onComplete }: OnboardingWizardProps) {
);
}
-
diff --git a/portal/src/components/preview/FeaturePreviewPage.tsx b/portal/src/components/preview/FeaturePreviewPage.tsx
new file mode 100644
index 0000000..3b889f6
--- /dev/null
+++ b/portal/src/components/preview/FeaturePreviewPage.tsx
@@ -0,0 +1,81 @@
+'use client';
+
+import Link from 'next/link';
+
+import { Badge } from '@/components/ui/badge';
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/Card';
+import { cn } from '@/lib/utils';
+
+interface PreviewAction {
+ href: string;
+ label: string;
+}
+
+interface FeaturePreviewPageProps {
+ eyebrow: string;
+ title: string;
+ description: string;
+ status?: 'preview' | 'request-only' | 'active';
+ bullets: readonly string[];
+ primaryAction?: PreviewAction;
+ secondaryAction?: PreviewAction;
+}
+
+export function FeaturePreviewPage({
+ eyebrow,
+ title,
+ description,
+ status = 'preview',
+ bullets,
+ primaryAction,
+ secondaryAction,
+}: FeaturePreviewPageProps) {
+ const primaryActionClassName =
+ 'inline-flex h-10 items-center justify-center rounded-md bg-gray-700 px-4 text-base font-medium text-white transition-colors hover:bg-gray-600';
+ const secondaryActionClassName =
+ 'inline-flex h-10 items-center justify-center rounded-md border border-gray-600 bg-transparent px-4 text-base font-medium text-white transition-colors hover:bg-gray-800';
+
+ return (
+
+
+
{eyebrow}
+
+
{title}
+
+ {status === 'request-only' ? 'Request Only' : status === 'active' ? 'Active' : 'Preview'}
+
+
+
{description}
+
+
+
+
+ Current Scope
+
+ This route is now real and intentionally describes the supported boundary for this workspace area.
+
+
+
+
+ {bullets.map((bullet) => (
+ - • {bullet}
+ ))}
+
+
+
+ {primaryAction ? (
+
+ {primaryAction.label}
+
+ ) : null}
+ {secondaryAction ? (
+
+ {secondaryAction.label}
+
+ ) : null}
+
+
+
+
+ );
+}
diff --git a/portal/src/components/security/CloudflareTurnstile.tsx b/portal/src/components/security/CloudflareTurnstile.tsx
new file mode 100644
index 0000000..52801b2
--- /dev/null
+++ b/portal/src/components/security/CloudflareTurnstile.tsx
@@ -0,0 +1,116 @@
+'use client';
+
+/**
+ * Cloudflare Turnstile widget. Site key is public (NEXT_PUBLIC_*).
+ * Pair with Turnstile secret on any backend route you protect — not the same as Cloudflare DNS API keys.
+ */
+
+import { useEffect, useRef } from 'react';
+
+const SCRIPT_SRC = 'https://challenges.cloudflare.com/turnstile/v0/api.js';
+
+declare global {
+ interface Window {
+ turnstile?: {
+ render: (
+ container: HTMLElement,
+ options: {
+ sitekey: string;
+ callback: (token: string) => void;
+ 'error-callback'?: () => void;
+ 'expired-callback'?: () => void;
+ }
+ ) => string;
+ remove?: (widgetId: string) => void;
+ };
+ }
+}
+
+function loadTurnstileScript(): Promise {
+ if (typeof window === 'undefined') {
+ return Promise.reject(new Error('no window'));
+ }
+ if (window.turnstile) {
+ return Promise.resolve();
+ }
+ const existing = document.querySelector(`script[src="${SCRIPT_SRC}"]`);
+ if (existing) {
+ return new Promise((resolve, reject) => {
+ const t0 = Date.now();
+ const tick = () => {
+ if (window.turnstile) {
+ resolve();
+ return;
+ }
+ if (Date.now() - t0 > 15000) {
+ reject(new Error('Turnstile script timeout'));
+ return;
+ }
+ requestAnimationFrame(tick);
+ };
+ tick();
+ });
+ }
+ return new Promise((resolve, reject) => {
+ const script = document.createElement('script');
+ script.src = SCRIPT_SRC;
+ script.async = true;
+ script.onload = () => resolve();
+ script.onerror = () => reject(new Error('Failed to load Turnstile'));
+ document.head.appendChild(script);
+ });
+}
+
+export interface CloudflareTurnstileProps {
+ siteKey: string;
+ onToken: (token: string | null) => void;
+}
+
+export function CloudflareTurnstile({ siteKey, onToken }: CloudflareTurnstileProps) {
+ const containerRef = useRef(null);
+ const widgetIdRef = useRef(null);
+ const onTokenRef = useRef(onToken);
+ onTokenRef.current = onToken;
+
+ useEffect(() => {
+ if (!siteKey || !containerRef.current) {
+ return;
+ }
+
+ let cancelled = false;
+
+ void (async () => {
+ try {
+ await loadTurnstileScript();
+ if (cancelled || !containerRef.current || !window.turnstile) {
+ return;
+ }
+ containerRef.current.innerHTML = '';
+ const id = window.turnstile.render(containerRef.current, {
+ sitekey: siteKey,
+ callback: (token: string) => onTokenRef.current(token),
+ 'error-callback': () => onTokenRef.current(null),
+ 'expired-callback': () => onTokenRef.current(null),
+ });
+ widgetIdRef.current = id;
+ } catch {
+ onTokenRef.current(null);
+ }
+ })();
+
+ return () => {
+ cancelled = true;
+ const id = widgetIdRef.current;
+ widgetIdRef.current = null;
+ if (id && window.turnstile?.remove) {
+ try {
+ window.turnstile.remove(id);
+ } catch {
+ /* noop */
+ }
+ }
+ };
+ }, [siteKey]);
+
+ return ;
+}
diff --git a/portal/src/data/institutional-grades.generated.json b/portal/src/data/institutional-grades.generated.json
new file mode 100644
index 0000000..ed21077
--- /dev/null
+++ b/portal/src/data/institutional-grades.generated.json
@@ -0,0 +1,228 @@
+{
+ "generatedAt": "2026-06-11T08:18:45.004140+00:00",
+ "source": "config/institutional-a-plus-readiness.v1.json",
+ "rubric": [
+ {
+ "grade": "A",
+ "range": "90\u2013100",
+ "meaning": "Production-grade; audit-ready; minimal material gaps"
+ },
+ {
+ "grade": "B",
+ "range": "80\u201389",
+ "meaning": "Pilot-ready with documented, bounded residual risk"
+ },
+ {
+ "grade": "C",
+ "range": "70\u201379",
+ "meaning": "Operational with material gaps blocking institutional scale"
+ },
+ {
+ "grade": "D",
+ "range": "60\u201369",
+ "meaning": "Engineering-complete; evidence/jurisdiction incomplete"
+ },
+ {
+ "grade": "F",
+ "range": "<60",
+ "meaning": "Not suitable for broad institutional deployment claims"
+ }
+ ],
+ "sovereignInstitutionGrades": [
+ {
+ "id": "ecosystem-composite",
+ "name": "DBIS / Chain 138 ecosystem",
+ "score": 83.0,
+ "maxScore": 100,
+ "letterGrade": "B",
+ "tier": "Target 90/100 \u2014 mixed work remaining",
+ "audience": "sovereign",
+ "assessmentDate": "2026-06-11",
+ "href": "https://gitea.d-bis.org/d-bis/proxmox/src/branch/main/ECOSYSTEM_READINESS.md",
+ "atTarget": false,
+ "blockerClass": "mixed"
+ },
+ {
+ "id": "sovereign-cloud",
+ "name": "Sovereign cloud infrastructure",
+ "score": 90.0,
+ "maxScore": 100,
+ "letterGrade": "A-",
+ "tier": "At or above A-tier target score",
+ "audience": "sovereign",
+ "assessmentDate": "2026-06-11",
+ "href": null,
+ "atTarget": true,
+ "blockerClass": "operator"
+ },
+ {
+ "id": "public-sector-compliance",
+ "name": "Public sector & jurisdiction matrices",
+ "score": 74.0,
+ "maxScore": 100,
+ "letterGrade": "C",
+ "tier": "External gate (counsel/treasury/listings) \u2014 see README.md",
+ "audience": "sovereign",
+ "assessmentDate": "2026-06-11",
+ "href": "https://docs.d-bis.org",
+ "atTarget": false,
+ "blockerClass": "external"
+ },
+ {
+ "id": "settlement-rtgs",
+ "name": "Settlement, RTGS & Rail",
+ "score": 72.0,
+ "maxScore": 100,
+ "letterGrade": "C-",
+ "tier": "Target 90/100 \u2014 mixed work remaining",
+ "audience": "sovereign",
+ "assessmentDate": "2026-06-11",
+ "href": null,
+ "atTarget": false,
+ "blockerClass": "mixed"
+ },
+ {
+ "id": "rwa-gov-critical",
+ "name": "RWA index factory (government-critical lens)",
+ "score": 58.0,
+ "maxScore": 100,
+ "letterGrade": "D",
+ "tier": "External gate (counsel/treasury/listings) \u2014 see RWA_TOKEN_FACTORY_INSTITUTIONAL_GRADE.md",
+ "audience": "sovereign",
+ "assessmentDate": "2026-06-11",
+ "href": "https://docs.d-bis.org",
+ "atTarget": false,
+ "blockerClass": "external"
+ },
+ {
+ "id": "pmm-liquidity",
+ "name": "PMM / on-chain liquidity & peg",
+ "score": 75.0,
+ "maxScore": 100,
+ "letterGrade": "C+",
+ "tier": "Target 90/100 \u2014 operator work remaining",
+ "audience": "platform",
+ "assessmentDate": "2026-06-11",
+ "href": null,
+ "atTarget": false,
+ "blockerClass": "operator"
+ },
+ {
+ "id": "cross-chain",
+ "name": "Cross-chain & CCIP lanes",
+ "score": 51.0,
+ "maxScore": 100,
+ "letterGrade": "D",
+ "tier": "Target 90/100 \u2014 operator work remaining",
+ "audience": "platform",
+ "assessmentDate": "2026-06-11",
+ "href": null,
+ "atTarget": false,
+ "blockerClass": "operator"
+ },
+ {
+ "id": "ei138-at-pillars",
+ "name": "EI-138-AT institutional pillars (not machine composite)",
+ "score": 67.3,
+ "maxScore": 100,
+ "letterGrade": "D+",
+ "tier": "External gate (counsel/treasury/listings) \u2014 see EI_MATRIX_CHAIN138_AUTOMATED_TRADING_INSTITUTIONAL_GRADE.md",
+ "audience": "regulated",
+ "assessmentDate": "2026-06-11",
+ "href": "https://docs.d-bis.org",
+ "atTarget": false,
+ "blockerClass": "external"
+ },
+ {
+ "id": "complete-credential",
+ "name": "Complete Credential / eIDAS program",
+ "score": 70.0,
+ "maxScore": 100,
+ "letterGrade": "C",
+ "tier": "External gate (counsel/treasury/listings) \u2014 see PUBLIC_SECTOR_LIVE_DEPLOYMENT_CHECKLIST.md",
+ "audience": "regulated",
+ "assessmentDate": "2026-06-11",
+ "href": null,
+ "atTarget": false,
+ "blockerClass": "external"
+ },
+ {
+ "id": "omnl-registry",
+ "name": "OMNL / HYBX institutional entity registry",
+ "score": 63.0,
+ "maxScore": 100,
+ "letterGrade": "D",
+ "tier": "External gate (counsel/treasury/listings) \u2014 see OMNL_HYBX_INSTITUTIONAL_ENTITY_REGISTRY_GRADE.md",
+ "audience": "regulated",
+ "assessmentDate": "2026-06-11",
+ "href": null,
+ "atTarget": false,
+ "blockerClass": "external"
+ },
+ {
+ "id": "chain138-l1",
+ "name": "Chain 138 L1 health & oracle integrity",
+ "score": 91.0,
+ "maxScore": 100,
+ "letterGrade": "A-",
+ "tier": "At or above A-tier target score",
+ "audience": "platform",
+ "assessmentDate": "2026-06-11",
+ "href": null,
+ "atTarget": true,
+ "blockerClass": "automatable"
+ },
+ {
+ "id": "security-audit",
+ "name": "Security & third-party audit",
+ "score": 74.0,
+ "maxScore": 100,
+ "letterGrade": "C",
+ "tier": "External gate (counsel/treasury/listings) \u2014 see INSTITUTION_ONBOARDING_CHARTER.md",
+ "audience": "sovereign",
+ "assessmentDate": "2026-06-11",
+ "href": "https://docs.d-bis.org",
+ "atTarget": false,
+ "blockerClass": "external"
+ },
+ {
+ "id": "ura-strict-closure",
+ "name": "URA strict closure (no TBD evidence)",
+ "score": 78.0,
+ "maxScore": 100,
+ "letterGrade": "C+",
+ "tier": "Target 90/100 \u2014 automatable work remaining",
+ "audience": "platform",
+ "assessmentDate": "2026-06-11",
+ "href": null,
+ "atTarget": false,
+ "blockerClass": "automatable"
+ },
+ {
+ "id": "stack-b-drift",
+ "name": "PMM Stack B drift eliminated",
+ "score": 85.0,
+ "maxScore": 100,
+ "letterGrade": "B+",
+ "tier": "Target 100/100 \u2014 automatable work remaining",
+ "audience": "platform",
+ "assessmentDate": "2026-06-11",
+ "href": null,
+ "atTarget": false,
+ "blockerClass": "automatable"
+ },
+ {
+ "id": "external-visibility",
+ "name": "External visibility & listings",
+ "score": 76.0,
+ "maxScore": 100,
+ "letterGrade": "C+",
+ "tier": "External gate (counsel/treasury/listings) \u2014 see CHAIN138_DEFILLAMA_ECOSYSTEM_MAP.md",
+ "audience": "platform",
+ "assessmentDate": "2026-06-11",
+ "href": null,
+ "atTarget": false,
+ "blockerClass": "external"
+ }
+ ]
+}
diff --git a/portal/src/lib/auth/claims.test.ts b/portal/src/lib/auth/claims.test.ts
new file mode 100644
index 0000000..3465d75
--- /dev/null
+++ b/portal/src/lib/auth/claims.test.ts
@@ -0,0 +1,33 @@
+import { decodeJwtPayload, extractPortalClaimState } from '@/lib/auth/claims';
+
+function makeToken(payload: Record) {
+ const encoded = Buffer.from(JSON.stringify(payload)).toString('base64url');
+ return `header.${encoded}.signature`;
+}
+
+describe('portal auth claims', () => {
+ it('decodes JWT payloads safely', () => {
+ const payload = decodeJwtPayload(makeToken({ tenantId: 'tenant-123' }));
+ expect(payload.tenantId).toBe('tenant-123');
+ });
+
+ it('extracts client, tenant, subscription, and roles from mixed claim shapes', () => {
+ const state = extractPortalClaimState(
+ {
+ client_id: 'client-001',
+ tenantId: 'tenant-001',
+ subscription_id: 'sub-001',
+ realm_access: { roles: ['tenant-admin'] },
+ },
+ {
+ roles: ['billing-admin'],
+ role: 'operator',
+ }
+ );
+
+ expect(state.clientId).toBe('client-001');
+ expect(state.tenantId).toBe('tenant-001');
+ expect(state.subscriptionId).toBe('sub-001');
+ expect(state.roles).toEqual(expect.arrayContaining(['tenant-admin', 'billing-admin', 'operator']));
+ });
+});
diff --git a/portal/src/lib/auth/claims.ts b/portal/src/lib/auth/claims.ts
new file mode 100644
index 0000000..9056b0c
--- /dev/null
+++ b/portal/src/lib/auth/claims.ts
@@ -0,0 +1,86 @@
+export interface PortalClaimState {
+ clientId?: string;
+ tenantId?: string;
+ subscriptionId?: string;
+ roles: string[];
+}
+
+type JsonRecord = Record;
+
+function decodeBase64Url(value: string): string {
+ const normalized = value.replace(/-/g, '+').replace(/_/g, '/');
+ const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, '=');
+ return Buffer.from(padded, 'base64').toString('utf8');
+}
+
+export function decodeJwtPayload(token?: string | null): JsonRecord {
+ if (!token) return {};
+
+ const [, payload] = token.split('.');
+ if (!payload) return {};
+
+ try {
+ const decoded = decodeBase64Url(payload);
+ const parsed = JSON.parse(decoded);
+ return parsed && typeof parsed === 'object' ? (parsed as JsonRecord) : {};
+ } catch {
+ return {};
+ }
+}
+
+function readStringClaim(source: JsonRecord, keys: string[]): string | undefined {
+ for (const key of keys) {
+ const value = source[key];
+ if (typeof value === 'string' && value.trim() !== '') return value.trim();
+ }
+ return undefined;
+}
+
+function readStringArrayClaim(source: JsonRecord, keys: string[]): string[] {
+ for (const key of keys) {
+ const value = source[key];
+ if (Array.isArray(value)) {
+ const strings = value.filter((item): item is string => typeof item === 'string' && item.trim() !== '');
+ if (strings.length > 0) return strings;
+ }
+ }
+ return [];
+}
+
+function readRealmRoles(source: JsonRecord): string[] {
+ const realmAccess = source.realm_access;
+ if (!realmAccess || typeof realmAccess !== 'object') return [];
+
+ const roles = (realmAccess as JsonRecord).roles;
+ if (!Array.isArray(roles)) return [];
+
+ return roles.filter((item): item is string => typeof item === 'string' && item.trim() !== '');
+}
+
+export function extractPortalClaimState(...sources: JsonRecord[]): PortalClaimState {
+ const clientId =
+ sources.map((source) => readStringClaim(source, ['clientId', 'client_id', 'client'])).find(Boolean) ||
+ undefined;
+ const tenantId =
+ sources.map((source) => readStringClaim(source, ['tenantId', 'tenant_id', 'tenant'])).find(Boolean) ||
+ undefined;
+ const subscriptionId =
+ sources
+ .map((source) => readStringClaim(source, ['subscriptionId', 'subscription_id', 'subscription']))
+ .find(Boolean) || undefined;
+
+ const roleSet = new Set();
+ for (const source of sources) {
+ for (const role of readRealmRoles(source)) roleSet.add(role);
+ for (const role of readStringArrayClaim(source, ['roles', 'groups'])) roleSet.add(role);
+ const directRole = readStringClaim(source, ['role']);
+ if (directRole) roleSet.add(directRole);
+ }
+
+ return {
+ clientId,
+ tenantId,
+ subscriptionId,
+ roles: Array.from(roleSet),
+ };
+}
diff --git a/portal/src/lib/corporate-site-data.ts b/portal/src/lib/corporate-site-data.ts
new file mode 100644
index 0000000..0466883
--- /dev/null
+++ b/portal/src/lib/corporate-site-data.ts
@@ -0,0 +1,343 @@
+import type { LucideIcon } from 'lucide-react';
+import {
+ Cloud,
+ Shield,
+ Wallet,
+ Blocks,
+ Cpu,
+ Landmark,
+ Globe2,
+ Sparkles,
+ Network,
+ FileKey2,
+ Building2,
+ Scale,
+} from 'lucide-react';
+
+import generatedGrades from '@/data/institutional-grades.generated.json';
+
+export interface CorporateNavItem {
+ label: string;
+ href: string;
+ external?: boolean;
+}
+
+export interface ProductDivision {
+ id: string;
+ name: string;
+ tagline: string;
+ description: string;
+ href: string;
+ external?: boolean;
+ icon: LucideIcon;
+ highlights: string[];
+}
+
+export interface ServiceOffering {
+ name: string;
+ category: string;
+ description: string;
+ icon: LucideIcon;
+}
+
+export interface SolutionVertical {
+ name: string;
+ description: string;
+ icon: LucideIcon;
+}
+
+export const ECOSYSTEM_SIGN_IN_PATH = '/api/auth/signin?callbackUrl=/dashboard';
+
+export const corporateNav: CorporateNavItem[] = [
+ { label: 'Products', href: '#products' },
+ { label: 'Services', href: '#services' },
+ { label: 'Solutions', href: '#solutions' },
+ { label: 'Institutional grade', href: '#institutional-grade' },
+ { label: 'Resources', href: '#resources' },
+];
+
+export const productDivisions: ProductDivision[] = [
+ {
+ id: 'phoenix',
+ name: 'Phoenix Cloud',
+ tagline: 'Sovereign cloud platform',
+ description:
+ 'Infrastructure, identity, orchestration, and marketplace primitives — the sovereign alternative to hyperscale public cloud.',
+ href: 'https://phoenix.sankofa.nexus',
+ external: true,
+ icon: Cloud,
+ highlights: ['Multi-tenant compute', 'Keycloak identity plane', 'GitOps & Crossplane', 'Marketplace catalog'],
+ },
+ {
+ id: 'complete-credential',
+ name: 'Complete Credential',
+ tagline: 'Sovereign credential issuance',
+ description:
+ 'Institutional-grade verifiable credentials, entity packs, and eIDAS-aligned issuance hosted on Phoenix.',
+ href: 'https://cc.sankofa.nexus',
+ external: true,
+ icon: FileKey2,
+ highlights: ['Entity credential packs', 'OIDC / SAML integration', 'Compliance workflows', 'Operator admin'],
+ },
+ {
+ id: 'studio',
+ name: 'Sankofa Studio',
+ tagline: 'FusionAI creator platform',
+ description:
+ 'Build, deploy, and manage AI-powered applications on sovereign infrastructure with full data residency control.',
+ href: 'https://studio.sankofa.nexus/studio/',
+ external: true,
+ icon: Sparkles,
+ highlights: ['Model orchestration', 'Creator workflows', 'Tenant isolation', 'API-first design'],
+ },
+ {
+ id: 'blockchain',
+ name: 'Blockchain & DeFi',
+ tagline: 'Chain 138 & cross-chain mesh',
+ description:
+ 'Defi Oracle Meta Mainnet, PMM liquidity, CCIP bridges, and institutional-grade on-chain settlement rails.',
+ href: 'https://explorer.d-bis.org',
+ external: true,
+ icon: Blocks,
+ highlights: ['Chain 138 RPC & explorer', 'PMM / DODO liquidity', 'CCIP cross-chain', 'Token & RWA factory'],
+ },
+ {
+ id: 'identity',
+ name: 'Identity & Access',
+ tagline: 'Sovereign IAM',
+ description:
+ 'Centralized OIDC/SAML identity with device binding, passkeys, RBAC, and zero dependency on hyperscaler IdPs.',
+ href: 'https://keycloak.sankofa.nexus',
+ external: true,
+ icon: Shield,
+ highlights: ['Keycloak IdP', 'Multi-tenant RBAC', 'Passkeys & MFA', 'Client SSO portal'],
+ },
+ {
+ id: 'treasury',
+ name: 'Treasury & Settlement',
+ tagline: 'Institutional finance rails',
+ description:
+ 'Double-entry ledger, virtual accounts, wallet registry, and RTGS-aligned settlement for sovereign finance.',
+ href: 'https://portal.sankofa.nexus',
+ external: true,
+ icon: Landmark,
+ highlights: ['Phoenix Ledger', 'Virtual accounts', 'Wallet policy engine', 'DBIS settlement'],
+ },
+];
+
+export const platformServices: ServiceOffering[] = [
+ {
+ name: 'Phoenix Ledger Service',
+ category: 'Financial',
+ description: 'Double-entry ledger with virtual accounts, holds, and multi-asset support.',
+ icon: Landmark,
+ },
+ {
+ name: 'Phoenix Identity Service',
+ category: 'Security',
+ description: 'Users, orgs, roles, permissions, device binding, passkeys, and OAuth/OIDC.',
+ icon: Shield,
+ },
+ {
+ name: 'Phoenix Wallet Registry',
+ category: 'Custody',
+ description: 'Wallet mapping, chain support, MPC/HSM policy engine, and ERC-4337 accounts.',
+ icon: Wallet,
+ },
+ {
+ name: 'Transaction Orchestrator',
+ category: 'Orchestration',
+ description: 'On-chain and off-chain workflows with retries, compensations, and failover.',
+ icon: Network,
+ },
+ {
+ name: 'Messaging Orchestrator',
+ category: 'Communications',
+ description: 'Multi-provider SMS, email, voice, and push with automatic failover.',
+ icon: Globe2,
+ },
+ {
+ name: 'Voice Orchestrator',
+ category: 'AI Media',
+ description: 'TTS/STT with caching, multi-provider routing, and PII-aware moderation.',
+ icon: Cpu,
+ },
+];
+
+export const solutionVerticals: SolutionVertical[] = [
+ {
+ name: 'Public Sector',
+ description: 'Sovereign tenancy, SMOA compliance, and institutional registry for government programs.',
+ icon: Building2,
+ },
+ {
+ name: 'Financial Services',
+ description: 'Ledger, settlement, EMI wallet infrastructure, and cross-border RTGS integration.',
+ icon: Scale,
+ },
+ {
+ name: 'Healthcare & Identity',
+ description: 'Verifiable credentials, eIDAS-aligned issuance, and HIPAA-ready identity planes.',
+ icon: FileKey2,
+ },
+ {
+ name: 'Telecommunications',
+ description: 'PanTel 6G infrastructure JV, sovereign edge compute, and carrier-grade networking.',
+ icon: Network,
+ },
+];
+
+export const philosophySteps = [
+ { verb: 'Remember', detail: 'Where we came from — ancestral wisdom and institutional continuity' },
+ { verb: 'Retrieve', detail: 'What was essential — sovereign identity and owned core primitives' },
+ { verb: 'Restore', detail: 'Identity and sovereignty — independent infrastructure under our control' },
+ { verb: 'Rise', detail: 'Forward with purpose — world-class cloud without hyperscaler dependency' },
+];
+
+export interface InstitutionalGradeEntry {
+ id: string;
+ name: string;
+ score: number;
+ maxScore: number;
+ letterGrade: string;
+ tier: string;
+ audience: 'sovereign' | 'regulated' | 'platform';
+ assessmentDate: string;
+ href?: string;
+}
+
+/** Engineering readiness scores for sovereign & regulated institutions (not legal advice). */
+const FALLBACK_RUBRIC = [
+ { grade: 'A', range: '90–100', meaning: 'Production-grade; audit-ready; minimal material gaps' },
+ { grade: 'B', range: '80–89', meaning: 'Pilot-ready with documented, bounded residual risk' },
+ { grade: 'C', range: '70–79', meaning: 'Operational with material gaps blocking institutional scale' },
+ { grade: 'D', range: '60–69', meaning: 'Engineering-complete; evidence and jurisdiction coverage incomplete' },
+ { grade: 'F', range: '<60', meaning: 'Not suitable for broad institutional deployment claims' },
+];
+
+const FALLBACK_GRADES: InstitutionalGradeEntry[] = [
+ {
+ id: 'ecosystem-composite',
+ name: 'DBIS / Chain 138 ecosystem',
+ score: 83,
+ maxScore: 100,
+ letterGrade: 'B',
+ tier: 'Advanced pilot — 138-native flows with disclosures',
+ audience: 'sovereign',
+ assessmentDate: '2026-05-26',
+ href: 'https://gitea.d-bis.org/d-bis/proxmox/src/branch/main/ECOSYSTEM_READINESS.md',
+ },
+ {
+ id: 'sovereign-cloud',
+ name: 'Sovereign cloud infrastructure',
+ score: 90,
+ maxScore: 100,
+ letterGrade: 'A−',
+ tier: 'CI-clean operator stack; LAN inventory & E2E routing',
+ audience: 'sovereign',
+ assessmentDate: '2026-05-26',
+ },
+ {
+ id: 'public-sector-compliance',
+ name: 'Public sector & jurisdiction matrices',
+ score: 74,
+ maxScore: 100,
+ letterGrade: 'C',
+ tier: 'Indonesia pilot-ready; US stub; counsel sign-off paths documented',
+ audience: 'sovereign',
+ assessmentDate: '2026-05-26',
+ href: 'https://docs.d-bis.org',
+ },
+ {
+ id: 'settlement-rtgs',
+ name: 'Settlement, RTGS & Rail',
+ score: 72,
+ maxScore: 100,
+ letterGrade: 'C−',
+ tier: 'Verification tooling live; on-chain Rail not yet deployed',
+ audience: 'sovereign',
+ assessmentDate: '2026-05-26',
+ },
+ {
+ id: 'rwa-gov-critical',
+ name: 'RWA index factory (government-critical lens)',
+ score: 58,
+ maxScore: 100,
+ letterGrade: 'D',
+ tier: 'Engineering pilot only — not for sovereign treasury without counsel',
+ audience: 'sovereign',
+ assessmentDate: '2026-05-24',
+ href: 'https://docs.d-bis.org',
+ },
+ {
+ id: 'complete-credential',
+ name: 'Complete Credential / eIDAS program',
+ score: 70,
+ maxScore: 100,
+ letterGrade: 'C',
+ tier: 'SMOA evidence partial; QTSP path documented',
+ audience: 'regulated',
+ assessmentDate: '2026-03-25',
+ href: 'https://cc.sankofa.nexus',
+ },
+ {
+ id: 'omnl-registry',
+ name: 'OMNL / HYBX institutional entity registry',
+ score: 63,
+ maxScore: 100,
+ letterGrade: 'D',
+ tier: 'Live Fineract offices; CB counterparty MOU evidence outstanding',
+ audience: 'regulated',
+ assessmentDate: '2026-06-05',
+ },
+ {
+ id: 'chain138-l1',
+ name: 'Chain 138 L1 health & oracle integrity',
+ score: 91,
+ maxScore: 100,
+ letterGrade: 'A−',
+ tier: '16/16 oracle strict; Stack A PMM live (~$87.8M)',
+ audience: 'platform',
+ assessmentDate: '2026-05-26',
+ },
+];
+
+export const institutionalGradeRubric =
+ generatedGrades.rubric?.length ? generatedGrades.rubric : FALLBACK_RUBRIC;
+
+export const sovereignInstitutionGrades: InstitutionalGradeEntry[] =
+ generatedGrades.sovereignInstitutionGrades?.length
+ ? (generatedGrades.sovereignInstitutionGrades as InstitutionalGradeEntry[])
+ : FALLBACK_GRADES;
+
+export function gradeBadgeClass(letterGrade: string): string {
+ const g = letterGrade.charAt(0).toUpperCase();
+ switch (g) {
+ case 'A':
+ return 'bg-emerald-500/15 text-emerald-300 ring-emerald-500/30';
+ case 'B':
+ return 'bg-sky-500/15 text-sky-300 ring-sky-500/30';
+ case 'C':
+ return 'bg-amber-500/15 text-amber-300 ring-amber-500/30';
+ case 'D':
+ return 'bg-orange-500/15 text-orange-300 ring-orange-500/30';
+ default:
+ return 'bg-red-500/15 text-red-300 ring-red-500/30';
+ }
+}
+
+export function scoreBarClass(letterGrade: string): string {
+ const g = letterGrade.charAt(0).toUpperCase();
+ switch (g) {
+ case 'A':
+ return 'bg-emerald-500';
+ case 'B':
+ return 'bg-sky-500';
+ case 'C':
+ return 'bg-amber-500';
+ case 'D':
+ return 'bg-orange-500';
+ default:
+ return 'bg-red-500';
+ }
+}
diff --git a/portal/src/lib/portal-navigation.test.ts b/portal/src/lib/portal-navigation.test.ts
new file mode 100644
index 0000000..1130dae
--- /dev/null
+++ b/portal/src/lib/portal-navigation.test.ts
@@ -0,0 +1,42 @@
+import fs from 'node:fs';
+import path from 'node:path';
+
+import { primaryNavigation, supportNavigation } from '@/lib/portal-navigation';
+
+const appRoot = '/home/intlc/projects/Sankofa/portal/src/app';
+
+function hasBackingRoute(href: string): boolean {
+ const direct =
+ href === '/'
+ ? path.join(appRoot, 'page.tsx')
+ : path.join(appRoot, href.replace(/^\//, ''), 'page.tsx');
+ if (fs.existsSync(direct)) return true;
+
+ if (href.startsWith('/admin/')) {
+ return fs.existsSync(path.join(appRoot, 'admin/[section]/page.tsx'));
+ }
+
+ if (href.startsWith('/partner/')) {
+ return fs.existsSync(path.join(appRoot, 'partner/[section]/page.tsx'));
+ }
+
+ if (href.startsWith('/help/')) {
+ return fs.existsSync(path.join(appRoot, 'help/[section]/page.tsx'));
+ }
+
+ return false;
+}
+
+describe('portal navigation integrity', () => {
+ it('backs every primary navigation entry with a route', () => {
+ for (const item of primaryNavigation) {
+ expect(hasBackingRoute(item.href)).toBe(true);
+ }
+ });
+
+ it('backs every support navigation entry with a route', () => {
+ for (const item of supportNavigation) {
+ expect(hasBackingRoute(item.href)).toBe(true);
+ }
+ });
+});
diff --git a/portal/src/lib/portal-navigation.ts b/portal/src/lib/portal-navigation.ts
new file mode 100644
index 0000000..d38bc68
--- /dev/null
+++ b/portal/src/lib/portal-navigation.ts
@@ -0,0 +1,30 @@
+import {
+ CreditCard,
+ FileText,
+ HelpCircle,
+ LayoutDashboard,
+ Network,
+ Server,
+ Settings,
+ Shield,
+ Users,
+ Activity,
+} from 'lucide-react';
+
+export const primaryNavigation = [
+ { name: 'Dashboard', href: '/', icon: LayoutDashboard },
+ { name: 'Infrastructure', href: '/infrastructure', icon: Server },
+ { name: 'Resources', href: '/resources', icon: Server },
+ { name: 'Virtual Machines', href: '/vms', icon: Server },
+ { name: 'Networking', href: '/network', icon: Network },
+ { name: 'Monitoring', href: '/dashboards', icon: Activity },
+ { name: 'Users & Access', href: '/users', icon: Users },
+ { name: 'Billing', href: '/billing', icon: CreditCard },
+ { name: 'Security', href: '/security', icon: Shield },
+ { name: 'Settings', href: '/settings', icon: Settings },
+] as const;
+
+export const supportNavigation = [
+ { name: 'Documentation', href: '/help/docs', icon: FileText },
+ { name: 'Support', href: '/help/support', icon: HelpCircle },
+] as const;
diff --git a/portal/src/types/next-auth.d.ts b/portal/src/types/next-auth.d.ts
index 65456fb..2f4911e 100644
--- a/portal/src/types/next-auth.d.ts
+++ b/portal/src/types/next-auth.d.ts
@@ -6,7 +6,9 @@ declare module 'next-auth' {
accessToken?: string;
roles?: string[];
/** Optional tenant scope for billing/dashboard GraphQL */
+ clientId?: string;
tenantId?: string;
+ subscriptionId?: string;
user?: DefaultSession['user'] & {
id?: string;
role?: string;
@@ -26,6 +28,8 @@ declare module 'next-auth/jwt' {
refreshToken?: string;
idToken?: string;
roles?: string[];
+ clientId?: string;
+ tenantId?: string;
+ subscriptionId?: string;
}
}
-