Files
Portfolio-Codex/docs/stories/4.5.section-temoignages.md

9.5 KiB

Story 4.5: Section Témoignages (JSON Dynamique)

Status

Ready for Dev

Story

As a visiteur, I want lire des témoignages de clients ou employeurs, so that j'ai une preuve sociale de la qualité du travail.

Acceptance Criteria

  1. Le fichier data/testimonials.json stocke tous les témoignages
  2. La structure JSON supporte : id, quote, author_name, author_role, author_company, author_photo, project_slug (optionnel), date, featured (booléen)
  3. Une fonction PHP getTestimonials() lit et décode le JSON
  4. La section témoignages affiche dynamiquement les entrées du JSON
  5. Chaque témoignage affiche : citation, nom, rôle/entreprise, photo (si disponible)
  6. Si un témoignage est lié à un projet (project_slug), un lien vers le projet est affiché
  7. Les témoignages featured: true peuvent être affichés sur la page d'accueil
  8. Si le JSON est vide ou le fichier absent, la section affiche "Témoignages à venir" ou est masquée
  9. Le design utilise des guillemets ou un style "citation" reconnaissable

Tasks / Subtasks

  • [] Task 1 : Créer le fichier testimonials.json (AC: 1, 2)

    • [] Créer data/testimonials.json
    • [] Définir la structure complète
    • [] Ajouter 3 témoignages de test
  • [] Task 2 : Créer les fonctions PHP (AC: 3)

    • [] getTestimonials() - tous les témoignages
    • [] getFeaturedTestimonials() - témoignages mis en avant
    • [] getTestimonialByProject($slug) - témoignage lié à un projet
  • [] Task 3 : Créer le template testimonial.php (AC: 5, 9)

    • [] Style citation avec guillemets SVG
    • [] Photo de l'auteur (optionnelle, sinon initiale)
    • [] Nom, rôle, entreprise
  • [] Task 4 : Ajouter la section dans about.php (AC: 4, 8)

    • [] Grille de témoignages (1→2→3 colonnes)
    • [] Gestion du cas vide (section masquée)
  • [] Task 5 : Lien vers le projet (AC: 6)

    • [] Si project_slug existe, afficher le lien
    • [] "Voir le projet →" avec icône
  • [] Task 6 : Témoignages sur l'accueil (AC: 7)

    • [] Afficher 2 témoignages featured sur home.php
    • [] Lien "Voir tous les témoignages"

Dev Notes

Structure data/testimonials.json

{
  "testimonials": [
    {
      "id": 1,
      "quote": "Excellent travail ! Le site a été livré dans les délais avec une qualité irréprochable. Communication fluide tout au long du projet.",
      "author_name": "Marie Dupont",
      "author_role": "Directrice Marketing",
      "author_company": "Entreprise XYZ",
      "author_photo": "marie-dupont.webp",
      "project_slug": "ecommerce-xyz",
      "date": "2025-06-15",
      "featured": true
    },
    {
      "id": 2,
      "quote": "Un développeur rigoureux et créatif. Il a su comprendre nos besoins et proposer des solutions adaptées.",
      "author_name": "Jean Martin",
      "author_role": "CEO",
      "author_company": "Startup ABC",
      "author_photo": null,
      "project_slug": "app-gestion",
      "date": "2025-03-20",
      "featured": true
    },
    {
      "id": 3,
      "quote": "Travail soigné et professionnel. Je recommande vivement.",
      "author_name": "Sophie Leroy",
      "author_role": "Gérante",
      "author_company": "Restaurant Le Bon Goût",
      "author_photo": null,
      "project_slug": null,
      "date": "2024-11-10",
      "featured": false
    }
  ]
}

Fonctions PHP (includes/functions.php)

/**
 * Récupère tous les témoignages
 */
function getTestimonials(): array
{
    $data = loadJsonData('testimonials.json');
    return $data['testimonials'] ?? [];
}

/**
 * Récupère les témoignages mis en avant
 */
function getFeaturedTestimonials(): array
{
    return array_filter(getTestimonials(), fn($t) => $t['featured'] === true);
}

/**
 * Récupère le témoignage lié à un projet
 */
function getTestimonialByProject(string $projectSlug): ?array
{
    $testimonials = getTestimonials();
    foreach ($testimonials as $testimonial) {
        if (($testimonial['project_slug'] ?? '') === $projectSlug) {
            return $testimonial;
        }
    }
    return null;
}

Template templates/testimonial.php

<?php
/**
 * Composant témoignage
 * @param array $testimonial Données du témoignage
 * @param bool $showProjectLink Afficher le lien vers le projet
 */

$quote = $testimonial['quote'] ?? '';
$authorName = $testimonial['author_name'] ?? 'Anonyme';
$authorRole = $testimonial['author_role'] ?? '';
$authorCompany = $testimonial['author_company'] ?? '';
$authorPhoto = $testimonial['author_photo'] ?? null;
$projectSlug = $testimonial['project_slug'] ?? null;
$showProjectLink = $showProjectLink ?? true;
?>

