Files
cv-maker/README.md
T
2026-09-21 20:12:36 +00:00

246 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CV-Maker
Application web de création de CV en temps réel : saisie **Markdown** dans un panneau latéral, **preview A4** instantanée, sauvegarde locale automatique et export **PDF** — le tout sans backend, 100 % côté navigateur.
## Sommaire
1. [Prérequis](#prérequis)
2. [Installation et lancement](#installation-et-lancement)
3. [Scripts](#scripts)
4. [Stack technique](#stack-technique)
5. [Structure du projet](#structure-du-projet)
6. [Modèle de données](#modèle-de-données)
7. [Fonctionnement clé](#fonctionnement-clé)
- [Rendu Markdown sécurisé](#rendu-markdown-sécurisé)
- [Persistance locale](#persistance-locale)
- [Gestion des expériences](#gestion-des-expériences)
- [Impression PDF](#impression-pdf)
- [PWA](#pwa)
8. [Conception détaillée](#conception-détaillée)
9. [Limitations connues](#limitations-connues)
---
## Prérequis
| Outil | Version minimale |
| --- | --- |
| Node.js | 20.9+ |
| pnpm | 12.x (`corepack enable` puis `pnpm -v` — figé via `packageManager`) |
## Installation et lancement
```bash
# 1. Installer les dépendances
pnpm install
# 2. Lancer en développement
pnpm dev # → http://localhost:3000
# 3. Construire + servir en production
pnpm build
pnpm start
```
## Scripts
| Script | Commande | Description |
| --- | --- | --- |
| `dev` | `pnpm dev` | Serveur de développement (Turbopack) |
| `dev:sw` | `concurrently …` | Dev + re-build du service worker en continu (`--watch`) |
| `build` | `next build && serwist build` | Build de production + génération du service worker |
| `start` | `pnpm start` | Lancement du build de production |
| `lint` | `pnpm lint` | Vérification ESLint (Next.js) |
| `typecheck` | `tsc --noEmit` | Validation TypeScript strict |
## Stack technique
| Brique | Choix | Rôle |
| --- | --- | --- |
| Framework | Next.js (App Router) | Routage, composants serveur/client, métadonnées |
| Langage | TypeScript (strict) | Typage du modèle de données |
| Styles | Tailwind CSS | Design tokens, responsive, print |
| Markdown | `marked` | Conversion Markdown → HTML |
| Sécurité | `dompurify` | Assainissement du HTML avant injection (anti-XSS) |
| Persistance | `localStorage` | Données locales, aucune transmission |
| PWA | `@serwist/next` + `@serwist/cli` | Service worker (mode configurator), manifest, installabilité |
| Icônes | `lucide-react` | Iconographie |
| Conteneurisation | Docker (multi-stage) | Image de production, distribution Docker Hub |
## Structure du projet
```
cv-maker/
├── app/
│ ├── layout.tsx # Layout racine (lang, meta, manifest)
│ ├── page.tsx # Éditeur principal
│ ├── globals.css # Tokens Tailwind + @media print
│ ├── documentation/
│ │ └── page.tsx # Page d'aide (statique)
│ ├── mentions-legales/
│ │ └── page.tsx # Page Mentions légales (statique)
│ └── sw.ts # Source du service worker (Serwist)
├── components/
│ ├── ui/ # Primitives réutilisables
│ │ ├── Button.tsx
│ │ ├── IconButton.tsx
│ │ ├── Textarea.tsx
│ │ ├── Tabs.tsx
│ │ └── Footer.tsx # Pied de page (lien Mentions légales)
│ └── cv/ # Composants métier du CV
│ ├── CvWorkspace.tsx # Assemblage 2 colonnes + toolbar
│ ├── CvToolbar.tsx # Barre d'app (PDF, JSON, liens)
│ ├── CvInputPanel.tsx # Panneau gauche (tabs + expériences)
│ ├── CvTabs.tsx # Onglets 1..6
│ ├── MarkdownTextarea.tsx # Textarea contrôlé
│ ├── ExperienceManager.tsx # Formulaire + liste expériences
│ ├── ExperienceItem.tsx # Ligne « n. titre [Modifier] [×] »
│ ├── CvPreview.tsx # Feuille A4
│ ├── CvHeader.tsx # Accroche (zone 1)
│ ├── CvLeftColumn.tsx # Sections 2→6 (1/3)
│ ├── CvSection.tsx # Section générique
│ ├── CvRightColumn.tsx # Expériences pro (2/3)
│ └── CvExperiencePreview.tsx
├── lib/
│ ├── types/cv.ts # Contracts du domaine
│ ├── markdown.ts # markdownToHtml (marked + DOMPurify)
│ ├── storage.ts # Adapter localStorage
│ └── hooks/
│ ├── useLocalStorage.ts # Persistance générique
│ └── useCVData.ts # Façade métier d'édition
├── public/
│ ├── manifest.webmanifest # Manifest PWA
│ └── icons/ # icônes 192 / 512
├── Dockerfile # Image multi-stage (deps/builder/runner)
├── docker-compose.yml # Service cv-maker, healthcheck
├── .dockerignore # Exclusions de contexte build
├── serwist.config.mjs # Config service worker (mode configurator)
├── next.config.ts # output: "standalone"
├── tsconfig.json # strict
├── GOAL.md # Cahier des charges source
└── README.md # Ce document
```
## Modèle de données
Contenu stocké dans **une seule clé localStorage** : `cv-maker:v1` (JSON).
```ts
interface CVCareerData {
version: 1;
accroche: string; // onglet 1 — message d'accroche
etatCivil: string; // onglet 2 — état civil + contacts + site + GitHub
formations: string; // onglet 3 — formations
competences: string; // onglet 4 — compétences techniques
softSkills: string; // onglet 5 — soft skills
centresInteret: string; // onglet 6 — centres d'intérêt
experiences: Experience[];
}
interface Experience {
id: string; // crypto.randomUUID() — identifiant stable
titre: string; // titre affiché (liste + section CV)
contenu: string; // corps en Markdown
createdAt: number;
updatedAt: number;
}
```
- Les 6 zones sont du **Markdown brut** (le HTML rendu n'est jamais stocké).
- Les expériences sont affichées **dans l'ordre de saisie** : la 1ʳᵉ ajoutée en haut de la section « Expériences professionnelles », la suivante en dessous, etc. (la 1ʳᵉ porte le numéro 1).
- Sauvegarde invalide/corrompue → état par défaut, sans crash.
## Fonctionnement clé
### Rendu Markdown sécurisé
`lib/markdown.ts` — toute injection HTML passe par ici :
```ts
const rawHtml = marked.parse(source, { async: false }) as string;
return DOMPurify.sanitize(rawHtml);
```
`marked` génère le HTML ; **`DOMPurify` neutralise** tout script/événement URL dangereuse avant le `dangerouslySetInnerHTML`. Aucun HTML brut saisi n'est injecté directement.
### Persistance locale
`useLocalStorage` synchronise l'état React avec localStorage **à chaque changement** (via `useEffect`) ; l'enregistrement est silencieux (aucun indicateur affiché). `useCVData` expose la façade d'édition (`setText`, `addExperience`, `updateExperience`, `removeExperience`, `resetCV`, `importCV`), et la barre d'application permet l'**export/import JSON** (`cv-maker.json`) pour partager un CV entre appareils.
### Gestion des expériences
- Bouton **+** : nouvelle expérience (formulaire titre + contenu).
- Liste numérotée avec **Modifier** / **×** (suppression confirmée).
- Identifiants UUID : jamais d'index comme identifiant (robustesse lors des suppressions).
### Impression PDF
- Bouton « Imprimer / PDF » → `window.print()`.
- CSS `@media print` dans `app/globals.css` : `@page { size: A4; margin: 0 }`, panneau de saisie et toolbar masqués, seule la feuille A4 est imprimée.
### PWA
- **Mode « configurator »** (`@serwist/next` + `@serwist/cli`, compatible Turbopack) : `serwist.config.mjs` décrit le service worker, compilé par `pnpm build` après Next.js (`next build && serwist build`).
- `public/sw.js` pré-cache les routes `/`, `/documentation`, `/mentions-legales` et tous les assets (787 kB, 20 URL).
- En développement le service worker est **désactivé** (`SerwistProvider disable`) ; utiliser `pnpm dev:sw` pour le reconstruire à chaud.
- `public/manifest.webmanifest` + icônes 192/512 → installable, fonctionne hors-ligne (les données du CV restent dans localStorage).
### Dockerisation
L'image est construite en **3 étapes** (`Dockerfile` multi-stage) :
| Étape | Image | Contenu |
| --- | --- | --- |
| `deps` | `node:20-alpine` | Installation déterministe des dépendances (`pnpm install --frozen-lockfile`, incl. `pnpm-workspace.yaml`) |
| `builder` | `node:20-alpine` | Compilation (`pnpm build`, sortie `standalone` + `sw.js`) |
| `runner` | `node:20-alpine` | Exécution uniquement (`node server.js`, image ≈ 60 Mo) |
**Build et lancement :**
```bash
docker build -t cv-maker:latest .
docker run --rm -p 3001:3000 cv-maker:latest # → http://localhost:3001
```
> Si le port 3000 est déjà occupé en local, mapper un autre port hôte (`-p 3001:3000`).
**Avec Docker Compose :**
```bash
docker compose up -d
docker compose down
```
**Publication sur Docker Hub :**
```bash
docker login
docker tag cv-maker:latest <mon-user-dockerhub>/cv-maker:latest
docker push <mon-user-dockerhub>/cv-maker:latest
```
Voir la [documentation détaillée Docker](./docs/conception/08-deploiement-docker.md) pour le contenu complet des fichiers (`Dockerfile`, `docker-compose.yml`, `.dockerignore`) et la recette de validation.
## Conception détaillée
Le dossier de conception complet (analyse fonctionnelle, architecture, modèle de données, UI, plan de réalisation pas-à-pas, sécurité/RGPD, plan de tests) se trouve dans :
```
docs/conception/
├── README.md → sommaire et ordre de lecture
├── 01-analyse-fonctionnelle.md
├── 02-architecture-technique.md
├── 03-modele-donnees.md
├── 04-specifications-ui.md
├── 05-etapes-realisation.md
├── 06-securite-rgpd.md
├── 07-plan-tests.md
└── 08-deploiement-docker.md
```
## Limitations connues
- **Desktop first** : tablette/mobile = niveau « utilisable », non optimisé.
- **One page A4 à l'impression** : feuille de preview à hauteur flexible ; un contenu très long peut générer plusieurs pages PDF.
- **Pas de tests automatisés** (v1) : recette manuelle documentée dans `docs/conception/07-plan-tests.md` ; tests Vitest prévus en v2.
- **Pas de drag & drop** de réorganisation des expériences (évolution prévue).