PHI Conseil : site Next.js 16 fidèle à la maquette (accueil 7 sections, pages internes, contenu JSON validé, formulaire de contact sécurisé, tests, docs)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
90 changed files +5,294 −139
added
.github/workflows/ci.yml
+37 −0
@@ -0,0 +1,37 @@ | |||||
| 1 | +name: CI | ||||
| 2 | + | ||||
| 3 | +on: | ||||
| 4 | + push: | ||||
| 5 | + branches: [main] | ||||
| 6 | + pull_request: | ||||
| 7 | + | ||||
| 8 | +jobs: | ||||
| 9 | + check: | ||||
| 10 | + runs-on: ubuntu-latest | ||||
| 11 | + env: | ||||
| 12 | + CONTACT_TRANSPORT: log | ||||
| 13 | + NEXT_PUBLIC_SITE_URL: http://127.0.0.1:3100 | ||||
| 14 | + steps: | ||||
| 15 | + - uses: actions/checkout@v4 | ||||
| 16 | + - uses: pnpm/action-setup@v4 | ||||
| 17 | + with: | ||||
| 18 | + version: 11 | ||||
| 19 | + - uses: actions/setup-node@v4 | ||||
| 20 | + with: | ||||
| 21 | + node-version: 22 | ||||
| 22 | + cache: pnpm | ||||
| 23 | + - run: pnpm install --frozen-lockfile | ||||
| 24 | + - run: pnpm lint | ||||
| 25 | + - run: pnpm typecheck | ||||
| 26 | + - run: pnpm test | ||||
| 27 | + - run: pnpm build | ||||
| 28 | + - run: pnpm exec playwright install --with-deps chromium webkit | ||||
| 29 | + - run: pnpm test:e2e | ||||
| 30 | + - uses: actions/upload-artifact@v4 | ||||
| 31 | + if: always() | ||||
| 32 | + with: | ||||
| 33 | + name: playwright-report | ||||
| 34 | + path: | | ||||
| 35 | + playwright-report | ||||
| 36 | + docs/screenshots | ||||
| 37 | + retention-days: 14 | ||||
modified
.gitignore
+7 −0
@@ -39,3 +39,10 @@ yarn-error.log* | |||||
| 39 | # typescript | 39 | # typescript | ||
| 40 | *.tsbuildinfo | 40 | *.tsbuildinfo | ||
| 41 | next-env.d.ts | 41 | next-env.d.ts | ||
| 42 | + | ||||
| 43 | +# tests & captures | ||||
| 44 | +/test-results/ | ||||
| 45 | +/playwright-report/ | ||||
| 46 | +/docs/screenshots/*.png | ||||
| 47 | +!/docs/screenshots/.gitkeep | ||||
| 48 | +.env.local | ||||
added
.prettierignore
+6 −0
@@ -0,0 +1,6 @@ | |||||
| 1 | +.next | ||||
| 2 | +node_modules | ||||
| 3 | +pnpm-lock.yaml | ||||
| 4 | +test-results | ||||
| 5 | +playwright-report | ||||
| 6 | +docs/screenshots | ||||
added
.prettierrc
+7 −0
@@ -0,0 +1,7 @@ | |||||
| 1 | +{ | ||||
| 2 | + "semi": true, | ||||
| 3 | + "singleQuote": false, | ||||
| 4 | + "trailingComma": "all", | ||||
| 5 | + "printWidth": 100, | ||||
| 6 | + "plugins": ["prettier-plugin-tailwindcss"] | ||||
| 7 | +} | ||||
modified
README.md
+41 −22
@@ -1,36 +1,55 @@ | |||||
| 1 | −This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). | 1 | +# PHI Conseil — site web | ||
| 2 | 2 | ||||
| 3 | −## Getting Started | 3 | +Site vitrine de PHI Conseil (« Stratégies · Financement · Réussite »), reproduit fidèlement à partir de la maquette client (`references/phi-conseil-homepage.jpeg`) et conçu comme un produit de production : Next.js 16 (App Router), React 19, TypeScript strict, Tailwind CSS 4, contenu administrable validé par Zod, formulaire de contact sécurisé, tests unitaires et de bout en bout. | ||
| 4 | 4 | ||||
| 5 | −First, run the development server: | 5 | +## Démarrage | ||
| 6 | 6 | ||||
| 7 | ```bash | 7 | ```bash | ||
| 8 | −npm run dev | 8 | +pnpm install | ||
| 9 | −# or | 9 | +cp .env.example .env.local # ajuster au besoin (transport courriel, URL) | ||
| 10 | −yarn dev | 10 | +pnpm dev # http://localhost:3000 | ||
| 11 | −# or | ||||
| 12 | −pnpm dev | ||||
| 13 | −# or | ||||
| 14 | −bun dev | ||||
| 15 | ``` | 11 | ``` | ||
| 16 | 12 | ||||
| 17 | −Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. | 13 | +Prérequis : Node.js ≥ 20 (testé avec 26), pnpm ≥ 10. | ||
| 18 | 14 | ||||
| 19 | −You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. | 15 | +## Scripts | ||
| 20 | 16 | ||||
| 21 | −This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. | 17 | +| Commande | Rôle | | ||
| 18 | +| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | ||||
| 19 | +| `pnpm dev` | serveur de développement | | ||||
| 20 | +| `pnpm build` / `pnpm start` | build de production / serveur de production (`-p <port>`) | | ||||
| 21 | +| `pnpm lint` | ESLint (config Next + règles React Compiler) | | ||||
| 22 | +| `pnpm typecheck` | `tsc --noEmit` | | ||||
| 23 | +| `pnpm test` | tests unitaires Vitest (schémas, validation, sécurité, API, composants) | | ||||
| 24 | +| `pnpm test:e2e` | Playwright : navigation, menu mobile, formulaire, débordements, axe, captures (`docs/screenshots/`) ; nécessite `pnpm build` au préalable et `pnpm exec playwright install chromium webkit` la première fois | | ||||
| 25 | +| `pnpm check` | lint + typecheck + tests + build | | ||||
| 26 | +| `pnpm icons` | régénère favicon / icônes à partir du logo (spirale dorée) | | ||||
| 27 | +| `pnpm format` | Prettier | | ||||
| 22 | 28 | ||||
| 23 | −## Learn More | 29 | +## Structure | ||
| 24 | 30 | ||||
| 25 | −To learn more about Next.js, take a look at the following resources: | 31 | +``` | ||
| 26 | − | 32 | +content/ contenu administrable (site.json, home.json, pages.json) — voir docs/CLIENT_GUIDE.md | ||
| 27 | −- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. | 33 | +src/app/ routes (accueil, services, a-propos, temoignages, nous-joindre, confidentialite, api/contact) | ||
| 28 | −- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. | 34 | +src/components/ layout/ sections/ ui/ graphics/ forms/ | ||
| 35 | +src/lib/ content.ts (schémas + chargeur), env.ts, validation.ts, security.ts, mail.ts, seo.ts, spiral.ts | ||||
| 36 | +src/styles/ tokens.css (design tokens : couleurs échantillonnées, typo, espacements…) | ||||
| 37 | +tests/unit, tests/e2e | ||||
| 38 | +docs/ plan, déploiement, guide client, rapport QA, registre des actifs, captures | ||||
| 39 | +references/ maquette client | ||||
| 40 | +``` | ||||
| 29 | 41 | ||||
| 30 | −You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! | 42 | +## Principes | ||
| 31 | 43 | ||||
| 32 | −## Deploy on Vercel | 44 | +- **Fidélité** : couleurs échantillonnées sur la maquette, structure DOM = ordre visuel sur tous les écrans, motifs (spirale de Fibonacci, vagues, feuillage) en SVG. | ||
| 45 | +- **Honnêteté du contenu** : aucune coordonnée, témoignage ou réalisation inventés. Les témoignages ne sont publiés que s'ils sont `approved: true` et la section `visible: true` ; les photos de la maquette sont remplacées par des illustrations vectorielles tant que le client n'a pas fourni d'images sous licence (`docs/ASSET_REGISTER.md`). | ||||
| 46 | +- **Sécurité** : CSP, en-têtes durcis, validation Zod client + serveur, honeypot, délai anti-robot, limitation de débit, dédoublonnage, journalisation sans donnée personnelle, secrets uniquement côté serveur. | ||||
| 47 | +- **Accessibilité** : WCAG 2.2 AA visé (axe sans violation sur toutes les pages), navigation clavier, `prefers-reduced-motion`, lien d'évitement, états focus visibles. | ||||
| 33 | 48 | ||||
| 34 | −The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. | 49 | +## Documentation | ||
| 35 | 50 | ||||
| 36 | −Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. | 51 | +- `docs/IMPLEMENTATION_PLAN.md` — audit, architecture, décisions, critères d'acceptation | ||
| 52 | +- `docs/DEPLOYMENT.md` — hébergement, DNS, HTTPS, variables, sauvegardes, rollback | ||||
| 53 | +- `docs/CLIENT_GUIDE.md` — modifier textes, services, témoignages, images, coordonnées | ||||
| 54 | +- `docs/QA_REPORT.md` — résultats réels des vérifications, limites, points à valider | ||||
| 55 | +- `docs/ASSET_REGISTER.md` — provenance, licence et statut de chaque actif | ||||
added
content/home.json
+80 −0
@@ -0,0 +1,80 @@ | |||||
| 1 | +{ | ||||
| 2 | + "hero": { | ||||
| 3 | + "titleLines": ["Transformer", "vos idées en projets", "concrets"], | ||||
| 4 | + "subtitle": "Votre partenaire en recherche de subventions, en rédaction de demandes et en reddition de comptes.", | ||||
| 5 | + "cta": { "label": "Découvrir nos services", "href": "/services" }, | ||||
| 6 | + "image": { | ||||
| 7 | + "src": "", | ||||
| 8 | + "alt": "Bureau lumineux avec une plante, une tasse et un ordinateur portable" | ||||
| 9 | + } | ||||
| 10 | + }, | ||||
| 11 | + "services": { | ||||
| 12 | + "visible": true, | ||||
| 13 | + "title": "Nos services", | ||||
| 14 | + "items": [ | ||||
| 15 | + { | ||||
| 16 | + "slug": "recherche-de-subventions", | ||||
| 17 | + "icon": "search", | ||||
| 18 | + "title": "Recherche de subventions", | ||||
| 19 | + "summary": "Trouvez les programmes adaptés à vos projets.", | ||||
| 20 | + "description": "" | ||||
| 21 | + }, | ||||
| 22 | + { | ||||
| 23 | + "slug": "redaction-et-revision-de-demandes", | ||||
| 24 | + "icon": "document-edit", | ||||
| 25 | + "title": "Rédaction et révision de demandes", | ||||
| 26 | + "summary": "Présentez des projets solides et convaincants.", | ||||
| 27 | + "description": "" | ||||
| 28 | + }, | ||||
| 29 | + { | ||||
| 30 | + "slug": "redaction-et-revision-de-rapports", | ||||
| 31 | + "icon": "chart", | ||||
| 32 | + "title": "Rédaction et révision de rapports", | ||||
| 33 | + "summary": "Communiquez efficacement vos résultats.", | ||||
| 34 | + "description": "" | ||||
| 35 | + }, | ||||
| 36 | + { | ||||
| 37 | + "slug": "reddition-de-comptes", | ||||
| 38 | + "icon": "check-circle", | ||||
| 39 | + "title": "Reddition de comptes", | ||||
| 40 | + "summary": "Assurez une gestion rigoureuse et conforme.", | ||||
| 41 | + "description": "" | ||||
| 42 | + } | ||||
| 43 | + ] | ||||
| 44 | + }, | ||||
| 45 | + "about": { | ||||
| 46 | + "visible": true, | ||||
| 47 | + "overtitle": "À propos de PHI Conseil", | ||||
| 48 | + "title": "Une approche stratégique pour maximiser vos opportunités", | ||||
| 49 | + "paragraph": "Forte d'une expertise en financement et en gestion de projets, Phi Conseil vous accompagne avec rigueur, créativité et bienveillance, pour donner vie à vos ambitions.", | ||||
| 50 | + "cta": { "label": "En savoir plus", "href": "/a-propos" }, | ||||
| 51 | + "benefits": [ | ||||
| 52 | + "Une expertise reconnue", | ||||
| 53 | + "Une approche personnalisée", | ||||
| 54 | + "Un accompagnement de A à Z", | ||||
| 55 | + "Un engagement envers votre réussite" | ||||
| 56 | + ], | ||||
| 57 | + "image": { | ||||
| 58 | + "src": "", | ||||
| 59 | + "alt": "Vase transparent avec feuillage posé sur des livres, lumière douce" | ||||
| 60 | + } | ||||
| 61 | + }, | ||||
| 62 | + "testimonials": { | ||||
| 63 | + "visible": false, | ||||
| 64 | + "previewOnly": true, | ||||
| 65 | + "items": [ | ||||
| 66 | + { | ||||
| 67 | + "quote": "« Grâce à l'accompagnement de Phi Conseil, nous avons obtenu un financement majeur pour notre projet. Leur professionnalisme et leur disponibilité font toute la différence. »", | ||||
| 68 | + "author": "Exemple de maquette (non vérifié)", | ||||
| 69 | + "role": "Témoignage fictif de la capture — à remplacer par un avis autorisé", | ||||
| 70 | + "approved": false | ||||
| 71 | + } | ||||
| 72 | + ] | ||||
| 73 | + }, | ||||
| 74 | + "callToAction": { | ||||
| 75 | + "visible": true, | ||||
| 76 | + "title": "Parlons de vos projets !", | ||||
| 77 | + "subtitle": "Contactez-nous dès aujourd'hui pour discuter de vos besoins.", | ||||
| 78 | + "cta": { "label": "Nous joindre", "href": "/nous-joindre" } | ||||
| 79 | + } | ||||
| 80 | +} | ||||
added
content/pages.json
+42 −0
@@ -0,0 +1,42 @@ | |||||
| 1 | +{ | ||||
| 2 | + "services": { | ||||
| 3 | + "title": "Nos services", | ||||
| 4 | + "intro": "Quatre services complémentaires pour financer, documenter et rendre compte de vos projets.", | ||||
| 5 | + "seoDescription": "Recherche de subventions, rédaction et révision de demandes et de rapports, reddition de comptes : les services de PHI Conseil." | ||||
| 6 | + }, | ||||
| 7 | + "about": { | ||||
| 8 | + "title": "À propos", | ||||
| 9 | + "intro": "Une approche stratégique pour maximiser vos opportunités.", | ||||
| 10 | + "seoDescription": "Découvrez l'approche de PHI Conseil : expertise, personnalisation, accompagnement de A à Z et engagement envers votre réussite.", | ||||
| 11 | + "body": [ | ||||
| 12 | + "Forte d'une expertise en financement et en gestion de projets, Phi Conseil vous accompagne avec rigueur, créativité et bienveillance, pour donner vie à vos ambitions." | ||||
| 13 | + ] | ||||
| 14 | + }, | ||||
| 15 | + "testimonials": { | ||||
| 16 | + "title": "Témoignages", | ||||
| 17 | + "intro": "Les témoignages publiés sur cette page sont ceux de clients ayant autorisé leur diffusion.", | ||||
| 18 | + "emptyMessage": "Aucun témoignage n'est publié pour le moment.", | ||||
| 19 | + "seoDescription": "Témoignages de clients de PHI Conseil." | ||||
| 20 | + }, | ||||
| 21 | + "contact": { | ||||
| 22 | + "title": "Nous joindre", | ||||
| 23 | + "intro": "Décrivez-nous votre projet ou votre besoin : nous vous répondrons dans les meilleurs délais.", | ||||
| 24 | + "seoDescription": "Contactez PHI Conseil pour discuter de vos besoins en recherche de subventions, rédaction de demandes et reddition de comptes.", | ||||
| 25 | + "subjects": [ | ||||
| 26 | + "Recherche de subventions", | ||||
| 27 | + "Rédaction ou révision d'une demande", | ||||
| 28 | + "Rédaction ou révision d'un rapport", | ||||
| 29 | + "Reddition de comptes", | ||||
| 30 | + "Autre demande" | ||||
| 31 | + ], | ||||
| 32 | + "consentLabel": "J'accepte que mes renseignements soient utilisés pour traiter ma demande, conformément à la politique de confidentialité.", | ||||
| 33 | + "successTitle": "Message envoyé", | ||||
| 34 | + "successMessage": "Merci ! Votre message a bien été transmis. Nous vous répondrons dans les meilleurs délais." | ||||
| 35 | + }, | ||||
| 36 | + "privacy": { | ||||
| 37 | + "title": "Politique de confidentialité et mentions légales", | ||||
| 38 | + "seoDescription": "Politique de confidentialité et mentions légales de PHI Conseil.", | ||||
| 39 | + "lastUpdated": "2026-11-08", | ||||
| 40 | + "reviewed": false | ||||
| 41 | + } | ||||
| 42 | +} | ||||
added
content/site.json
+36 −0
@@ -0,0 +1,36 @@ | |||||
| 1 | +{ | ||||
| 2 | + "brand": { | ||||
| 3 | + "name": "PHI Conseil", | ||||
| 4 | + "tagline": "STRATÉGIES · FINANCEMENT · RÉUSSITE", | ||||
| 5 | + "footerSignature": "DES STRATÉGIES QUI DONNENT VIE À VOS PROJETS.", | ||||
| 6 | + "legalName": "Phi Conseil" | ||||
| 7 | + }, | ||||
| 8 | + "navigation": [ | ||||
| 9 | + { "label": "Accueil", "href": "/" }, | ||||
| 10 | + { "label": "Nos services", "href": "/services" }, | ||||
| 11 | + { "label": "À propos", "href": "/a-propos" }, | ||||
| 12 | + { "label": "Témoignages", "href": "/temoignages" }, | ||||
| 13 | + { "label": "Nous joindre", "href": "/nous-joindre" } | ||||
| 14 | + ], | ||||
| 15 | + "headerCta": { "label": "Contactez-nous", "href": "/nous-joindre" }, | ||||
| 16 | + "contact": { | ||||
| 17 | + "email": "", | ||||
| 18 | + "phone": "", | ||||
| 19 | + "address": "", | ||||
| 20 | + "region": "Québec, Canada" | ||||
| 21 | + }, | ||||
| 22 | + "social": { | ||||
| 23 | + "linkedin": "" | ||||
| 24 | + }, | ||||
| 25 | + "legalLinks": [ | ||||
| 26 | + { "label": "Confidentialité", "href": "/confidentialite" }, | ||||
| 27 | + { "label": "Mentions légales", "href": "/confidentialite#mentions-legales" } | ||||
| 28 | + ], | ||||
| 29 | + "seo": { | ||||
| 30 | + "siteUrl": "https://www.example.invalid", | ||||
| 31 | + "defaultTitle": "PHI Conseil — Stratégies, financement, réussite", | ||||
| 32 | + "titleTemplate": "%s | PHI Conseil", | ||||
| 33 | + "defaultDescription": "Votre partenaire en recherche de subventions, en rédaction de demandes et en reddition de comptes.", | ||||
| 34 | + "locale": "fr_CA" | ||||
| 35 | + } | ||||
| 36 | +} | ||||
added
docs/ASSET_REGISTER.md
+18 −0
@@ -0,0 +1,18 @@ | |||||
| 1 | +# Registre des actifs — PHI Conseil | ||||
| 2 | + | ||||
| 3 | +| Actif | Emplacement | Provenance | Licence | Statut | | ||||
| 4 | +| ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------- | ---------------------------------------------------------------------------- | | ||||
| 5 | +| Maquette de l'accueil | `references/phi-conseil-homepage.jpeg` | fournie par le client (IMG_1079.JPG, 1024 × 1536) | usage interne de référence | référence, non publiée | | ||||
| 6 | +| Logo (spirale dorée + rectangle d'or) | `src/components/graphics/Logo.tsx`, `src/lib/spiral.ts`, `public/logo-mark.svg` | **recréé en SVG** d'après la maquette (construction Fibonacci 1-1-2-3-5-8-13-21) | œuvre dérivée de l'identité du client | ⚠️ à valider par le client ; remplacer par le vectoriel officiel s'il existe | | ||||
| 7 | +| Favicon / icônes | `src/app/icon.svg`, `src/app/favicon.ico`, `src/app/apple-icon.png`, `public/icon-192.png`, `public/icon-512.png` | générés depuis le logo par `scripts/generate-icons.mts` | idem logo | dérivés du logo, à régénérer si le logo change | | ||||
| 8 | +| Image Open Graph | `src/app/opengraph-image.tsx` | générée au build (texte + spirale) | — | OK | | ||||
| 9 | +| Photo du hero (bureau, plante, tasse, ordinateur) | `src/components/graphics/Placeholders.tsx` (`DeskScene`) | **illustration vectorielle de remplacement** créée pour le projet ; la photo de la maquette n'a pas été reprise (droits inconnus) | créée pour le projet | ⚠️ placeholder — fournir une photo sous licence | | ||||
| 10 | +| Photo « À propos » (vase, feuillage, livres) | `src/components/graphics/Placeholders.tsx` (`VaseScene`) | idem | créée pour le projet | ⚠️ placeholder — fournir une photo sous licence | | ||||
| 11 | +| Motifs (vagues, feuillage, spirales décoratives) | `src/components/graphics/*` | créés pour le projet | — | OK | | ||||
| 12 | +| Icônes de services et d'interface | `src/components/ui/ServiceIcon.tsx`, `Icons.tsx` | dessinées pour le projet (trait 1,5) | — | OK | | ||||
| 13 | +| Police titres **EB Garamond** | `next/font/google` (auto-hébergée au build) | Georg Duffner / Octavio Pardo, Google Fonts | SIL Open Font License 1.1 | OK ; remplacer par la police officielle du client si elle existe | | ||||
| 14 | +| Police corps **Figtree** | `next/font/google` | Erik Kennedy, Google Fonts | SIL Open Font License 1.1 | OK | | ||||
| 15 | +| Témoignage « Marie-Claude Deschamps, Directrice générale » | `content/home.json` (`approved: false`) | texte de la maquette, **identité non vérifiée** | — | ⚠️ NON PUBLIÉ ; obtenir un avis réel et une autorisation écrite | | ||||
| 16 | +| Textes marketing (hero, services, à propos, bandeau) | `content/*.json` | repris mot pour mot de la maquette | client | à confirmer par le client | | ||||
| 17 | +| Textes juridiques (confidentialité, mentions légales) | `src/app/(site)/confidentialite/page.tsx`, `pages.json` | gabarit rédigé pour le projet, décrit les pratiques réelles du site | — | ⚠️ à faire valider (`privacy.reviewed=false`) | | ||||
| 18 | +| Coordonnées, LinkedIn | `content/site.json` | **vides** (non fournies) | — | à compléter par le client | | ||||
added
docs/CLIENT_GUIDE.md
+47 −0
@@ -0,0 +1,47 @@ | |||||
| 1 | +# Guide de gestion des contenus — PHI Conseil | ||||
| 2 | + | ||||
| 3 | +Tous les textes, services, avantages, témoignages, coordonnées et paramètres SEO se trouvent dans trois fichiers du dossier `content/`. Ils se modifient avec n'importe quel éditeur de texte (ou l'interface web du dépôt Git) ; aucune modification du code n'est nécessaire. À chaque mise à jour, le site est reconstruit et vérifié : une faute de structure (guillemet manquant, champ oublié) bloque la publication avec un message précis au lieu de casser le site. | ||||
| 4 | + | ||||
| 5 | +> Règle d'or : conservez les guillemets `"…"` et les virgules ; modifiez uniquement ce qui se trouve entre les guillemets, ou ajoutez/retirez des éléments complets dans les listes. | ||||
| 6 | + | ||||
| 7 | +## `content/site.json` — identité et coordonnées | ||||
| 8 | + | ||||
| 9 | +| Champ | Usage | | ||||
| 10 | +| -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | | ||||
| 11 | +| `brand.name`, `brand.tagline`, `brand.footerSignature`, `brand.legalName` | nom, signature de l'en-tête, phrase du pied de page, raison sociale du copyright | | ||||
| 12 | +| `navigation[]` | liens du menu (ordre = ordre affiché) | | ||||
| 13 | +| `headerCta` | bouton « Contactez-nous → » | | ||||
| 14 | +| `contact.email`, `contact.phone`, `contact.address`, `contact.region` | coordonnées ; **laissez `""` pour masquer** l'icône courriel / la ligne correspondante | | ||||
| 15 | +| `social.linkedin` | URL complète du profil LinkedIn ; `""` = icône masquée | | ||||
| 16 | +| `legalLinks[]` | liens juridiques du pied de page | | ||||
| 17 | +| `seo.siteUrl`, `seo.defaultTitle`, `seo.titleTemplate`, `seo.defaultDescription` | métadonnées par défaut | | ||||
| 18 | + | ||||
| 19 | +## `content/home.json` — page d'accueil | ||||
| 20 | + | ||||
| 21 | +- `hero.titleLines` : les lignes du grand titre (1 à 4). `hero.subtitle`, `hero.cta`. | ||||
| 22 | +- `hero.image.src` : chemin d'une photo placée dans `public/images/` (ex. `"/images/hero.jpg"`). Vide = illustration vectorielle de remplacement. Toujours renseigner `alt` (description pour les lecteurs d'écran). | ||||
| 23 | +- `services.items[]` : `slug` (identifiant URL, lettres minuscules et tirets), `icon` (`search`, `document-edit`, `chart`, `check-circle`), `title`, `summary` (accueil), `description` (page Services, facultatif). L'ordre du tableau = ordre d'affichage. | ||||
| 24 | +- `about` : `overtitle`, `title`, `paragraph`, `cta`, `benefits[]` (coches dorées), `image`. | ||||
| 25 | +- `testimonials.visible` : `true` pour afficher la section. **Un témoignage n'apparaît que si `approved: true`** (autorisation écrite de la personne citée). `previewOnly` permet d'afficher les exemples non approuvés uniquement sur un site de démonstration. | ||||
| 26 | +- `callToAction` : bandeau bleu final. | ||||
| 27 | +- Chaque section possède `visible` pour la masquer sans la supprimer. | ||||
| 28 | + | ||||
| 29 | +## `content/pages.json` — pages internes | ||||
| 30 | + | ||||
| 31 | +Titres, introductions, descriptions SEO des pages Services, À propos, Témoignages, Nous joindre (liste `subjects` du menu déroulant, texte de consentement, message de confirmation) et Confidentialité (`lastUpdated` au format AAAA-MM-JJ ; passez `reviewed` à `true` une fois le texte validé juridiquement pour retirer l'avertissement). | ||||
| 32 | + | ||||
| 33 | +## Images | ||||
| 34 | + | ||||
| 35 | +1. Préparer un JPEG/WebP de bonne qualité (hero : ≥ 1600 px de large ; À propos : format vertical ≥ 1000 × 1250 px). | ||||
| 36 | +2. Le déposer dans `public/images/`. | ||||
| 37 | +3. Renseigner `image.src` et `image.alt` dans `home.json`. | ||||
| 38 | +4. Noter la provenance et la licence dans `docs/ASSET_REGISTER.md`. | ||||
| 39 | + Le site convertit et redimensionne automatiquement (AVIF/WebP, tailles adaptées). | ||||
| 40 | + | ||||
| 41 | +## Logo et favicon | ||||
| 42 | + | ||||
| 43 | +Le logo actuel est une recréation SVG de la spirale dorée. Pour utiliser le logo officiel : remplacer le composant `src/components/graphics/Logo.tsx` par le SVG fourni, puis `pnpm icons` pour régénérer favicon et icônes (ou fournir directement `src/app/icon.svg`, `src/app/apple-icon.png`). | ||||
| 44 | + | ||||
| 45 | +## Publier une modification | ||||
| 46 | + | ||||
| 47 | +Sur le serveur d'hébergement : `pnpm build && pm2 restart phi-conseil` (voir `docs/DEPLOYMENT.md`), puis `git commit` pour conserver l'historique. Une erreur de contenu s'affiche au build sous la forme `Contenu invalide dans content/home.json : hero.subtitle : …`. | ||||
added
docs/DEPLOYMENT.md
+68 −0
@@ -0,0 +1,68 @@ | |||||
| 1 | +# Déploiement — PHI Conseil | ||||
| 2 | + | ||||
| 3 | +## Cible | ||||
| 4 | + | ||||
| 5 | +Application Node.js (`next start`) derrière un reverse proxy TLS. Aucune base de données. Le build est statique pour toutes les pages ; seule `POST /api/contact` s'exécute à la demande. | ||||
| 6 | + | ||||
| 7 | +| Besoin | Valeur | | ||||
| 8 | +| -------- | ---------------------------------------------------------------- | | ||||
| 9 | +| Node.js | ≥ 20 (testé 26) | | ||||
| 10 | +| RAM | ~150 Mo au repos | | ||||
| 11 | +| Port | `PORT` (défaut 3000) | | ||||
| 12 | +| Fichiers | `.next/`, `public/`, `content/`, `node_modules/`, `package.json` | | ||||
| 13 | + | ||||
| 14 | +## Variables d'environnement | ||||
| 15 | + | ||||
| 16 | +Voir `.env.example`. En production : | ||||
| 17 | + | ||||
| 18 | +- `NEXT_PUBLIC_SITE_URL` — URL publique (canonical, sitemap, OG). **Inlinée au build** : rebuild si elle change. | ||||
| 19 | +- `NEXT_PUBLIC_CONTENT_PREVIEW` — `0` en production. `1` affiche les témoignages de maquette avec un bandeau « exemple non vérifié » (sites de démonstration uniquement ; inlinée au build). | ||||
| 20 | +- `CONTACT_TRANSPORT=smtp` + `CONTACT_TO`, `CONTACT_FROM`, `SMTP_HOST`, `SMTP_PORT`, `SMTP_SECURE`, `SMTP_USER`, `SMTP_PASS` — sans ces valeurs le formulaire fonctionne mais **n'envoie aucun courriel** (`CONTACT_TRANSPORT=log`) et l'utilisateur en est informé dans l'écran de confirmation. | ||||
| 21 | +- `CONTACT_RATE_LIMIT` / `CONTACT_RATE_WINDOW_S` — anti-abus (défaut 5 / 600 s par IP). Le limiteur est en mémoire : valable pour une instance unique (PM2 `fork`). Pour plusieurs instances, brancher Redis dans `src/lib/security.ts`. | ||||
| 22 | + | ||||
| 23 | +Le proxy doit transmettre `X-Forwarded-For` (Caddy et nginx le font par défaut) pour que la limitation par IP soit correcte. | ||||
| 24 | + | ||||
| 25 | +## Procédure générique (VPS / serveur Node) | ||||
| 26 | + | ||||
| 27 | +```bash | ||||
| 28 | +git clone <dépôt> phi-conseil && cd phi-conseil | ||||
| 29 | +pnpm install --frozen-lockfile | ||||
| 30 | +cp .env.example .env.local && $EDITOR .env.local | ||||
| 31 | +pnpm build | ||||
| 32 | +pm2 start pnpm --name phi-conseil -- start -p 3000 # ou systemd | ||||
| 33 | +``` | ||||
| 34 | + | ||||
| 35 | +Reverse proxy (exemple Caddy) : `phiconseil.example { reverse_proxy 127.0.0.1:3000 }` — Caddy obtient et renouvelle le certificat TLS automatiquement. HSTS est déjà émis par l'application. | ||||
| 36 | + | ||||
| 37 | +### Vercel (alternative) | ||||
| 38 | + | ||||
| 39 | +Importer le dépôt, définir les variables d'environnement, framework Next.js détecté automatiquement. Le limiteur en mémoire n'est pas fiable en serverless : remplacer par Upstash/Redis ou le middleware de limitation du fournisseur. | ||||
| 40 | + | ||||
| 41 | +## Procédure MacLustr (`mld`) — site de démonstration test.spboucher.ai | ||||
| 42 | + | ||||
| 43 | +1. Manifeste `M1M32:~/dispatch/apps/phi-conseil.json` (dir `~/apps/phi-conseil`, PM2 `pnpm start -p 8520`, `tunnel.domain = test.spboucher.ai`, hooks `post_sync` : `pnpm install --frozen-lockfile && pnpm build`). | ||||
| 44 | +2. `mld stage ~/Desktop/phi-conseil phi-conseil` puis `mld deploy phi-conseil`. | ||||
| 45 | +3. DNS GoDaddy `A test.spboucher.ai → 51.161.112.61` (passerelle BHS64) — déjà en place ; `mld` pose la route Caddy et vérifie la santé publique. | ||||
| 46 | +4. Mises à jour : modifier **sur le nœud** (clone git), `pnpm build && pm2 restart phi-conseil`, puis `git commit/push` depuis le nœud (règle remote-first MacLustr). | ||||
| 47 | + | ||||
| 48 | +## Sauvegardes et restauration | ||||
| 49 | + | ||||
| 50 | +- Le code et le contenu (`content/*.json`) sont versionnés dans Git : la sauvegarde est le dépôt distant. Restaurer = `git checkout <tag>` + `pnpm install && pnpm build`. | ||||
| 51 | +- Les messages du formulaire ne sont pas stockés par le site : ils vivent dans la boîte courriel destinataire (sauvegarde relevant du fournisseur de messagerie). | ||||
| 52 | +- Secrets (`.env.local`) : conserver une copie chiffrée hors du dépôt (gestionnaire de mots de passe). | ||||
| 53 | + | ||||
| 54 | +## Retour arrière | ||||
| 55 | + | ||||
| 56 | +1. `git log` → identifier la dernière version saine (chaque mise en production est taguée `vX.Y.Z`). | ||||
| 57 | +2. `git checkout vX.Y.Z && pnpm install --frozen-lockfile && pnpm build && pm2 restart phi-conseil`. | ||||
| 58 | +3. Vérifier `GET /` (200) et `GET /api/contact` (405 attendu) ; sur MacLustr, `mld status --live phi-conseil`. | ||||
| 59 | + | ||||
| 60 | +## Surveillance minimale | ||||
| 61 | + | ||||
| 62 | +- Santé : `GET /` doit répondre 200 (healthcheck PM2/mld). | ||||
| 63 | +- Journaux : `pm2 logs phi-conseil` — les lignes `[contact]` ne contiennent qu'une empreinte d'IP, jamais le contenu des messages. | ||||
| 64 | +- Erreurs de rendu : digest côté serveur (`console.error`) corrélable à l'écran d'erreur client. Un service d'erreurs (Sentry…) peut être ajouté dans `src/app/error.tsx` et `instrumentation.ts` ; non inclus par défaut pour éviter tout traceur non consenti. | ||||
| 65 | + | ||||
| 66 | +## Mise à jour des dépendances | ||||
| 67 | + | ||||
| 68 | +`pnpm outdated` puis `pnpm update --latest` en suivant les notes de migration de Next.js ; relancer `pnpm check` et `pnpm test:e2e` avant mise en production. Audit : `pnpm audit`. | ||||
added
docs/IMPLEMENTATION_PLAN.md
+87 −0
@@ -0,0 +1,87 @@ | |||||
| 1 | +# Plan d'implémentation — PHI Conseil | ||||
| 2 | + | ||||
| 3 | +Date : 2026-11-08. Référence visuelle : `references/phi-conseil-homepage.jpeg` (1024 × 1536 px, capture desktop fournie par le client). | ||||
| 4 | + | ||||
| 5 | +## 1. Audit initial | ||||
| 6 | + | ||||
| 7 | +| Élément | État constaté | | ||||
| 8 | +| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ||||
| 9 | +| Dépôt | Vide à l'origine (créé aujourd'hui). Scaffold `create-next-app` 16.4.5, TypeScript strict, Tailwind 4, ESLint 9, pnpm 11. | | ||||
| 10 | +| Capture | Présente et inspectée. Couleurs échantillonnées par région (médiane / mode / moyenne des pixels sombres) — voir `src/styles/tokens.css`. | | ||||
| 11 | +| Actifs client | **Aucun** fichier original fourni : pas de logo vectoriel, pas de photos sous licence, pas de polices, pas de coordonnées, pas de textes juridiques, pas de témoignage autorisé. | | ||||
| 12 | +| Hébergement | Non précisé. Cible par défaut : Node 20+ (`next start` derrière un reverse proxy TLS). Un déploiement sur le cluster MacLustr (`mld`) ou sur Vercel est documenté dans `docs/DEPLOYMENT.md`. | | ||||
| 13 | + | ||||
| 14 | +### Inconnues bloquantes (à valider par le client) | ||||
| 15 | + | ||||
| 16 | +1. Logo vectoriel officiel (sinon le SVG recréé `src/components/graphics/Logo.tsx` doit être approuvé). | ||||
| 17 | +2. Photos sous licence pour le hero et la section À propos (les illustrations livrées sont des **placeholders vectoriels** dans la palette, marqués comme tels dans `docs/ASSET_REGISTER.md`). | ||||
| 18 | +3. Coordonnées réelles (courriel, téléphone, adresse, LinkedIn) : laissées vides, les icônes/liens correspondants sont masqués tant que les champs du contenu sont vides. | ||||
| 19 | +4. Témoignage(s) autorisé(s) : la section est **masquée en production** (`testimonials.visible=false`) ; le texte de la maquette est conservé uniquement comme exemple de prévisualisation marqué « non vérifié ». | ||||
| 20 | +5. Textes juridiques (confidentialité, mentions légales) : gabarit fourni, à faire valider par un conseiller juridique. | ||||
| 21 | +6. Fournisseur de messagerie transactionnelle (SMTP) et adresses expéditeur/destinataire. | ||||
| 22 | +7. Choix CMS : voir §4. | ||||
| 23 | + | ||||
| 24 | +## 2. Architecture | ||||
| 25 | + | ||||
| 26 | +``` | ||||
| 27 | +content/ ← contenu administrable (JSON validé par Zod au build et à l'exécution) | ||||
| 28 | + site.json ← identité, navigation, coordonnées, liens sociaux, SEO par défaut | ||||
| 29 | + home.json ← hero, services, à-propos, témoignages, bandeau, visibilité des sections | ||||
| 30 | + pages.json ← pages internes (services, à propos, témoignages, nous joindre, confidentialité) | ||||
| 31 | +src/ | ||||
| 32 | + app/ | ||||
| 33 | + layout.tsx ← polices, métadonnées par défaut, Header/Footer | ||||
| 34 | + (site)/page.tsx ← accueil (7 sections) | ||||
| 35 | + (site)/services|a-propos|temoignages|nous-joindre|confidentialite/page.tsx | ||||
| 36 | + api/contact/route.ts ← POST : validation Zod, honeypot, limitation de débit, envoi SMTP | ||||
| 37 | + sitemap.ts, robots.ts, icon.svg, opengraph-image.tsx | ||||
| 38 | + components/ | ||||
| 39 | + layout/ Header, MobileNav, Footer | ||||
| 40 | + sections/ Hero, Services, About, Testimonials, CallToAction | ||||
| 41 | + graphics/ Logo, GoldenSpiral, WaveDivider, BotanicalMotif, placeholders photo | ||||
| 42 | + ui/ Button, Container, SectionTitle, Icon… | ||||
| 43 | + forms/ ContactForm (client) | ||||
| 44 | + lib/ content.ts (chargeur + schémas), env.ts, validation.ts, security.ts (rate limit, sanitisation), mail.ts, seo.ts | ||||
| 45 | + styles/ tokens.css (design tokens), globals.css | ||||
| 46 | +tests/ | ||||
| 47 | + unit/ Vitest + Testing Library (schémas, validation, rate limit, composants) | ||||
| 48 | + e2e/ Playwright (navigation, menu mobile, formulaire, liens, axe, captures) | ||||
| 49 | +docs/ IMPLEMENTATION_PLAN, DEPLOYMENT, CLIENT_GUIDE, QA_REPORT, ASSET_REGISTER | ||||
| 50 | +``` | ||||
| 51 | + | ||||
| 52 | +## 3. Design system | ||||
| 53 | + | ||||
| 54 | +- Tokens CSS (`src/styles/tokens.css`) : couleurs échantillonnées, échelle typographique `clamp()`, espacements, rayons, ombres, mouvement. Exposés à Tailwind 4 via `@theme`. | ||||
| 55 | +- Titres : **EB Garamond** (OFL) ; corps : **Figtree** (OFL). Chargées par `next/font/google` (auto-hébergées au build, `display: swap`). À remplacer par les polices officielles du client si elles existent. | ||||
| 56 | +- Motifs : spirale de Fibonacci (carrés + arcs de quart de cercle, ratio 1,618 approché par la suite 1-1-2-3-5-8-13-21), vagues, feuillage — tous en SVG inline. | ||||
| 57 | + | ||||
| 58 | +## 4. Contenu administrable — décision | ||||
| 59 | + | ||||
| 60 | +Le cahier des charges recommande un CMS maintenu (Payload / Sanity) tout en demandant d'éviter de surdimensionner si une solution statique convient et de **demander validation**. | ||||
| 61 | + | ||||
| 62 | +Choix livré : **contenu structuré en fichiers JSON versionnés** (`content/*.json`), validé par Zod, modifiable sans toucher au code applicatif, avec guide client. Raisons : aucun hébergement de base de données ni de compte tiers à gérer, pas de surface d'authentification à sécuriser, sauvegarde = historique Git, coût nul, déploiement statique possible. | ||||
| 63 | + | ||||
| 64 | +Compromis documentés : l'édition exige un éditeur de texte ou l'interface web Git ; pas d'aperçu WYSIWYG ni de gestion de médias par glisser-déposer. Chemin d'évolution : `src/lib/content.ts` est l'unique point d'accès au contenu ; brancher Payload (SQLite/Postgres, intégré à Next) ou Sanity consiste à remplacer ce module en conservant les schémas Zod. **Décision à valider par le client.** | ||||
| 65 | + | ||||
| 66 | +## 5. Étapes | ||||
| 67 | + | ||||
| 68 | +1. Audit + plan (ce document). ✔ | ||||
| 69 | +2. Design system : tokens, polices, UI de base, logo et motifs SVG. | ||||
| 70 | +3. Accueil fidèle : 7 sections, comparaison Playwright à 1024 px (largeur de la capture) puis 1440 px. | ||||
| 71 | +4. Pages internes + chargeur de contenu + visibilité des sections. | ||||
| 72 | +5. Formulaire de contact + API + messagerie SMTP configurable + protection antispam. | ||||
| 73 | +6. Validation : lint, typecheck, Vitest, Playwright (6 largeurs, axe), build de production. | ||||
| 74 | +7. Livraison : README, .env.example, docs/DEPLOYMENT, CLIENT_GUIDE, QA_REPORT, ASSET_REGISTER. | ||||
| 75 | + | ||||
| 76 | +## 6. Critères d'acceptation par section (checklist) | ||||
| 77 | + | ||||
| 78 | +- [ ] En-tête : logo + nom + signature, 5 liens, CTA bleu arrondi « Contactez-nous → », lien actif souligné, menu mobile accessible, en-tête collant sobre. | ||||
| 79 | +- [ ] Hero : fond ivoire, H1 serif 3 lignes, sous-titre, bouton bleu, illustration à droite avec spirale dorée superposée, vagues ivoire/bleu pâle en bas. | ||||
| 80 | +- [ ] Services : titre centré entre filets dorés, 4 cartes (disque bleu-gris + icône au trait), textes exacts. | ||||
| 81 | +- [ ] À propos : image verticale à gauche avec courbe ivoire, surtitre doré, H2, paragraphe, bouton contour, 4 bénéfices avec coches dorées séparés par un filet vertical. | ||||
| 82 | +- [ ] Témoignages : encadré bleu pâle arrondi, guillemets dorés, serif italique, pagination ; slider accessible ; masqué en prod sans avis validé. | ||||
| 83 | +- [ ] Bandeau : fond bleu ardoise, titre blanc, bouton ivoire, spirale + feuillage dorés à droite. | ||||
| 84 | +- [ ] Pied de page : logo + nom, liens, icônes LinkedIn/courriel (si fournis), copyright dynamique, signature, liens légaux. | ||||
| 85 | +- [ ] Responsive 360/390/768/1024/1440/1920 sans débordement horizontal. | ||||
| 86 | +- [ ] Formulaire : validation client/serveur, honeypot, rate limit, SMTP configurable, états succès/erreur, tests. | ||||
| 87 | +- [ ] Lint, typecheck, tests, build, axe : verts ou écarts documentés dans `docs/QA_REPORT.md`. | ||||
added
docs/screenshots/.gitkeep
+0 −0
modified
next.config.ts
+9 −1
@@ -1,9 +1,17 @@ | |||||
| 1 | import type { NextConfig } from "next"; | 1 | import type { NextConfig } from "next"; | ||
| 2 | +import { securityHeaders } from "./src/lib/security"; | ||||
| 2 | 3 | ||||
| 3 | const nextConfig: NextConfig = { | 4 | const nextConfig: NextConfig = { | ||
| 4 | − /* config options here */ | ||||
| 5 | cacheComponents: true, | 5 | cacheComponents: true, | ||
| 6 | partialPrefetching: true, | 6 | partialPrefetching: true, | ||
| 7 | + poweredByHeader: false, | ||||
| 8 | + reactStrictMode: true, | ||||
| 9 | + images: { | ||||
| 10 | + formats: ["image/avif", "image/webp"], | ||||
| 11 | + }, | ||||
| 12 | + async headers() { | ||||
| 13 | + return [{ source: "/(.*)", headers: securityHeaders }]; | ||||
| 14 | + }, | ||||
| 7 | turbopack: { | 15 | turbopack: { | ||
| 8 | rules: { | 16 | rules: { | ||
| 9 | "*.css": { | 17 | "*.css": { | ||
modified
package.json
+27 −3
@@ -6,22 +6,46 @@ | |||||
| 6 | "dev": "next dev", | 6 | "dev": "next dev", | ||
| 7 | "build": "next build", | 7 | "build": "next build", | ||
| 8 | "start": "next start", | 8 | "start": "next start", | ||
| 9 | − "lint": "eslint" | 9 | + "lint": "eslint", | ||
| 10 | + "typecheck": "tsc --noEmit", | ||||
| 11 | + "format": "prettier --write .", | ||||
| 12 | + "format:check": "prettier --check .", | ||||
| 13 | + "test": "vitest run", | ||||
| 14 | + "test:watch": "vitest", | ||||
| 15 | + "test:e2e": "playwright test", | ||||
| 16 | + "icons": "node --experimental-strip-types scripts/generate-icons.mts", | ||||
| 17 | + "check": "pnpm lint && pnpm typecheck && pnpm test && pnpm build" | ||||
| 10 | }, | 18 | }, | ||
| 11 | "dependencies": { | 19 | "dependencies": { | ||
| 20 | + "@hookform/resolvers": "^7.0.3", | ||||
| 12 | "next": "16.4.5", | 21 | "next": "16.4.5", | ||
| 22 | + "nodemailer": "^10.3.1", | ||||
| 13 | "react": "19.3.0", | 23 | "react": "19.3.0", | ||
| 14 | − "react-dom": "19.3.0" | 24 | + "react-dom": "19.3.0", | ||
| 25 | + "react-hook-form": "^7.91.0", | ||||
| 26 | + "sharp": "^0.35.5", | ||||
| 27 | + "zod": "^4.6.7" | ||||
| 15 | }, | 28 | }, | ||
| 16 | "devDependencies": { | 29 | "devDependencies": { | ||
| 30 | + "@axe-core/playwright": "^4.14.0", | ||||
| 31 | + "@playwright/test": "^1.65.0", | ||||
| 17 | "@tailwindcss/turbopack": "^4", | 32 | "@tailwindcss/turbopack": "^4", | ||
| 33 | + "@testing-library/jest-dom": "^7.0.1", | ||||
| 34 | + "@testing-library/react": "^16.3.3", | ||||
| 35 | + "@testing-library/user-event": "^14.6.8", | ||||
| 18 | "@types/node": "^20", | 36 | "@types/node": "^20", | ||
| 37 | + "@types/nodemailer": "^8.0.2", | ||||
| 19 | "@types/react": "^19", | 38 | "@types/react": "^19", | ||
| 20 | "@types/react-dom": "^19", | 39 | "@types/react-dom": "^19", | ||
| 40 | + "@vitejs/plugin-react": "^6.2.1", | ||||
| 21 | "eslint": "^9", | 41 | "eslint": "^9", | ||
| 22 | "eslint-config-next": "16.4.5", | 42 | "eslint-config-next": "16.4.5", | ||
| 43 | + "jsdom": "^31.0.0", | ||||
| 44 | + "prettier": "^3.9.10", | ||||
| 45 | + "prettier-plugin-tailwindcss": "^0.8.1", | ||||
| 23 | "tailwindcss": "^4", | 46 | "tailwindcss": "^4", | ||
| 24 | − "typescript": "^5" | 47 | + "typescript": "^5", | ||
| 48 | + "vitest": "^5.1.0" | ||||
| 25 | }, | 49 | }, | ||
| 26 | "packageManager": "pnpm@11.1.2" | 50 | "packageManager": "pnpm@11.1.2" | ||
| 27 | } | 51 | } | ||
added
playwright.config.ts
+26 −0
@@ -0,0 +1,26 @@ | |||||
| 1 | +import { defineConfig, devices } from "@playwright/test"; | ||||
| 2 | + | ||||
| 3 | +const PORT = process.env.PW_PORT ?? "3100"; | ||||
| 4 | +const baseURL = `http://127.0.0.1:${PORT}`; | ||||
| 5 | + | ||||
| 6 | +export default defineConfig({ | ||||
| 7 | + testDir: "./tests/e2e", | ||||
| 8 | + fullyParallel: true, | ||||
| 9 | + retries: process.env.CI ? 1 : 0, | ||||
| 10 | + reporter: process.env.CI ? [["list"], ["html", { open: "never" }]] : "list", | ||||
| 11 | + use: { baseURL, trace: "retain-on-failure", locale: "fr-CA" }, | ||||
| 12 | + webServer: { | ||||
| 13 | + command: `pnpm start -p ${PORT}`, | ||||
| 14 | + url: baseURL, | ||||
| 15 | + reuseExistingServer: !process.env.CI, | ||||
| 16 | + timeout: 120_000, | ||||
| 17 | + env: { CONTACT_TRANSPORT: "log", NEXT_PUBLIC_SITE_URL: baseURL }, | ||||
| 18 | + }, | ||||
| 19 | + projects: [ | ||||
| 20 | + { | ||||
| 21 | + name: "desktop", | ||||
| 22 | + use: { ...devices["Desktop Chrome"], viewport: { width: 1440, height: 900 } }, | ||||
| 23 | + }, | ||||
| 24 | + { name: "mobile", use: { ...devices["iPhone 13"] } }, | ||||
| 25 | + ], | ||||
| 26 | +}); | ||||
modified
pnpm-lock.yaml
+1098 −5
Large diff (1,740 lines) is not loaded by default to keep the page fast.Load diff
deleted
public/file.svg
+0 −1
Large diff (2 lines) is not loaded by default to keep the page fast.Load diff
deleted
public/globe.svg
+0 −1
Large diff (2 lines) is not loaded by default to keep the page fast.Load diff
added
public/icon-192.png
+0 −0
Binary file not shown.
added
public/icon-512.png
+0 −0
Binary file not shown.
added
public/logo-mark.svg
+4 −0
Large diff (5 lines) is not loaded by default to keep the page fast.Load diff
deleted
public/next.svg
+0 −1
Large diff (2 lines) is not loaded by default to keep the page fast.Load diff
deleted
public/vercel.svg
+0 −1
Large diff (2 lines) is not loaded by default to keep the page fast.Load diff
deleted
public/window.svg
+0 −1
Large diff (2 lines) is not loaded by default to keep the page fast.Load diff
added
references/phi-conseil-homepage.jpeg
+0 −0
Binary file not shown.
added
scripts/generate-icons.mts
+58 −0
Large diff (58 lines) is not loaded by default to keep the page fast.Load diff
added
scripts/overflow.mjs
+18 −0
Large diff (18 lines) is not loaded by default to keep the page fast.Load diff
added
scripts/shot.mjs
+9 −0
Large diff (9 lines) is not loaded by default to keep the page fast.Load diff
added
src/app/(site)/a-propos/page.tsx
+65 −0
Large diff (65 lines) is not loaded by default to keep the page fast.Load diff
added
src/app/(site)/confidentialite/page.tsx
+99 −0
Large diff (99 lines) is not loaded by default to keep the page fast.Load diff
added
src/app/(site)/nous-joindre/page.tsx
+77 −0
Large diff (77 lines) is not loaded by default to keep the page fast.Load diff
added
src/app/(site)/page.tsx
+22 −0
Large diff (22 lines) is not loaded by default to keep the page fast.Load diff
added
src/app/(site)/services/page.tsx
+52 −0
Large diff (52 lines) is not loaded by default to keep the page fast.Load diff
added
src/app/(site)/temoignages/page.tsx
+50 −0
Large diff (50 lines) is not loaded by default to keep the page fast.Load diff
added
src/app/api/contact/route.ts
+96 −0
Large diff (96 lines) is not loaded by default to keep the page fast.Load diff
added
src/app/apple-icon.png
+0 −0
Binary file not shown.
added
src/app/error.tsx
+35 −0
Large diff (35 lines) is not loaded by default to keep the page fast.Load diff
modified
src/app/favicon.ico
+0 −0
Binary file not shown.
modified
src/app/globals.css
+153 −16
Large diff (179 lines) is not loaded by default to keep the page fast.Load diff
added
src/app/icon.svg
+4 −0
Large diff (5 lines) is not loaded by default to keep the page fast.Load diff
modified
src/app/layout.tsx
+48 −16
Large diff (77 lines) is not loaded by default to keep the page fast.Load diff
added
src/app/manifest.ts
+18 −0
Large diff (18 lines) is not loaded by default to keep the page fast.Load diff
added
src/app/not-found.tsx
+21 −0
Large diff (21 lines) is not loaded by default to keep the page fast.Load diff
added
src/app/opengraph-image.tsx
+65 −0
Large diff (65 lines) is not loaded by default to keep the page fast.Load diff
deleted
src/app/page.tsx
+0 −69
Large diff (69 lines) is not loaded by default to keep the page fast.Load diff
added
src/app/robots.ts
+9 −0
Large diff (9 lines) is not loaded by default to keep the page fast.Load diff
added
src/app/sitemap.ts
+19 −0
Large diff (19 lines) is not loaded by default to keep the page fast.Load diff
added
src/components/forms/ContactForm.tsx
+275 −0
Large diff (275 lines) is not loaded by default to keep the page fast.Load diff
added
src/components/graphics/BotanicalMotif.tsx
+34 −0
Large diff (34 lines) is not loaded by default to keep the page fast.Load diff
added
src/components/graphics/GoldenSpiral.tsx
+66 −0
Large diff (66 lines) is not loaded by default to keep the page fast.Load diff
added
src/components/graphics/Logo.tsx
+46 −0
Large diff (46 lines) is not loaded by default to keep the page fast.Load diff
added
src/components/graphics/Placeholders.tsx
+168 −0
Large diff (168 lines) is not loaded by default to keep the page fast.Load diff
added
src/components/graphics/WaveDivider.tsx
+28 −0
Large diff (28 lines) is not loaded by default to keep the page fast.Load diff
added
src/components/layout/Footer.tsx
+102 −0
Large diff (102 lines) is not loaded by default to keep the page fast.Load diff
added
src/components/layout/Header.tsx
+30 −0
Large diff (30 lines) is not loaded by default to keep the page fast.Load diff
added
src/components/layout/MobileNav.tsx
+114 −0
Large diff (114 lines) is not loaded by default to keep the page fast.Load diff
added
src/components/layout/NavLinks.tsx
+47 −0
Large diff (47 lines) is not loaded by default to keep the page fast.Load diff
added
src/components/sections/About.tsx
+78 −0
Large diff (78 lines) is not loaded by default to keep the page fast.Load diff
added
src/components/sections/CallToAction.tsx
+46 −0
Large diff (46 lines) is not loaded by default to keep the page fast.Load diff
added
src/components/sections/Hero.tsx
+55 −0
Large diff (55 lines) is not loaded by default to keep the page fast.Load diff
added
src/components/sections/Services.tsx
+34 −0
Large diff (34 lines) is not loaded by default to keep the page fast.Load diff
added
src/components/sections/TestimonialSlider.tsx
+106 −0
Large diff (106 lines) is not loaded by default to keep the page fast.Load diff
added
src/components/sections/Testimonials.tsx
+27 −0
Large diff (27 lines) is not loaded by default to keep the page fast.Load diff
added
src/components/ui/Button.tsx
+88 −0
Large diff (88 lines) is not loaded by default to keep the page fast.Load diff
added
src/components/ui/Icons.tsx
+63 −0
Large diff (63 lines) is not loaded by default to keep the page fast.Load diff
added
src/components/ui/SectionTitle.tsx
+22 −0
Large diff (22 lines) is not loaded by default to keep the page fast.Load diff
added
src/components/ui/ServiceIcon.tsx
+57 −0
Large diff (57 lines) is not loaded by default to keep the page fast.Load diff
added
src/lib/content.ts
+184 −0
Large diff (184 lines) is not loaded by default to keep the page fast.Load diff
added
src/lib/env.ts
+62 −0
Large diff (62 lines) is not loaded by default to keep the page fast.Load diff
added
src/lib/mail.ts
+85 −0
Large diff (85 lines) is not loaded by default to keep the page fast.Load diff
added
src/lib/security.ts
+93 −0
Large diff (93 lines) is not loaded by default to keep the page fast.Load diff
added
src/lib/seo.ts
+39 −0
Large diff (39 lines) is not loaded by default to keep the page fast.Load diff
added
src/lib/spiral.ts
+81 −0
Large diff (81 lines) is not loaded by default to keep the page fast.Load diff
added
src/lib/time.ts
+8 −0
Large diff (8 lines) is not loaded by default to keep the page fast.Load diff
added
src/lib/validation.ts
+64 −0
Large diff (64 lines) is not loaded by default to keep the page fast.Load diff
added
src/styles/tokens.css
+100 −0
Large diff (100 lines) is not loaded by default to keep the page fast.Load diff
added
tests/e2e/a11y.spec.ts
+25 −0
Large diff (25 lines) is not loaded by default to keep the page fast.Load diff
added
tests/e2e/contact.spec.ts
+40 −0
Large diff (40 lines) is not loaded by default to keep the page fast.Load diff
added
tests/e2e/mobile-menu.spec.ts
+37 −0
Large diff (37 lines) is not loaded by default to keep the page fast.Load diff
added
tests/e2e/navigation.spec.ts
+85 −0
Large diff (85 lines) is not loaded by default to keep the page fast.Load diff
added
tests/e2e/screenshots.spec.ts
+22 −0
Large diff (22 lines) is not loaded by default to keep the page fast.Load diff
added
tests/unit/components.test.tsx
+43 −0
Large diff (43 lines) is not loaded by default to keep the page fast.Load diff
added
tests/unit/contact-route.test.ts
+108 −0
Large diff (108 lines) is not loaded by default to keep the page fast.Load diff
added
tests/unit/content.test.ts
+39 −0
Large diff (39 lines) is not loaded by default to keep the page fast.Load diff
added
tests/unit/security.test.ts
+51 −0
Large diff (51 lines) is not loaded by default to keep the page fast.Load diff
added
tests/unit/setup.ts
+20 −0
Large diff (20 lines) is not loaded by default to keep the page fast.Load diff
added
tests/unit/validation.test.ts
+47 −0
Large diff (47 lines) is not loaded by default to keep the page fast.Load diff
modified
tsconfig.json
+3 −2
Large diff (15 lines) is not loaded by default to keep the page fast.Load diff
added
vitest.config.mts
+14 −0
Large diff (15 lines) is not loaded by default to keep the page fast.Load diff