Laravel 7

Shopping : le panier

Dans cet article on va afficher les produits, permettre leur sélection et utiliser un panier pour mémoriser et comptabiliser les achats.

Vous pouvez télécharger un ZIP du projet ici.

Affichage de la boutique

La première chose à faire est d’afficher les produits sur la page d’accueil. Pour le moment on a la page d’accueil standard de Laravel avec la vue welcome. On va supprimer cette vue qui ne nous sert à rien et on va recycler la vue home.

Dans le contrôleur HomeController on va prévoir ce code (on supprime au passage le middleware guest) :

<?php

namespace App\Http\Controllers;

use App\Models\Product;

class HomeController extends Controller
{
    /**
     * Show home page
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        $products = Product::whereActive(true)->get();

        return view('home', compact('products'));
    }
}

Pour cette application j’ai décidé de me passer de repositories, c’est un choix délibéré. On a une totale liberté sur l’organisation de son code.

On change la route pour l’accueil :

Route::get('/', 'HomeController@index')->name('home');

Comme on a peu de produits on se passe de pagination. On charge donc tous les produits actifs dans une variable $products que l’on envoie dans la vue home. On change le code de cette vue pour utiliser le layout et afficher les produits :

@extends('layouts.app')

@section('content')
<div class="container">
  
  <div class="row">
    <div class="col s12 cards-container">
      @foreach($products as $product)
        <div class="card">
          <div class="card-image">
            @if($product->quantity)
              <a href="#">
            @endif
              <img src="/images/thumbs/{{ $product->image }}">
            @if($product->quantity) </a> @endif
          </div>          
          <div class="card-content center-align">
            <p>{{ $product->name }}</p>
            @if($product->quantity)
              <p><strong>{{ number_format($product->price, 2, ',', ' ') }} € TTC</strong></p>
            @else
              <p class="red-text"><strong>Produit en rupture de stock</strong></p>
            @endif
          </div>
        </div>
      @endforeach
    </div>
  </div>

</div>
@endsection

Et pour la répartition des produits sur la page un peu de style pour un effet Masonry (sass/app.scss) :

.cards-container {
  .card {
    display: inline-block;
    overflow: visible;
    width: 100%;
  }
}

@media only screen and (max-width: 600px) {
  .cards-container {
    -webkit-column-count: 1;
    -moz-column-count: 1;
    column-count: 1;
  }
}
@media only screen and (min-width: 601px) {
  .cards-container {
    -webkit-column-count: 2;
    -moz-column-count: 2;
    column-count: 2;
  }
}
@media only screen and (min-width: 993px) {
  .cards-container {
    -webkit-column-count: 3;
    -moz-column-count: 3;
    column-count: 3;
  }
}
@media only screen and (min-width: 1200px) {
  .cards-container {
    -webkit-column-count: 4;
    -moz-column-count: 4;
    column-count: 4;
  }
}

On se retrouve avec 1 à 4 produits par ligne selon la largeur de l’affichage :

On complète aussi les liens dans la barre du layout :

<a href="{{ route('home') }}" class="brand-logo"><img src="/images/logo1.png" width="210px" alt="Logo"></a>
<a href="{{ route('home') }}" data-target="mobile" class="sidenav-trigger"><i class="material-icons">menu</i></a>

Si un produit est en rupture de stock c’est signalé :

Le détail des produits

Quand on clique sur un produit il faut afficher une page avec l’image haute définition, la description, la quantité désirée et un bouton pour ajouter au panier.

On va créer un contrôleur pour les produits :

php artisan make:controller ProductController

Avec ce code :

<?php

namespace App\Http\Controllers;

use App\Models\Product;
use Illuminate\Http\Request;

class ProductController extends Controller
{
    /**
     * Display the specified resource.
     *
     * @param  \App\Models\Product  $product
     * @return \Illuminate\Http\Response
     */
    public function __invoke(Request $request, Product $produit)
    {
        if($produit->active || $request->user()->admin) {
            return view('products.show', compact('produit'));
        }

        return redirect(route('home'));
    }
}

On affiche la page produit que si celui-ci est actif ou si c’est un administrateur qui veut la voir.

On prévoit la route :

Route::name('produits.show')->get('produits/{produit}', 'ProductController');

On crée la vue dans un dossier :

