318 lines
13 KiB
Markdown
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) |
|