Portal & Navigation¶
The portal is the authenticated workspace where users interact with their organizations and teams.
Portal Structure¶
/portal/[organizationSlug]/ # Organization scope
├── settings/
│ ├── members # Org members & invites
│ ├── teams # Org teams management
│ └── billing # Stripe billing
└── [teamID]/ # Team scope
├── chat/ # AI chat
├── sessions/ # Live sessions
├── context/ # Embeddings
├── training/ # Training jobs
└── settings/ # Team settings
Layout Components¶
ConditionalLayout¶
components/layout/conditional-layout.tsx
Determines which layout to render based on the route: - Marketing routes: Full navbar, footer - Portal routes: Sidebar navigation - Profile routes: Profile sidebar
PortalSidebar¶
components/layout/sidebars/portal-sidebar.tsx
The main portal navigation. Accepts:
- user - Current user data
- slug - Organization slug
- teamID - Active team ID
Menu Structure:
| Section | Items |
|---|---|
| Chat | Main chat, pinned conversations |
| Recruiting | Import, manage players |
| Sessions | Create, view, manage forms |
| Context | Upload, manage |
| Training | New job, view jobs (admin+ only) |
| Settings | Team settings |
Implementation highlights:
// Uses useChat to display pinned chats
const { pinnedChats } = useChat(slug);
// Role-based visibility
{isAdmin && (
<SidebarMenuItem>Training</SidebarMenuItem>
)}
ProfileSidebar¶
components/layout/sidebars/profile-sidebar.tsx
Navigation for /profile/** routes:
- Profile home
- Settings
- Organizations
- Invites
- Teams
OrgSidebar¶
components/layout/sidebars/org-sidebar.tsx
Navigation for organization settings: - Members - Teams - Billing
Role-Based Access Control¶
RoleProvider¶
context/role-provider.tsx
Wraps portal pages to enforce role-based access:
Role Hierarchy¶
| Role | Level | Capabilities |
|---|---|---|
owner |
Highest | Full access, billing, delete org |
admin |
Middle | Member management, training |
member |
Base | Read/write sessions, chat, context |
useRole Hook¶
hooks/use-role.ts
const { role, isAdmin, isManager, isBasic } = useRole();
// isAdmin = role === "admin" || role === "owner"
// isManager = role === "owner"
// isBasic = role === "member"
Role Enforcement¶
The RoleProvider handles three scenarios:
- Loading: Shows spinner while fetching role
- No membership: Redirects to
/(home) - Insufficient role: Redirects to
/portal/[slug]
// Implementation
if (isLoading) return <Spinner />;
if (!role) {
router.push("/");
return null;
}
if (requireRole && !hasRole(role, requireRole)) {
router.push(`/portal/${slug}`);
return null;
}
Navigation Patterns¶
Organization Switching¶
Users can switch organizations via: 1. Profile organizations list 2. Header org dropdown (if implemented)
When switching:
Team Navigation¶
Teams are selected from the portal sidebar or settings:
Breadcrumbs¶
Common pattern for nested pages:
<Breadcrumb>
<BreadcrumbItem>
<Link href={`/portal/${slug}`}>Organization</Link>
</BreadcrumbItem>
<BreadcrumbItem>
<Link href={`/portal/${slug}/${teamID}/sessions`}>Sessions</Link>
</BreadcrumbItem>
<BreadcrumbItem>Session Detail</BreadcrumbItem>
</Breadcrumb>
Sidebar Components¶
The sidebar uses shadcn/ui patterns:
<SidebarProvider>
<Sidebar>
<SidebarHeader>
{/* Logo, org name */}
</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Features</SidebarGroupLabel>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton asChild>
<Link href="/portal/org/team/chat">
<MessageSquare />
<span>Chat</span>
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>
{/* User avatar, logout */}
</SidebarFooter>
</Sidebar>
</SidebarProvider>
User Menu¶
The sidebar footer includes:
- User avatar
- Link to /profile
- Logout button