Avec ce code :

@extends('layouts.app')

@section('content')
<div class="container">

  <div class=row>
    <div class="col s12 m6">
      <img style="width: 100%" src="/images/{{ $produit->image }}">
    </div>
    <div class="col s12 m6">
      <h4>{{ $produit->name }}</h4>
      <p><strong>{{ number_format($produit->price, 2, ',', ' ') }} € TTC</strong></p>
      <p>{{ $produit->description }}</p>
      <form  method="POST" action="#">
        @csrf
        <div class="input-field col">
          <input type="hidden" id="id" name="id" value="{{ $produit->id }}">
          <input id="quantity" name="quantity" type="number" value="1" min="1">
          <label for="quantity">Quantité</label>        
          <p>
            <button class="btn waves-effect waves-light" style="width:100%" type="submit" id="addcart">Ajouter au panier
              <i class="material-icons left">add_shopping_cart</i>
            </button>
          </p>    
        </div>    
      </form>
    </div>
  </div>

</div>
@endsection

Dans la vue home on ajoute le lien pour afficher la page d’un produit :

<a href="{{ route('produits.show', $product->id) }}">

Maintenant le clic sur un produit ouvre sa page de détail :

Il ne nous reste plus qu’à faire fonctionner le panier !

Mise en place du panier

Pour le panier on va utiliser ce package.

composer require darryldecode/cart

On crée un contrôleur de ressource :

php artisan make:controller CartController --resource

On n’utilisera que 4 méthodes donc voici les routes :

Route::resource('panier', 'CartController')->only(['index', 'store', 'update', 'destroy']);

On va compléter la vue products.show avec une vue modale, le lien du formulaire, et du Javascript pour le traitement :

@extends('layouts.app')

@section('content')
<div class="container">

  @if(session()->has('cart'))
    <div class="modal">
      <div class="modal-content center-align">
        <h5>Produit ajouté au panier avec succès</h5>
        <hr>
        <p>Il y a {{ $cartCount }} @if($cartCount > 1) articles @else article @endif dans votre panier pour un total de <strong>{{ number_format($cartTotal, 2, ',', ' ') }} € TTC</strong> hors frais de port.</p>
        <p><em>Vous avez la possibilité de venir chercher vos produits sur place, dans ce cas vous cocherez la case correspondante lors de la confirmation de votre commande et aucun frais de port ne vous sera facturé.</em></p>
        <div class="modal-footer">     
          <button class="modal-close btn waves-effect waves-light left" id="continue">
            Continuer mes achats
          </button>
          <a href="{{ route('panier.index') }}" class="btn waves-effect waves-light">
            <i class="material-icons left">check</i>
            Commander          
          </a>
        </div>
      </div>
    </div>
  @endif

  <div class=row>
    <div class="col s12 m6">
      <img style="width: 100%" src="/images/{{ $produit->image }}">
    </div>
    <div class="col s12 m6">
      <h4>{{ $produit->name }}</h4>
      <p><strong>{{ number_format($produit->price, 2, ',', ' ') }} € TTC</strong></p>
      <p>{{ $produit->description }}</p>
      <form  method="POST" action="{{ route('panier.store') }}">
        @csrf
        <div class="input-field col">
          <input type="hidden" id="id" name="id" value="{{ $produit->id }}">
          <input id="quantity" name="quantity" type="number" value="1" min="1">
          <label for="quantity">Quantité</label>        
          <p>
            <button class="btn waves-effect waves-light" style="width:100%" type="submit" id="addcart">Ajouter au panier
              <i class="material-icons left">add_shopping_cart</i>
            </button>
          </p>    
        </div>    
      </form>
    </div>
  </div>

</div>
@endsection

@section('javascript')
  <script>
    @if(session()->has('cart'))
      document.addEventListener('DOMContentLoaded', () => {      
        const instance = M.Modal.init(document.querySelector('.modal'));
        instance.open();    
      });
    @endif    
  </script>
@endsection

Dans le contrôleur CartController on code la méthode store :

use App\Models\Product;
use Cart;

...

public function store(Request $request)
{
    $product = Product::findOrFail($request->id);
      
    Cart::add([
        'id' => $product->id,
        'name' => $product->name,
        'price' => $product->price,
        'quantity' => $request->quantity,
        'attributes' => [],
        'associatedModel' => $product,
      ]
    );

    return redirect()->back()->with('cart', 'ok');
}

