Files
cv-maker/docs/conception/05-etapes-realisation.md
T
2026-09-16 21:34:09 +02:00

418 lines
16 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.
# 05 — Plan de réalisation pas-à-pas
> Feuille de route d'implémentation. Chaque étape produit un état du projet **vérifiable** (commande de lancement + contrôle visuel/fonctionnel). Les commandes utilisent **pnpm**.
## Étape 0 — Prérequis et vérification environnement
1. Vérifier Node : `node -v` (≥ 20 attendu).
2. Activer pnpm :
```bash
corepack enable
pnpm -v
```
(ou installer globalement avec `npm install -g pnpm` si corepack indisponible.)
3. Créer le répertoire de projet s'il n'existe pas.
## Étape 1 — Scaffold du projet Next.js (App Router + TypeScript)
Depuis la racine `cv-maker/` :
```bash
pnpm create next-app@latest . \
--typescript \
--tailwind \
--eslint \
--app \
--src-dir=false \
--import-alias "@/*" \
--turbopack \
--use-pnpm
```
Contrôles :
- `pnpm dev` puis ouvrir `http://localhost:3000` (page d'accueil Next.js) ;
- fichiers générés : `app/`, `components/` (facultatif), `lib/` (absent par défaut), `public/`, `next.config.ts`, `tsconfig.json`.
> Si le dossier contient déjà des fichiers, le scaffolder demande. Ici il est vide (seul `GOAL.md` présent → déplacer `GOAL.md` temporairement hors du dossier racine si le scaffolder refuse, puis le remettre).
## Étape 2 — Nettoyage du scaffold
1. Vider `app/page.tsx` (remplacer par un composant squelette `CvWorkspace` minimal).
2. Supprimer les assets de démo de `public/` (`vercel.svg`, `next.svg`, etc.) sauf ce qui est utile.
3. Nettoyer `app/globals.css` du style de démo : ne conserver que `@import "tailwindcss";` et les tokens/print CSS ajoutés plus tard.
4. Vérifier `pnpm dev` → page vide sans erreur.
## Étape 3 — Configuration TypeScript strict
Dans `tsconfig.json` :
```json
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"forceConsistentCasingInFileNames": true,
"paths": { "@/*": ["./*"] }
}
}
```
Ajouter un script de vérification dans `package.json` :
```json
"typecheck": "tsc --noEmit"
```
Contrôle : `pnpm typecheck` → aucune erreur.
## Étape 4 — Installation des dépendances
```bash
pnpm add marked dompurify lucide-react
pnpm add @serwist/next # PWA (plugin + runtime)
```
DevDependencies :
```bash
pnpm add -D @types/dompurify
```
Contrôle : `pnpm typecheck` et `pnpm lint` restent verts après édition du package.json si besoin.
## Étape 5 — Types du domaine
Créer `lib/types/cv.ts` avec le contenu du module `03-` (contrats `CVTabId`, `CV_TAB_ORDER`, `CV_TABS`, `Experience`, `CVCareerData`, `createDefaultCVData`).
Contrôle : `pnpm typecheck` sans erreur.
## Étape 6 — Couche de stockage
Créer `lib/storage.ts` :
- constante `STORAGE_KEY = "cv-maker:v1"` ;
- interface `CVStorageAdapter` (`load`, `save`, `clear`) ;
- implémentation `createLocalStorageAdapter` avec try/catch (JSON invalide → `null`, quota → levée d'erreur remontée).
Créer `lib/hooks/useLocalStorage.ts` : hook générique (`value`, `setValue`, `status`) — écriture dans `useEffect`, initialisation paresseuse et garde `typeof window`.
Créer `lib/hooks/useCVData.ts` : façade `setText`, `addExperience`, `updateExperience`, `removeExperience`, `resetCV` (comportements du module `03-` §6).
Contrôle : `pnpm typecheck` vert.
## Étape 7 — Rendu Markdown sécurisé
Créer `lib/markdown.ts` :
```ts
import { marked } from "marked";
import DOMPurify from "dompurify";
marked.setOptions({ gfm: true, breaks: true });
export function markdownToHtml(source: string): string {
const rawHtml = marked.parse(source, { async: false }) as string;
return DOMPurify.sanitize(rawHtml);
}
```
> **DOMPurify côté client uniquement** : `dompurify` dépend de `window`. Ne l'utiliser que dans des Client Components (bilan : jamais importé dans `app/layout.tsx` ou pages statiques).
Contrôle : `pnpm typecheck` vert ; petit test manuel dans un `useEffect` de la page principale.
## Étape 8 — Primitives UI réutilisables
Créer sous `components/ui/` :
| Fichier | Rôle |
| --- | --- |
| `Button.tsx` | variantes `primary` / `ghost` / `danger` / `icon`, support `variant` + `size` |
| `IconButton.tsx` | bouton icône avec `aria-label` obligatoire en prop |
| `Textarea.tsx` | textarea contrôlé stylé (mono, `resize-y`) |
| `Tabs.tsx` | primitive d'onglets accessible (gère `role`/`aria-selected`, callbacks) |
Contrôle : intégration temporaire dans la page, `pnpm dev` + `pnpm typecheck`.
## Étape 9 — Composants du panneau gauche
Créer `components/cv/` dans cet ordre :
1. `CvInputPanel.tsx` — colonne gauche : gère `activeTab` (useState local), titre « Contenu du CV », compose `CvTabs` + `MarkdownTextarea` + `ExperienceManager`. Reçoit `data`, les callbacks de `useCVData` en props.
2. `CvTabs.tsx` — rend via `CV_TABS`/`CV_TAB_ORDER` (module 03) : boutons `numero + label`, état actif, aria.
3. `MarkdownTextarea.tsx` — `label` + `Textarea` contrôlé + lien « Aide Markdown ».
4. `ExperienceManager.tsx` — formulaire (titre + contenu), bouton +, liste `ExperienceItem`, gestion asynchrone des modes (`closed/new/edit`).
5. `ExperienceItem.tsx` — ligne `n. titre`, boutons Modifier / Supprimer, confirmation inline.
Contrôle visuel : saisir du texte dans chaque onglet, ajouter/modifier/supprimer des expériences (état React OK avant même la preview).
## Étape 10 — Composants de la preview A4
Créer dans `components/cv/` :
1. `CvPreview.tsx` — wrapper feuille (dimensions `210mm`/`297mm`, ombre, classe d'impression) ; appelle `markdownToHtml` depuis `lib/markdown` pour les 5 zones + chaque expérience ; compose header/colonnes.
2. `CvHeader.tsx` — accroche (zone 1).
3. `CvLeftColumn.tsx` — 4 × `CvSection` (État civil, Formations, Soft skills, Centres d'intérêt).
4. `CvSection.tsx` — titre stylé + `dangerouslySetInnerHTML` (HTML assaini) ; masquée si vide.
5. `CvRightColumn.tsx` — titre « Expériences professionnelles » + liste `CvExperiencePreview` + message vide alternatif (`print:hidden`).
6. `CvExperiencePreview.tsx` — titre en gras + HTML assaini du contenu.
Contrôle : remplir le panneau gauche → vérifier le rendu temps réel dans la feuille (structure 1/3–2/3, header).
## Étape 11 — Assemblage de la page principale
Créer `components/cv/CvWorkspace.tsx` :
- `"use client"` ;
- appelle `useCVData()` ;
- « remonte l'état » : passe `data` + callbacks à `CvInputPanel` et `data` à `CvPreview` ;
- compose `CvToolbar` au-dessus, grille `grid-cols-[1fr_2fr]` (desktop), stacking < 1024 px.
Créer `components/cv/CvToolbar.tsx` : logo + bouton « Imprimer / PDF » (`window.print()`), boutons « Importer JSON » puis « Exporter JSON » (import via fichier validé, téléchargement via `Blob`), bouton « Réinitialiser », lien `/documentation`. Créer `components/ui/Footer.tsx` (lié dans le layout, `print-hidden`) : nom de l'app + lien **Mentions légales**.
Remplacer le contenu de `app/page.tsx` :
```tsx
import { CvWorkspace } from "@/components/cv/CvWorkspace";
export default function HomePage() {
return <CvWorkspace />;
}
```
Contrôle : `pnpm dev` → éditeur temps réel fonctionnel, persistance au rechargement.
## Étape 12 — Impression PDF
Dans `app/globals.css`, ajouter :
```css
@page { size: A4; margin: 0; }
@media print {
body { background: #ffffff; }
.print-hidden { display: none !important; }
.cv-sheet {
box-shadow: none;
border: 0;
margin: 0;
width: auto;
height: auto;
/* hauteur max nécessaire si le contenu dépasse une page */
}
}
```
Classes utilitaires : panneau gauche et toolbar marqués `print-hidden` (ou `print:hidden` de Tailwind via `print:` variant si configuré).
Contrôle : bouton « Imprimer / PDF » → aperçu ne contenant que la feuille A4 ; « Enregistrer en PDF » → fichier propre.
## Étape 13 — Pages Documentation et Mentions légales
- `app/documentation/page.tsx` : **Server Component** statique. Contenu : prise en main (tabs, expériences, impression, sauvegarde locale) + aide sur la syntaxe Markdown supportée (titres, gras, italique, listes, liens, code) illustrée d'exemples.
- `app/mentions-legales/page.tsx` : **Server Component** statique. Contenu conforme au module `06-`, sans paragraphe contact.
Ajouter un `Link` vers `/documentation` (via `CvToolbar`) et vers `/mentions-legales` (via le `Footer`), plus un en-tête de retour « ← Retour à l'éditeur » sur chaque page.
Contrôle : navigation depuis la toolbar (Documentation) et le footer (Mentions légales), `pnpm build` → routes générées statiques (`○` dans la sortie de build).
## Étape 14 — PWA (manifest + service worker)
Avec `@serwist/next` :
1. `next.config.ts` (inclut `output: "standalone"` requis par le Dockerfile) :
```ts
import withSerwistInit from "@serwist/next";
const nextConfig = { output: "standalone" };
const withSerwist = withSerwistInit({
swSrc: "app/sw.ts",
swDest: "public/sw.js",
disable: process.env.NODE_ENV === "development",
});
export default withSerwist(nextConfig);
```
2. Créer `app/sw.ts` : `import { defaultCache } from "@serwist/next/worker";` + `precacheAndRoute`/`registerRoute` sur `/`, `/documentation`, `/mentions-legales`, icônes, manifest.
3. Créer `public/manifest.webmanifest` (name, short_name, start_url `/`, display `standalone`, theme_color, icons 192/512).
4. Créer les icônes PNG dans `public/icons/` (192, 512) — génération locale (outil image, ou conversion SVG d'une icône proche du logo).
5. `app/layout.tsx` : ajouter `<link rel="manifest" href="/manifest.webmanifest">`, theme-color, description, icône apple-touch si souhaité.
Contrôle : `pnpm build` sans erreur ; en production (`pnpm start`), onglet Lighthouse/PWA → service worker actif, installable.
## Étape 15 — Réglages finaux / polish
- Espacements de la grille (gap), tailles de police du CV, comportement « feuille qui s'allonge »,
- Sauvegarde locale silencieuse (pastille de statut non retenue — inutile vu l'enregistrement automatique),
- États de focus, `aria-label` manquants,
- Ajout du bouton « Réinitialiser le CV » (optionnel, appelle `resetCV` après confirmation),
- Vérification des limites : contenu vide, expérience sans titre (titre par défaut « Expérience »),
- Mise à jour de `README.md` (documentation technique) et du dossier `docs/conception`.
## Étape 16 — Recette finale selon le GOAL.md
Exécuter la checklist complète du module `07-plan-tests.md`. Corriger toute anomalie, re-exécuter `pnpm lint` et `pnpm typecheck` (zéro erreur).
## Étape 17 — Dockerisation et publication sur Docker Hub
> Spécification complète : [`08-deploiement-docker.md`](./08-deploiement-docker.md)
**Prérequis** : Docker ou Docker Desktop installé, compte Docker Hub créé (`docker login` une seule fois).
### 17.1 Créer `.dockerignore`
```gitignore
node_modules
.next
out
.git
.gitignore
docs
*.md
Dockerfile
docker-compose.yml
.dockerignore
*.log
.env*
.DS_Store
```
Contrôle : le contexte de build (`docker build .`) ne contient plus que les fichiers de source (app/, components/, lib/, public/, next.config.ts, tsconfig.json, package.json, pnpm-lock.yaml).
### 17.2 Créer `Dockerfile`
```dockerfile
# ── Étape de dépendances ────────────────────────────────────────
FROM node:20-alpine AS deps
RUN corepack enable
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
# ── Étape de build ─────────────────────────────────────────────
FROM node:20-alpine AS builder
RUN corepack enable
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN pnpm build
# ── Étape d'exécution ──────────────────────────────────────────
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV HOSTNAME=0.0.0.0
ENV PORT=3000
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
EXPOSE 3000
CMD ["node", "server.js"]
```
Contrôle : aucune erreur au build.
### 17.3 Créer `docker-compose.yml`
```yaml
services:
cv-maker:
build:
context: .
dockerfile: Dockerfile
image: cv-maker:local
container_name: cv-maker
restart: unless-stopped
ports:
- "3000:3000"
environment:
- HOSTNAME=0.0.0.0
- PORT=3000
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3000"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
```
Contrôle : `docker compose up -d` → conteneur healthy, application accessible sur `http://localhost:3000`.
### 17.4 Build de l'image et test local
```bash
docker build -t cv-maker:latest .
docker run --rm -p 3000:3000 cv-maker:latest
# → ouvrir http://localhost:3000 et tester l'éditeur
```
Vérifications spécifiques conteneur :
- Le service worker (`sw.js`) est chargé (PWA fonctionne).
- La sauvegarde locale est fonctionnelle (localStorage côté navigateur).
- `docker images cv-maker` : taille < 200 Mo.
### 17.5 Publication sur Docker Hub
```bash
# 1. Se connecter au registre
docker login
# 2. Taguer (remplacer <mon-user-dockerhub> par votre identifiant)
docker tag cv-maker:latest <mon-user-dockerhub>/cv-maker:latest
docker tag cv-maker:latest <mon-user-dockerhub>/cv-maker:1.0.0
# 3. Pousser
docker push <mon-user-dockerhub>/cv-maker:latest
docker push <mon-user-dockerhub>/cv-maker:1.0.0
```
Contrôle : l'image apparaît sur la page Docker Hub du compte (`https://hub.docker.com/r/<mon-user-dockerhub>/cv-maker`).
### 17.6 Déploiement depuis Docker Hub
```bash
# Sur une autre machine
docker pull <mon-user-dockerhub>/cv-maker:latest
docker run -d -p 3000:3000 --name cv-maker <mon-user-dockerhub>/cv-maker:latest
# ou via compose (échanger image: dans docker-compose.yml)
docker compose up -d
```
## Récapitulatif des fichiers à créer/modifier
```
créés app/page.tsx (réécrit)
app/documentation/page.tsx
app/mentions-legales/page.tsx
app/sw.ts
app/layout.tsx (modifié : manifest, meta)
app/globals.css (modifié : tokens + @media print)
lib/types/cv.ts
lib/markdown.ts
lib/storage.ts
lib/hooks/useLocalStorage.ts
lib/hooks/useCVData.ts
components/ui/{Button,IconButton,Textarea,Tabs}.tsx
components/cv/{CvWorkspace,CvToolbar,CvInputPanel,CvTabs,
MarkdownTextarea,ExperienceManager,ExperienceItem,
CvPreview,CvHeader,CvLeftColumn,CvSection,
CvRightColumn,CvExperiencePreview}.tsx
public/manifest.webmanifest
public/icons/icon-192.png, public/icons/icon-512.png
Dockerfile (multi-stage : deps/builder/runner)
docker-compose.yml (service cv-maker, healthcheck)
.dockerignore
next.config.ts (modifié : withSerwist + output: "standalone")
tsconfig.json (modifié : strict)
package.json (modifié : scripts + deps)
modifié README.md (documentation technique)
```
## Jalons de contrôle qualité
| Jalon | État | Contrôle |
| --- | --- | --- |
| J1 (fin étape 4) | Deps installées | `pnpm typecheck`, `pnpm lint` |
| J2 (fin étape 11) | MVP fonctionnel | édition temps réel + persistance |
| J3 (fin étape 12) | PDF ok | impression → PDF propre |
| J4 (fin étape 13) | Pages statiques | routes OK, build ○ |
| J5 (fin étape 14) | PWA | installable, offline |
| J6 (fin étape 16) | Recette GOAL.md | 100 % critères passés |
| J7 (fin étape 17) | Docker + DockerHub | image < 200 Mo, `docker compose up`, image sur Docker Hub |