Files
Business-Management/frontend/CLAUDE.md
T
curo1305 cfec3bb906 feat: Phase 4+5 — admin storage UI, backend proxy, CLAUDE.md enforcement
- backend/app/routers/storage_config.py: 5 admin-only endpoints proxying
  storage-service config + migration API (GET/PATCH/POST/DELETE)
- backend/app/main.py: register storage_config router
- frontend/src/api/client.ts: StorageStatus, MigrationStatus,
  StorageBackendConfig interfaces + 5 API functions
- frontend/src/pages/StorageAdminPage.tsx: full admin UI — backend health
  dot, driver selector (local/S3/WebDAV), conditional credential fields,
  Test & Migrate button, live 2s-poll migration progress bar, Cancel
- frontend/src/App.tsx: /admin/storage route (AdminRoute guard)
- CLAUDE.md: storage enforcement rule, updated Docker tables (6 services,
  3 volumes), §20 in merge checklist
- backend/CLAUDE.md, frontend/CLAUDE.md, doc-service/CLAUDE.md,
  ai-service/CLAUDE.md: updated to reflect storage-service integration
- tests/ALL_TESTS.md + tests/storage-service_tests.md: §20 (20 tests)
- backend/STATUS.md, frontend/STATUS.md: updated with new endpoints/routes
- changelog/2026-04-20_storage-service.md: full change log

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 16:13:05 +02:00

8.0 KiB

frontend — Claude context

