# 🛍️ Luxe Shop — Documentation technique complète > **E-commerce fullstack** : Laravel 12 (API) + React 19 (SPA) + Stripe Checkout > Projet réalisé en février 2026 --- ## 📑 Table des matières 1. [Présentation du projet](#-présentation-du-projet) 2. [Stack technique](#-stack-technique) 3. [Architecture du projet](#-architecture-du-projet) 4. [Prérequis](#-prérequis) 5. [Installation Backend (Laravel)](#-installation-backend-laravel) 6. [Installation Frontend (React)](#-installation-frontend-react) 7. [Configuration de la base de données](#-configuration-de-la-base-de-données) 8. [Migrations — Structure des tables](#-migrations--structure-des-tables) 9. [Modèles Eloquent et relations](#-modèles-eloquent-et-relations) 10. [Seeder — Données de démonstration](#-seeder--données-de-démonstration) 11. [Authentification avec Sanctum](#-authentification-avec-sanctum) 12. [Routes API](#-routes-api) 13. [Controllers Backend](#-controllers-backend) 14. [Intégration Stripe Checkout](#-intégration-stripe-checkout) 15. [Frontend React — Structure](#-frontend-react--structure) 16. [Service API (Axios)](#-service-api-axios) 17. [Routage React](#-routage-react) 18. [Pages Frontend](#-pages-frontend) 19. [Composants réutilisables](#-composants-réutilisables) 20. [Gestion des images produits](#-gestion-des-images-produits) 21. [Styles et thème](#-styles-et-thème) 22. [Lancer le projet](#-lancer-le-projet) 23. [Résumé des fonctionnalités](#-résumé-des-fonctionnalités) --- ## 🎯 Présentation du projet **Luxe Shop** est une boutique en ligne complète avec : - Catalogue de **30 produits** répartis en **9 catégories** - Système d'**inscription / connexion** sécurisé par token - **Panier** persistant (côté serveur) - **Paiement en ligne** via Stripe Checkout - **Historique des commandes** avec statut de paiement - **Dashboard** utilisateur - Interface **responsive** avec thème sombre luxueux --- ## 🧰 Stack technique | Couche | Technologie | Version | |--------|------------|---------| | **Backend** | Laravel (PHP) | 12.x | | **Authentification** | Laravel Sanctum | 4.3 | | **Base de données** | MySQL (via XAMPP) | 8.x | | **Paiement** | Stripe PHP SDK | 19.3 | | **Frontend** | React | 19.x | | **Routing SPA** | React Router DOM | 7.x | | **HTTP Client** | Axios | 1.x | | **Serveur local** | XAMPP (Apache + MySQL) | — | --- ## 🏗️ Architecture du projet ``` Laravel 3/ ├── backend/ ← API Laravel (port 8000) │ ├── app/ │ │ ├── Http/Controllers/Api/ │ │ │ ├── AuthController.php │ │ │ ├── CartController.php │ │ │ ├── CategoryController.php │ │ │ ├── OrderController.php │ │ │ └── ProductController.php │ │ ├── Models/ │ │ │ ├── User.php │ │ │ ├── Product.php │ │ │ ├── Category.php │ │ │ ├── CartItem.php │ │ │ ├── Order.php │ │ │ └── OrderItem.php │ │ └── Providers/ │ ├── config/ │ │ └── sanctum.php │ ├── database/ │ │ ├── migrations/ │ │ └── seeders/ │ │ └── ShopSeeder.php │ └── routes/ │ └── api.php │ └── frontend/ ← SPA React (port 3000) └── src/ ├── components/ │ ├── Navbar.js │ └── PrivateRoute.js ├── pages/ │ ├── Home.js │ ├── Login.js / Register.js │ ├── Products.js / ProductDetail.js │ ├── Cart.js │ ├── Orders.js / OrderDetail.js │ ├── Dashboard.js │ ├── Success.js / Cancel.js │ └── ... ├── services/ │ └── api.js ├── utils/ │ └── productUtils.js └── index.css ``` --- ## ✅ Prérequis - **XAMPP** installé (Apache + MySQL démarrés) - **PHP** 8.2+ - **Composer** installé globalement - **Node.js** 18+ et **npm** - **Compte Stripe** (clé secrète pour le paiement) --- ## ⚙️ Installation Backend (Laravel) ### 1. Créer le projet Laravel ```bash cd C:\xampp\htdocs composer create-project laravel/laravel backend cd backend ``` ### 2. Installer les dépendances supplémentaires ```bash composer require laravel/sanctum composer require stripe/stripe-php ``` ### 3. Configurer le fichier `.env` ```env APP_NAME="Luxe Shop" APP_URL=http://127.0.0.1:8000 DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=ecommerce_db DB_USERNAME=root DB_PASSWORD= SANCTUM_STATEFUL_DOMAINS=localhost:3000,127.0.0.1:3000 STRIPE_SECRET=sk_test_VOTRE_CLE_STRIPE_SECRETE FRONTEND_URL=http://localhost:3000 ``` ### 4. Configurer CORS Dans `config/cors.php` (ou middleware), s'assurer que `localhost:3000` est autorisé : ```php 'paths' => ['api/*', 'sanctum/csrf-cookie'], 'allowed_origins' => ['http://localhost:3000'], 'supports_credentials' => true, ``` --- ## ⚙️ Installation Frontend (React) ### 1. Créer l'application React ```bash cd C:\xampp\htdocs\Laravel 3 npx create-react-app frontend cd frontend ``` ### 2. Installer les dépendances ```bash npm install axios react-router-dom ``` --- ## 🗄️ Configuration de la base de données ### 1. Créer la base dans phpMyAdmin 1. Ouvrir [http://localhost/phpmyadmin](http://localhost/phpmyadmin) 2. Créer une nouvelle base de données : `ecommerce_db` 3. Encodage : `utf8mb4_unicode_ci` ### 2. Configurer `.env` (déjà fait ci-dessus) ### 3. Lancer les migrations ```bash cd backend php artisan migrate ``` --- ## 📐 Migrations — Structure des tables ### `categories` ```php Schema::create('categories', function (Blueprint $table) { $table->id(); $table->string('name'); $table->timestamps(); }); ``` ### `products` ```php Schema::create('products', function (Blueprint $table) { $table->id(); $table->string('name'); $table->text('description'); $table->decimal('price', 8, 2); $table->string('image')->nullable(); $table->foreignId('category_id')->constrained()->onDelete('cascade'); $table->timestamps(); }); ``` ### `cart_items` ```php Schema::create('cart_items', function (Blueprint $table) { $table->id(); $table->foreignId('user_id')->constrained()->onDelete('cascade'); $table->foreignId('product_id')->constrained()->onDelete('cascade'); $table->integer('quantity')->default(1); $table->timestamps(); }); ``` ### `orders` ```php Schema::create('orders', function (Blueprint $table) { $table->id(); $table->foreignId('user_id')->constrained()->onDelete('cascade'); $table->decimal('total', 10, 2); $table->string('status')->default('pending'); $table->timestamps(); }); ``` ### `order_items` ```php Schema::create('order_items', function (Blueprint $table) { $table->id(); $table->foreignId('order_id')->constrained()->onDelete('cascade'); $table->foreignId('product_id')->constrained()->onDelete('cascade'); $table->integer('quantity'); $table->decimal('price', 8, 2); $table->timestamps(); }); ``` ### Migration ajout Stripe (sur `orders`) ```php Schema::table('orders', function (Blueprint $table) { $table->string('stripe_session_id')->nullable()->after('status'); $table->timestamp('paid_at')->nullable()->after('stripe_session_id'); }); ``` ### Diagramme des relations ``` ┌──────────┐ ┌────────────┐ ┌──────────┐ │ users │────<│ cart_items │>────│ products │ │ │ └────────────┘ │ │ │ │ │ │>───┐ │ │ ┌────────────┐ └──────────┘ │ │ │────<│ orders │ │ │ │ │ │ ┌─────────────┐ │ └──────────┘ │ │────<│ order_items │>┘ └────────────┘ └─────────────┘ ┌────────────┐ │ categories │>────── products └────────────┘ ``` --- ## 🧱 Modèles Eloquent et relations ### `User.php` ```php use Laravel\Sanctum\HasApiTokens; class User extends Authenticatable { use HasApiTokens, HasFactory, Notifiable; protected $fillable = ['name', 'email', 'password']; protected $hidden = ['password', 'remember_token']; public function cartItems() { return $this->hasMany(CartItem::class); } public function orders() { return $this->hasMany(Order::class); } } ``` > ⚠️ **Important** : Le trait `HasApiTokens` est indispensable pour que Sanctum fonctionne. ### `Product.php` ```php protected $fillable = ['name', 'description', 'price', 'image', 'category_id']; public function category() { return $this->belongsTo(Category::class); } ``` ### `Category.php` ```php protected $fillable = ['name']; public function products() { return $this->hasMany(Product::class); } ``` ### `CartItem.php` ```php protected $fillable = ['user_id', 'product_id', 'quantity']; public function product() { return $this->belongsTo(Product::class); } ``` ### `Order.php` ```php protected $fillable = ['user_id', 'total', 'status']; public function items() { return $this->hasMany(OrderItem::class); } public function user() { return $this->belongsTo(User::class); } ``` ### `OrderItem.php` ```php protected $fillable = ['order_id', 'product_id', 'quantity', 'price']; public function product() { return $this->belongsTo(Product::class); } public function order() { return $this->belongsTo(Order::class); } ``` --- ## 🌱 Seeder — Données de démonstration Le fichier `ShopSeeder.php` injecte **9 catégories** et **30 produits** avec images. ```bash php artisan db:seed --class=ShopSeeder ``` **Catégories :** | # | Catégorie | |---|-----------| | 1 | Informatique | | 2 | Gaming | | 3 | Audio | | 4 | Smartphone | | 5 | Maison | | 6 | Cuisine | | 7 | Sport | | 8 | Bureau | | 9 | Accessoires | **Extrait produits (30 au total) :** | Produit | Prix | Catégorie | |---------|------|-----------| | PC Portable 15" | 699.90 CHF | Informatique | | Clavier mécanique | 79.90 CHF | Gaming | | Écouteurs sans fil | 39.90 CHF | Audio | | Chargeur rapide 30W | 19.90 CHF | Smartphone | | Lampe de bureau | 19.90 CHF | Maison | | Blender | 49.90 CHF | Cuisine | | Tapis de yoga | 24.90 CHF | Sport | | Chaise ergonomique | 129.90 CHF | Bureau | | Sac à dos tech | 39.90 CHF | Accessoires | Chaque produit possède une URL d'image externe (Unsplash ou liens directs). --- ## 🔐 Authentification avec Sanctum ### Principe Laravel Sanctum gère l'authentification par **tokens API (Bearer Token)** : 1. L'utilisateur s'inscrit ou se connecte → reçoit un **token** 2. Le token est stocké dans `localStorage` côté React 3. Chaque requête API protégée envoie le header `Authorization: Bearer <token>` 4. Le middleware `auth:sanctum` vérifie le token côté Laravel ### Configuration `config/sanctum.php` ```php 'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1' )), ``` ### Flux d'authentification ``` ┌─────────┐ POST /api/register ┌─────────┐ │ React │ ──────────────────────> │ Laravel │ │ (front) │ <────────────────────── │ (API) │ │ │ { token, user } │ │ │ │ │ │ │ │ GET /api/cart │ │ │ │ Authorization: Bearer │ │ │ │ ──────────────────────> │ │ │ │ <────────────────────── │ │ └─────────┘ [cart items] └─────────┘ ``` --- ## 🛣️ Routes API ### Routes publiques | Méthode | URI | Description | |---------|-----|-------------| | `POST` | `/api/register` | Inscription | | `POST` | `/api/login` | Connexion | | `GET` | `/api/categories` | Liste des catégories | | `GET` | `/api/products` | Liste des produits | | `GET` | `/api/products/{id}` | Détail d'un produit | ### Routes protégées (`auth:sanctum`) | Méthode | URI | Description | |---------|-----|-------------| | `GET` | `/api/user` | Infos utilisateur connecté | | `POST` | `/api/logout` | Déconnexion | | `GET` | `/api/cart` | Afficher le panier | | `POST` | `/api/cart/add` | Ajouter au panier | | `DELETE` | `/api/cart/{id}` | Retirer du panier | | `POST` | `/api/checkout` | Créer commande + Stripe | | `GET` | `/api/orders` | Historique commandes | | `GET` | `/api/orders/{id}` | Détail d'une commande | | `POST` | `/api/payment/confirm` | Confirmer paiement Stripe | --- ## 🎮 Controllers Backend ### `AuthController` | Action | Description | |--------|-------------| | `register()` | Valide les données, crée le user, retourne un token Sanctum | | `login()` | Vérifie email/password avec `Hash::check`, retourne un token | | `logout()` | Supprime le token courant | ```php // Inscription $user = User::create([...]); $token = $user->createToken('auth_token')->plainTextToken; return response()->json(['token' => $token, 'user' => $user], 201); ``` ### `ProductController` ```php // Liste tous les produits avec leur catégorie public function index() { return Product::with('category')->get(); } // Détail d'un produit public function show($id) { return Product::with('category')->findOrFail($id); } ``` ### `CategoryController` ```php public function index() { return Category::all(); } ``` ### `CartController` | Action | Description | |--------|-------------| | `index()` | Récupère les articles du panier de l'utilisateur connecté | | `add()` | Ajoute un produit au panier (ou incrémente la quantité) | | `remove()` | Supprime un article du panier | ```php // Ajout intelligent : firstOrCreate + increment $item = CartItem::firstOrCreate( ['user_id' => $user->id, 'product_id' => $request->product_id], ['quantity' => 0] ); $item->increment('quantity'); ``` ### `OrderController` | Action | Description | |--------|-------------| | `checkout()` | Crée la commande + session Stripe Checkout | | `index()` | Liste les commandes de l'utilisateur | | `show()` | Détail d'une commande | | `confirmPayment()` | Vérifie le paiement via l'API Stripe | --- ## 💳 Intégration Stripe Checkout ### Principe 1. L'utilisateur clique **"Payer avec Stripe"** dans le panier 2. Le backend crée une **session Stripe Checkout** avec les articles 3. L'utilisateur est **redirigé vers la page Stripe** pour payer 4. Après paiement, redirection vers `/success?session_id=...` 5. Le frontend appelle `/api/payment/confirm` pour valider ### Code serveur (dans `OrderController@checkout`) ```php \Stripe\Stripe::setApiKey(env('STRIPE_SECRET')); $session = \Stripe\Checkout\Session::create([ 'mode' => 'payment', 'line_items' => $lineItems, // produits du panier 'success_url' => $frontend . '/success?session_id={CHECKOUT_SESSION_ID}', 'cancel_url' => $frontend . '/cancel', 'client_reference_id' => (string) $order->id, 'metadata' => [ 'user_id' => (string) $user->id, 'order_id' => (string) $order->id, ], ]); ``` ### Confirmation de paiement ```php // POST /api/payment/confirm $session = \Stripe\Checkout\Session::retrieve($request->session_id); if ($session->payment_status === 'paid') { $order->status = 'paid'; $order->paid_at = now(); $order->save(); } ``` ### Flux complet ``` Panier → POST /checkout → Stripe Checkout → /success → POST /payment/confirm ↓ Paiement annulé → /cancel ``` --- ## ⚛️ Frontend React — Structure ### `src/services/api.js` — Client HTTP ```javascript import axios from "axios"; const api = axios.create({ baseURL: "http://127.0.0.1:8000/api", }); // Intercepteur : injecte le token Bearer automatiquement api.interceptors.request.use((config) => { const token = localStorage.getItem("token"); if (token) config.headers.Authorization = `Bearer ${token}`; return config; }); export default api; ``` > Le token est stocké dans `localStorage` et envoyé automatiquement sur chaque requête. --- ## 🗺️ Routage React Fichier : `App.js` ```javascript <BrowserRouter> <Navbar /> <Routes> {/* Publiques */} <Route path="/" element={<Home />} /> <Route path="/login" element={<Login />} /> <Route path="/register" element={<Register />} /> <Route path="/products" element={<Products />} /> <Route path="/products/:id" element={<ProductDetail />} /> {/* Stripe */} <Route path="/success" element={<PrivateRoute><Success /></PrivateRoute>} /> <Route path="/cancel" element={<Cancel />} /> {/* Protégées */} <Route path="/cart" element={<PrivateRoute><Cart /></PrivateRoute>} /> <Route path="/orders" element={<PrivateRoute><Orders /></PrivateRoute>} /> <Route path="/orders/:id" element={<PrivateRoute><OrderDetail /></PrivateRoute>} /> <Route path="/dashboard" element={<PrivateRoute><Dashboard /></PrivateRoute>} /> {/* Fallback */} <Route path="*" element={<Navigate to="/" replace />} /> </Routes> </BrowserRouter> ``` --- ## 📄 Pages Frontend | Page | Route | Description | |------|-------|-------------| | **Home** | `/` | Page d'accueil avec hero et présentation | | **Login** | `/login` | Formulaire de connexion | | **Register** | `/register` | Formulaire d'inscription | | **Products** | `/products` | Catalogue avec filtres par catégorie + recherche | | **ProductDetail** | `/products/:id` | Fiche produit avec image, bouton panier | | **Cart** | `/cart` | Panier avec résumé + bouton Stripe | | **Orders** | `/orders` | Historique des commandes | | **OrderDetail** | `/orders/:id` | Détail d'une commande | | **Dashboard** | `/dashboard` | Tableau de bord utilisateur | | **Success** | `/success` | Confirmation de paiement réussi | | **Cancel** | `/cancel` | Paiement annulé | --- ## 🧩 Composants réutilisables ### `Navbar.js` - Logo **Luxe Shop** - Navigation : Produits, Panier, Commandes, Dashboard - Bouton **Connexion** / **Déconnexion** selon l'état ### `PrivateRoute.js` ```javascript // Redirige vers /login si pas de token const token = localStorage.getItem("token"); if (!token) return <Navigate to="/login" />; return children; ``` ### `productUtils.js` Deux fonctions utilitaires : - `getProductEmoji(category, name)` → retourne un emoji selon la catégorie - `getCategoryColor(category)` → retourne une couleur de fond RGBA --- ## 🖼️ Gestion des images produits ### Côté backend (Seeder) Chaque produit a un champ `image` contenant une **URL directe** vers une image : ```php ['PC Portable 15"', 'Laptop polyvalent.', 699.90, 'Informatique', 'https://images.unsplash.com/photo-1496181133206-80ce9b88a853?w=800&h=800&fit=crop'], ``` Sources d'images utilisées : - **Unsplash** (images gratuites haute qualité) - **Liens directs** de sites marchands (pour certains produits spécifiques) ### Côté frontend Les images sont affichées avec un **fallback emoji** si l'image ne charge pas : ```jsx {product.image ? ( <img src={product.image} alt={product.name} style={{ objectFit: "contain", background: "#1a1a2e" }} onError={(e) => { e.target.style.display = 'none'; e.target.nextSibling.style.display = 'flex'; }} /> ) : null} {/* Fallback emoji */} <div className="product-img-placeholder" style={{ display: product.image ? 'none' : 'flex' }}> <span>{getProductEmoji(category, name)}</span> </div> ``` **Pages concernées :** - `Products.js` — carte produit (grille) - `ProductDetail.js` — page détail - `Cart.js` — miniature dans le panier --- ## 🎨 Styles et thème Le thème est défini dans `index.css` avec des **variables CSS** : ```css :root { --bg: #0a0a0f; --surface: #12121a; --surface2: #1a1a2e; --border: #2a2a3e; --text: #f5f5f5; --muted: #888; --gold: #c9a84c; --gold2: #e8c547; --green: #22c55e; /* ... */ } ``` ### Caractéristiques du design - **Thème sombre** (dark mode exclusif) - Accents **dorés** pour le côté luxe - Typographie : **Playfair Display** (titres) + **Inter** (corps) - Border-radius arrondis (16px / 24px) - Grille responsive 4 colonnes → 2 → 1 --- ## 🚀 Lancer le projet ### Terminal 1 — Backend Laravel ```bash cd C:\xampp\htdocs\Laravel 3\backend php artisan serve ``` > API disponible sur `http://127.0.0.1:8000` ### Terminal 2 — Frontend React ```bash cd C:\xampp\htdocs\Laravel 3\frontend npm start ``` > App disponible sur `http://localhost:3000` ### Seeder (première fois ou reset) ```bash cd backend php artisan migrate:fresh php artisan db:seed --class=ShopSeeder ``` --- ## ✨ Résumé des fonctionnalités | Fonctionnalité | Status | |----------------|--------| | Inscription / Connexion (Sanctum) | ✅ | | Catalogue produits avec images | ✅ | | Filtres par catégorie | ✅ | | Recherche de produits | ✅ | | Fiche produit détaillée | ✅ | | Panier (ajout / suppression) | ✅ | | Paiement Stripe Checkout | ✅ | | Confirmation de paiement | ✅ | | Historique des commandes | ✅ | | Dashboard utilisateur | ✅ | | Responsive design | ✅ | | Thème sombre luxueux | ✅ | | Fallback images (emoji) | ✅ | | 30 produits / 9 catégories | ✅ | --- > **Luxe Shop** — Projet fullstack Laravel + React avec paiement Stripe > Réalisé en février 2026