import { Outlet, useNavigate, useLocation } from 'react-router'; import { AppShell, Sidebar, TopBar, } from '@cameleer/design-system'; import { LayoutDashboard, ShieldCheck, Users, Settings, Shield, Building, ScrollText, Mail, BarChart3, Server, ExternalLink, Key, Lock, UserCog } from 'lucide-react'; import { useQuery } from '@tanstack/react-query'; import { useAuth } from '../auth/useAuth'; import { useScopes } from '../auth/useScopes'; import { useOrgStore } from '../auth/useOrganization'; import { api } from '../api/client'; import type { VendorTenantSummary } from '../types/api'; import cameleerLogo from '@cameleer/design-system/assets/cameleer-logo.svg'; function CameleerLogo() { return ( ); } function isActive(location: ReturnType, path: string) { return location.pathname === path || location.pathname.startsWith(path + '/'); } export function Layout() { const navigate = useNavigate(); const location = useLocation(); const { logout } = useAuth(); const scopes = useScopes(); const { username } = useOrgStore(); const isVendor = scopes.has('platform:admin'); const { data: vendorTenants } = useQuery({ queryKey: ['vendor', 'tenants'], queryFn: () => api.get('/vendor/tenants'), enabled: isVendor, refetchInterval: 30_000, }); const isTenantAdmin = scopes.has('tenant:manage'); const onVendorRoute = location.pathname.startsWith('/vendor'); // Vendor on vendor routes: show only TENANTS. On tenant routes: show tenant portal too (for debugging). const showTenantPortal = isTenantAdmin && (!isVendor || !onVendorRoute); // Build breadcrumbs from path const segments = location.pathname.replace(/^\//, '').split('/').filter(Boolean); const breadcrumb = segments.map((seg) => { const label = seg.charAt(0).toUpperCase() + seg.slice(1).replace(/-/g, ' '); return { label }; }); const userMenuItems = [ { label: 'Account Settings', icon: , onClick: () => navigate('/settings/account'), }, ]; const sidebar = ( {}}> } title="Cameleer SaaS" onClick={() => navigate(isVendor ? '/vendor/tenants' : '/tenant')} /> {/* Vendor console — only visible to platform:admin */} {isVendor && ( } label="Vendor" open={onVendorRoute} active={isActive(location, '/vendor')} onToggle={() => navigate('/vendor/tenants')} >
navigate('/vendor/tenants')} > Tenants
{vendorTenants?.filter(t => t.status !== 'DELETED').map(t => (
navigate(`/vendor/tenants/${t.id}`)} title={t.name} > {t.name}
))}
navigate('/vendor/audit')} > Audit Log
navigate('/vendor/certificates')} > Certificates
navigate('/vendor/metrics')} > Metrics
navigate('/vendor/infrastructure')} > Infrastructure
navigate('/vendor/email')} > Email Connector
navigate('/vendor/license-tools')} > License Tools
navigate('/vendor/auth-policy')} > Auth Policy
navigate('/vendor/admins')} > Administrators
window.open(`${window.location.protocol}//${window.location.hostname}:3002`, '_blank', 'noopener')} > Logto Console
)} {/* Tenant portal — visible to tenant admins; hidden for vendor on vendor routes */} {showTenantPortal && ( <> } label="Dashboard" open={false} active={location.pathname === '/tenant'} onToggle={() => navigate('/tenant')} > {null} } label="License" open={false} active={isActive(location, '/tenant/license')} onToggle={() => navigate('/tenant/license')} > {null} } label="Security" open={false} active={isActive(location, '/tenant/sso')} onToggle={() => navigate('/tenant/sso')} > {null} } label="Team" open={false} active={isActive(location, '/tenant/team')} onToggle={() => navigate('/tenant/team')} > {null} } label="Audit Log" open={false} active={isActive(location, '/tenant/audit')} onToggle={() => navigate('/tenant/audit')} > {null} } label="Settings" open={false} active={isActive(location, '/tenant/settings')} onToggle={() => navigate('/tenant/settings')} > {null} )}
); return ( ); }