Dashboard and Settings
Navigate the authenticated dashboard surface and its flag-gated settings and admin tabs.
The dashboard is the authenticated surface at config.routes.dashboard (/dashboard), a Server Component that fetches GET /dashboard/status (packages/api/src/routes/internal/dashboard.ts) once on the server. Its tabbed sub-surfaces come from the shared sectionTabsConfig in @repo/config (packages/config/src/section-tabs.ts), where each tab declares optional requires (feature flags) and orgRoles gates so anything the viewer or active config can't access hides automatically.
Page chrome
When you add an authenticated page, stack its chrome in this order: top bar (the section title - see Navigation), section tabs (where the section has them), PageHeader, then content.
PageHeader is imported from @repo/ui-next/components/ui/page-header and takes an optional title, an optional description, and an optional actions node. On a tabbed page pass only description (and any page action) - the active tab is the title. On an untabbed page add a title only when it says more than the top bar does (a greeting, a record's name); otherwise omit it.
Dashboard surface
The page renders from one server-side fetch - no client loading spinner. The metric row, security/team grid, usage chart, and quick-actions/recent-activity grid all hydrate from the single /dashboard/status payload.
| Metric card | Source field |
|---|---|
| Current plan | planName (falls back to free-plan label) |
| Plan status | planType + isActive (free / lifetime / active / inactive) |
| Security score | security flags, scored 0-2 |
| Team members (multi-tenant) or Member since (single-tenant) | team.memberCount or organizationCreatedAt |
Dashboard status payload
GET /dashboard/status (auth-guarded) returns plan, security, and team stats (multi-tenant only).
// GET /dashboard/status - packages/api/src/routes/internal/dashboard.ts
{
planName, planId, planType, planExpiresAt, isActive,
organizationCreatedAt, // null when single-tenant / no active org
security: { twoFactorEnabled, emailVerified, activeSessionCount, passkeyCount },
team: config.tenancy.multiTenant
? { memberCount, pendingInviteCount, userRole }
: null, // null when single-tenant
}planType is "free" | "lifetime" | "subscription" | null (derived from the resolved billing plan). When config.tenancy.billingScope is not "user", the endpoint returns 400 No active organization if the session has no active org.
Account and Settings tabs
The /settings/* sub-tabs split across two sidebar entries, each driven by its own group in sectionTabsConfig. Account (sectionTabsConfig.account) holds the user's own account tabs; Settings (sectionTabsConfig.settings) holds product configuration, mostly the AI surfaces. Each tab shows only when its gate passes.
| Tab | Group | Route | Gate |
|---|---|---|---|
| Profile | Account | /settings/profile | always on |
| Billing | Account | /settings/billing | tab always present; sections gated by config.payment.enabled |
| Transactions | Account | /settings/billing/history | always on; the billing + credit transaction ledger |
| Security | Account | /settings/security | always on |
| Models | Settings | /settings/models | requires: ["aiByok"] |
| Integrations | Settings | /settings/integrations | requires: ["ai"] |
| Companions | Settings | /settings/companions | requires: ["companion"] |
| MCP | Settings | /settings/mcp | requires: ["mcpServer"] |
| Developers | Settings | /settings/developers | requires: ["apiKeys"] |
The Billing tab always renders, but its page sections only appear when config.payment.enabled is true (default true). With payments off, the tab shows nothing actionable.
When config.tenancy.multiTenant is true, an Organization group (sectionTabsConfig.organization) is added:
- Overview:
/settings/organization,match: "exact". - Activity:
/settings/organization-activity, gatedorgRoles: ["owner", "admin"].
Admin panel
The admin panel lives under the (dashboard)/admin route group. Access is enforced server-side in admin/layout.tsx: any session whose user.role !== "admin" is redirected to config.routes.loginRedirect (/dashboard). The sidebar link is separately hidden by roles: ["admin"] in the per-app config/sidebar.ts, so non-admins never see the entry.
Its tabs come from sectionTabsConfig.admin. The whole panel is role-gated by the layout, so individual tabs carry no roles gate - only requires feature flags hide a tab from admins:
| Tab | Route | Tab gate |
|---|---|---|
| Users | /admin/users | none (panel role-gated) |
| Organizations | /admin/organizations | requires: ["multiTenant"] |
| Audit logs | /admin/audit-logs | none (panel role-gated) |
| Billing logs | /admin/billing-logs | none (panel role-gated) |
| API keys | /admin/api-keys | requires: ["apiKeys"] |
| Announcements | /admin/announcements | none (panel role-gated) |
config.routes.admin is /admin/users (the default landing route), not /admin. /admin itself redirects to config.routes.admin, preserving any query string.
Frequently asked questions
Why does the Billing tab show but Developers does not?
They sit in different groups. Billing is an Account tab, always present so users can view their plan; Developers is a Settings tab carrying requires: ["apiKeys"]. When the apiKeys flag is off, the Developers tab is removed.
How do I get the Organization tabs?
Enable config.tenancy.multiTenant. That adds the Organization group (Overview + Activity) to settings and the Organizations tab to the admin panel.
Who can see the admin panel?
Only users with user.role === "admin". admin/layout.tsx redirects everyone else to config.routes.loginRedirect, and the sidebar link is hidden via roles: ["admin"] in config/sidebar.ts - see authorization for how roles are assigned and checked.
Navigation
Configure the Next.js navbar, sidebar, user menu, and section tabs from typed config, with role, org-role, and feature-flag gating plus i18n labels.
Banners
Add marketing and dashboard announcement banners in your Next.js SaaS, toggled by config.banner with role and plan targeting and dismissal.