# CLAUDE.md

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

## Overview

Corporate web portal for **SAR** (Société Africaine de Raffinage). Two independent apps in one repo:

- `portail-sar-backend/` — Laravel 10 REST API (PHP 8.1+, MySQL, Sanctum auth)
- `portail-sar-frontend/` — Next.js 16 App Router site (React 19, TypeScript, Tailwind v4, next-intl)

The frontend is the public website + admin dashboard; the backend serves all dynamic data over JSON at `/api/*` and integrates with two external systems: **Odoo** (SIRH — job offers) and **LinkedIn** (news/actualités).

## Commands

### Backend (`portail-sar-backend/`)
```bash
php artisan serve              # dev server on :8000
php artisan migrate            # run migrations
php artisan test               # run full PHPUnit suite
php artisan test --filter=Foo  # run a single test class/method
./vendor/bin/pint              # format PHP (Laravel Pint)
php artisan odoo:sync-jobs     # manually pull published jobs from Odoo into cached_jobs
php artisan linkedin:sync      # trigger LinkedIn actualités sync (calls the frontend)
php artisan safety:increment-days
```

### Frontend (`portail-sar-frontend/`)
```bash
pnpm dev      # dev server on :3000 (pnpm is the package manager — note .pnpm-store/)
pnpm build    # production build
pnpm lint     # eslint
pnpm start    # serve production build
```

There is no automated test suite on the frontend.

## Architecture

### Frontend ↔ Backend wiring
- All pages/components reach the backend through `process.env.NEXT_PUBLIC_API_URL`. **Every file hardcodes its own fallback**, either `|| "http://localhost:8000"` or the dev machine's LAN address. Those LAN fallbacks used to disagree with each other (`.13`, `.26`, `.36`, `.164`); they are now unified on `http://10.113.245.13:8000`. Always set `NEXT_PUBLIC_API_URL` in `.env.local` rather than relying on them, and when the dev machine's IP changes, update `.env.local`, `portail-sar-backend/.env` (`APP_URL`, `CORS_ALLOWED_ORIGINS`, `FRONTEND_URL`) and the fallbacks together. When adding API calls, follow the existing `const API_URL = process.env.NEXT_PUBLIC_API_URL || ...` pattern.
- Backend CORS is configured in `config/cors.php`; admin auth uses Laravel Sanctum bearer tokens issued by `POST /api/auth/login`.

### i18n / routing (frontend)
- Locales are `fr` and `en`, defined in `i18n.ts`. All public pages live under `app/[locale]/...` and the `next-intl` plugin (wired in `next.config.mjs` → `createNextIntlPlugin('./i18n.ts')`) injects messages from `messages/{fr,en}.json`.
- `output: 'export'` is intentionally **not** used — it's incompatible with the next-intl locale middleware. `trailingSlash: true` is on.
- Note: `next.config.mjs` sets `typescript.ignoreBuildErrors: true` and `images.unoptimized: true` — type errors won't fail the build, so type-check intent manually.

### Backend API surface
- Routes are all in `routes/api.php` (there is essentially no `web.php` UI). Public read endpoints (`actualites`, `appel-offres`, `jobs`, `hero-images`, `sar/questions`, `search`, `safety/*`) plus public write endpoints for form submissions (`demande-agrement`, `internships/applications`, `contact`, soumissions).
- Admin endpoints are grouped under `admin/*` prefixes and protected by `middleware('auth:sanctum')` — except `admin/appel-offres` and `admin/actualites` CRUD, which are currently unprotected; check before assuming auth.
- Controllers live in `app/Http/Controllers/Api/`. Models in `app/Models/`.

### External integrations
- **Odoo (jobs)**: `app/Services/OdooClient.php` is a hand-rolled **XML-RPC** client (manual XML encoding/decoding over `Illuminate\Http\Http`). Credentials come from `config/odoo.php` (env: `ODOO_URL`, `ODOO_DB`, `ODOO_USERNAME`, `ODOO_PASSWORD`). Jobs are pulled and cached in the `cached_jobs` table (`CachedJob` model) so the public site never hits Odoo live. Sync happens via `odoo:sync-jobs` (scheduled nightly) or `POST /api/admin/sync-jobs`. The sync deletes cached jobs no longer present in Odoo.
- **LinkedIn (actualités)**: the actual scraping/sync logic lives on the **frontend** (`lib/linkedin-sync.ts`, `lib/cron-jobs.ts`). The Next.js server starts a midnight cron via `instrumentation.ts` → `lib/server-startup.ts` → `startLinkedInCron()`, gated on `NODE_ENV=production` or `ENABLE_LINKEDIN_SYNC=true`. The backend's `linkedin:sync` command just calls back into the frontend endpoint.

### Scheduling
Backend `app/Console/Kernel.php` schedules three nightly (00:00) jobs: `safety:increment-days`, `odoo:sync-jobs`, `linkedin:sync`. These require a system cron running `php artisan schedule:run` every minute to fire.

## Conventions & gotchas
- **Never use the em dash (`—`) in any text you write.** Not in UI strings, not in code comments, not in commit messages, not in replies to the user. It reads as an AI tell and the project owner has explicitly rejected it. Use a colon, a comma, parentheses, or split the sentence in two. This applies to new text only: existing em dashes in the codebase are pre-existing content, leave them unless asked.
- The codebase is **French-first**: comments, commit messages, and `$description` strings are in French. Match this when editing.
- `lib/*-data.ts` and `data/` on the frontend hold static/seed content (projects, actualités fallbacks, static search index) used when the API is unavailable or for SEO — distinct from live API data.
- `GUIDE_ODOO_EMPLOI.txt` at the repo root documents the Odoo job-offer integration (in French) — consult it before changing Odoo logic.
- Laravel `storage/framework/{cache,sessions}/` artifacts are committed and show up as noise in `git status`; don't treat them as meaningful changes.
- `i18n.ts` and several frontend modules contain heavy `console.log` debug output — intentional, not stray.
