Dashboard

Built-in dashboard pages, navigation structure, and how to add new pages.

Built-in Pages

The dashboard is located at app/(dashboard)/dashboard/ and includes these pages out of the box:

RoutePageAccess
/dashboardOverview with analytics and chartsAll users
/dashboard/organizationOrg management, members, invitationsAll users
/dashboard/settingsProfile settingsAll users
/dashboard/settings/notificationsNotification preferencesAll users
/dashboard/billingSubscription, plan, payment historyAll users
/dashboard/adminAdmin panelADMIN role only
/dashboard/superadminPlatform overviewSUPERADMIN role only

Navigation is config-driven in lib/config/navigation.ts. It defines groups with role-based visibility:

export const navigationConfig: NavGroup[] = [
  {
    heading: "Overview",
    items: [
      { label: "Dashboard", href: "/dashboard", icon: LayoutDashboard },
    ],
  },
  {
    heading: "Settings",
    items: [
      { label: "Profile", href: "/dashboard/settings", icon: User },
      { label: "Billing", href: "/dashboard/billing", icon: CreditCard },
      { label: "Notifications", href: "/dashboard/settings/notifications", icon: Bell },
    ],
  },
  {
    heading: "Admin",
    items: [
      { label: "Admin Panel", href: "/dashboard/admin", icon: Shield },
    ],
    roles: ["ADMIN"],   // only visible to ADMIN role
  },
]

The sidebar calls getFilteredNav(user.role) to show only groups the user's role is allowed to see.

Adding a New Dashboard Page

Follow this pattern to add a new page:

1

Create the page (server component)

// app/(dashboard)/dashboard/my-page/page.tsx
import type { Metadata } from "next"
import { getRequiredSession } from "@/lib/auth-utils"
import { MyContent } from "@/components/core/dashboard/my-page/my-content"

export const metadata: Metadata = { title: "My Page — YourSaaS" }

export default async function MyPage() {
  const session = await getRequiredSession()
  return <MyContent user={session.user} />
}
2

Create the client component

// components/core/dashboard/my-page/my-content.tsx
"use client"
import { useMyData } from "@/hooks/core/use-my-data"

export function MyContent({ user }: { user: SessionUser }) {
  const { data, isLoading } = useMyData()
  // Build your UI here
}
3

Add TanStack Query hook

// hooks/core/use-my-data.ts
import { useQuery } from "@tanstack/react-query"
import { api } from "@/lib/api"
import { queryKeys } from "@/lib/query-keys"

export function useMyData() {
  return useQuery({
    queryKey: queryKeys.myData.list(),
    queryFn: () => api.get("/api/my-data"),
  })
}
4

Add to navigation

// lib/config/navigation.ts
{ label: "My Page", href: "/dashboard/my-page", icon: SomeIcon }

Role-Gated Pages

To restrict a page to admins only:

// page.tsx
import { getAdminSession } from "@/lib/auth-utils"

export default async function AdminPage() {
  await getAdminSession()  // throws 403 if not ADMIN or SUPERADMIN
  return <AdminContent />
}

For SUPERADMIN only:

import { getSuperAdminSession } from "@/lib/auth-utils"

export default async function SuperAdminPage() {
  await getSuperAdminSession()  // throws 403 if not SUPERADMIN
  return <SuperAdminContent />
}

Dashboard Header

The header (components/core/dashboard/dashboard-header.tsx) includes:

  • Breadcrumb — shows current page path
  • Search button — opens the CommandPalette (Cmd+K); hidden when NEXT_PUBLIC_ENABLE_SEARCH=false
  • Notification bell — shows unread count badge, opens NotificationCenter
  • User dropdown — profile link, settings link, sign out

Notification System

Notifications use the Notification Prisma model:

model Notification {
  id        String   @id @default(cuid())
  userId    String
  type      String
  title     String
  message   String
  read      Boolean  @default(false)
  link      String?
  createdAt DateTime @default(now())
}

Notifications are created server-side via lib/notifications.ts helpers (notify.success, notify.error, notify.warning). The bell icon shows unread count. Users mark notifications as read via PATCH /api/notifications/read.

Dashboard Layout

The layout at app/(dashboard)/layout.tsx wraps all dashboard pages. It renders:

  • The collapsible sidebar (desktop + mobile sheet)
  • The top header with collapse toggle
  • The onboarding wizard (inline, when onboardingDone === false)

The sidebar collapse state is stored in a cookie and read server-side to avoid layout shift.

Onboarding Wizard

When session.user.onboardingDone === false, the dashboard renders an inline 3-step wizard instead of the page content:

  1. Profile — set your name and avatar
  2. Workspace — create or join an organization
  3. Done — marks onboardingDone = true via POST /api/user/onboarding-complete

The wizard uses AnimatePresence for step transitions.

Demo Mode — Explore freely. Some actions are restricted. demo@launchfst.dev / demo1234

Get LaunchFst →