Complete visual redesign via a single centralized design system in src/index.css (no JSX/logic changes; all existing class selectors kept 1:1, so all ~20 pages restyle at once): blackened-steel dark palette, molten-ember accent with heat glow, blueprint-grid + grain background, Saira Condensed / Hanken Grotesk / JetBrains Mono web fonts, staggered load animations, reduced-motion fallback. README written from stub. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
182 lines
7.4 KiB
Markdown
182 lines
7.4 KiB
Markdown
# HarborForge.Frontend
|
|
|
|
Single-page web client for HarborForge — the agent/human collaborative
|
|
task-management platform. It provides the operator UI for projects,
|
|
milestones, tasks, proposals, calendar/meetings, users, roles and the
|
|
live monitor, and walks operators through first-time provisioning via the
|
|
SSH-tunnel Setup Wizard.
|
|
|
|
Part of the [HarborForge](../README.md) platform.
|
|
|
|
## Tech Stack
|
|
|
|
- **React 18** (`react`, `react-dom`)
|
|
- **TypeScript 5**
|
|
- **Vite 5** dev server / bundler (`@vitejs/plugin-react`)
|
|
- **react-router-dom 6** for client-side routing
|
|
- **axios** for HTTP, with a shared request/response interceptor layer
|
|
- **dayjs** for date handling
|
|
- **Vitest 4** + Testing Library (`@testing-library/react`, `jsdom`) for unit tests
|
|
|
|
Path alias `@` → `src/` (configured in `vite.config.ts` and `tsconfig.json`).
|
|
|
|
## Pages
|
|
|
|
Routes are declared in `src/App.tsx`. The app gates rendering on three
|
|
states — `checking` (probing configuration), `setup` (Setup Wizard), and
|
|
`ready` (authenticated app). `/monitor`, `/login`, `/roles` and `/users`
|
|
are reachable without an authenticated session; everything else requires login.
|
|
|
|
| Route | Page | Purpose |
|
|
|---|---|---|
|
|
| `/` | `DashboardPage` | Landing dashboard |
|
|
| `/tasks` | `TasksPage` | Task list / filtering |
|
|
| `/tasks/:taskCode` | `TaskDetailPage` | Task detail, transitions, comments |
|
|
| `/projects` | `ProjectsPage` | Project list |
|
|
| `/projects/:id` | `ProjectDetailPage` | Project detail / editor |
|
|
| `/milestones` | `MilestonesPage` | Milestone list |
|
|
| `/milestones/:milestoneCode` | `MilestoneDetailPage` | Milestone detail / actions |
|
|
| `/proposals` (`/proposes`) | `ProposalsPage` | Proposal list |
|
|
| `/proposals/:proposalCode` (`/proposes/:proposalCode`) | `ProposalDetailPage` | Proposal detail, accept/reject/reopen |
|
|
| `/calendar` | `CalendarPage` | Calendar view |
|
|
| `/meetings/:meetingCode` | `MeetingDetailPage` | Meeting detail |
|
|
| `/supports/:supportCode` | `SupportDetailPage` | Support request detail |
|
|
| `/notifications` | `NotificationsPage` | Notification inbox |
|
|
| `/roles` | `RoleEditorPage` | Role / permission (RBAC) editor |
|
|
| `/users` | `UsersPage` | User management |
|
|
| `/monitor` | `MonitorPage` | Live system monitor (public) |
|
|
| `/login` | `LoginPage` | Authentication |
|
|
| _(state-gated)_ | `SetupWizardPage` | First-run provisioning wizard |
|
|
|
|
Shared building blocks live in `src/components/` (`Sidebar`,
|
|
`CreateTaskModal`, `MilestoneFormModal`, `ProjectFormModal`,
|
|
`CopyableCode`); the auth hook is `src/hooks/useAuth.ts`; shared types are
|
|
in `src/types/index.ts`.
|
|
|
|
## How It Talks to the Backend
|
|
|
|
All API access goes through the shared axios instance in
|
|
`src/services/api.ts`:
|
|
|
|
- The base URL is **resolved at runtime** from
|
|
`localStorage['HF_BACKEND_BASE_URL']` (re-read on every request via an
|
|
axios request interceptor), not baked in at build time.
|
|
- A bearer token from `localStorage['token']` is attached automatically
|
|
when present.
|
|
- A response interceptor clears the token and redirects to `/login` on
|
|
HTTP `401`, except on the public paths `/monitor` and `/login`.
|
|
|
|
Authentication (`src/hooks/useAuth.ts`) posts form-encoded credentials to
|
|
`/auth/token`, stores the returned `access_token`, and loads the current
|
|
user from `/auth/me`.
|
|
|
|
In local dev, `vite.config.ts` also proxies `/api/*` to
|
|
`http://backend:8000` (stripping the `/api` prefix) for same-origin
|
|
development against a Compose backend.
|
|
|
|
## Setup Wizard / SSH-Tunnel Flow
|
|
|
|
On startup `App.tsx` runs `checkInitialized()`:
|
|
|
|
1. It calls the backend `GET /config/status`. If that returns
|
|
`initialized: true`, the app is `ready` (and `backend_url`, if present,
|
|
is cached into `localStorage['HF_BACKEND_BASE_URL']`).
|
|
2. Otherwise, if a wizard port was previously saved
|
|
(`localStorage['HF_WIZARD_PORT']`), it tries
|
|
`http://127.0.0.1:<port>/api/v1/config/harborforge.json` directly.
|
|
3. If neither indicates an initialized install, it renders
|
|
`SetupWizardPage`.
|
|
|
|
The Setup Wizard (`src/pages/SetupWizardPage.tsx`) is a 4-step flow —
|
|
**Wizard → Admin → Backend → Finish**:
|
|
|
|
- **Wizard**: the operator enters the local port that an SSH tunnel
|
|
forwards to **AbstractWizard**
|
|
(`ssh -L <port>:127.0.0.1:<port> user@server`). The UI health-checks
|
|
`http://127.0.0.1:<port>/health` and stores the port.
|
|
- **Admin**: collects the first admin account (username, password, email,
|
|
full name).
|
|
- **Backend**: optional backend base URL override.
|
|
- **Finish**: writes the assembled config to AbstractWizard via
|
|
`PUT http://127.0.0.1:<port>/api/v1/config/harborforge.json`, caches the
|
|
backend URL locally, and instructs the operator to
|
|
`docker compose restart` on the server before refreshing.
|
|
|
|
## Local Development
|
|
|
|
```bash
|
|
npm install
|
|
npm run dev # Vite dev server on http://0.0.0.0:3000
|
|
```
|
|
|
|
The dev server proxies `/api` to `http://backend:8000` and allows the
|
|
hosts `frontend`, `127.0.0.1`, `localhost`.
|
|
|
|
### Build
|
|
|
|
```bash
|
|
npm run build # tsc type-check, then vite build → dist/
|
|
npm run preview # serve the production build locally
|
|
```
|
|
|
|
### Test
|
|
|
|
```bash
|
|
npm test # vitest run (jsdom)
|
|
npm run test:watch # watch mode
|
|
npm run test:ui # Vitest UI
|
|
```
|
|
|
|
Tests live in `src/test/**/*.test.{ts,tsx}` (e.g. `calendar.test.tsx`,
|
|
`proposal-essential.test.tsx`) with global setup in `src/test/setup.ts`.
|
|
|
|
### Docker
|
|
|
|
```bash
|
|
docker build -t harborforge-frontend .
|
|
docker run -p 3000:3000 harborforge-frontend
|
|
```
|
|
|
|
The multi-stage `Dockerfile` builds with Node 20 and serves `dist/` via
|
|
`serve` on port 3000. Set `FRONTEND_DEV_MODE=1` to run the Vite dev server
|
|
inside the container instead of serving the static build.
|
|
|
|
## Environment / Configuration
|
|
|
|
- **`VITE_API_BASE`** (`.env`, default `http://backend:8000`) — a build
|
|
hint for the default backend location.
|
|
- The effective backend base URL is determined at **runtime** from
|
|
`localStorage['HF_BACKEND_BASE_URL']`, which is populated by the Setup
|
|
Wizard or by the backend's `/config/status` response — so changing the
|
|
backend target does not require a rebuild.
|
|
- `localStorage['HF_WIZARD_PORT']` — last AbstractWizard tunnel port.
|
|
- `localStorage['token']` — current JWT bearer token.
|
|
|
|
## Theming
|
|
|
|
The entire visual design is a single self-contained design system in
|
|
**`src/index.css`** — there is no CSS-in-JS, no Tailwind, and no
|
|
per-component stylesheets. Components only reference global CSS classes;
|
|
all colors, typography, spacing and effects are driven by CSS custom
|
|
properties on `:root`. To re-theme the app, edit this one file.
|
|
|
|
The theme is **"Foundry Deck"** — a shipwright's drafting table meets a
|
|
foundry control panel:
|
|
|
|
- **Blackened-steel dark palette** — cool-tinted near-black surfaces
|
|
(`--bg`, `--bg-card`, `--bg-hover`, `--bg-sink`) with machined,
|
|
tight-radius borders.
|
|
- **Molten-ember accent** — a single hot orange accent
|
|
(`--accent`, `--accent-hover`, `--ember` gradient, ember glow) used
|
|
sparingly as the signature element.
|
|
- **Oxidized-steel secondary** plus a forge-mapped status palette
|
|
(patina success, heat-amber warning, rust danger, arc violet).
|
|
- **Blueprint-grid background** — layered radial ember/steel vignettes, a
|
|
fine 40px draughting grid, and a subtle SVG film-grain overlay.
|
|
- **Technical web fonts** loaded via a Google Fonts `@import` at the top
|
|
of `src/index.css`: **Saira Condensed** (headings), **Hanken Grotesk**
|
|
(body), and **JetBrains Mono** (code/monospace).
|
|
|
|
Selectors are kept 1:1 with the existing markup, so theming changes are
|
|
made entirely in `src/index.css` via the CSS variables and global classes.
|