# Panel

## Stack

- React 19
- React Router 7
- Vite 6
- Tailwind CSS v4 via `@tailwindcss/vite`
- Local utilities: `clsx`, `tailwind-merge`, `class-variance-authority`
- Icons: `lucide-react`

The panel lives in `panel/src` and is built to `panel/dist`.

## Dev and runtime

- `bun run panel:dev` starts Vite on `http://localhost:5173`
- Vite proxies `/api`, `/auth`, `/assets`, and `/ws` to `http://localhost:3000`
- The Bun server serves the built panel from `panel/dist` in integrated mode

## Auth model

- `useAuth()` calls `GET /auth/me`
- unauthenticated users are sent through `/auth/discord`
- logout uses `POST /auth/logout`
- non-enrolled users see the `NotEnrolled` page

Roles:

- `admin`: admin routes plus player/game routes
- `player`: player/game routes only

## Active routes

- `/dashboard`
- `/leaderboards`
- `/games`
- `/:gameSlug/:roomId`
- `/admin`
- `/admin/users`
- `/admin/items`
- `/admin/classes`
- `/admin/quests`
- `/admin/lootdrops`
- `/admin/moderation`
- `/admin/transactions`
- `/admin/settings`

## Data layer

Shared hooks in `panel/src/lib`:

- `useAuth`
- `useDashboard`
- `useUsers`
- `useItems`
- `useSettings`
- `useWebSocket`
- `useGameRoom`

`panel/src/lib/api.ts` is a thin fetch wrapper:

- base path is empty because the panel is usually same-origin with the Bun server
- 401 triggers a redirect back into `/auth/discord`
- 204 and empty responses return `undefined`

## WebSocket and games

- `useWebSocket()` keeps a singleton browser WebSocket connection
- reconnects with exponential backoff up to 30 seconds
- `useGameRoom()` multiplexes room traffic over that shared socket
- current built-in game UIs are chess and blackjack

## Styling

- Theme tokens live in `panel/src/index.css`
- Fonts currently loaded:
  - Noto Serif
  - Manrope
  - Space Grotesk
  - JetBrains Mono
- The implemented visual system is the "Stellar Editorial" direction documented in `docs/new-design/DESIGN.md`

## Current patterns

- Admin pages tend to use explicit `refetch()` after mutations instead of a shared cache layer
- Search inputs use a 300 ms debounce in hooks such as `useUsers()` and `useItems()`
- Layout and sidebar ownership lives in `panel/src/components/Layout.tsx`
