Skip to content

Commit 459a6f4

Browse files
iammukeshmclaude
andcommitted
feat(admin): scaffold React + Vite admin console
Standalone Vite app at clients/admin with auth, tenant list, and app shell. No pnpm workspace so Aspire can mount it as a plain ExecutableResource. - React 19 + TypeScript + Tailwind 4 CSS-first + shadcn/ui (new-york) - TanStack Query v5, react-router v7 - JWT auth with localStorage + single-flight refresh on 401 - First real page: paginated tenants list via GET /api/v1/tenants/ Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 6e2e853 commit 459a6f4

38 files changed

Lines changed: 6564 additions & 0 deletions

clients/admin/.gitignore

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
node_modules
2+
dist
3+
dist-ssr
4+
*.local
5+
*.tsbuildinfo
6+
.DS_Store
7+
8+
# Env
9+
.env
10+
.env.local
11+
.env.*.local
12+
13+
# Editor
14+
.vscode/*
15+
!.vscode/extensions.json
16+
.idea
17+
18+
# Logs
19+
logs
20+
*.log
21+
npm-debug.log*
22+
pnpm-debug.log*
23+
24+
# Generated
25+
src/api/schema.d.ts

clients/admin/README.md

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
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.

clients/admin/components.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"$schema": "https://ui.shadcn.com/schema.json",
3+
"style": "new-york",
4+
"rsc": false,
5+
"tsx": true,
6+
"tailwind": {
7+
"config": "",
8+
"css": "src/styles/globals.css",
9+
"baseColor": "slate",
10+
"cssVariables": true,
11+
"prefix": ""
12+
},
13+
"aliases": {
14+
"components": "@/components",
15+
"utils": "@/lib/cn",
16+
"ui": "@/components/ui",
17+
"lib": "@/lib",
18+
"hooks": "@/hooks"
19+
},
20+
"iconLibrary": "lucide"
21+
}

clients/admin/index.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!doctype html>
2+
<html lang="en" class="h-full">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>FullStackHero — Admin</title>
8+
</head>
9+
<body class="h-full bg-background text-foreground antialiased">
10+
<div id="root" class="h-full"></div>
11+
<script type="module" src="/src/main.tsx"></script>
12+
</body>
13+
</html>

0 commit comments

Comments
 (0)