Files
cv-maker/docs/conception/05-etapes-realisation.md

16 KiB
Raw Permalink Blame History

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 :
    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/ :

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 :

{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "forceConsistentCasingInFileNames": true,
    "paths": { "@/*": ["./*"] }
  }
}

Ajouter un script de vérification dans package.json :

"typecheck": "tsc --noEmit"

Contrôle : pnpm typecheck → aucune erreur.

Étape 4 — Installation des dépendances

pnpm add marked dompurify lucide-react
pnpm add @serwist/next                  # PWA (plugin + runtime)

DevDependencies :

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 :

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 6 zones + chaque expérience ; compose header/colonnes.
  2. CvHeader.tsx — accroche (zone 1).
  3. CvLeftColumn.tsx — 5 × CvSection (État civil, Formations, Compétences, 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 :

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 :

@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) :
    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

Prérequis : Docker ou Docker Desktop installé, compte Docker Hub créé (docker login une seule fois).

17.1 Créer .dockerignore

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

# ── É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

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

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

# 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

# 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