Files
portfolio/.planning/phases/03-pages-ship/03-02-PLAN.md
T
kayjaydee 3e38ea02b1 docs(03): create phase 3 plans — pages, components, Docker SSR
4 plans across 3 waves: shared components + deps (wave 1),
pages landing/projects/detail + about/contact/fiverr/404 (wave 2),
Dockerfile SSR + GA4 + docker-compose (wave 3).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 18:25:28 +02:00

224 lines
10 KiB
Markdown

---
phase: 03-pages-ship
plan: 02
type: execute
wave: 2
depends_on: ["03-01"]
files_modified:
- app/pages/index.vue
- app/pages/projects.vue
- app/pages/project/[id].vue
autonomous: true
requirements:
- PAGE-01
- PAGE-02
- PAGE-03
must_haves:
truths:
- "Landing page affiche 6 sections : Hero, FeaturedProjects, Services, Testimonials, FAQ, CTA"
- "Projects page filtre par recherche texte et boutons categorie"
- "Project detail affiche description, features, technologies, galerie modale"
- "Chaque page a ses metadonnees SEO via useSeoMeta()"
artifacts:
- path: "app/pages/index.vue"
provides: "Landing page avec 6 sections"
- path: "app/pages/projects.vue"
provides: "Liste projets avec filtres"
- path: "app/pages/project/[id].vue"
provides: "Detail projet avec galerie"
key_links:
- from: "app/pages/index.vue"
to: "app/components/sections/*.vue"
via: "auto-import composants"
pattern: "HeroSection|FeaturedProjectsSection|ServicesSection"
- from: "app/pages/project/[id].vue"
to: "app/components/ProjectGallery.vue"
via: "useTemplateRef + openGallery"
pattern: "ProjectGallery|openGallery"
---
<objective>
Construire les 3 pages principales du portfolio : Landing (accueil), Projects (liste), et Project Detail (detail + galerie). Ces pages consomment les composants crees en Plan 01.
Purpose: Ce sont les pages les plus visitees du portfolio — la landing convertit les visiteurs, la liste projets montre le travail, et le detail permet l'exploration approfondie.
Output: 3 pages fonctionnelles dans app/pages/.
</objective>
<execution_context>
@C:\Users\minit\.claude\get-shit-done\workflows\execute-plan.md
@C:\Users\minit\.claude\get-shit-done\templates\summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/phases/03-pages-ship/03-CONTEXT.md
@.planning/phases/03-pages-ship/03-RESEARCH.md
@.planning/phases/03-pages-ship/03-01-SUMMARY.md
@src/views/HomePage.vue
@src/views/ProjectsPage.vue
@src/views/ProjectDetailPage.vue
@app/composables/useProjects.ts
@app/data/projects.ts
@app/data/testimonials.ts
@app/data/faq.ts
<interfaces>
From app/composables/useProjects.ts:
```typescript
export function useProjects(): {
projects: ComputedRef<Project[]>
featuredProjects: ComputedRef<Project[]>
filterByCategory(cat: string): ComputedRef<Project[]>
search(query: Ref<string> | string): ComputedRef<Project[]>
findById(id: string): ComputedRef<Project | undefined>
}
```
From app/data/faq.ts:
```typescript
export const homeFAQs: FAQ[] // { questionKey, answerKey, featuresKey }
```
From app/data/testimonials.ts:
```typescript
export const testimonials: Testimonial[]
export const testimonialsStats: TestimonialsStats
```
Composants disponibles (auto-importes, crees en Plan 01):
- HeroSection — pas de props (utilise i18n interne)
- FeaturedProjectsSection — pas de props (utilise useProjects interne)
- ServicesSection — pas de props (utilise i18n interne)
- TestimonialsSection — props: title, subtitle, testimonials, stats, statsLabels, ctaTitle, ctaSubtitle, ctaText, ctaLink
- FAQSection — props: faqs (FAQ[]), title, subtitle
- CTASection — pas de props (utilise i18n interne)
- ProjectCard — props: project (Project)
- ProjectGallery — props: gallery (string[]), projectTitle (string); expose: openGallery(index)
- TechBadge — props: tech (Technology | string), showLevel?, showImage?
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: Page Landing (index.vue) avec 6 sections</name>
<files>app/pages/index.vue</files>
<action>
Remplacer le contenu stub de `app/pages/index.vue` par la page landing complete (per D-01, D-02, D-03).
Structure exacte — 6 sections dans cet ordre (per D-01) :
1. `<HeroSection />` — auto-importe, texte seul (per D-02)
2. `<FeaturedProjectsSection />` — auto-importe, 3 projets featured (per D-03)
3. `<ServicesSection />` — auto-importe
4. `<TestimonialsSection>` — passer les props i18n depuis `t()`, importer `testimonials` et `testimonialsStats` depuis `~/data/testimonials`
5. `<FAQSection>` — passer `homeFAQs` depuis `~/data/faq` et titres i18n
6. `<CTASection />` — auto-importe
SEO via `useSeoMeta()` : titre, description, og:title, og:description, og:image (per SEO-01). Conserver le JSON-LD Person + ProfessionalService deja present dans le stub via `useHead({ script })`.
Wrapper `<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">` pour le contenu selon le layout Phase 2 (D-16 max-w-7xl).
</action>
<verify>
<automated>cd C:/Users/minit/Desktop/portfolio/portfolio && grep -c "Section" app/pages/index.vue | grep -q "[456789]" && grep -q "useSeoMeta" app/pages/index.vue && echo "PASS"</automated>
</verify>
<done>Page landing avec 6 sections dans l'ordre Hero > FeaturedProjects > Services > Testimonials > FAQ > CTA, SEO meta configurees</done>
</task>
<task type="auto">
<name>Task 2: Page Projects (projects.vue) avec filtres recherche + categorie</name>
<files>app/pages/projects.vue</files>
<action>
Remplacer le stub `app/pages/projects.vue` par la page projets complete (per D-04, PAGE-02).
Migrer depuis `src/views/ProjectsPage.vue` en adaptant pour Nuxt :
1. **Script setup** : `const { projects } = useProjects()` (auto-import). Refs : `searchQuery`, `selectedCategory` (defaut 'all'). Computed `categories` : `['all', ...new Set(projects.value.map(p => p.category))]`. Computed `filteredProjects` : filtre par searchQuery (titre, description, technologies) puis par selectedCategory.
2. **Template** :
- Section hero : titre `t('projects.title')`, sous-titre, stats (total projets, featured, categories)
- Section filtres (per D-04) : `UInput` pour recherche avec icone search (`icon="i-lucide-search"`) + boutons categorie `UButton` pour chaque categorie (variant `soft` pour inactif, `solid` pour actif). PAS de select/dropdown — boutons cliquables comme l'actuel.
- Grille projets : `grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6`. Utiliser `<ProjectCard :project="project" />` pour chaque projet filtre.
- Etat vide : message "Aucun resultat" avec bouton reset filtres.
3. **SEO** : `useSeoMeta()` avec titre, description, og specifiques a la page projets.
</action>
<verify>
<automated>cd C:/Users/minit/Desktop/portfolio/portfolio && grep -q "filteredProjects" app/pages/projects.vue && grep -q "searchQuery" app/pages/projects.vue && grep -q "ProjectCard" app/pages/projects.vue && echo "PASS"</automated>
</verify>
<done>Page projects avec recherche texte + filtres categorie boutons, grille ProjectCard, etat vide, SEO meta</done>
</task>
<task type="auto">
<name>Task 3: Page Project Detail (project/[id].vue) avec galerie modale</name>
<files>app/pages/project/[id].vue</files>
<action>
Creer `app/pages/project/[id].vue` — route dynamique (per PAGE-03).
Migrer depuis `src/views/ProjectDetailPage.vue` :
1. **Script setup** :
- `const route = useRoute()` puis `const { findById } = useProjects()`
- `const project = findById(route.params.id as string)`
- 404 si non trouve : `if (!project.value) throw createError({ status: 404, statusText: 'Project not found' })` (per RESEARCH.md Code Examples)
- `const galleryRef = useTemplateRef('gallery')` pour acceder a ProjectGallery.openGallery
- Computed `relatedProjects` : meme categorie, exclure le projet courant, slice(0, 3)
- `useSeoMeta()` avec titre du projet, description
2. **Template** :
- Breadcrumb : `UButton variant="link"` vers /projects avec icone fleche retour
- Hero grid 2 colonnes : image principale `NuxtImg` a gauche, infos (categorie, date, titre, description, boutons CTA) a droite
- Boutons CTA : `UButton` pour demo, source code, boutons custom du projet
- Section "A propos" : longDescription ou description, liste features avec checkmarks
- Section technologies : grille `TechBadge` pour chaque tech
- Section galerie : grille thumbnails cliquables. Au clic sur une image : `galleryRef.value?.openGallery(index)`. Chaque thumbnail : `NuxtImg` avec overlay zoom au hover.
- Sidebar : card infos projet (date, categorie, status) + projets lies `NuxtLink`
- `<ProjectGallery ref="gallery" :gallery="project.gallery" :project-title="project.title" />` en bas du template
3. **Responsive** : layout 2 colonnes (main + sidebar) sur desktop, stack sur mobile.
</action>
<verify>
<automated>cd C:/Users/minit/Desktop/portfolio/portfolio && test -f app/pages/project/\[id\].vue && grep -q "findById" app/pages/project/\[id\].vue && grep -q "ProjectGallery" app/pages/project/\[id\].vue && grep -q "createError" app/pages/project/\[id\].vue && echo "PASS"</automated>
</verify>
<done>Page project detail avec route dynamique [id], 404 si non trouve, galerie modale via ProjectGallery, projets lies, SEO meta</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| URL params -> findById | route.params.id est une entree utilisateur |
## STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-03-05 | Tampering | project/[id].vue | mitigate | createError(404) si projet non trouve — pas d'injection possible via les donnees statiques |
</threat_model>
<verification>
- `npx nuxi dev` puis naviguer vers `/` — 6 sections visibles
- `/projects` — filtres fonctionnels, cards affichees
- `/project/flowboard` — detail avec galerie, clic image ouvre modal
- `/project/inexistant` — redirige vers page 404
- `curl http://localhost:3000/` contient les balises meta SEO
</verification>
<success_criteria>
- Landing affiche les 6 sections dans l'ordre correct (per D-01)
- Hero est texte seul, pas d'image (per D-02)
- 3 projets featured affiches (per D-03)
- Projects page a recherche texte + boutons categorie (per D-04)
- Project detail a galerie modale UModal+UCarousel fonctionnelle
- Route /project/[id] inexistant retourne 404
- Toutes les pages ont useSeoMeta()
</success_criteria>
<output>
After completion, create `.planning/phases/03-pages-ship/03-02-SUMMARY.md`
</output>