✨ Story 4.5: temoignages
This commit is contained in:
37
data/testimonials.json
Normal file
37
data/testimonials.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## Status
|
||||
|
||||
Ready for Dev
|
||||
review
|
||||
|
||||
## Story
|
||||
|
||||
@@ -24,32 +24,32 @@ Ready for Dev
|
||||
|
||||
## 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
|
||||
- [x] **Task 1 : Créer le fichier testimonials.json** (AC: 1, 2)
|
||||
- [x] Créer `data/testimonials.json`
|
||||
- [x] Définir la structure complète
|
||||
- [x] 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
|
||||
- [x] **Task 2 : Créer les fonctions PHP** (AC: 3)
|
||||
- [x] `getTestimonials()` - tous les témoignages
|
||||
- [x] `getFeaturedTestimonials()` - témoignages mis en avant
|
||||
- [x] `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
|
||||
- [x] **Task 3 : Créer le template testimonial.php** (AC: 5, 9)
|
||||
- [x] Style citation avec guillemets SVG
|
||||
- [x] Photo de l'auteur (optionnelle, sinon initiale)
|
||||
- [x] 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)
|
||||
- [x] **Task 4 : Ajouter la section dans about.php** (AC: 4, 8)
|
||||
- [x] Grille de témoignages (1→2→3 colonnes)
|
||||
- [x] 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
|
||||
- [x] **Task 5 : Lien vers le projet** (AC: 6)
|
||||
- [x] Si project_slug existe, afficher le lien
|
||||
- [x] "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"
|
||||
- [x] **Task 6 : Témoignages sur l'accueil** (AC: 7)
|
||||
- [x] Afficher 2 témoignages featured sur home.php
|
||||
- [x] Lien "Voir tous les témoignages"
|
||||
|
||||
## Dev Notes
|
||||
|
||||
@@ -249,7 +249,7 @@ assets/img/testimonials/
|
||||
## Dev Agent Record
|
||||
|
||||
### Agent Model Used
|
||||
Claude Opus 4.5 (claude-opus-4-5-20251101)
|
||||
GPT-5 Codex
|
||||
|
||||
### File List
|
||||
| File | Action | Description |
|
||||
@@ -259,6 +259,8 @@ Claude Opus 4.5 (claude-opus-4-5-20251101)
|
||||
| `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 |
|
||||
| `tests/testimonials.test.php` | Created | Tests témoignages |
|
||||
| `tests/run.ps1` | Modified | Ajout tests témoignages |
|
||||
|
||||
### Completion Notes
|
||||
- Structure JSON complète : id, quote, author_name, author_role, author_company, author_photo, project_slug, date, featured
|
||||
@@ -268,7 +270,7 @@ Claude Opus 4.5 (claude-opus-4-5-20251101)
|
||||
- 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/`
|
||||
- Tests: `powershell -ExecutionPolicy Bypass -File tests/run.ps1`
|
||||
|
||||
### Debug Log References
|
||||
Aucun problème rencontré.
|
||||
@@ -278,4 +280,4 @@ Aucun problème rencontré.
|
||||
| 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) |
|
||||
| 2026-02-04 | 1.0 | Implémentation complète | Amelia |
|
||||
|
||||
@@ -150,4 +150,34 @@ function getToolIcon(string $icon): string
|
||||
];
|
||||
|
||||
return $icons[$icon] ?? '🛠';
|
||||
}
|
||||
|
||||
/**
|
||||
* 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_values(array_filter(getTestimonials(), fn($t) => ($t['featured'] ?? false) === true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère le témoignage lié à un projet
|
||||
*/
|
||||
function getTestimonialByProject(string $projectSlug): ?array
|
||||
{
|
||||
foreach (getTestimonials() as $testimonial) {
|
||||
if (($testimonial['project_slug'] ?? '') === $projectSlug) {
|
||||
return $testimonial;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -196,6 +196,26 @@ include_template('navbar', compact('currentPage'));
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?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; ?>
|
||||
|
||||
<section class="section bg-surface">
|
||||
<div class="container-content text-center">
|
||||
<h2 class="text-heading mb-4">Envie d'en savoir plus ?</h2>
|
||||
|
||||
@@ -9,6 +9,8 @@ $pageTitle = 'Accueil';
|
||||
$pageDescription = 'Portfolio de développeur web full-stack. Découvrez mes projets, compétences et parcours.';
|
||||
$currentPage = 'home';
|
||||
|
||||
$featuredTestimonials = array_slice(getFeaturedTestimonials(), 0, 2);
|
||||
|
||||
include_template('header', compact('pageTitle', 'pageDescription'));
|
||||
include_template('navbar', compact('currentPage'));
|
||||
?>
|
||||
@@ -98,6 +100,27 @@ include_template('navbar', compact('currentPage'));
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php if (!empty($featuredTestimonials)): ?>
|
||||
<section class="section">
|
||||
<div class="container-content">
|
||||
<div class="section-header">
|
||||
<h2 class="section-title">Ils m'ont fait confiance</h2>
|
||||
<p class="section-subtitle">Quelques retours de clients et collaborateurs.</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<?php foreach ($featuredTestimonials as $testimonial): ?>
|
||||
<?php include_template('testimonial', ['testimonial' => $testimonial, 'showProjectLink' => false]); ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<div class="text-center mt-10">
|
||||
<a href="/a-propos#temoignages" class="btn-ghost">Voir tous les témoignages</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
</main>
|
||||
|
||||
<?php include_template('footer'); ?>
|
||||
61
templates/testimonial.php
Normal file
61
templates/testimonial.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?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">
|
||||
<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>
|
||||
|
||||
<p class="text-text-primary text-lg leading-relaxed mb-6 italic">
|
||||
"<?= htmlspecialchars($quote, ENT_QUOTES, 'UTF-8') ?>"
|
||||
</p>
|
||||
|
||||
<footer class="flex items-center gap-4">
|
||||
<?php if ($authorPhoto): ?>
|
||||
<img
|
||||
src="/assets/img/testimonials/<?= htmlspecialchars($authorPhoto, ENT_QUOTES, 'UTF-8') ?>"
|
||||
alt="<?= htmlspecialchars($authorName, ENT_QUOTES, 'UTF-8') ?>"
|
||||
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, ENT_QUOTES, 'UTF-8') ?></p>
|
||||
<p class="text-sm text-text-muted">
|
||||
<?= htmlspecialchars($authorRole, ENT_QUOTES, 'UTF-8') ?>
|
||||
<?php if ($authorCompany): ?>
|
||||
<span class="text-text-muted">—</span> <?= htmlspecialchars($authorCompany, ENT_QUOTES, 'UTF-8') ?>
|
||||
<?php endif; ?>
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<?php if ($showProjectLink && $projectSlug): ?>
|
||||
<a href="/projet/<?= htmlspecialchars($projectSlug, ENT_QUOTES, 'UTF-8') ?>" 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>
|
||||
@@ -19,4 +19,5 @@ php (Join-Path $here 'skills.test.php')
|
||||
php (Join-Path $here 'tools.test.php')
|
||||
php (Join-Path $here 'about.test.php')
|
||||
php (Join-Path $here 'passions.test.php')
|
||||
php (Join-Path $here 'testimonials.test.php')
|
||||
'OK'
|
||||
26
tests/testimonials.test.php
Normal file
26
tests/testimonials.test.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../includes/functions.php';
|
||||
|
||||
function assertTrue($cond, $msg) {
|
||||
if (!$cond) {
|
||||
fwrite(STDERR, $msg . PHP_EOL);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
$testimonials = getTestimonials();
|
||||
assertTrue(count($testimonials) === 3, 'expected 3 testimonials');
|
||||
|
||||
$featured = getFeaturedTestimonials();
|
||||
assertTrue(count($featured) === 2, 'expected 2 featured');
|
||||
|
||||
$byProject = getTestimonialByProject('ecommerce-xyz');
|
||||
assertTrue(is_array($byProject), 'missing project testimonial');
|
||||
|
||||
$missing = getTestimonialByProject('inexistant');
|
||||
assertTrue($missing === null, 'expected null for missing testimonial');
|
||||
|
||||
$content = file_get_contents(__DIR__ . '/../pages/home.php');
|
||||
assertTrue(strpos($content, 'Ils m\'ont fait confiance') !== false, 'missing home featured section');
|
||||
|
||||
fwrite(STDOUT, "OK\n");
|
||||
Reference in New Issue
Block a user