On récupère le produit concerné et on renseigne le panier, on renvoie la même vue en flashant la variable cart.

On va aussi envoyer les informations de quantité de produits dans le panier et de coût total mais comme ça va servir dans plusieurs situations on passe par un composeur de vue dans AppServiceProvider :

use Illuminate\Support\Facades\View;
use Cart;

...

public function boot()
{
    View::composer(['layouts.app', 'products.show'], function ($view) {
        $view->with([
            'cartCount' => Cart::getTotalQuantity(), 
            'cartTotal' => Cart::getTotal(),
        ]);
    });
}

Maintenant quand on utilise le bouton « Ajouter au panier » au retour ça ouvre la page modale :

Voyons un peu comment agit le Javascript dans cette vue…

Au chargement de la vue on a :

@if(session()->has('cart'))
  document.addEventListener('DOMContentLoaded', () => {      
    const instance = M.Modal.init(document.querySelector('.modal'));
    instance.open();    
  });
@endif

Si on a flashé la variable cart dans la session on ouvre la page modale avec la librairie de Materialize.

Si on clique sur « Continuer mes achats » la page modale se ferme et on se retrouve avec la vue du produit.

Maintenant ce qui serait bien c’est d’informer l’utilisateur qu’il a un panier actif avec le nombre de produits dedans. On va le faire dans la barre de menu (layout) en prévoyant les deux emplacement (barre normale et barre latérale pour mobiles) :

<ul class="right hide-on-med-and-down">
@if($cartCount)
  <li>
    <a class="tooltipped" href="{{ route('panier.index') }}" data-position="bottom" data-tooltip="Voir mon panier"><i class="material-icons left">shopping_cart</i>Panier({{ $cartCount }})</a>
  </li>
@endif
@guest  

...

<ul class="sidenav" id="mobile">
  @if($cartCount)
    <li>
      <a class="tooltipped" href="{{ route('panier.index') }}" data-position="bottom" data-tooltip="Voir mon panier">Panier({{ $cartCount }})</a>
    </li>
  @endif
  @guest

Le panier

Maintenant on va coder pour voir le panier. On crée une vue :

Avec ce code :

@extends('layouts.app')

@section('content')
<div class="container">

  <div class="row">
    <div class="col s12">
      <div class="card">        
        <div class="card-content">
          <div id="wrapper">          
            @if($total)
              <span class="card-title">Mon panier</span>            
              @foreach ($content as $item)
                <hr><br>
                <div class="row">
                  <form action="{{ route('panier.update', $item->id) }}" method="POST">
                    @csrf
                    @method('PUT')
                    <div class="col m6 s12">{{ $item->name }}</div>
                    <div class="col m3 s12"><strong>{{ number_format($item->quantity * $item->price, 2, ',', ' ') }} €</strong></div>
                    <div class="col m2 s12">
                      <input name="quantity" type="number" style="height: 2rem" min="1" value="{{ $item->quantity }}">
                    </div>
                  </form>
                  <form action="{{ route('panier.destroy', $item->id) }}" method="POST">
                    @csrf
                    @method('DELETE')
                    <div class="col m1 s12"><i class="material-icons deleteItem" style="cursor: pointer">delete</i></div>
                  </form>              
                </div>
              @endforeach
              <hr><br>
              <div class="row" style="background-color: lightgrey">
                <div class="col s6">
                  Total TTC (hors livraison)
                </div>
                <div class="col s6">
                  <strong>{{ number_format($total, 2, ',', ' ') }} €</strong>
                </div>
              </div>
            @else
            <span class="card-title center-align">Le panier est vide</span>
            @endif
          </div>        
          <div id="loader" class="hide">
            <div class="loader"></div>
          </div>
        </div>
        <div id="action" class="card-action">
          <p>
            <a  href="{{ route('home') }}">Continuer mes achats</a>
            @if($total)
              <a href="#">Commander</a>
            @endif
          </p>
        </div>
      </div>
    </div>
  </div>

</div>
@endsection

