diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 658a476..4d2c9e6 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -59,7 +59,12 @@ Plans: 3. Submitting the contact form with valid data shows a success toast; EmailJS delivers the email 4. `docker build` completes and `docker run` serves the SSR app on port 3000 5. Google Analytics 4 events appear in GA4 DebugView when browsing in production mode -**Plans**: TBD +**Plans**: 4 plans +Plans: +- [ ] 03-01-PLAN.md — Composants partages + deps + ContactForm + nodemailer server route +- [ ] 03-02-PLAN.md — Landing + Projects + Project Detail pages +- [ ] 03-03-PLAN.md — About + Contact + Fiverr + 404 pages +- [ ] 03-04-PLAN.md — Dockerfile SSR + GA4 config + docker-compose + formation redirect **UI hint**: yes ## Progress @@ -71,4 +76,4 @@ Phases execute in numeric order: 1 → 2 → 3 |-------|----------------|--------|-----------| | 1. Foundation | 2/2 | Complete | 2026-04-08 | | 2. SSR Shell | 3/3 | Complete | 2026-04-08 | -| 3. Pages & Ship | 0/TBD | Not started | - | +| 3. Pages & Ship | 0/4 | Not started | - | diff --git a/.planning/phases/03-pages-ship/03-01-PLAN.md b/.planning/phases/03-pages-ship/03-01-PLAN.md new file mode 100644 index 0000000..606c8a8 --- /dev/null +++ b/.planning/phases/03-pages-ship/03-01-PLAN.md @@ -0,0 +1,315 @@ +--- +phase: 03-pages-ship +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - package.json + - package-lock.json + - app/data/site.ts + - shared/types/index.ts + - app/components/sections/HeroSection.vue + - app/components/sections/ServicesSection.vue + - app/components/sections/FeaturedProjectsSection.vue + - app/components/sections/TestimonialsSection.vue + - app/components/sections/FAQSection.vue + - app/components/sections/CTASection.vue + - app/components/ProjectCard.vue + - app/components/TechBadge.vue + - app/components/ProjectGallery.vue + - app/components/ContactForm.vue + - server/api/contact.post.ts + - nuxt.config.ts + - app/app.vue +autonomous: true +requirements: + - COMP-01 + - COMP-02 + - COMP-03 + - COMP-04 +must_haves: + truths: + - "Gallery modal opens with UModal + UCarousel and thumbnails, keyboard nav works" + - "Contact form validates with Zod, sends via nodemailer SMTP, shows UToast" + - "FAQ accordion renders i18n content with UAccordion" + - "Testimonials section renders all testimonials with UCard" + - "Project cards link to detail pages with translated content" + - "Site config data (contact, social, fiverr) is available as typed data" + artifacts: + - path: "app/components/ProjectGallery.vue" + provides: "UModal + UCarousel gallery with thumbnails and keyboard nav" + - path: "app/components/ContactForm.vue" + provides: "UForm + Zod validated contact form" + - path: "server/api/contact.post.ts" + provides: "Nodemailer SMTP server route" + - path: "app/components/sections/FAQSection.vue" + provides: "UAccordion FAQ section" + - path: "app/components/sections/TestimonialsSection.vue" + provides: "Testimonials with UCard" + key_links: + - from: "app/components/ContactForm.vue" + to: "server/api/contact.post.ts" + via: "$fetch('/api/contact', { method: 'POST' })" + pattern: "\\$fetch.*api/contact" + - from: "app/components/ProjectGallery.vue" + to: "UModal + UCarousel" + via: "v-model:open + useTemplateRef" + pattern: "UModal|UCarousel" +--- + + +Installer les dependances manquantes (nodemailer, zod), migrer la config site, et creer tous les composants partages reutilisables : sections landing (Hero, Services, FeaturedProjects, Testimonials, FAQ, CTA), ProjectCard, TechBadge, ProjectGallery (UModal+UCarousel), ContactForm (UForm+Zod+nodemailer), et la route serveur contact. + +Purpose: Ces composants sont consommes par toutes les pages en Wave 2-3. Les construire d'abord evite la duplication et permet le parallelisme. +Output: Composants dans app/components/, route serveur dans server/api/, dependances installees. + + + +@C:\Users\minit\.claude\get-shit-done\workflows\execute-plan.md +@C:\Users\minit\.claude\get-shit-done\templates\summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/phases/03-pages-ship/03-CONTEXT.md +@.planning/phases/03-pages-ship/03-RESEARCH.md + +@app/data/projects.ts +@app/data/testimonials.ts +@app/data/faq.ts +@app/data/techstack.ts +@app/composables/useProjects.ts +@shared/types/index.ts +@src/config/site.ts +@src/components/sections/HeroSection.vue +@src/components/sections/ServicesSection.vue +@src/components/TestimonialsSection.vue +@src/components/ServiceFAQ.vue +@src/components/ProjectCard.vue +@src/components/TechBadge.vue +@src/components/GalleryModal.vue +@src/components/FiverrHero.vue +@src/components/FiverrServiceCard.vue +@app/app.vue +@nuxt.config.ts + + +From shared/types/index.ts: +```typescript +export interface Project { id: string; title: string; description: string; longDescription?: string; image: string; technologies: string[]; category: string; date: string; featured?: boolean; buttons?: ProjectButton[]; gallery?: string[]; demoUrl?: string; githubUrl?: string; features?: string[] } +export interface Technology { name: string; level: 'Beginner' | 'Intermediate' | 'Advanced'; image: string } +export interface TechStack { programming: Technology[]; front: Technology[]; database: Technology[]; devtools: Technology[]; operating_systems: Technology[]; socials: Technology[] } +export interface Testimonial { name: string; role: string; company: string; avatar: string; rating: number; content: string; date: string; platform: string; featured?: boolean; project_type: string; results?: string[] } +export interface TestimonialsStats { totalReviews: number; averageRating: number; projectsCompleted: number } +export interface FAQ { questionKey: string; answerKey: string; featuresKey?: string } +``` + +From app/composables/useProjects.ts: +```typescript +export function useProjects(): { projects: ComputedRef; featuredProjects: ComputedRef; filterByCategory(cat: string): ComputedRef; search(query: Ref | string): ComputedRef; findById(id: string): ComputedRef } +``` + +From src/config/site.ts (to migrate): +```typescript +export interface SiteConfig { name: string; title: string; description: string; author: string; contact: ContactInfo; social: SocialLink[]; fiverr: FiverrConfig; url: string; seo: {...}; performance: {...} } +export interface FiverrService { id: string; url: string; image: string; price: string } +export interface FiverrConfig { profileUrl: string; services: FiverrService[] } +export interface ContactInfo { email: string; phone: string; location: string } +export interface SocialLink { name: string; url: string; icon: string; username?: string } +``` + + + + + + + Task 1: Installer deps, migrer site config, ajouter UApp, configurer runtimeConfig SMTP + package.json, package-lock.json, app/data/site.ts, shared/types/index.ts, nuxt.config.ts, app/app.vue + +1. Installer les dependances : + ```bash + npm install nodemailer zod + npm install --save-dev @types/nodemailer + ``` + +2. Creer `app/data/site.ts` en migrant le contenu de `src/config/site.ts`. Copier la structure exacte (siteConfig avec contact, social, fiverr, seo, performance). Ajuster les chemins images fiverr : remplacer `@/assets/images/fiverr/` par `/images/fiverr/` (images dans public/). Exporter `siteConfig` et les interfaces `SiteConfig`, `ContactInfo`, `SocialLink`, `FiverrService`, `FiverrConfig`. + +3. Ajouter les interfaces manquantes dans `shared/types/index.ts` : `SiteConfig`, `ContactInfo`, `SocialLink`, `FiverrService`, `FiverrConfig` (ou les exporter depuis `app/data/site.ts` directement — au choix du plus simple). + +4. Mettre a jour `nuxt.config.ts` pour ajouter le runtimeConfig SMTP prive (per D-11, D-13) : + ```typescript + runtimeConfig: { + smtpHost: '', // NUXT_SMTP_HOST + smtpUser: '', // NUXT_SMTP_USER + smtpPass: '', // NUXT_SMTP_PASS + smtpTo: '', // NUXT_SMTP_TO + public: { + gtag: { id: '' }, + }, + }, + ``` + IMPORTANT : les credentials SMTP dans la section privee, JAMAIS dans public (per RESEARCH.md Pitfall 4). + +5. Mettre a jour `app/app.vue` pour wrapper avec `` — requis pour que `useToast()` fonctionne (per D-10, RESEARCH.md Pitfall 1) : + ```vue + + + + + + + + ``` + Conserver le ` + + + + {{ error.statusCode }} + + {{ error.statusCode === 404 ? t('error.notFound') : t('error.generic') }} + + + {{ t('error.backHome') }} + + + +``` + +Ajouter les cles i18n manquantes si necessaire : `error.notFound` ("Page introuvable"), `error.generic` ("Une erreur est survenue"), `error.backHome` ("Retour a l'accueil") dans les fichiers de traduction FR/EN. Si les cles n'existent pas encore, les ajouter dans `i18n/locales/fr.json` et `i18n/locales/en.json`. + + + cd C:/Users/minit/Desktop/portfolio/portfolio && test -f app/error.vue && grep -q "clearError" app/error.vue && grep -q "statusCode" app/error.vue && echo "PASS" + + error.vue dans app/ avec affichage code erreur, message i18n, bouton retour accueil via clearError + + + + + +## Trust Boundaries + +| Boundary | Description | +|----------|-------------| +| Aucune nouvelle | Les pages About/Fiverr/404 ne traitent pas de donnees utilisateur. Contact est gere par ContactForm du Plan 01. | + +## STRIDE Threat Register + +| Threat ID | Category | Component | Disposition | Mitigation Plan | +|-----------|----------|-----------|-------------|-----------------| +| T-03-06 | Information Disclosure | contact.vue | accept | email/telephone affiches publiquement — voulu par le proprietaire du site | + + + +- `npx nuxi dev` puis naviguer vers `/about` — bio + 5 categories tech visible +- `/contact` — formulaire 3 champs fonctionnel + infos contact visibles +- `/fiverr` — 4 services, FAQ accordion, boutons CTA +- `/une-page-inexistante` — page 404 custom avec bouton retour +- `curl http://localhost:3000/about` — HTML complet avec meta tags + + + +- About affiche bio + tech stack par categorie avec TechBadge (per D-17) +- Contact affiche ContactForm (3 champs) + infos contact + reseaux (per D-08) +- Fiverr affiche hero + services + FAQ accordion + CTA (per D-18) +- error.vue dans app/ (pas pages/), affiche 404, bouton clearError (per D-20) +- Toutes les pages ont useSeoMeta() + + + +After completion, create `.planning/phases/03-pages-ship/03-03-SUMMARY.md` + diff --git a/.planning/phases/03-pages-ship/03-04-PLAN.md b/.planning/phases/03-pages-ship/03-04-PLAN.md new file mode 100644 index 0000000..030809e --- /dev/null +++ b/.planning/phases/03-pages-ship/03-04-PLAN.md @@ -0,0 +1,201 @@ +--- +phase: 03-pages-ship +plan: 04 +type: execute +wave: 3 +depends_on: ["03-02", "03-03"] +files_modified: + - Dockerfile + - docker-compose.yml + - nuxt.config.ts + - app/pages/formation.vue +autonomous: true +requirements: + - INFRA-01 + - INFRA-04 + - PAGE-07 +must_haves: + truths: + - "docker build -t portfolio . reussit sans erreur" + - "docker run -p 3000:3000 portfolio sert l'app SSR sur port 3000" + - "GA4 est actif uniquement en production" + - "Route /formation redirige vers / ou retourne 404" + artifacts: + - path: "Dockerfile" + provides: "Multi-stage SSR build node:22-alpine" + - path: "docker-compose.yml" + provides: "Config Traefik avec port 3000" + key_links: + - from: "Dockerfile" + to: ".output/server/index.mjs" + via: "node .output/server/index.mjs" + pattern: "node.*\\.output/server/index\\.mjs" + - from: "docker-compose.yml" + to: "Traefik" + via: "loadbalancer.server.port=3000" + pattern: "port=3000" +--- + + +Finaliser l'infrastructure de deploiement : Dockerfile SSR multi-stage, config GA4 production-only, mise a jour docker-compose Traefik, gestion de la page formation supprimee, et nettoyage legacy. + +Purpose: Rend le portfolio deployable en production via Docker + Traefik avec analytics. +Output: Dockerfile SSR fonctionnel, GA4 configure, docker-compose mis a jour. + + + +@C:\Users\minit\.claude\get-shit-done\workflows\execute-plan.md +@C:\Users\minit\.claude\get-shit-done\templates\summary.md + + + +@.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-02-SUMMARY.md +@.planning/phases/03-pages-ship/03-03-SUMMARY.md + +@Dockerfile +@docker-compose.yml +@nuxt.config.ts + + + + + + Task 1: Dockerfile SSR multi-stage + docker-compose Traefik port 3000 + Dockerfile, docker-compose.yml + +**Dockerfile** (per D-12, D-13, INFRA-01) : Reecrire completement le Dockerfile existant (qui copie dist/ vers nginx). Implementation exacte du RESEARCH.md Pattern 6 : + +```dockerfile +# Stage 1: Build +FROM node:22-alpine AS builder +WORKDIR /app +COPY package*.json ./ +RUN npm ci +COPY . . +RUN npm run build + +# Stage 2: Runtime +FROM node:22-alpine AS runner +ENV NODE_ENV=production +ENV HOST=0.0.0.0 +ENV PORT=3000 +WORKDIR /app +COPY --from=builder /app/.output /app/.output +EXPOSE 3000 +CMD ["node", "/app/.output/server/index.mjs"] +``` + +IMPORTANT : Copie `.output/` PAS `dist/` (per RESEARCH.md Pitfall 3). Pas de nginx. Node sert directement. + +Ajouter un `.dockerignore` s'il n'existe pas : +``` +node_modules +.nuxt +.output +dist +src +.git +*.md +.planning +``` + +**docker-compose.yml** (per D-14) : Modifier la ligne port Traefik : +```yaml +- 'traefik.http.services.portfolio.loadbalancer.server.port=3000' # was 80 +``` +Changer uniquement cette ligne. Conserver tout le reste intact (labels Traefik TLS, routeurs, redirections www). + +Ajouter les variables d'environnement SMTP dans la section `environment` du service portfolio : +```yaml +environment: + - TZ=Europe/Paris + - NUXT_SMTP_HOST=${NUXT_SMTP_HOST} + - NUXT_SMTP_USER=${NUXT_SMTP_USER} + - NUXT_SMTP_PASS=${NUXT_SMTP_PASS} + - NUXT_SMTP_TO=${NUXT_SMTP_TO} + - NUXT_PUBLIC_GTAG_ID=${NUXT_PUBLIC_GTAG_ID} +``` + + + cd C:/Users/minit/Desktop/portfolio/portfolio && grep -q ".output/server/index.mjs" Dockerfile && grep -q "node:22-alpine" Dockerfile && grep -q "port=3000" docker-compose.yml && echo "PASS" + + Dockerfile SSR multi-stage node:22-alpine avec .output/, docker-compose port 3000, variables env SMTP/GA4 + + + + Task 2: GA4 production-only + formation redirect + cleanup + nuxt.config.ts, app/pages/formation.vue + +**GA4 nuxt-gtag** (per D-15, INFRA-04) : Verifier/mettre a jour `nuxt.config.ts` pour que nuxt-gtag soit configure correctement. Le config existant a deja : +```typescript +gtag: { + id: '', + enabled: import.meta.env.NODE_ENV === 'production', +}, +``` +Verifier que `runtimeConfig.public.gtag.id` est bien present (deja fait en Plan 01 pour SMTP). Le `NUXT_PUBLIC_GTAG_ID` sera injecte au runtime sans rebuild (per D-13). Rien a changer si deja correct — juste verifier. + +**Page Formation** (per D-19) : La page formation est supprimee. Si `app/pages/formation.vue` existe comme stub, le remplacer par une redirection : +```vue + + + + + +``` +Si le fichier n'existe pas, le creer quand meme pour eviter les 404 sur d'anciens liens. + +**Nettoyage :** NE PAS supprimer le dossier `src/` dans cette phase — il sert de reference et sera nettoye dans une future phase de maintenance. Supprimer uniquement `nginx.conf` s'il existe (plus utile avec le Dockerfile SSR). + + + cd C:/Users/minit/Desktop/portfolio/portfolio && grep -q "production" nuxt.config.ts && grep -q "navigateTo\|redirect" app/pages/formation.vue && echo "PASS" + + GA4 nuxt-gtag actif en production via runtimeConfig, formation.vue redirige vers /, nginx.conf supprime si present + + + + + +## Trust Boundaries + +| Boundary | Description | +|----------|-------------| +| Docker env vars -> runtimeConfig | Variables SMTP passees au container via docker-compose | + +## STRIDE Threat Register + +| Threat ID | Category | Component | Disposition | Mitigation Plan | +|-----------|----------|-----------|-------------|-----------------| +| T-03-07 | Information Disclosure | docker-compose.yml | mitigate | Variables SMTP referencent ${VAR} pas de valeurs hardcodees — .env non commite | +| T-03-08 | Information Disclosure | Dockerfile | mitigate | .dockerignore exclut .planning, .git, src, node_modules | + + + +- `docker build -t portfolio .` complete sans erreur +- `docker run --rm -p 3000:3000 portfolio` sert l'app sur http://localhost:3000 +- `curl http://localhost:3000/` retourne du HTML complet SSR +- L'image Docker finale est < 300MB (node:22-alpine + .output seulement) +- `/formation` redirige vers `/` avec status 301 + + + +- Dockerfile utilise node:22-alpine en 2 stages, copie .output/, lance node server/index.mjs (per D-12) +- docker-compose port Traefik = 3000 (per D-14) +- Variables env SMTP + GA4 passees via docker-compose environment +- nuxt-gtag actif uniquement en production (per D-15) +- formation.vue redirige vers / en 301 (per D-19) +- .dockerignore exclut node_modules, .nuxt, .output, src, .git + + + +After completion, create `.planning/phases/03-pages-ship/03-04-SUMMARY.md` +
+ {{ error.statusCode === 404 ? t('error.notFound') : t('error.generic') }} +