# CLAUDE.md

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

## Commands

```bash
npm run build       # Production build → build/js/smartweb-webcomponents-compiled.js
npm run build-dev   # Dev build (source maps, no minification)
npm run watch       # Dev server on port 2000 (proxies API to localhost:8080)
npm run lint        # ESLint (zero warnings tolerance)
npm run prettier    # Check formatting (4-space tabs, 100-char width, trailing comma es5)
```

## Architecture

### Entry point & custom element registration

`src/index.jsx` registers 5 custom HTML elements via `react-web-component`. Shadow root is disabled (`false`) so external CSS applies:

```javascript
ReactWebComponent.create(<Annuaire />, "smartweb-annuaire", false);
```

Props are passed as HTML attributes (kebab-case → camelCase), JSON strings decoded via `JSON.parse()`.

### Component structure

Each of the 5 components (`Annuaire`, `Events`, `News`, `Campaign`, `Search`) lives in `src/components/<Name>/` and follows a common pattern:

- **Main component** — holds all state, fetches data via `useAxios`, renders routes
- **List** — renders array of cards
- **Card** — single item preview (links to detail route)
- **Content** — full item detail view
- **Filters/Filter.jsx** — filter UI that updates URL params via `useNavigate`

**Search is different**: no routing, no pagination, no maps — shows 6 results from 4 sources (site pages, news, events, contacts) on one flat page.

| Component | Map | Date filter | Pagination | Routing |
|-----------|-----|-------------|------------|---------|
| Annuaire  | Yes | No          | Load More  | 2 routes |
| Events    | Yes | Yes         | Load More  | 2 routes |
| News      | No  | No          | Load More  | 2 routes |
| Campaign  | Yes (custom) | Yes  | Load More  | 2 routes |
| Search    | No  | No          | No         | None    |

### State management

No Redux — pure React hooks:
- **`useState`** for filters, items, batch cursor, loading flags
- **`useContext`**: `ScrollContext` (scroll position on navigation), `LanguageContext` (news subtree)
- State flows down from parent component; filter `onChange` callbacks bubble changes up

### Data fetching

**`src/hooks/useAxios.js`** — wraps axios with AbortController cleanup. Accepts single URL or array of URLs for parallel requests. `params` change triggers refetch; `load` flag appends (pagination).

Two prop URLs per component: `queryUrl` (data) and `queryFilterUrl` (filter options). Campaign also has `queryZonesUrl` / `queryTopicsUrl`.

### Routing

React Router DOM v6. List/detail routes: `/` and `/:name?u={UID}`. Slugs generated by lowercasing + removing accents from the item title. Search params preserved via `src/hooks/useFilterQuery.js` (`new URLSearchParams(useLocation().search)`).

### Shared filter components (`src/components/Filters/`)

- **`TaxonomyFilter.jsx`** — 3-level nested react-select for hierarchical categories
- **`DateFilter.jsx`** — Preset periods + custom range (react-datepicker), date-fns locale
- **`SelectStyles.js`** — react-select style presets (`menuStyles`, `moreFilterStyles`)
- **`IamData.jsx`** / **`PublicTargetData.jsx`** — static profile/age-group lists

Text filter debounce: only triggers fetch when input length > 2 characters. First-render skip via `useRef` guard.

### Utilities

- **`src/utils/translation.js`** — 100+ keys, 4 languages (fr, en, de, nl)
- **`src/utils/url.js`** — `getPath()`, `getPortalUrl()`, `getBundleUrl()` for Plone URLs
- **`src/utils/Map.jsx`** — Leaflet/OpenStreetMap; filters out default Imio coordinates (50.4989185, 4.7184485)

### Build output

Webpack 5 produces `build/js/smartweb-webcomponents-compiled.js` plus auto-split chunks. Dev server proxies `localhost:2000 → localhost:8080` (Plone). Performance budget: 2 MB assets / 750 KB entrypoint.

### CSS conventions

Component-level `.scss` files. Class prefix: `r-*` (e.g. `r-search`, `r-filter`, `r-result`). React Bootstrap + react-select + Leaflet CSS imported per component.
