Skip to content

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:

<RoleProvider requireRole="admin">
  {/* Only admins and owners can see this */}
</RoleProvider>

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:

  1. Loading: Shows spinner while fetching role
  2. No membership: Redirects to / (home)
  3. 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;
}

Organization Switching

Users can switch organizations via: 1. Profile organizations list 2. Header org dropdown (if implemented)

When switching:

await authClient.organization.setActive({ organizationSlug });
router.push(`/portal/${newSlug}`);

Team Navigation

Teams are selected from the portal sidebar or settings:

await authClient.organization.setActiveTeam({ teamId });
router.push(`/portal/${slug}/${teamId}`);

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>

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

<SidebarFooter>
  <div className="flex items-center gap-2">
    <Avatar>
      <AvatarImage src={user.image} />
      <AvatarFallback>{user.name?.[0]}</AvatarFallback>
    </Avatar>
    <Link href="/profile">{user.name}</Link>
  </div>
  <LogoutButton />
</SidebarFooter>