✅ Phase 1 : Configuration & Fondations - TERMINÉE
Tout est prêt pour commencer le développement des composants !
pnpm install
Si vous n’avez pas pnpm :
npm install -g pnpm
Créez un fichier .env.local à la racine avec ce contenu :
# Resend (Email Service) - Laisser vide pour l'instant
RESEND_API_KEY=
# reCAPTCHA - Laisser vide pour l'instant
RECAPTCHA_SECRET_KEY=
NEXT_PUBLIC_RECAPTCHA_SITE_KEY=
# Google Analytics - Optionnel
NEXT_PUBLIC_GA_ID=
# Base URL
NEXT_PUBLIC_BASE_URL=http://localhost:3000
pnpm dev
Ouvrez http://localhost:3000
Créez components/layout/Header.tsx :
'use client';
import { useState, useEffect } from 'react';
import { Menu, X } from 'lucide-react';
import { NAV_SECTIONS } from '@/lib/constants';
import { scrollToSection } from '@/lib/utils';
import { cn } from '@/lib/utils';
export default function Header() {
const [isScrolled, setIsScrolled] = useState(false);
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const [activeSection, setActiveSection] = useState('accueil');
// Détection du scroll pour header sticky
useEffect(() => {
const handleScroll = () => {
setIsScrolled(window.scrollY > 50);
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
const handleNavClick = (sectionId: string) => {
scrollToSection(sectionId);
setIsMobileMenuOpen(false);
};
return (
<header
className={cn(
'fixed top-0 left-0 right-0 z-50 transition-all duration-300',
isScrolled
? 'bg-white shadow-md py-4'
: 'bg-white/95 backdrop-blur-sm py-6'
)}
>
<div className="container-custom flex items-center justify-between">
{/* Logo */}
<div className="text-2xl font-bold text-brand">
Arbres du Lauragais
</div>
{/* Navigation Desktop */}
<nav className="hidden md:flex items-center gap-8">
{NAV_SECTIONS.map((section) => (
<button
key={section.id}
onClick={() => handleNavClick(section.id)}
className={cn(
'text-text hover:text-accent transition-colors relative',
activeSection === section.id && 'text-accent font-medium'
)}
>
{section.label}
{activeSection === section.id && (
<span className="absolute -bottom-1 left-0 right-0 h-0.5 bg-accent" />
)}
</button>
))}
</nav>
{/* Menu Mobile */}
<button
className="md:hidden text-brand"
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
aria-label="Toggle menu"
>
{isMobileMenuOpen ? <X size={24} /> : <Menu size={24} />}
</button>
</div>
{/* Menu Mobile Overlay */}
{isMobileMenuOpen && (
<div className="md:hidden fixed inset-0 top-[72px] bg-white z-40">
<nav className="flex flex-col items-center gap-6 py-8">
{NAV_SECTIONS.map((section) => (
<button
key={section.id}
onClick={() => handleNavClick(section.id)}
className="text-xl text-text hover:text-accent transition-colors"
>
{section.label}
</button>
))}
</nav>
</div>
)}
</header>
);
}
Modifiez app/layout.tsx :
import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import './globals.css';
import Header from '@/components/layout/Header';
const inter = Inter({
subsets: ['latin'],
variable: '--font-inter',
display: 'swap',
});
export const metadata: Metadata = {
// ... metadata existante
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="fr" className={inter.variable}>
<body>
<Header />
<main className="pt-20">{children}</main>
</body>
</html>
);
}
pnpm devcomponents/layout/Footer.tsximport { Phone, Mail, MapPin, Facebook, Instagram } from 'lucide-react';
import { CONTACT_INFO, INTERVENTION_ZONE, SOCIAL_LINKS } from '@/lib/constants';
export default function Footer() {
return (
<footer className="bg-brand text-white py-12">
<div className="container-custom">
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
{/* Coordonnées */}
<div>
<h3 className="text-xl font-semibold mb-4">Contact</h3>
<div className="space-y-3">
<a
href={`tel:${CONTACT_INFO.phone}`}
className="flex items-center gap-2 hover:text-accent transition-colors"
>
<Phone size={18} />
{CONTACT_INFO.phone}
</a>
<a
href={`mailto:${CONTACT_INFO.email}`}
className="flex items-center gap-2 hover:text-accent transition-colors"
>
<Mail size={18} />
{CONTACT_INFO.email}
</a>
<div className="flex items-center gap-2">
<MapPin size={18} />
{CONTACT_INFO.address}
</div>
</div>
</div>
{/* Zone d'intervention */}
<div>
<h3 className="text-xl font-semibold mb-4">Zone d'intervention</h3>
<p className="mb-2">{INTERVENTION_ZONE.description}</p>
<p className="text-sm">
{INTERVENTION_ZONE.cities.join(' • ')}
</p>
<p className="mt-4 text-sm">{CONTACT_INFO.hours}</p>
</div>
{/* Liens */}
<div>
<h3 className="text-xl font-semibold mb-4">Suivez-nous</h3>
<div className="flex gap-4 mb-6">
<a
href={SOCIAL_LINKS.facebook}
target="_blank"
rel="noopener noreferrer"
className="hover:text-accent transition-colors"
aria-label="Facebook"
>
<Facebook size={24} />
</a>
<a
href={SOCIAL_LINKS.instagram}
target="_blank"
rel="noopener noreferrer"
className="hover:text-accent transition-colors"
aria-label="Instagram"
>
<Instagram size={24} />
</a>
</div>
<a
href="/mentions-legales"
className="text-sm hover:text-accent transition-colors underline"
>
Mentions légales
</a>
</div>
</div>
<div className="mt-8 pt-8 border-t border-white/20 text-center text-sm">
<p>© {new Date().getFullYear()} Arbres du Lauragais Élagage - Tous droits réservés</p>
</div>
</div>
</footer>
);
}
Modifiez app/layout.tsx :
import Header from '@/components/layout/Header';
import Footer from '@/components/layout/Footer';
export default function RootLayout({ children }) {
return (
<html lang="fr" className={inter.variable}>
<body>
<Header />
<main className="pt-20">{children}</main>
<Footer />
</body>
</html>
);
}
components/sections/Hero.tsx'use client';
import { Phone } from 'lucide-react';
import Button from '@/components/ui/Button';
import { scrollToSection } from '@/lib/utils';
export default function Hero() {
return (
<section
id="accueil"
className="relative min-h-screen flex items-center justify-center bg-gradient-to-br from-brand to-accent"
>
{/* Overlay pour lisibilité */}
<div className="absolute inset-0 bg-black/30" />
{/* Contenu */}
<div className="relative z-10 container-custom text-center text-white">
<h1 className="text-4xl md:text-6xl font-bold mb-6 animate-fade-in">
Votre expert élagage dans le Lauragais
</h1>
<p className="text-xl md:text-2xl mb-8 animate-fade-in">
Professionnel certifié - Interventions sécurisées
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center animate-fade-in">
<Button
variant="primary"
size="lg"
onClick={() => window.open('tel:06XXXXXXXX')}
>
<Phone className="mr-2" size={20} />
Appeler
</Button>
<Button
variant="secondary"
size="lg"
onClick={() => scrollToSection('contact')}
>
📋 Devis gratuit
</Button>
</div>
<button
onClick={() => scrollToSection('prestations')}
className="mt-12 text-white hover:text-accent transition-colors animate-bounce"
>
Découvrir mes services ↓
</button>
</div>
</section>
);
}
Modifiez app/page.tsx :
import Hero from '@/components/sections/Hero';
export default function HomePage() {
return (
<>
<Hero />
{/* Les autres sections viendront ici */}
</>
);
}
.documentation/INDEX.md - Vue d’ensemble de toute la doc.documentation/GETTING-STARTED.md - Guide complet de démarrage.documentation/BEST-PRACTICES.md - Bonnes pratiques à suivre.documentation/ROADMAP.md - Feuille de route détailléeButton - Boutons avec variantsCard - Cartes avec hoverInput - Champs de formulaireTextarea - Zone de textecn() - Fusion de classes TailwindscrollToSection() - Scroll doux vers sectionformatPhoneNumber() - Formater numéro de téléphoneVoir .documentation/ROADMAP.md pour les détails complets.
.documentation/ en cas de doute.documentation/BEST-PRACTICES.md.documentation/INDEX.md.documentation/GETTING-STARTED.md.documentation/BEST-PRACTICES.mdVous êtes prêt à développer ! 🚀
Commencez par créer le Header, puis le Footer, puis la section Hero.
Bon développement ! 🌳