# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## What this is

ROOFI CRM (crm.roofi.in) — lead distribution, quotations, proforma invoices and invoicing for a stone-coated metal tile business across Indian states. Two independent npm packages: `server/` (Express + Mongoose API) and `client/` (React 19 + Vite SPA). The repo root doubles as the **cPanel deploy directory**: the root `index.html`, `assets/`, `favicon.png`, `robots.txt` are the *built* client copied out of `client/dist/`, and `.htaccess` reverse-proxies `/api` and `/uploads` to the Node process on `127.0.0.1:5039`.

## Commands

```bash
# server (port 5039)
cd server && npm run dev        # tsx watch src/index.ts
cd server && npm run build      # tsc -> dist/
cd server && npm start          # node dist/index.js

# client (port 3000, proxies /api + /uploads to 127.0.0.1:5039)
cd client && npm run dev
cd client && npm run build      # vite build -> client/dist
cd client && npm run lint       # eslint (+ prettier plugin)
```

No test suite exists. Production runs under PM2 via `server/ecosystem.config.cjs` (`server.js` is a guard wrapper that fails loudly if `dist/index.js` is missing). Deploying the frontend means building in `client/` and copying `client/dist/*` to the repo root.

`client/bunfig.toml` sets `minimumReleaseAge = 86400` — a supply-chain guard. Keep it if you touch client deps.

## Architecture

### Server (`server/src`)
- `index.ts` — app wiring. Note two non-REST endpoints registered before the router: `app.all("/delete")` and `/api/delete` both call `purgeDataExceptCore` (destructive; wipes everything but core entities). Treat with care.
- `routes/apiRoutes.ts` — the single flat router; every route is here.
- `controllers/dataController.ts` (~800 lines) — all non-auth handlers. This is where nearly all business logic lives.
- `controllers/authController.ts` — JWT login (7d expiry).
- `config/db.ts` — `connectDB()` also runs `ensureUsersSeeded()`: it seeds `seed/seedData.ts` when `users` is empty, otherwise upserts any missing account from `config/credentials.ts`. **Baseline users reappear on every boot** — deleting one from Mongo won't stick unless removed from `credentials.ts`.
- `config/upload.ts` — resolves the uploads dir (`UPLOAD_DIR` env, else `./uploads`, else `../uploads`) and builds public URLs from `BASE_URL`. Files arrive as **base64 JSON bodies** (hence `express.json({ limit: "50mb" })`), not multipart — there is no multer.

Conventions worth matching:
- Human-readable string ids (`LD-1001`, `QT-2026-0001`, `PI-2026-0001`, `CL-001`, `MT-001`, `U-001`) generated from a `countDocuments()` + `padStart`. Mongo `_id` is never used as the app identity; models carry an `id: String, unique` field.
- Dates are stored as **strings** (`YYYY-MM-DD`, or `YYYY-MM-DD HH:mm` for `lastLogin`), not `Date`.
- Most POST routes are duplicated as PUT and vice versa (`router.put(...)` + `router.post(...)` on the same handler) because the client only ever sends POST.

### Auth is effectively advisory
`login` falls back to matching *any active user of the requested role* when the email isn't found, and accepts the literal password `roofi@2026` regardless of the stored hash. No route is behind auth middleware — `getMe` reads `(req as any).user`, which nothing populates. Role scoping is enforced **client-side only** (`useScope`). Assume any API endpoint is reachable unauthenticated; don't claim a change is "secured" by adding a client-side check.

### Client (`client/src`)
- `App.tsx` — all routes; everything except `/` (login) sits inside `AppLayout`. `/invoices*` intentionally reuses the proforma pages/components.
- `services/api.ts` — the only fetch layer. `apiRequest` attaches `localStorage["roofi.session.token"]` as a Bearer header.
- `main.tsx` mounts a `QueryClientProvider` (staleTime 5min, no refetch-on-focus), but no page uses `useQuery` yet — server state currently lives in the context providers below, which call `services/api.ts` directly.
- Context providers carry app state: `session-context` (auth + states/clusters + the `useScope()` role-visibility helper), `leads-context` (lead CRUD, id generation, timeline entries, duplicate-phone check), `settings-context`, `theme-context`.
- `data/mock-data.ts` is **live fallback data, not fixtures**. Contexts fetch from the API and fall back to these arrays when a call fails or returns empty; `setLiveNetworkRef()` swaps the real states/clusters in so `stateName()` / `clusterName()` resolve correctly. Don't delete it assuming it's dead test data.
- UI is shadcn/ui "new-york" + Tailwind v4 (`@tailwindcss/vite`, styles in `src/styles.css`, no tailwind.config). Path alias `@/*` via `vite-tsconfig-paths`. Add components with the shadcn CLI per `components.json`.

### Roles and scoping
Three roles: `ho` (head office, sees everything), `state` (one `stateId`), `cluster` (one `clusterId`). `useScope().scope(rows)` filters lists by role and additionally hides HO-created rows from state/cluster users. Settings are resolved hierarchically server-side in `getSettings`/`updateSettings`: cluster-specific → state-specific → global fallback.

### Environment
`server/.env` holds `PORT`, `BASE_URL`, `MONGO_URI`, `JWT_SECRET`, `NODE_ENV`; the same values are duplicated in `ecosystem.config.cjs` for PM2. `docs/CREDENTIALS.md` documents the seeded accounts (all use the shared default password).

### Documentation and what ships
`docs/` holds all reference material — **it is never uploaded to the server**, because the repo root doubles as the public web root. See `docs/README.md` for the full upload/do-not-upload matrix; `docs/DEPLOY.md` is the release checklist and `docs/OPERATIONS.md` the server stability runbook. `CLAUDE.md` stays in the root (Claude Code loads it from there) but must not be uploaded either.
