# Internal Dashboard Frontend

Production-ready Next.js dashboard template with Mantine UI, Better Auth (Keycloak OIDC), and a typed axios data layer. Designed for internal tooling that talks to a Go backend (see [backend-dev](../backend-dev/SKILL.md)).

## Table of Contents

1. [Quick Start](#quick-start)
2. [Documentation](#documentation)
3. [Project Structure](#project-structure)
4. [Core Principles](#core-principles)
5. [Commands](#commands)
6. [Environment Variables](#environment-variables)
7. [Authentication](#authentication)
8. [UI and Theming](#ui-and-theming)
9. [Data Fetching](#data-fetching)
10. [Next Steps](#next-steps)

---

## Quick Start

### 1. Prerequisites

- Node.js 20+
- bun 1.1+
- PostgreSQL 15+ (Better Auth session/user storage)
- Keycloak realm reachable from dev machine
- A Go backend implementing the backend-dev contract (Keycloak JWT validation)

### 2. Scaffold

```bash
bun create next-app dashboard-fe \
  --typescript \
  --eslint \
  --app \
  --src-dir \
  --import-alias "@/*"
cd dashboard-fe
```

### 3. Install Dependencies

```bash
bun add @mantine/core@^9.4 @mantine/hooks@^9.4 @mantine/form@^9.4 \
  @mantine/notifications@^9.4 @mantine/modals@^9.4 @mantine/dates@^9.4 \
  @tabler/icons-react \
  better-auth@^1.6 \
  axios \
  zod \
  @tanstack/react-query \
  @tanstack/react-table \
  recharts

bun add -d postcss postcss-preset-mantine postcss-simple-vars \
  @types/node vitest @vitest/coverage-v8 \
  @testing-library/react @testing-library/jest-dom @testing-library/user-event \
  jsdom @playwright/test msw prettier eslint-config-prettier
```

### 4. Configure Environment

Copy and edit `.env.local`:

```env
# Public (browser-visible)
NEXT_PUBLIC_API_BASE_URL=http://localhost:8080
NEXT_PUBLIC_APP_NAME=Dashboard

# Better Auth
BETTER_AUTH_SECRET=replace-with-32-byte-random
BETTER_AUTH_URL=http://localhost:3000

# Better Auth session DB
DATABASE_URL=postgres://user:pass@localhost:5432/dashboard_auth?sslmode=disable

# Keycloak OIDC (server-only — never NEXT_PUBLIC_)
KEYCLOAK_ISSUER=https://auth.example.com/realms/production
KEYCLOAK_CLIENT_ID=dashboard-fe
KEYCLOAK_CLIENT_SECRET=replace-with-realm-client-secret
```

### 5. Run

```bash
bun run dev
```

Open <http://localhost:3000>. Sign in redirects to Keycloak; on success you land on `/dashboard`.

---

## Documentation

| File | Contents |
|------|----------|
| [references/ARCHITECTURE.md](references/ARCHITECTURE.md) | App Router structure, providers tree, server/client boundaries, feature-sliced layout |
| [references/AUTHENTICATION.md](references/AUTHENTICATION.md) | Better Auth + Keycloak OIDC, route groups, axios auth interceptor, middleware |
| [references/UI-AND-THEMING.md](references/UI-AND-THEMING.md) | Mantine 9.4 setup, AppShell, theme, Notifications, ModalsProvider |
| [references/DATA-FETCHING.md](references/DATA-FETCHING.md) | axios setup, interceptors, TanStack Query integration |
| [references/FORMS-AND-VALIDATION.md](references/FORMS-AND-VALIDATION.md) | Mantine form + Zod resolver, shared schemas with API |
| [references/TABLES-AND-CHARTS.md](references/TABLES-AND-CHARTS.md) | TanStack Table + Recharts patterns |
| [references/TESTING.md](references/TESTING.md) | Vitest + Testing Library + Playwright |
| [references/CODING-STANDARDS.md](references/CODING-STANDARDS.md) | TypeScript strict, ESLint, Prettier, accessibility |
| [references/DEPLOYMENT.md](references/DEPLOYMENT.md) | Docker, CI/CD, env management |

---

## Project Structure

```
src/
├── app/
│   ├── (public)/               # Public routes — login page lives here
│   ├── (authed)/               # Protected routes — wrapped by middleware
│   │   └── dashboard/
│   ├── api/
│   │   └── auth/[...all]/      # Better Auth catch-all
│   ├── layout.tsx              # Root providers (Mantine, Query, Notifications, Modals)
│   └── providers.tsx           # Client-side providers tree
├── components/                 # Shared, feature-agnostic UI
├── features/                   # Feature-sliced modules
│   └── <feature>/
│       ├── api/                # axios calls for this feature
│       ├── components/         # Feature components
│       ├── hooks/              # Feature hooks (queries, mutations)
│       ├── schemas/            # Zod schemas
│       └── types/              # Feature-specific types
├── lib/
│   ├── axios.ts                # Configured axios instance + interceptors
│   ├── auth/
│   │   ├── server.ts           # Better Auth server instance + helpers
│   │   └── client.ts           # Better Auth client instance
│   └── env.ts                  # Zod-validated env loader
├── server/                     # Server-only utilities (RSC helpers)
└── middleware.ts               # Route protection via Better Auth session
```

---

## Core Principles

### Server-First

- Default to React Server Components.
- Promote to client components only when you need state, effects, or browser APIs.
- Never put secrets in `NEXT_PUBLIC_*` — server-only env lives in `src/lib/env.ts`.

### Route Group Boundaries

- `(public)` for login, logout, error pages — no auth required.
- `(authed)` for everything else — guarded by `middleware.ts`.

### Providers Tree at Root

A single `providers.tsx` client component composes, in order: `MantineProvider` → `QueryClientProvider` → `ModalsProvider` → `Notifications`. Never instantiate providers inside feature components.

### Feature-Sliced Layout

Each feature owns its API calls, components, hooks, schemas, and types. Cross-feature imports go through `src/components` or a public barrel — never reach into another feature's internals.

### Strict TypeScript

- `strict: true`, `noUncheckedIndexedAccess: true`, `exactOptionalPropertyTypes: true`.
- No `any`. Use `unknown` and narrow.
- API response types are inferred from Zod schemas; do not duplicate.

### Accessibility Baseline

- Semantic HTML first.
- All interactive elements keyboard-reachable.
- Color contrast meets WCAG AA.
- Mantine `Label` + `aria-describedby` for every form field.

### Env Safety

- `src/lib/env.ts` validates all env vars with Zod at boot; missing or malformed values fail loud.
- Only `NEXT_PUBLIC_*` vars are imported by client code.

---

## Commands

```bash
bun run dev              # Next dev server with HMR
bun run build            # Production build (standalone output)
bun run start            # Run standalone build
bun run lint             # ESLint
bun run typecheck        # tsc --noEmit
bun run format           # Prettier write
bun run format:check     # Prettier check
bun run test             # Vitest unit + integration
bun run test:coverage    # Vitest with coverage
bun run test:e2e         # Playwright
bun run test:e2e:ui      # Playwright UI mode
```

---

## Environment Variables

| Variable | Required | Scope | Description |
|----------|----------|-------|-------------|
| `NEXT_PUBLIC_API_BASE_URL` | Yes | Public | Base URL of the Go backend |
| `NEXT_PUBLIC_APP_NAME` | No | Public | Display name in header (default: `Dashboard`) |
| `BETTER_AUTH_SECRET` | Yes | Server | 32+ byte random secret; used to sign Better Auth sessions |
| `BETTER_AUTH_URL` | Yes | Server | Public URL of this app (e.g. `https://dashboard.example.com`) |
| `DATABASE_URL` | Yes | Server | Postgres DSN for Better Auth session/user tables |
| `KEYCLOAK_ISSUER` | Yes | Server | Keycloak issuer URL (no trailing slash) |
| `KEYCLOAK_CLIENT_ID` | Yes | Server | Keycloak client ID registered for this app |
| `KEYCLOAK_CLIENT_SECRET` | Yes | Server | Keycloak client secret |
| `LOG_LEVEL` | No | Server | `debug` / `info` / `warn` / `error` (default `info`) |

### Configuration Strategy

| Environment | Source |
|-------------|--------|
| `local` | `.env.local` |
| `staging` | Container env injected at runtime (secrets from OpenBao) |
| `production` | Container env injected at runtime (secrets from OpenBao) |

See [references/DEPLOYMENT.md](references/DEPLOYMENT.md) for env injection patterns.

---

## Authentication

Better Auth runs as a Keycloak OIDC client. On login, Better Auth exchanges the Keycloak code, stores tokens in its Postgres-backed session, and exposes helpers to read the Keycloak access token at request time. The axios layer attaches this token to every request to the Go backend, which validates it via JWKS (see [backend-dev AUTHENTICATION](../backend-dev/references/AUTHENTICATION.md)).

```
Browser → Next.js → Keycloak → Better Auth (stores session) → Go backend (validates JWT via JWKS)
```

See [references/AUTHENTICATION.md](references/AUTHENTICATION.md) for full setup, middleware, and interceptor details.

---

## UI and Theming

Mantine 9.4 is wired with SSR-safe color scheme handling, a single primary color, default radius, and `Notifications` + `ModalsProvider` registered at the root. The dashboard chrome uses `AppShell` with a header (user menu, color scheme toggle) and a collapsible navbar.

See [references/UI-AND-THEMING.md](references/UI-AND-THEMING.md).

---

## Data Fetching

A single configured axios instance lives in `src/lib/axios.ts`. It attaches the Keycloak access token (read from the Better Auth session) on every request and normalizes Go-backend `ApiError` responses into typed exceptions. TanStack Query handles caching, retries, and optimistic updates.

See [references/DATA-FETCHING.md](references/DATA-FETCHING.md).

---

## Next Steps

1. [references/ARCHITECTURE.md](references/ARCHITECTURE.md) — understand the layout and providers tree.
2. [references/AUTHENTICATION.md](references/AUTHENTICATION.md) — wire Better Auth + Keycloak.
3. [references/UI-AND-THEMING.md](references/UI-AND-THEMING.md) — bring up Mantine and AppShell.
4. [references/DATA-FETCHING.md](references/DATA-FETCHING.md) — configure axios and React Query.
5. [references/DEPLOYMENT.md](references/DEPLOYMENT.md) — Dockerize and ship.