
On a déjà mis en place la page d'accueil de la boutique avec les produits affichés dans des vignettes. On doit à présent obtenir le détail de chaque produit lorsqu'on clique sur une vignette. D'autre part, il faut implémenter un panier pour que le client y place les produits qu'il désire acheter.
Vous pouvez trouver le code dans ce dépôt Github.
Pour le panier, on va utiliser ce package :
composer require darryldecode/cart
Le détail des produits
Quand on clique sur un produit sur la page d'accueil de la boutique, il faut afficher dans une page la description, la quantité désirée et un bouton pour ajouter au panier.
On crée un nouveau composant Volt :
php artisan make:volt product --class
Avec ce code :
<?php
use Livewire\Volt\Component;
use App\Models\Product;
use Mary\Traits\Toast;
new class extends Component {
use Toast;
public Product $product;
public int $quantity = 1;
public function mount(Product $product): void
{
if (!$product->active) {
abort(404);
}
$this->product = $product;
}
public function save(): void
{
Cart::add([
'id' => $this->product->id,
'name' => $this->product->name,
'price' => $this->product->price,
'quantity' => $this->quantity,
'attributes' => ['image' => $this->product->image],
'associatedModel' => $this->product,
]);
$this->dispatch('cart-updated');
$this->info(__('Product added to cart.'), position: 'bottom-end');
}
}; ?>
<div class="container p-5 mx-auto">
<div class="grid gap-10 lg:grid-cols-2">
<div>
<img class="mx-auto" src="{{ asset('storage/photos/' . $product->image) }}" alt="{{ $product->name }}" />
</div>
<div>
<div class="text-2xl font-bold">{{ $product->name }}</div>
<x-badge class="p-3 my-4 badge-neutral" value="{{ number_format($product->price, 2, ',', ' ') . ' € TTC' }}" />
<p class="mb-4">{{ $product->description }}</p>
<x-input class="!w-[80px]" wire:model="quantity" type="number" min="1" label="{{ __('Quantity')}}" />
<x-button class="mt-4 btn-primary" wire:click="save" icon="o-shopping-cart" spinner >{{ __('Add to cart')}}</x-button>
</div>
</div>
</div>
On a besoin de quelques traductions :
"Quantity": "Quantité",
"Add to cart": "Ajouter au panier",
"Product added to cart.": "Produit ajouté au panier.",
D'une route :
Volt::route('/products/{product}', 'product')->name('products.show');
Et on renseigne le lien sur la page d'accueil :
@if ($product->image)
<x-slot:figure>
@if($product->quantity)
<a href="{{ route('products.show', $product) }}">
Les produits s'affichent sur une page quand on clique sur leur image :
On peut déterminer la quantité désirée et ajouter au panier. Mais pour le moment, on ne voit pas ce panier.
La barre de navigation
On doit avoir un suivi du panier au niveau de la barre de navigation, alors on complète le composant navigation/navbar (je remets tout le code) :
<?php
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\{Auth, Session};
use Livewire\Volt\Component;
use Livewire\Attributes\On;
use Darryldecode\Cart\CartCollection;
use Mary\Traits\Toast;
new class extends Component {
use Toast;
public int $CartItems = 0;
public CartCollection $content;
public float $total;
public string $url;
public function mount(): void
{
$this->CartItems = Cart::getTotalQuantity();
$this->content = Cart::getContent();
$this->total = Cart::getTotal();
$this->url = request()->url();
}
public function logout(): void
{
Auth::guard('web')->logout();
Session::invalidate();
Session::regenerateToken();
$this->redirect('/');
}
public function cleanCart(): void
{
Cart::clear();
$this->updateCartItems();
$this->info(__('Cart cleaned.'), position: 'toast-bottom');
}
public function deleteItem($item): void
{
Cart::remove($item);
$this->updateCartItems();
$this->info(__('Item deleted.'), position: 'toast-bottom');
}
#[On('cart-updated')]
public function updateCartItems()
{
$this->CartItems = Cart::getTotalQuantity();
$this->content = Cart::getContent();
$this->total = Cart::getTotal();
}
};
?>
<x-nav sticky full-width :class="'bg-cyan-700 text-white'">
<x-slot:brand>
<label for="main-drawer" class="mr-3 lg:hidden">
<x-icon name="o-bars-3" class="cursor-pointer" />
</label>
<a href="/" wire:navigate>
<x-app-brand />
</a>
</x-slot:brand>
<x-slot:actions>
@if($CartItems > 0 && $url !== route('cart') && $url !== route('order.index'))
<x-dropdown>
<x-slot:trigger>
<x-button label="{{ __('Cart') }}" icon="o-shopping-cart" badge="{{ $CartItems }}" badge-classes="badge-ghost" class="btn-ghost" />
</x-slot:trigger>
<div class="p-2 text-black {{ $content->isNotEmpty()? 'min-w-[300px]' : '' }}">
@foreach ($content as $item)
<div class="flex justify-between mb-2">
<div class="flex gap-4">
<img class="object-cover w-14 h-14" src="{{ asset('storage/photos/' . $item->attributes->image) }}" alt="{{ $item->name }}" />
<div class="mt-2">
<span class="font-bold">{{ $item->name }}</span><br>
{{ number_format($item->quantity * $item->price, 2, ',', ' ') }} €
<br>@lang('Quantity:') {{ $item->quantity }}
</div>
</div>
<x-button icon="o-trash" wire:click="deleteItem({{ $item->id }})" class="text-red-500 btn-circle btn-ghost btn-sm" />
</div>
<hr><br>
@endforeach
<br>
<div class="flex justify-between items-center mb-1">
<div class="font-bold">
@if($CartItems > 1)
@lang('Total of my') {{ $CartItems }} @lang('articles')
@else
@lang('Total of my article')
@endif
</div>
<div class="font-bold">{{ number_format($total, 2, ',', ' ') }} € TTC</div>
</div>
<p class="mb-4 text-right"><em>@lang('Excluding delivery')</em></p>
<hr>
<div class="flex gap-2 justify-between items-center mt-4">
<x-button label="{{ __('Trash my cart') }}" wire:click="cleanCart" class="text-red-500 btn-ghost btn-sm" />
<x-button label="{{ __('View my cart') }}" link="{{ route('cart') }}" icon-right="c-arrow-right" class="btn-primary btn-sm" />
</div>
</div>
</x-dropdown>
@endif
<span class="hidden lg:block">
@if ($user = auth()->user())
<x-dropdown>
<x-slot:trigger>
<x-button label="{{ $user->name }} {{ $user->firstname }}" class="btn-ghost" />
</x-slot:trigger>
<span class="text-black">
<x-menu-item title="{{ __('My profile') }}" link="{{ route('profile') }}" />
<x-menu-item title="{{ __('My addresses') }}" link="{{ route('addresses') }}" />
<x-menu-item title="{{ __('My orders') }}" link="{{ route('orders') }}" />
<x-menu-item title="{{ __('RGPD') }}" link="{{ route('rgpd') }}" />
<x-menu-item title="{{ __('Logout') }}" wire:click="logout" />
</span>
</x-dropdown>
@else
<x-button label="{{ __('Login') }}" link="/login" class="btn-ghost" />
@endif
</span>
</x-slot:actions>
</x-nav>
Il nous faut quelques traductions :
"Quantity": "Quantité",
"Add to cart": "Ajouter au panier",
"Product added to cart.": "Produit ajouté au panier.",
"Cart": "Panier",
"Quantity:": "Quantité :",
"Total of my": "Total de mes",
"Total of my article": "Total de mon article",
"articles": "articles",
"Excluding delivery": "Hors livraison",
"Trash my cart": "Vider mon panier",
"Cart cleaned.": "Panier vidé.",
"View my cart": "Voir mon panier",
"Item deleted.": "Produit supprimé.",
Et on obtient le visuel :
Vérifiez que tout fonctionne : ajout d'un article, suppression d'un article, vidage du panier. Pour le moment le bouton pour voir le panier n'est actif.
Le panier
Il ne nous reste plus qu'à prévoir une page pour le panier avec un nouveau composant Volt :
php artisan make:volt cart --class
Avec ce code :
<?php
use Livewire\Volt\Component;
use Darryldecode\Cart\CartCollection;
new class extends Component {
public int $CartItems = 0;
public CartCollection $content;
public float $total;
public array $quantities = [];
public function mount(): void
{
$this->refreshCart();
foreach ($this->content as $item) {
$this->quantities[$item->id] = $item->quantity;
}
}
public function updateQuantity($itemId)
{
Cart::update($itemId, [
'quantity' => ['relative' => false, 'value' => $this->quantities[$itemId]],
]);
$this->refreshCart();
}
public function deleteItem($itemId)
{
Cart::remove($itemId);
$this->refreshCart();
}
public function cleanCart(): void
{
Cart::clear();
$this->refreshCart();
}
private function refreshCart(): void
{
$this->CartItems = Cart::getTotalQuantity();
$this->content = Cart::getContent();
$this->total = Cart::getTotal();
}
}; ?>
<div class="flex justify-center mt-6">
<x-card class="w-full md:w-3/4" title="{{ __('My cart') }}" shadow separator progress-indicator>
@if(session()->has('message'))
<x-alert title="{!! trans(session('message')) !!}" icon="o-exclamation-triangle" class="alert-warning" /><br>
@endif
@foreach ($content as $item)
<div class="flex justify-between items-center">
<div class="flex gap-4 justify-between items-center mt-2">
<img class="w-[60px]" src="{{ asset('storage/photos/' . $item->associatedModel->image) }}" alt="" />
<div>
<span class="font-bold">{{ $item->name }}</span><br>
{{ number_format($item->quantity * $item->price, 2, ',', ' ') }} €
</div>
</div>
<div class="flex gap-2 justify-between items-center">
<x-input class="!w-[80px]" wire:model="quantities.{{ $item->id }}" type="number" min="1" wire:change="updateQuantity({{ $item->id }})" />
<x-button icon="o-trash" wire:click="deleteItem({{ $item->id }})"
class="text-red-500 btn-circle btn-ghost" />
</div>
</div>
<br>
<hr>
@endforeach
@if ($content->isNotEmpty())
<br>
<div class="flex justify-between items-center">
<div>
@if($CartItems > 1)
@lang('Total of my') {{ $CartItems }} @lang('articles')
@else
@lang('Total of my article')
@endif
</div>
<div class="font-bold">{{ number_format($total, 2, ',', ' ') }} € TTC </div>
</div>
<div class="flex justify-end mb-4">
<em>@lang('Excluding delivery')</em>
</div>
<hr>
<div class="flex justify-between items-center mt-4">
<x-button label="{{ __('Trash my cart') }}" wire:click="cleanCart" icon="o-trash"
class="text-red-500 btn-ghost btn-sm" />
<x-button label="{{ auth()->check() ? __('I order') : __('Log in to order') }}" icon-right="c-arrow-right" link="" class="btn-primary btn-sm" />
</div>
@else
@lang('The cart is empty.')
@endif
</x-card>
</div>
Une route :
Volt::route('/cart', 'cart')->name('cart');
Des traductions :
"My cart": "Mon panier",
"The cart is empty.": "Le panier est vide.",
"I order": "Je commande",
On ajoute le lien dans le bouton au niveau de la barre de navigation :
<x-button label="{{ __('View my cart') }}" link="{{ route('cart') }}" icon-right="c-arrow-right" class="btn-primary btn-sm" />
Et si tout va bien, vous obtenez le panier sur la nouvelle page :
Là encore, vérifiez que tout fonctionne.
Conclusion
On a maintenant une boutique qui sait afficher ses produits, montrer le détail d'un produit, qui 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.
Par bestmomo
Aucun commentaire