Laravel 7

Shopping : le compte client 1/3

Après avoir codé la boutique, les commandes le paiement et la facturation il nous reste encore le compte client à créer pour qu’il puisse mettre à jour ses informations personnelles, ses adresses, accéder au détail de ses commandes et à toutes ses données pour satisfaire les exigences du RGPD.

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

Un contrôleur et sa route

On va créer un contrôleur pour l’accès au compte client :

php artisan make:controller AccountController

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class AccountController extends Controller
{
    public function __invoke(Request $request)
    {
        $orders = $request->user()->orders()->count();

        return view('account.index', compact('orders'));
    }
}

On a une seule méthode. On regarde si le client a des commandes pour afficher le bouton d’accès à ces commandes.

On crée une route pour accéder à la méthode :

// Utilisateur authentifié
Route::middleware('auth')->group(function () {
    // Gestion du compte
    Route::prefix('compte')->group(function () {
        Route::name('account')->get('/', 'AccountController');
    });
    // Commandes
    Route::prefix('commandes')->group(function () {
...

La vue principale du compte

On crée une vue pour afficher le compte :

@extends('layouts.app')

@section('content')
<div class="container" id="account">
  <h2>Mon compte</h2>
  <div class="row">
    <div class="col s12 m6"><a href="#" class="btn-large"><i class="material-icons left">person</i>Mes Données personnelles</a></div>
    <div class="col s12 m6"><a href="#" class="btn-large"><i class="material-icons left">location_on</i>Mes Adresses</a></div>
    <div class="col s12 m6"><a href="#" class="btn-large @unless($orders) disabled @endif"><i class="material-icons left">shopping_cart</i>Mes Commandes</a></div>
    <div class="col s12 m6"><a href="#" class="btn-large"><i class="material-icons left">visibility</i>RGPD</a></div>  
  </div>
</div>
@endsection

On y accède avec l’url …/compte.

Pour le moment aucun lien n’est renseigné.

Bon là on a besoin de quelques règles de style (à ajouter à sass/app.scss) :

.btn-large {
  margin-bottom: 30px;
}

#account a {
  width: 100%;
}

Voilà qui est plus esthétique !

Le layout

On va ajouter un lien dans la menu pour accéder au compte (vue layouts/app.blade.php) :

@guest        
  ...
@else
  <li><a class="tooltipped" href="{{ route('account') }}" data-position="bottom" data-tooltip="Voir mon compte client">{{ auth()->user()->firstname . ' ' . auth()->user()->name }}</a></li>
  <li><a href="{{ route('logout') }}"
    ...
  </a></li>
@endguest

Il faut mettre ce code aussi pour le menu latéral en mode mobile.

On a maintenant le nom du client qui apparaît et un tooltip pour l’informer :

Et en mode mobile :

Des messages en configuration

Pour mieux gérer les messages de la boutique on va en prévoir en configuration, ils seront ainsi centralisés :

Et on prévoit déjà ces deux messages :

<?php

return [
    'newletter' => "Je désire recevoir votre lettre d'information",
    'accept' => "J'accepte les termes et conditions de la politique de confidentialité.",
    'accountupdated' => 'Vos informations ont bien été mises à jour.',
];

Il vont nous servir très bientôt…

Les informations personnelles

On va autoriser le client à modifier ses informations personnelles :

  • nom
  • prénom
  • email
  • inscription à la lettre d’information

Le formulaire

On crée un nouveau contrôleur :

php artisan make:controller IdentiteController

Et on ajoute la méthode pour afficher le formulaire :

public function edit(Request $request)
{
    return view('account.identite', ['user' => $request->user()]);
}

On ajoute une route :

Route::middleware('auth')->group(function () {
    // Gestion du compte
    Route::prefix('compte')->group(function () {
        ..
        Route::name('identite.edit')->get('identite', 'IdentiteController@edit');
    });

Dans la vue account.index on ajoute le lien :

<div class="col s12 m6"><a href="{{ route('identite.edit') }}" class="btn-large"><i class="material-icons left">person</i>Mes Données personnelles</a></div>

On crée une nouvelle vue pour le formulaire :

Et on prévoit ce code :

@extends('layouts.app')

@section('content')
<div class="container">
  <h2>Mes informations personnelles</h2>
  <div class="row">
    <div class="col s12 m10 offset-m1 l8 offset-l2">
      <div class="card">
        <form  method="POST" action="#">
          @csrf
          @method('PUT')

          @if(session()->has('message'))
            <div class="col s12">
              <div class="card purple darken-3">
                <div class="card-content white-text center-align">
                  {{ session('message') }}
                </div>
              </div>
            </div>
          @endif

          <div class="card-content">
            
            <x-input
              name="firstname"
              type="text"
              icon="person"
              label="Prénom"
              required="true"
              :value="$user->firstname"
            ></x-input>

            <x-input
              name="name"
              type="text"
              icon="person"
              label="Nom"
              required="true"
              :value="$user->name"
            ></x-input>

            <x-input
              name="email"
              type="email"
              icon="mail"
              label="Adresse mail"
              required="true"
              :value="$user->email"
            ></x-input>

            <div class="row col s12">
              <label>
                <input type="checkbox" name="newsletter" id="newsletter" {{ old('newsletter', $user->newsletter) ? 'checked' : '' }}>
                <span>{{ config('messages.newletter') }}</span>
              </label>
            </div>

            <div class="row col s12">
              <label>
                <input type="checkbox" name="rgpd" id="rgpd" {{ old('rgpd') ? 'checked' : '' }}>
                <span>{{ config('messages.accept') }}</span>
              </label>
            </div>
            
            <p>
              <button class="btn waves-effect waves-light disabled" style="width: 100%" type="submit" name="action">
                Enregistrer
              </button>
            </p>

          </div>
        </form>
      </div>
    </div>
  </div>
  <div class="row">
    <a class="waves-effect waves-light btn" href="{{ route('account') }}"> <i class="material-icons left">chevron_left</i>Retour à mon compte</a>
  </div>
</div>
@endsection

@section('javascript')
  <script>
    document.addEventListener('DOMContentLoaded', function() {
      const rgpd = document.querySelector('#rgpd');
      rgpd.checked = false;
      rgpd.addEventListener('click', () => document.querySelector('button[type=submit]').classList.toggle('disabled'));
    });
  </script>
@endsection

Normalement vous devriez obtenir ce formulaire :

La mise à jour

Maintenant qu’on a un formulaire on va ajouter la gestion de la mise à jour des informations.

On ajoute la méthode update au contrôleur :

public function update(Request $request)
{
    $user = $request->user();
    
    $request->validate([
        'name' => 'required|string|max:255',
        'firstname' => 'required|string|max:255',
        'email' => 'required|string|max:255|unique:users,email,' . $user->id,
    ]);

    $request->merge(['newsletter' => $request->has('newsletter')]);

    $user->update($request->all());

    $request->session()->flash('message', config('messages.accountupdated'));

    return back();
}

Une validation, on ajoute l’information pour la lettre d’information et on flashe un message.

On ajoute la route :

Route::middleware('auth')->group(function () {
    // Gestion du compte
    Route::prefix('compte')->group(function () {
        ...
        Route::name('identite.edit')->get('identite', 'IdentiteController@edit');
        Route::name('identite.update')->put('identite', 'IdentiteController@update');
    });

Et le lien dans le formulaire :

<form  method="POST" action="{{ route('identite.update') }}">

On peut vérifier que la validation fonctionne :

Si la validation passe on a le message rassurant :

Le RGPD

Le RGPD est encore un truc administratif qui nous occupe beaucoup ! Le client doit pouvoir accéder à toutes les informations qu’on possède sur lui.

La page d’informations

On ajoute une méthode dans le contrôleur IdentiteController :

use App\Models\Shop;

...

public function rgpd(Request $request)
{
    $email = Shop::select('email')->firstOrFail()->email;

    return view('account.rgpd.index', compact('email'));
}

On ajoute la route :

Route::middleware('auth')->group(function () {
    // Gestion du compte
    Route::prefix('compte')->group(function () {
        ...
        Route::name('rgpd')->get('rgpd', 'IdentiteController@rgpd');
    });

Dans la vue account.index on ajoute le lien :

<div class="col s12 m6"><a href="{{ route('rgpd') }}" class="btn-large"><i class="material-icons left">visibility</i>RGPD</a></div>

On crée une nouvelle vue pour la page :

@extends('layouts.app')

@section('content')
<div class="container">
  <h2>RGPD</h2>
  <div class="row">
    <div class="card">
      <div class="card-content">
        <ul class="collapsible">
          <li>
            <div class="collapsible-header"><i class="material-icons">info</i>Accès à mes informations</div>
            <div class="collapsible-body informations">
              <ul>
                <li class="center-align">{{ config('messages.rgpd') }}</li>
                <br>
                <li><a href="#" class="waves-effect waves-light btn" style="width: 100%">Récupérer mes informations</a></li>
              </ul>
            </div>
          </li>
          <li>
            <div class="collapsible-header"><i class="material-icons">edit</i>Rectification des erreurs</div>
            <div class="collapsible-body informations">
              <ul>
              <li class="center-align">Vous pouvez modifier toutes les informations personnelles accessibles depuis la page de votre compte : identité, adresses. Pour toute autre rectification contactez nous en nous envoyant un e-mail à cette adresse : {{ $email }}. Nous vous répondrons dans les plus brefs délais.</li>
              </ul>
            </div>
          </li>
        </ul>
      </div>
    </div>
  </div>
  <div class="row">
    <a class="waves-effect waves-light btn" href="{{ route('account') }}"> <i class="material-icons left">chevron_left</i>Retour à mon compte</a>
  </div>
</div>
@endsection

Dans config.messages on ajoute ce texte :

return [
    ...
    'rgpd' => "Vous avez la possibilité d'accéder à vos informations personnelles mémorisées dans notre base de données. Vous avez juste à cliquer sur le bouton ci-dessous pour télécharger un document PDF comportant la totalité de vos données.",
];

La page maintenant s’affiche quand on clique sur le bouton :

On ouvre le premier onglet :

Le bouton n’est pas encore fonctionnel, on va s’en occuper plus loin.

Le second onglet ne comporte que du texte :

On pourrait rendre l’email cliquable mais bon, ce n’est pas un email qui va être utilisé bien souvent !

Un PDF pour les détails

Pour les détails accessibles en cliquant sur le bouton vu ci-dessus on va créer un PDF. Pour ajouter cette fonctionnalité à Laravel on va ajouter cet excellent package qui utilise dompdf :

composer require barryvdh/laravel-dompdf

On va se contenter de la configuration par défaut.

On va générer un PDF temporaire et le télécharger automatiquement au click.

On complète le contrôleur IdentiteController :

use PDF;

...

public function pdf(Request $request)
{
    $user = $request->user();

    $user->load('addresses', 'orders', 'orders.state', 'orders.products');

    $shop= Shop::firstOrFail();

    $pdf = PDF::loadView('account.rgpd.pdf', compact('user', 'shop'));
    
    return $pdf->download('rgpd.pdf');        
}

On crée le PDF en utilisant une vue et en lui envoyant les données, ensuite la méthode download permet l’envoi du PDF.

Il nous faut une route :

Route::middleware('auth')->group(function () {
    // Gestion du compte
    Route::prefix('compte')->group(function () {
        ...
        Route::name('rgpd.pdf')->get('rgpd/pdf', 'IdentiteController@pdf');

On ajoute le lien pour le bouton dans la vue rgpd.index :

<li><a href="{{ route('rgpd.pdf') }}" class="waves-effect waves-light btn" style="width: 100%">Récupérer mes informations</a></li>

On crée une nouvelle vue pour la génération du PDF :

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
  </head>
  <body>
    <h2>{{ $shop->name }}</h2>
    <p>{{ $user->name }} {{ $user->firstname }}</p>
    <p>{{ \Carbon\Carbon::now() }}</p>
    <h3>Informations générales</h3>
    <table class="table">
      <thead>
        <tr>
          <th scope="col">Nom</th>
          <th scope="col">Prénom</th>
          <th scope="col">E-mail</th>
          <th scope="col">Création du compte</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>{{ $user->name }}</td>
          <td>{{ $user->firstname }}</td>
          <td>{{ $user->email }}</td>
          <td>{{ $user->created_at->calendar() }}</td>
        </tr>
      </tbody>
    </table>
    <h3>Adresses</h3>
    @foreach($user->addresses as $address)
      <table class="table table-bordered table-striped table-sm">
        <tbody>
          @isset($address->name)
            <tr>
              <td><strong>Nom</strong></td>
              <td>{{ "$address->civility $address->name $address->firstname" }}</td>
            </tr>
          @endisset
          @if($address->company)
            <tr>
              <td><strong>Entreprise</strong></td>
              <td>{{ $address->company }}</td>
            </tr>          
          @endif 
          <tr>
            <td><strong>Adresse</strong></td>
            <td>{{ $address->address }}</td>
          </tr>
          @if($address->addressbis)
            <tr>
              <td><strong>Adresse complément</strong></td>
              <td>{{ $address->addressbis }}</td>
            </tr>     
          @endif
          @if($address->bp)
            <tr>
              <td><strong>Boîte postale</strong></td>
              <td>{{ $address->bp }}</td>
            </tr>
          @endif
          <tr>
            <td><strong>Ville</strong></td>
            <td>{{ "$address->postal $address->city" }}</td>
          </tr>
          <tr>
            <td><strong>Pays</strong></td>
            <td>{{ $address->country->name }}</td>
          </tr>
          <tr>
            <td><strong>Téléphone</strong></td>
            <td>{{ $address->phone }}</td>
          </tr>
        </tbody>
      </table>
    @endforeach
    <h3>Commandes</h3>
    @foreach($user->orders as $order)
      <table class="table table-bordered table-striped table-sm">
        <thead>
          <tr>
            <th>Référence</th>
            <th>Date</th>
            <th>Prix total</th>
            <th>Paiement</th>
            <th>État</th>
          </tr>
        </thead>    
        <tbody>        
          <tr>
            <td>{{ $order->reference }}</td>
            <td>{{ $order->created_at->calendar() }}</td>
            <td>{{ number_format($order->total + $order->shipping, 2, ',', ' ') }} €</td>
            <td>{{ $order->payment_text }}</td>
            <td>{{ $order->state->name }}</td>
          </tr>
        </tbody>
      </table>
      <h5>Détails de la commande</h5>
      <table class="table table-bordered table-striped table-sm">
        @foreach ($order->products as $item)
          <tr>
            <td>{{ $item->name }} ({{ $item->quantity }} @if($item->quantity > 1) exemplaires) @else exemplaire) @endif</td>
            <td>{{ number_format($item->total_price_gross, 2, ',', ' ') }} €</td>
          </tr>
        @endforeach
        <tr>
          <td>Livraison en Colissimo</td>
          <td>{{ number_format($order->shipping, 2, ',', ' ') }}€</td>
        </tr>
        <tr>
          <td>Total TTC (TVA à {{ $item->tax * 100 }} %)</td>
          <td>{{ number_format($order->total + $order->shipping, 2, ',', ' ') }} €</td>
        </tr>
      </table>
      <hr>
    @endforeach
    <h3>Lettre d'information</h3>
    @if($user->newsletter)
      <p>Vous êtes inscrit à la lettre d'information.</p>
    @else
      <p>Vous n'êtes pas inscrit à la lettre d'information.</p>
    @endif
  </body>
</html>

On peut maintenant cliquer sur le bouton et on reçoit un PDF :

Ce n’est pas du grand art mais ça fait le taf. Je suis preneur pour toute amélioration 🙂

Conclusion

Il nous reste encore du travail avec le compte client. Dans le prochain article on va s’occuper des adresses.

 

 

Print Friendly, PDF & Email

5 commentaires

Leave a Reply