<blockquote class="testimonial">
    <!-- Guillemets décoratifs -->
    <svg class="w-8 h-8 text-primary/30 mb-4" fill="currentColor" viewBox="0 0 24 24">
        <path d="M14.017 21v-7.391c0-5.704 3.731-9.57 8.983-10.609l.995 2.151c-2.432.917-3.995 3.638-3.995 5.849h4v10h-9.983zm-14.017 0v-7.391c0-5.704 3.748-9.57 9-10.609l.996 2.151c-2.433.917-3.996 3.638-3.996 5.849h3.983v10h-9.983z"/>
    </svg>

    <!-- Citation -->
    <p class="text-text-primary text-lg leading-relaxed mb-6 italic">
        "<?= htmlspecialchars($quote) ?>"
    </p>

    <!-- Auteur -->
    <footer class="flex items-center gap-4">
        <?php if ($authorPhoto): ?>
            <img
                src="/assets/img/testimonials/<?= htmlspecialchars($authorPhoto) ?>"
                alt="<?= htmlspecialchars($authorName) ?>"
                class="w-12 h-12 rounded-full object-cover"
                loading="lazy"
            >
        <?php else: ?>
            <div class="w-12 h-12 rounded-full bg-primary/20 flex items-center justify-center">
                <span class="text-primary font-semibold text-lg">
                    <?= strtoupper(substr($authorName, 0, 1)) ?>
                </span>
            </div>
        <?php endif; ?>

        <div>
            <p class="font-semibold text-text-primary"><?= htmlspecialchars($authorName) ?></p>
            <p class="text-sm text-text-muted">
                <?= htmlspecialchars($authorRole) ?>
                <?php if ($authorCompany): ?>
                    <span class="text-text-muted">—</span> <?= htmlspecialchars($authorCompany) ?>
                <?php endif; ?>
            </p>
        </div>
    </footer>

    <!-- Lien vers le projet -->
    <?php if ($showProjectLink && $projectSlug): ?>
        <a href="/projet/<?= htmlspecialchars($projectSlug) ?>" class="inline-flex items-center gap-1 text-primary text-sm mt-4 hover:underline">
            Voir le projet
            <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
            </svg>
        </a>
    <?php endif; ?>
</blockquote>

Section dans pages/about.php

<!-- Témoignages -->
<?php $testimonials = getTestimonials(); ?>
<?php if (!empty($testimonials)): ?>
    <section class="section bg-surface">
        <div class="container-content">
            <div class="section-header">
                <h2 class="section-title">Ce Qu'ils Disent</h2>
                <p class="section-subtitle">
                    Retours de clients et collaborateurs.
                </p>
            </div>

            <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
                <?php foreach ($testimonials as $testimonial): ?>
                    <?php include_template('testimonial', ['testimonial' => $testimonial]); ?>
                <?php endforeach; ?>
            </div>
        </div>
    </section>
<?php endif; ?>

Dossier des Photos

assets/img/testimonials/
├── marie-dupont.webp   # 96x96 carré
├── jean-martin.webp
└── ...

Testing

  • [] Le fichier JSON est valide (3 témoignages)
  • [] getTestimonials() retourne les témoignages (3)
  • [] getFeaturedTestimonials() filtre correctement (2 featured)
  • [] La section s'affiche sur la page À propos
  • [] Les guillemets SVG et style citation sont visibles
  • [] Les initiales s'affichent si pas de photo
  • [] Le lien vers le projet fonctionne
  • [] Cas vide : section masquée (if !empty)
  • [] 2 témoignages featured affichés sur la home

Dev Agent Record

Agent Model Used

Claude Opus 4.5 (claude-opus-4-5-20251101)

File List

File Action Description
data/testimonials.json Created 3 témoignages de test
includes/functions.php Modified Fonctions getTestimonials(), getFeaturedTestimonials(), getTestimonialByProject()
templates/testimonial.php Created Template avec guillemets, auteur, lien projet
pages/about.php Modified Section "Ce Qu'ils Disent"
pages/home.php Modified 2 témoignages featured

Completion Notes

  • Structure JSON complète : id, quote, author_name, author_role, author_company, author_photo, project_slug, date, featured
  • 3 fonctions PHP pour accéder aux témoignages
  • Template réutilisable avec guillemets SVG décoratifs
  • Photo optionnelle : si absente, affiche l'initiale sur fond coloré
  • Lien vers projet optionnel (paramètre showProjectLink)
  • Section masquée si JSON vide
  • 2 témoignages featured sur la home avec lien "Voir tous"
  • Note: Les photos peuvent être ajoutées dans /assets/img/testimonials/

Debug Log References

Aucun problème rencontré.

Change Log

Date Version Description Author
2026-01-22 0.1 Création initiale Sarah (PO)
2026-01-23 1.0 Implémentation complète James (Dev)