Files
Portfolio-Codex/docs/stories/3.4.page-projet-individuelle.md

318 lines
13 KiB
Markdown

# Story 3.4: Page Projet Individuelle
## Status
Ready for Dev
## Story
**As a** visiteur,
**I want** consulter une page dédiée pour chaque projet vedette,
**so that** je comprends le contexte, la solution technique et vois le résultat.
## Acceptance Criteria
1. L'URL `/projet/{slug}` affiche le projet correspondant au slug
2. Le slug est récupéré depuis le router (pas depuis $_GET)
3. La page affiche les sections : Contexte, Solution technique, Travail d'équipe (si applicable), Durée, Témoignage (si disponible)
4. Un bouton/lien permet de visiter le projet en ligne (ou affiche "Non disponible")
5. Les technologies sont affichées sous forme de badges
6. Des captures d'écran sont affichées si disponibles (galerie simple)
7. Un lien "Retour aux projets" permet de revenir à la liste
8. Si le slug n'existe pas, la page 404 est affichée
## Tasks / Subtasks
- [] **Task 1 : Créer la page project-single.php** (AC: 1, 2, 8)
- [] Créer `pages/project-single.php`
- [] Récupérer le slug depuis `$GLOBALS['routeParams']`
- [] Charger le projet avec `getProjectBySlug()`
- [] Rediriger vers 404 si projet non trouvé
- [] **Task 2 : Afficher les informations principales** (AC: 3, 5)
- [] Titre du projet
- [] Badges technologies
- [] Section Contexte
- [] Section Solution technique
- [] Section Travail d'équipe (si non null)
- [] Durée du projet
- [] **Task 3 : Ajouter le lien vers le projet** (AC: 4)
- [] Bouton "Voir le projet en ligne" si URL disponible
- [] Bouton "Voir sur GitHub" si URL GitHub disponible
- [] Message "Projet non disponible en ligne" si aucun lien
- [] **Task 4 : Afficher la galerie de captures** (AC: 6)
- [] Grille de screenshots
- [] Lazy loading sur les images
- [ ] Lightbox optionnel (amélioration future)
- [] **Task 5 : Ajouter le témoignage** (AC: 3)
- [] Placeholder préparé pour Story 4.5
- [ ] Récupérer le témoignage lié au projet (Story 4.5)
- [] **Task 6 : Ajouter la navigation** (AC: 7)
- [] Breadcrumb en haut de page
- [] Lien "Retour aux projets"
- [] CTA "Me contacter" en bas
## Dev Notes
### Page pages/project-single.php
```php
<?php
/**
* Page projet individuelle
*/
// Récupérer le slug depuis le router
$slug = $GLOBALS['routeParams'][0] ?? null;
if (!$slug) {
http_response_code(404);
include __DIR__ . '/404.php';
exit;
}
$project = getProjectBySlug($slug);
if (!$project) {
http_response_code(404);
include __DIR__ . '/404.php';
exit;
}
// Récupérer le témoignage lié (si existe)
$testimonial = getTestimonialByProject($slug);
$pageTitle = $project['title'];
$pageDescription = $project['context'] ?? "Découvrez le projet {$project['title']}";
$currentPage = 'projects';
include_template('header', compact('pageTitle', 'pageDescription'));
include_template('navbar', compact('currentPage'));
?>
<main>
<article class="section">
<div class="container-content">
<!-- Breadcrumb -->
<nav class="breadcrumb mb-8">
<a href="/" class="breadcrumb-link">Accueil</a>
<span class="text-text-muted">/</span>
<a href="/projets" class="breadcrumb-link">Projets</a>
<span class="text-text-muted">/</span>
<span class="breadcrumb-current"><?= htmlspecialchars($project['title']) ?></span>
</nav>
<!-- Header du projet -->
<header class="mb-12">
<h1 class="text-display mb-4"><?= htmlspecialchars($project['title']) ?></h1>
<!-- Technologies -->
<div class="flex flex-wrap gap-2 mb-6">
<?php foreach ($project['technologies'] ?? [] as $tech): ?>
<span class="badge badge-primary"><?= htmlspecialchars($tech) ?></span>
<?php endforeach; ?>
</div>
<!-- Boutons d'action -->
<div class="flex flex-wrap gap-4">
<?php if (!empty($project['url'])): ?>
<a href="<?= htmlspecialchars($project['url']) ?>" target="_blank" rel="noopener" class="btn-primary">
Voir le projet en ligne
<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="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/>
</svg>
</a>
<?php endif; ?>
<?php if (!empty($project['github'])): ?>
<a href="<?= htmlspecialchars($project['github']) ?>" target="_blank" rel="noopener" class="btn-secondary">
Voir sur GitHub
</a>
<?php endif; ?>
<?php if (empty($project['url']) && empty($project['github'])): ?>
<span class="text-text-muted italic">Projet non disponible en ligne</span>
<?php endif; ?>
</div>
</header>
<!-- Image principale -->
<?php if (!empty($project['thumbnail'])): ?>
<div class="mb-12 rounded-lg overflow-hidden">
<img
src="/assets/img/projects/<?= htmlspecialchars($project['thumbnail']) ?>"
alt="<?= htmlspecialchars($project['title']) ?>"
class="w-full"
loading="lazy"
>
</div>
<?php endif; ?>
<!-- Contenu -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-12">
<!-- Colonne principale -->
<div class="lg:col-span-2 space-y-10">
<!-- Contexte -->
<?php if (!empty($project['context'])): ?>
<section>
<h2 class="text-heading mb-4">Contexte</h2>
<p class="text-text-secondary leading-relaxed">
<?= nl2br(htmlspecialchars($project['context'])) ?>
</p>
</section>
<?php endif; ?>
<!-- Solution technique -->
<?php if (!empty($project['solution'])): ?>
<section>
<h2 class="text-heading mb-4">Solution Technique</h2>
<p class="text-text-secondary leading-relaxed">
<?= nl2br(htmlspecialchars($project['solution'])) ?>
</p>
</section>
<?php endif; ?>
<!-- Travail d'équipe -->
<?php if (!empty($project['teamwork'])): ?>
<section>
<h2 class="text-heading mb-4">Travail d'Équipe</h2>
<p class="text-text-secondary leading-relaxed">
<?= nl2br(htmlspecialchars($project['teamwork'])) ?>
</p>
</section>
<?php endif; ?>
<!-- Galerie -->
<?php if (!empty($project['screenshots'])): ?>
<section>
<h2 class="text-heading mb-4">Captures d'écran</h2>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<?php foreach ($project['screenshots'] as $screenshot): ?>
<img
src="/assets/img/projects/<?= htmlspecialchars($screenshot) ?>"
alt="Capture d'écran - <?= htmlspecialchars($project['title']) ?>"
class="rounded-lg"
loading="lazy"
>
<?php endforeach; ?>
</div>
</section>
<?php endif; ?>
</div>
<!-- Sidebar -->
<aside class="space-y-6">
<!-- Durée -->
<div class="card">
<div class="card-body">
<h3 class="text-sm font-medium text-text-muted mb-1">Durée du projet</h3>
<p class="text-lg font-semibold"><?= htmlspecialchars($project['duration'] ?? 'Non spécifiée') ?></p>
</div>
</div>
<!-- Témoignage -->
<?php if ($testimonial): ?>
<div class="testimonial">
<blockquote class="text-text-secondary italic mb-4">
"<?= htmlspecialchars($testimonial['quote']) ?>"
</blockquote>
<footer>
<p class="font-medium text-text-primary"><?= htmlspecialchars($testimonial['author_name']) ?></p>
<p class="text-sm text-text-muted">
<?= htmlspecialchars($testimonial['author_role']) ?>
<?php if (!empty($testimonial['author_company'])): ?>
- <?= htmlspecialchars($testimonial['author_company']) ?>
<?php endif; ?>
</p>
</footer>
</div>
<?php endif; ?>
</aside>
</div>
<!-- Navigation bas de page -->
<footer class="mt-16 pt-8 border-t border-border flex flex-wrap justify-between items-center gap-4">
<a href="/projets" class="btn-ghost">
← Retour aux projets
</a>
<a href="/contact" class="btn-primary">
Me contacter
</a>
</footer>
</div>
</article>
</main>
<?php include_template('footer'); ?>
```
### Structure de la Page
```
┌─────────────────────────────────────────┐
│ Breadcrumb │
├─────────────────────────────────────────┤
│ TITRE DU PROJET │
│ [Badge] [Badge] [Badge] │
│ [Voir en ligne] [GitHub] │
├─────────────────────────────────────────┤
│ [Image principale] │
├─────────────────────────────────────────┤
│ CONTENU │ SIDEBAR │
│ ───────────── │ │
│ Contexte │ Durée │
│ Solution technique │ │
│ Travail d'équipe │ Témoignage │
│ Captures d'écran │ │
├─────────────────────────────────────────┤
│ [← Retour] [Me contacter] │
└─────────────────────────────────────────┘
```
## Testing
- [] `/projet/ecommerce-xyz` affiche le bon projet
- [] `/projet/inexistant` affiche la page 404
- [] Toutes les sections s'affichent correctement
- [] Le bouton "Voir en ligne" fonctionne (si URL)
- [] Le bouton "Voir sur GitHub" fonctionne (si GitHub)
- [] Le breadcrumb est navigable
- [] Les images sont en lazy loading
- [] La page est responsive
## Dev Agent Record
### Agent Model Used
Claude Opus 4.5 (claude-opus-4-5-20251101)
### File List
| File | Action | Description |
|------|--------|-------------|
| `pages/project-single.php` | Modified | Page projet individuelle complète |
### Completion Notes
- Récupération slug via router ($GLOBALS['routeParams'])
- Redirection 404 si projet non trouvé
- Sections: Contexte, Solution technique, Travail d'équipe (conditionnel)
- Boutons: Voir en ligne + GitHub avec icônes SVG
- Galerie screenshots en grille 2 colonnes
- Sidebar avec durée du projet
- Breadcrumb accessible avec aria-label
- Navigation: retour + CTA contact
- Lazy loading + fallback onerror sur images
- Témoignage: placeholder préparé pour Story 4.5
### 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) |