Dashboard and Settings
Build on the authenticated dashboard surface, its flag-gated account and settings tabs, and the role-gated admin panel.
The dashboard is the authenticated surface at config.routes.dashboard (/dashboard), a Server Component that fetches GET /dashboard/status once on the server. Its sub-tabs come from the shared sectionTabsConfig (packages/config/src/section-tabs.ts), where requires and orgRoles gates hide anything the viewer or the active config cannot reach.
Dashboard surface
The page renders from that single server-side fetch - no client loading spinner - and drives the plan cards, security score, team stats, usage chart, and activity grid from one payload.
// GET /dashboard/status - packages/api/src/routes/internal/dashboard.ts (authGuard)
{
fundedByOther, // true when the workspace owner pays, not you
planName, planId, planType, planExpiresAt, // ALL null when fundedByOther
isActive, // always present
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 fundedByOther is true the caller is a member of a workspace someone else funds, and every plan field comes back null - only isActive survives. The owner's balance is personal and funds every organization they own, so showing it inside one workspace would leak their spending in another. Render from isActive and point the member at the owner. See Organizations.
Page chrome
Stack an authenticated page's chrome in this order: top bar (the section title, see Navigation), section tabs where the section has them, PageHeader, then content. All of it renders inside one PageContent column.
PageContent comes from @repo/ui/components/ui/page-content. It caps the readable width, centres the column in the content area, and carries the page padding; the vertical rhythm stays with the page (space-y-* or flex flex-col gap-* via className).
width | Cap | Use for |
|---|---|---|
default | max-w-3xl | forms, settings, prose |
wide | max-w-6xl | data tables, admin, billing, overview grids |
full | none | pages that fill the area but still want the padding |
Deliberately full-bleed surfaces (chat) render OUTSIDE PageContent and take the whole content area.
PageHeader comes from @repo/ui/components/ui/page-header and takes optional title, description, and actions. On a tabbed page pass only description plus 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).
Account and Settings tabs
The /settings/* sub-tabs split across two user-menu entries: Account (sectionTabsConfig.account) holds the user's own account tabs, Settings (sectionTabsConfig.settings) holds product configuration, mostly the AI surfaces. The sidebar keeps only the workspace objects, Organization and Project.
| 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 and credit ledger |
| Security | Account | /settings/security | always on |
| Models | Settings | /settings/models | requires: ["aiModels"] |
| Integrations | Settings | /settings/integrations | requires: ["ai"] |
| Runners | Settings | /settings/runners | requires: ["runner"] |
| MCP | Settings | /settings/mcp | requires: ["mcpServer"] |
| Developers | Settings | /settings/developers | requires: ["apiKeys"] |
| Project | Settings | /settings/project | requires: ["projects"] - see projects |
With config.tenancy.multiTenant on, an Organization group adds Overview (/settings/organization, match: "exact") and Activity (/settings/organization-activity, gated orgRoles: ["owner", "admin"]).
The Billing tab always renders, but its sections only appear when config.payment.enabled is true (the default). With payments off, the tab shows nothing actionable.
Admin panel
Access is enforced server-side in admin/layout.tsx: any session whose user.role !== "admin" is redirected to config.routes.loginRedirect, and the sidebar link is separately hidden by roles: ["admin"] in config/sidebar.ts. Because the layout gates the whole panel, individual tabs carry no roles gate; only feature flags hide one from an admin.
| Tab | Route | Tab gate |
|---|---|---|
| Finance | /admin | requires: ["credits"] |
| Users | /admin/users | none |
| Organizations | /admin/organizations | requires: ["multiTenant"] |
| Audit logs | /admin/audit-logs | none |
| Billing logs | /admin/billing-logs | none |
| API keys | /admin/api-keys | requires: ["apiKeys"] |
| Announcements | /admin/announcements | none |
| Email analytics | /admin/emails | requires: ["emailTracking"] |
/admin is the finance dashboard - revenue per currency, stamped vendor cost, top spenders, and a ledger-integrity check. config.routes.admin stays /admin/users: it is the impersonation-return target.
Navigation
Configure the navbar, sidebar, user menu, and section tabs from typed config files, with role, org-role, and feature-flag gating plus i18n labels.
Banners
Ship announcement bars on the marketing and dashboard surfaces, each toggled by config.banner, with role and plan targeting plus dismissal on the dashboard.