React 18 SPA built with Vite, port 5173 dev / 80 prod, served by nginx-unprivileged in production. All /api/* requests are proxied to backend:8000. See root CLAUDE.md for architecture, Docker, and project-wide workflows.


Commands

All commands run inside Docker — never on the host.

docker compose exec frontend npm run typecheck
docker compose exec frontend npm run lint

File & Folder Tree

frontend/
├── src/
│   ├── main.tsx                       ← React root, QueryClientProvider, BrowserRouter
│   ├── App.tsx                        ← Route tree, PrivateRoute, AdminRoute
│   ├── api/client.ts                  ← Native fetch wrapper (`request()`) + ALL API functions (single source of truth); no Axios
│   ├── hooks/
│   │   ├── useAuth.ts                 ← Token state (localStorage), login/logout
│   │   └── useTheme.ts                ← Theme toggle
│   ├── components/
│   │   ├── AppShell.tsx               ← Layout: Sidebar + SourcePanel (on /apps/documents) + main
│   │   ├── Sidebar.tsx                ← Collapsible nav (icons ↔ icons+labels)
│   │   ├── SourcePanel.tsx            ← Views + searchable category tree (docs route only)
│   │   ├── ManageCategoriesDialog.tsx ← Category CRUD modal (rename, delete)
│   │   ├── DocumentSlideOver.tsx      ← Right slide-over: detail, edit, share, AI suggestions
│   │   ├── ThemeToggle.tsx            ← Light/dark mode toggle
│   │   ├── PluginSchemaForm.tsx       ← JSON Schema → React form (boolean/string/number/readOnly)
│   │   └── ui/                        ← shadcn/ui components (Button, Input, …)
│   ├── pages/                         ← One file per route
│   │   ├── DocServiceSettingsPage.tsx ← Combined doc-service settings: upload limits + watch directory
│   │   ├── PluginSettingsPage.tsx     ← Generic plugin settings page driven by manifest
│   │   └── StorageAdminPage.tsx       ← Admin storage backend config + live migration progress
│   ├── lib/utils.ts                   ← cn() = clsx + tailwind-merge
│   └── styles/theme.css               ← CSS custom properties, Tailwind setup
├── vite.config.ts                     ← /api/* proxied to backend:8000
├── tailwind.config.ts
├── components.json                    ← shadcn/ui config
├── Dockerfile                         ← Multi-stage: Node build → nginx-unprivileged
└── STATUS.md

Frontend Routes

Path Component Guard
/login LoginPage Public
/ DashboardPage PrivateRoute
/apps AppsPage PrivateRoute
/apps/documents DocumentsPage PrivateRoute
/apps/documents/settings DocServiceSettingsPage ServiceAdminRoute (is_admin OR doc-service-admin member)
/apps/ai/settings AIAdminSettingsPage ServiceAdminRoute (is_admin OR ai-service-admin member)
/profile ProfilePage PrivateRoute
/settings SettingsPage PrivateRoute
/settings/plugins/:id PluginSettingsPage PrivateRoute (auth enforced per-plugin by backend)
/admin AdminPage (→ /admin/users) AdminRoute
/admin/users AdminUsersPage AdminRoute
/admin/groups AdminGroupsPage AdminRoute
/admin/appearance AdminAppearancePage AdminRoute
/admin/storage StorageAdminPage AdminRoute
* redirect to /

PrivateRoute — checks token from useAuth, redirects to /login if absent. AdminRoute — checks token AND queries GET /api/users/me for is_admin; waits for query to avoid flash; redirects to /login (not /) if not admin.


Security Standards

XSS prevention

  • React JSX text interpolation ({value}) is HTML-escaped by the DOM renderer — never use dangerouslySetInnerHTML with user-supplied content.
  • Server-side sanitize_str provides defense-in-depth (control char stripping, max length).

Frontend Patterns & Conventions

API client (src/api/client.ts)

No Axios — uses a thin native fetch wrapper. All API calls live here, nowhere else.

The core request() function handles:

  • Prepends /api base URL
  • Injects Authorization: Bearer {token} from localStorage on every request
  • Global 401 handler: clears localStorage token and redirects to /login via window.location.href — this is the expired-session redirect
  • Throws ApiError(status, detail) on non-2xx responses (detail parsed from JSON body)
  • Returns undefined on 204 No Content
  • Supports blob: true for file download/preview responses
// The internal api object — use these methods in exported functions:
api.get<T>(path, params?)          // GET with optional query params object
api.post<T>(path, json?)           // POST with JSON body
api.postForm<T>(path, URLSearchParams)  // POST with form-encoded body (login)
api.postFile<T>(path, FormData)    // POST with multipart body (file upload)
api.patch<T>(path, json?)          // PATCH with JSON body
api.delete<T>(path)                // DELETE
api.getBlob(path)                  // GET → Blob (download / view)

Adding a new API call:

  1. Define a TypeScript interface for the response if it's new.
  2. Add a named export function (getX, createX, updateX, deleteX).
  3. Use the appropriate api.* method — return the promise directly (no .then((r) => r.data)).

Error handling in components: catch blocks receive an ApiError instance with .status and .message (the detail string).

TanStack Query conventions

Query keys (flat arrays, lowercase):

["me"]                    // current user
["services"]              // service health list
["dashboard-prefs"]       // user dashboard preferences
["categories"]            // document categories
["documents", params]     // document list (params object for cache isolation)
["documents-shared", params] // shared-with-me list
["document", id]          // single document
["document-shares", id]   // share list for a specific document
["my-groups"]             // current user's group memberships (for share picker)
["plugins"]               // accessible plugin list (filtered by user access)
["plugin-manifest", id]   // plugin manifest (cached)
["plugin-settings", id]   // plugin current settings

Mutation pattern:

const mutation = useMutation({
  mutationFn: apiFunction,
  onSuccess: () => {
    queryClient.invalidateQueries({ queryKey: ["affected-key"] });
    // additional side effects (close dialog, reset form, etc.)
  },
});
// Usage:
mutation.mutate(data);
mutation.isPending  // show spinner / disable button
mutation.isError    // show error message

Polling:

useQuery({ queryKey: ["services"], queryFn: getServices,
  refetchInterval: 30_000, refetchIntervalInBackground: true });

Route guards

// PrivateRoute — redirect to /login if no token
// AdminRoute   — redirect to /login if no token OR not admin
//   (waits for getMe() query to avoid flash; uses 404 semantics)

Component patterns

  • Functional components only.
  • Local useState for UI-only state (edit mode, pending values, open/closed).
  • Server state via useQuery / useMutation — no duplicated local copies.
  • cn() from lib/utils.ts for conditional Tailwind classes.
  • lucide-react for all icons.
  • Never use dangerouslySetInnerHTML with user-supplied content.

Naming & Code Conventions

  • TypeScript strict mode — no any.
  • API response types inferred from interfaces in client.ts only.
  • Error messages displayed inline (no alert); loading shown as disabled state or "…" text.
  • All user-facing text: safe via React JSX rendering (not innerHTML).

Default Values & Limits

Parameter Value Location
Token localStorage key "token" useAuth.ts