@section('javascript')
  <script>

    document.addEventListener('DOMContentLoaded', () => {
      const quantities = document.querySelectorAll('input[name="quantity"]');
      quantities.forEach( input => {
        input.addEventListener('input', e => {
          if(e.target.value < 1) {
            e.target.value = 1;
          } else {
            e.target.parentNode.parentNode.submit();
            document.querySelector('#wrapper').classList.add('hide');
            document.querySelector('#action').classList.add('hide');
            document.querySelector('#loader').classList.remove('hide');
          }
        });
      }); 

      const deletes = document.querySelectorAll('.deleteItem');
      deletes.forEach( icon => {
        icon.addEventListener('click', e => {
          e.target.parentNode.parentNode.submit();
          document.querySelector('#wrapper').classList.add('hide');
          document.querySelector('#loader').classList.remove('hide');
        });
      }); 
    });
    
  </script>
@endsection

Et dans le contrôleur CartController on complète la méthode index :

public function index()
{
    $content = Cart::getContent();
    $total = Cart::getTotal();

    return view('cart.index', compact('content', 'total'));
}

Maintenant quand on clique sur « Commander » (ou si on clique sur le lien du panier dans le menu) on obtient la page avec le contenu du panier :

Là l’utilisateur peut modifier les quantités et même supprimer un produit.

Pour que ça fonctionne on va compléter le contrôleur CartController :

public function update(Request $request, $id)
{
    Cart::update($id, [
        'quantity' => ['relative' => false, 'value' => $request->quantity],
    ]);

    return redirect(route('panier.index'));
}

public function destroy($id)
{
    Cart::remove($id);

    return redirect(route('panier.index'));
}

Pour la mise à jour de la quantité d’un produit il faut bien préciser relative à false sinon ça change relativement à la valeur mémorisée or là on envoie la nouvelle quantité.

Pour faire patienter l’utilisateur pendant les mises à jour et pour éviter qu’il clique encore quelque part on fait apparaître un loader. Pour que ça fonctionne on va ajouter un peu de style dans app.scss (n’oubliez pas de relancer la compilation) :

#loader {
  margin: 40px;
}
.loader {
  margin: auto;
  border: 16px solid #e3e3e3;
  border-radius: 50%;
  border-top: 16px solid #1565c0;
  width: 120px;
  height: 120px;
  animation: spin 2s linear infinite;
}
@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

Une petite animation fait patienter l’utilisateur :

Le Javascript a plusieurs fonctions :

  • mettre en route l’animation
  • empêcher la saisie d’une valeur inférieure à 1
  • lancer la soumission des formulaires

Conclusion

On a maintenant une boutique qui sait afficher ses produits, montrer le détail d’un produit, permet d’ajouter un produit au panier en choisissant la quantité voulue, d’afficher le panier en pouvant jouer sur les quantités. Dans le prochain article on mettra en place l’enregistrement d’une commande.

 

 

Print Friendly, PDF & Email

