|
| 1 | +# FullStackHero — Admin |
| 2 | + |
| 3 | +Operator console for the FullStackHero .NET Starter Kit. Built with React 19, Vite 7, TypeScript, TanStack Query, React Router and Tailwind 4 + shadcn/ui. |
| 4 | + |
| 5 | +This is a **standalone Vite app** — not part of a pnpm workspace — so it can be mounted into .NET Aspire as a plain `ExecutableResource` without monorepo friction. |
| 6 | + |
| 7 | +## Prerequisites |
| 8 | + |
| 9 | +- Node.js 20+ |
| 10 | +- The API running locally (`dotnet run --project src/Playground/FSH.Starter.Api`, defaults to `http://localhost:5030`) |
| 11 | + |
| 12 | +## Install & run |
| 13 | + |
| 14 | +```bash |
| 15 | +cd clients/admin |
| 16 | +npm install |
| 17 | +npm run dev # http://localhost:5173 |
| 18 | +``` |
| 19 | + |
| 20 | +The dev server proxies `/api`, `/openapi`, and `/scalar` to the API origin, so browser requests stay same-origin in development. |
| 21 | + |
| 22 | +## Scripts |
| 23 | + |
| 24 | +| Script | Purpose | |
| 25 | +|-------------------|--------------------------------------| |
| 26 | +| `npm run dev` | Vite dev server on port 5173 | |
| 27 | +| `npm run build` | `tsc -b` + `vite build` → `dist/` | |
| 28 | +| `npm run preview` | Preview the production build | |
| 29 | +| `npm run lint` | ESLint (flat config) | |
| 30 | + |
| 31 | +## Configuration |
| 32 | + |
| 33 | +Environment variables are read via `import.meta.env` and surfaced through `src/env.ts`: |
| 34 | + |
| 35 | +| Variable | Default | Purpose | |
| 36 | +|---------------------------|--------------------------|-----------------------------------------------| |
| 37 | +| `VITE_API_BASE_URL` | `http://localhost:5030` | API origin used by the dev proxy | |
| 38 | +| `VITE_DEFAULT_TENANT` | `root` | Default tenant header for unauthenticated calls | |
| 39 | + |
| 40 | +Create `.env.local` to override locally. |
| 41 | + |
| 42 | +## Structure |
| 43 | + |
| 44 | +``` |
| 45 | +src/ |
| 46 | +├── api/ # Typed API client functions (per backend feature) |
| 47 | +├── auth/ # Token store, JWT decode, auth context, protected route |
| 48 | +├── components/ |
| 49 | +│ ├── layout/ # Sidebar, Topbar, AppShell |
| 50 | +│ └── ui/ # shadcn primitives (Button, Card, Input, Label, Table) |
| 51 | +├── lib/ |
| 52 | +│ ├── api-client.ts # fetch wrapper: auth header, tenant header, single-flight refresh |
| 53 | +│ ├── query-client.ts # TanStack QueryClient |
| 54 | +│ └── cn.ts # clsx + tailwind-merge |
| 55 | +├── pages/ # Route-level components |
| 56 | +├── styles/globals.css # Tailwind 4 CSS-first + shadcn CSS variables |
| 57 | +├── App.tsx # Provider tree (QueryClient, Auth, Router) |
| 58 | +├── main.tsx # React entry |
| 59 | +└── routes.tsx # Route definitions |
| 60 | +``` |
| 61 | + |
| 62 | +## Authentication flow |
| 63 | + |
| 64 | +1. `POST /api/v1/identity/token/issue` with `{ email, password }` plus `tenant` header. |
| 65 | +2. Access + refresh tokens are stored in `localStorage` (keys prefixed `fsh.admin.`). |
| 66 | +3. The API client attaches `Authorization: Bearer <access>` and `tenant: <slug>` on every call. |
| 67 | +4. On `401`, a single-flight refresh call hits `POST /api/v1/identity/token/refresh`, retries the original request, and logs the user out if the refresh fails. |
| 68 | + |
| 69 | +## Styling |
| 70 | + |
| 71 | +- Tailwind 4 CSS-first config lives in `src/styles/globals.css` (no `tailwind.config.ts`). |
| 72 | +- Colors use shadcn/ui oklch CSS variables; dark mode is toggled via the `.dark` class on `<html>`. |
| 73 | +- shadcn components follow the **new-york** style; `components.json` is present for `npx shadcn add ...`. |
| 74 | + |
| 75 | +## Adding a new page |
| 76 | + |
| 77 | +1. Add the API function in `src/api/<feature>.ts`. |
| 78 | +2. Add the page component in `src/pages/<feature>/<name>.tsx`. |
| 79 | +3. Register it in `src/routes.tsx` as a child of the `AppShell` route. |
| 80 | +4. Add a nav entry in `src/components/layout/sidebar.tsx`. |
| 81 | + |
| 82 | +## Production build |
| 83 | + |
| 84 | +`npm run build` emits a static bundle to `dist/`. Host it behind any static web server (nginx, Caddy, Azure Static Web Apps, CloudFront, …). Configure the reverse proxy to forward `/api/*` to the backend and serve `index.html` as the SPA fallback for unmatched routes. |
0 commit comments