GenerateSaaS

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).

widthCapUse for
defaultmax-w-3xlforms, settings, prose
widemax-w-6xldata tables, admin, billing, overview grids
fullnonepages 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.

TabGroupRouteGate
ProfileAccount/settings/profilealways on
BillingAccount/settings/billingtab always present; sections gated by config.payment.enabled
TransactionsAccount/settings/billing/historyalways on - the billing and credit ledger
SecurityAccount/settings/securityalways on
ModelsSettings/settings/modelsrequires: ["aiModels"]
IntegrationsSettings/settings/integrationsrequires: ["ai"]
RunnersSettings/settings/runnersrequires: ["runner"]
MCPSettings/settings/mcprequires: ["mcpServer"]
DevelopersSettings/settings/developersrequires: ["apiKeys"]
ProjectSettings/settings/projectrequires: ["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.

TabRouteTab gate
Finance/adminrequires: ["credits"]
Users/admin/usersnone
Organizations/admin/organizationsrequires: ["multiTenant"]
Audit logs/admin/audit-logsnone
Billing logs/admin/billing-logsnone
API keys/admin/api-keysrequires: ["apiKeys"]
Announcements/admin/announcementsnone
Email analytics/admin/emailsrequires: ["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.

On this page