46 commentaires

  • softcode

    Bonjour best momo, j’ai un petit soucis qui me retient jusque là, c’est paraport à l’animation que vous venez d’ajouter dont le code est le suivant :

    et le code javascript aussi marche bien mais je remarque que cette animation au lieu que ca reste fermé avant que l’utilisateur change la quantité du produit par exemple, cette bar d’animation est toujours visible même quant il y aaucune action de la part de l’utilisateur tout le code javascript marche bien je ne sais pas pourquoi c’est toujours visible? Merci!

      • softcode

        Merci beaucoup pour la remarque, malheureusement jai utilisé bootstrap au lieu de materialize et je viens de corriger cette en la remplaçant par la classe d-none de bootstrap ça quand meme marché mais lorsque j’essaie de modifier la quantité ou supprimer toute les contenu qui se trouve dans la balise wrapper disparait voir même l’animation c’est peut être quoi le problème? merci pour votre aide

  • softcode

    Bonjour bestmomo, j’ai une péocupation, je cherche à contourner la proprité attributes pour ajouter d’autres informations dans le panier voilà à quoi ressemble mon code dans le controller:

    Cart::add([
    ‘id’ => $article->id,
    ‘name’ => $article->name,
    ‘price’ => $article->price,
    ‘quantity’ => $request->quantity,
    ‘attributes’ => [
    ‘taille’ => $request->taille,
    ‘color’ => $request->color,
    ‘image’ => $request->image,
    ],
    ]);

    $content = Cart::getContent();
    $valeurs = $content->attributes;

    après execuion je recois une erreur comme ceci:

    Property [attributes] does not exist on this collection instance.

    Svp vous pouver m’aider à debloquer cette situation ? Merci

      • softcode

        je crois pas que vous ayez repondu à ma question, cela consiste à ajouter d’autre champs au panier à part les 4 champs principals id,name,quantity, price moi j’aimairai bien ajouter d’autre champs à l’instar de ‘taille’ => $request->taille,
        ‘color’ => $request->color,

        c’est pour quoi je vous ai montré mes codes que j’ai essayer d’exécuter dans ma machine ça me renvoit une erreur que : Property [attributes] does not exist on this collection instance.

        voici à quoi ressemble mes codes:

        Cart::add([
        ‘id’ => $article->id,
        ‘name’ => $article->name,
        ‘price’ => $article->price,
        ‘quantity’ => $request->quantity,
        ‘attributes’ => [
        ‘taille’ => $request->taille,
        ‘color’ => $request->color,
        ‘image’ => $request->image,
        ],
        ]);

        • bestmomo

          Salut,

          Il me semble y avoir répondu. Tu utilises :

          $content = Cart::getContent();
          $valeurs = $content->attributes;

          Mais Cart::getContent() retourne une collection d’items. S’il n’y a qu’un item on peut le récupérer ainsi :

          Cart::getContent()->first()->attributes

          S’il y en a plusieurs, ce qui est souvent le cas dans un panier, il faut faire une boucle :

          $content = Cart::getContent();
          foreach ($content as $item) {
          echo $item->attributes;
          }

  • softcode

    Bonjour bestmomo, je voulais juste savoir quelque chose sur le packagé cart que vous aviez utiliser pour gérer le panier, est ce que qu’il est possible d’ajouter d’autre colonnes dans le panier au lieu d’avoir seulement 4 colonnes a l’instar de id, Price, name et quantity, j’aurai besoin d’ajouter une colonne date est ce qu’il est il possible, si oui j’aimerais avoir une documentation de comment le faire!Merci d’avance pour votre aide !

    • bestmomo

      Salut,

      On ne peut pas ajouter d’autres propriétés au panier, mais on peut détourner la propriété attributes qui est un tableau pour stocker ce qu’on veut et pourquoi pas une date ? Par contre, je trouve étrange de vouloir entrer une date dans un panier, ça m’intrigue.

        • bestmomo

          Pour l’utilisation de cette propriété, c’est aussi simple que pour les autres :
          Cart::add([
          'id' => 111,
          ...
          'attributes' => [ ici un tableau de valeurs ]
          ]);

          Pareil pour le update.
          Pour récupérer les données :
          $item = Cart::getContent();
          $attributes = $item->attributes // On a une collection ici avec toutes les méthodes disponibles

          • softcode

            Merci beaucoup bestmomo surtout pour votre simplicité a écouter et répondre à toutes nos préoccupations,
            Merci infiniment

    • bestmomo

      Bonjour,

      Intégrer des attributs dans le projet Shopping engendrerait trop de modifications, d’autant que ces attributs pourraient avoir une influence sur le prix du produit. Mais peut-être veux-tu dire juste un exemple isolé de panier avec attributs ?

  • jondelweb

    Re Salut,

    J’ai remarqué un comportement chelou lorsque le panier est affiché et que l’on souhaite ajouter un item…

    Lorsque l’on click sur la petite flêche du haut pour rajouter un item sur le PREMIER article, celui-ci ce recharge en DEUXIÈME position alors que celui-ci était en première.

    Et a chaque fois que l’on modifie un article, celui-ci ce retrouve en fin de liste… Je trouve ça un peu pertubant comme comportement…

    Est-ce qu’il n’y aurait pas une methode lorsque l’on fait Cart::getContent() pour les ramener dans le même ordre ? Ou alors les trier des le début par ordre alphabétique ?

    J’ai vu qu’ils s’affichaient par ordre du choix du user. Dans la session() peut-être ?

  • jondelweb

    Salut,

    Je continue sur le tuto donc forcément aussi sur les problèmes !

    J’ai un petit souci avec la modal… Elle ne s’affiche pas… Pourtant cela fonctionne bien car dans le menu nav j’ai bien le nombre à côté de panier qui augmente…

    Mais la modal ne s’affiche pas…

    J’ai testé sur Chrome, firefox, que dalle…

    Je suis un peu désabusé là…

  • Saady

    Bonsoir bestmomo, j’ai un problème au niveau de la mise en place du panier,le bouton ajouter panier renvoie une erreur :  »The post method is not supported for this route . Supported methods GET,HEAD » ainsi que tout le reste

  • blackcat68

    bonsoir,

    je viens vers toi car je désespère vraiment, je n’arrive pas utiliser la fonction card
    J’ai bien installé darryldecode/cart et écrit la même chose que toi dans le cardcontroller, mais rien y fait.  » Cart::  » est souligné en rouge (mon code ne le comprends pas) et lorsque j’essaye d’ajouter un produit a mon panier j’obtient ce message d’erreur :
    Darryldecode\Cart\Exceptions\InvalidItemException
    validation.required

    Je pensais que mon problème venait de ma version de laravel (7) qui n’aurait pas encore implémenté la fonction Cart mais si toi tu as réussi je ne comprends pas pourquoi mon code ne le reconnais pas

    Ps : j’ai tenté d’ajouter les codes nécessaires dans Providers Array et Aliases du fichier config\app.php. Mais rien n’y fait
    j’ai suivit les instruction du Git : https://github.com/darryldecode/laravelshoppingcart

  • julien

    Bonjour Bestmomo,
    Saurais tu comment je pourrais bloquer le panier pour qu’il n’y est qu’une seul fois le même id dans le panier.
    je pensais à cart::get($itemid) pour savoir si le produit est déjà dans le panier et ensuite retourner une notification de type « Véhicule déjà dans le panier » sinon je fais un add….?

  • Julienmivo

    Bonjour à tous,
    Je vous remercie pour tout
    j’ai encore une erreur « Class ‘App\Http\Controllers\Product’ not found »
    pointe sur cette fonction:

    public function store(Request $request)
    {
    $product = Product::findOrFail($request->id);
    Cart::add([
    'id' => $product->id,
    'name' => $product->name,
    'price' => $product->price,
    'quantity' => $request->quantity,
    'attributes' => [],
    'associatedModel' => $product,
    ]
    );
    return redirect()->back()->with('cart', 'ok');
    }

    merci de m’aide

  • Hamdi

    Bonsoir BestMomo, Je sais pas ce qui ne va pas chez moi mais aucune des images du dossier image ne s’affiche sur mon site. Et pourtant j’ai la même architecture que le projet zippé. Est-ce que vous avez une idée de comment je peux régler ce problème s’il vous plait ? Merci

        • Hamdi

          Bonjour BestMomo, je viens de voir que le code que j’ai copié-coller hier ne s’est pas affiché. Je m’en excuse sincèrement. Le code généré dans l’inspecteur de code est le même que celui dans l’éditeur de texte: . Est-ce que c’est normal ? quel devrait être le code ? PS: J’ai le même problème avec tous mes navigateurs à savoir : firefox, opéra et google chrome.

          Besoin de votre aide s’il vous plait. merci

          • Hamdi

            Voici le code que j’essaie de copier-coller mais qui ne s’affichepas : . Merci et encore une fois désolé

          • bestmomo

            Bonjour,
            Je peux quand même voir le code dans le message, en fait il apparaît public dans l’url. Normalement ça doit pas arriver si on a bien mis le fichier .htaccess dans le root.

          • Hamdi

            Salut BestMomo, Je n’ai pas bien compris ce que vous voulez dire par mettre le fichier .htaccess dans le root mais néanmoins j’ai pu trouver une autre solution pour afficher les images. il s’agit de l’utilisation de la fonction asset() et cela a marché. Merci beaucoup pour votre disponibilité. Vous êtes le meilleur.

          • fatou

            Bonjour Hamdi j’ai même problème que toi les images de mes produits s’affichent pas vous avez utilisez la fonction asset() comment?

    • bestmomo

      Salut,

      Comme on n’a pas encore codé la création d’adresse pour le client il faut se limiter aux clients créés avec le seeder pour passer une commande sinon on tombe forcément sur cette erreur. J’aurais sans doute dû prévoir la création du compte client avec cette création d’adresse avant d’aborder la commande mais bon, maintenant c’est fait…

Laisser un commentaire