Files
Business-Management/frontend/CLAUDE.md
T
curo1305 c5976882be Split monolithic CLAUDE.md into per-service sub-files
Root CLAUDE.md now contains only project-wide concerns (stack, architecture,
Docker, workflows, security hook). Service-specific details moved to:
- backend/CLAUDE.md — DB models, API endpoints, JWT/bcrypt, naming conventions
- frontend/CLAUDE.md — routes, TanStack Query patterns, XSS prevention
- features/ai-service/CLAUDE.md — queue endpoints, provider notes
- features/doc-service/CLAUDE.md — document models, PDF limits, proxy endpoints

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

6.9 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                  ← Axios instance + ALL API functions (single source of truth)
│   ├── 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
│   ├── 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
* 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)

Single Axios instance — all API calls live here, nowhere else:

const api = axios.create({ baseURL: "/api" });
api.interceptors.request.use((config) => {
  const token = localStorage.getItem("token");
  if (token) config.headers.Authorization = `Bearer ${token}`;
  return config;
});

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 api.get<T>(...), api.post<T>(...), etc.; always .then((r) => r.data).

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