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>
This commit is contained in:
@@ -0,0 +1,223 @@
|
||||
---
|
||||
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>
|
||||
Reference in New Issue
Block a user