Un site d’annonces – l’administration (1/2)
Maintenant que la partie client est pratiquement terminée nous allons nous consacrer à l’administration du site. L’administrateur a pour tâche de modérer les annonces et certains messages, d’autre part il doit gérer les annonces obsolètes.
Pour vous simplifier la vie vous pouvez télécharger le dossier complet pour le code de cet article.
sb-admin-2
Pour ce projet j’ai opté pour sb-admin-2 comme template :
Il est basé sur Bootstrap 4 qu’on utilise déjà et je le trouve plutôt esthétique.
Vous pouvez télécharger les fichiers à partir du site. Ensuite vous copiez le contenu du dossier scss dans un dossier sb-admin-2 de notre projet :
De la même manière on copie le fichier Javascript sb-admin-2.js dans le projet :
Au début de ce ficheir on va ajouter l’appel à Popper, JQuery et Bootstrap :
window.Popper = require('popper.js').default; window.$ = window.jQuery = require('jquery'); require('bootstrap'); ...
Enfin pour compiler tout ça on complète le fichier webpack.mix.js :
mix.js('resources/js/sb-admin-2.js', 'public/js') .sass('resources/sass/sb-admin-2/sb-admin-2.scss', 'public/css');
Et on compile !
Nos assets sont maintenant prêts…
Routes et contrôleur
Pour l’administration on va créer un contrôleur :
php artisan make:controller AdminController
On va déjà créer la méthode index et déclarer les deux repositories qu’on a déjà créés :
<?php namespace App\Http\Controllers; use Carbon\Carbon; use Illuminate\Http\Request; use App\Repositories\ { AdRepository, MessageRepository }; class AdminController extends Controller { protected $adRepository; protected $messagerepository; public function __construct(AdRepository $adRepository, Messagerepository $messagerepository) { $this->adRepository = $adRepository; $this->messagerepository = $messagerepository; } public function index() { return view('admin.index'); } }
On ajoute aussi une route en préparant un groupe pour l’administration :
Route::prefix('admin')->middleware('admin')->group(function () { Route::get('/', 'AdminController@index')->name('admin.index'); });
On crée le middleware admin :
php artisan make:middleware Admin
public function handle($request, Closure $next) { $user = $request->user(); if ($user && $user->admin) { return $next($request); } return redirect()->route('home'); }
Et on le déclare dans le Kernel :
protected $routeMiddleware = [ ... 'admin' => \App\Http\Middleware\Admin::class, ];
On va se contenter de ça pour le moment.
L’accès à l’administration
On va prévoir un accès à l’administration à partir de la page d’accueil dans la barre de menu.
Dans le provider AppServiceProvider on va ajouter une instruction pour Blade pour sélectionner les administrateurs. J’en profite pour ajouter la francisation de la ressource que j’ai oubliée dans un précédent article :
use Illuminate\Support\Facades\Blade; use Illuminate\Support\Facades\Route; ... public function boot() { Blade::if ('admin', function () { return auth()->check() && auth()->user()->admin; }); Route::resourceVerbs([ 'create' => 'creer', 'edit' => 'modifier', ]); }
Dans le layout app on ajoute le lien de l’administration en le conditionnant avec l’instruction qu’on vient de créer :
<div class="collapse navbar-collapse" id="navbarSupportedContent"> <!-- Right Side Of Navbar --> <ul class="navbar-nav ml-auto"> <!-- Authentication Links --> @guest <li class="nav-item"> <a class="nav-link" href="{{ route('login') }}">Connexion</a> </li> @else @admin <li class="nav-item"> <a class="nav-link" href="{{ route('admin.index') }}">Administration</a> </li> @endadmin <li class="nav-item"> <a class="nav-link" href="{{ route('logout') }}" onclick="event.preventDefault(); document.getElementById('logout-form').submit();"> Déconnexion </a> <form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;"> @csrf </form> </li> @endguest </ul> </div>
Le layout de l’administration
On crée un layout (admin) pour les vues de l’administration accompagné de deux vues pour le haut de la page (back-head) et le base de la page (back-footer) :
Cette organisation est due au fait qu’on utilisera aussi les deux vues annexes pour le profil utilisateur.
Vue back-head
Voici le code de l’entête :
<!DOCTYPE html> <html lang="fr"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="description" content=""> <meta name="author" content=""> <!-- CSRF Token --> <meta name="csrf-token" content="{{ csrf_token() }}"> <title>Annonces</title> <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css"> <link href="https://fonts.googleapis.com/css?family=Nunito:200,200i,300,300i,400,400i,600,600i,700,700i,800,800i,900,900i" rel="stylesheet"> <link href="{{ asset('css/sb-admin-2.css') }}" rel="stylesheet"> </head>
Vue back-footer
Voici le code du bas de page :
<script src="{{ asset('js/sb-admin-2.js') }}"></script> <script> $(function () { $('[data-toggle="tooltip"]').tooltip(); $.ajaxSetup({ headers: { 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') } }); }) </script> @yield('script') </body> </html>
Vue admin
Et enfin le layout dont le code est extrait de celui de sb-admin-2 :
@include('layouts.back-head') <body id="page-top"> <!-- Page Wrapper --> <div id="wrapper"> <!-- Sidebar --> <ul class="navbar-nav bg-gradient-primary sidebar sidebar-dark accordion" id="accordionSidebar"> <!-- Sidebar - Brand --> <a class="sidebar-brand d-flex align-items-center justify-content-center" href="{{ url('/') }}"> <div class="sidebar-brand-icon rotate-n-15"> <i class="fab fa-earlybirds fa-2x"></i> </div> <div class="sidebar-brand-text mx-3">Annonces</div> </a> <!-- Divider --> <hr class="sidebar-divider my-0"> <!-- Nav Item - Dashboard --> <li class="nav-item @if(request()->route()->getName() == 'admin.index') active @endif"> <a class="nav-link" href="{{ route('admin.index') }}"> <i class="fas fa-fw fa-tachometer-alt"></i> <span>Panneau</span></a> </li> <!-- Divider --> <hr class="sidebar-divider"> <!-- Heading --> <div class="sidebar-heading"> Annonces </div> <li class="nav-item @if(request()->route()->getName() == 'admin.ads') active @endif"> <a class="nav-link" href=""> <i class="fas fa-fw fa-question"></i> <span>A modérer</span></a> </li> <li class="nav-item @if(request()->route()->getName() == 'admin.obsoletes') active @endif"> <a class="nav-link" href=""> <i class="fas fa-fw fa-hourglass-end"></i> <span>Obsolètes</span></a> </li> <!-- Divider --> <hr class="sidebar-divider"> <!-- Heading --> <div class="sidebar-heading"> Messages </div> <li class="nav-item @if(request()->route()->getName() == 'admin.messages') active @endif"> <a class="nav-link" href=""> <i class="fas fa-fw fa-question"></i> <span>A modérer</span></a> </li> <!-- Divider --> <hr class="sidebar-divider d-none d-md-block"> <!-- Sidebar Toggler (Sidebar) --> <div class="text-center d-none d-md-inline"> <button class="rounded-circle border-0" id="sidebarToggle"></button> </div> </ul> <!-- End of Sidebar --> <!-- Content Wrapper --> <div id="content-wrapper" class="d-flex flex-column"> <!-- Main Content --> <div id="content"> <!-- Topbar --> <nav class="navbar navbar-expand navbar-light bg-white topbar mb-4 static-top shadow"> <!-- Sidebar Toggle (Topbar) --> <button id="sidebarToggleTop" class="btn btn-link d-md-none rounded-circle mr-3"> <i class="fa fa-bars"></i> </button> <!-- Topbar Navbar --> <ul class="navbar-nav ml-auto"> <!-- Nav Item - User Information --> <li class="nav-item dropdown no-arrow"> <a class="nav-link dropdown-toggle" href="#" id="userDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <span class="d-lg-inline text-gray-600 small">{{ auth()->user()->name }}</span> </a> <!-- Dropdown - User Information --> <div class="dropdown-menu dropdown-menu-right shadow animated--grow-in" aria-labelledby="userDropdown"> <div class="dropdown-divider"></div> <a class="dropdown-item" href="{{ route('logout') }}" onclick="event.preventDefault(); document.getElementById('logout-form').submit();"> <i class="fas fa-sign-out-alt fa-sm fa-fw mr-2 text-gray-400"></i> Déconnexion </a> <form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;"> @csrf </form> </div> </li> </ul> </nav> <!-- End of Topbar --> <!-- Begin Page Content --> <div class="container-fluid"> @yield('content') </div> <!-- /.container-fluid --> </div> <!-- End of Main Content --> <!-- Footer --> <footer class="sticky-footer bg-white"> <div class="container my-auto"> <div class="copyright text-center my-auto"> <span>Copyright © Annonces 2019</span> </div> </div> </footer> <!-- End of Footer --> </div> <!-- End of Content Wrapper --> </div> <!-- End of Page Wrapper --> @include('layouts.back-footer')
Vue index
Enfin on crée un dossier admin et la vue index qui est l’accueil de l’administration :
@extends('layouts.admin') @section('content') <!-- Page Heading --> <div class="d-sm-flex align-items-center justify-content-between mb-4"> <h1 class="h3 mb-0 text-gray-800">Tableau de bord</h1> </div> <!-- Content Row --> <div class="row"> <div class="col-xl-4 col-md-6 mb-4"> <div class="card border-left-primary shadow h-100 py-2"> <div class="card-body"> <div class="row no-gutters align-items-center"> <div class="col mr-2"> <div class="text-xs font-weight-bold text-primary text-uppercase mb-1">Annonces à modérer</div> <div class="h5 mb-0 font-weight-bold text-gray-800"></div> </div> <div class="col-auto"> <i class="fas fa-cog fa-2x text-gray-300"></i> </div> </div> </div> </div> </div> <div class="col-xl-4 col-md-6 mb-4"> <div class="card border-left-primary shadow h-100 py-2"> <div class="card-body"> <div class="row no-gutters align-items-center"> <div class="col mr-2"> <div class="text-xs font-weight-bold text-primary text-uppercase mb-1">Messages à modérer</div> <div class="h5 mb-0 font-weight-bold text-gray-800"></div> </div> <div class="col-auto"> <i class="fas fa-cog fa-2x text-gray-300"></i> </div> </div> </div> </div> </div> <div class="col-xl-4 col-md-6 mb-4"> <div class="card border-left-warning shadow h-100 py-2"> <div class="card-body"> <div class="row no-gutters align-items-center"> <div class="col mr-2"> <div class="text-xs font-weight-bold text-warning text-uppercase mb-1">Annonces obsolètes</div> <div class="h5 mb-0 font-weight-bold text-gray-800"></div> </div> <div class="col-auto"> <i class="fas fa-hourglass-end fa-2x text-gray-300"></i> </div> </div> </div> </div> </div> @endsection
L’accueil
Maintenant lorsqu’un administrateur de connecte il a le lien vers l’adminstration sur la page d’accueil du site :
Et si on clique on arrive enfin sur la page de l’administration :
On a maintenant notre squelette en place, il ne reste plus qu’à coder tout ça !
On va commencer par renseigner les cartes avec le nombre d’éléments de chaque catégorie.
AdRepository
Dans ce repository on va ajouter ces deux méthodes pour le comptage des annonces non actives et obsolètes :
public function noActiveCount($ads = null) { if($ads) { return $ads->where('active', false)->count(); } return Ad::where('active', false)->count(); } public function obsoleteCount($ads = null) { if($ads) { return $ads->where('active', true)->where('limit', '<', Carbon::now())->count(); } return Ad::where('limit', '<', Carbon::now())->count(); }
MessageRepository
De la même manière on crée cette méthode dans ce repository pour compter les message à modérer :
public function count() { return Message::count(); }
AdminController
On met à jour la méthode index du contrôleur :
public function index() { $adModerationCount = $this->adRepository->noActiveCount(); $adPerimesCount = $this->adRepository->obsoleteCount(); $messageModerationCount = $this->messagerepository->count(); return view('admin.index', compact('adModerationCount', 'messageModerationCount', 'adPerimesCount')); }
Vue index
Il ne nous reste plus qu’à ajouter les variables dans la vue index :
... <div class="h5 mb-0 font-weight-bold text-gray-800">{{ $adModerationCount }}</div> ... <div class="h5 mb-0 font-weight-bold text-gray-800">{{ $messageModerationCount }}</div> ... <div class="h5 mb-0 font-weight-bold text-gray-800">{{ $adPerimesCount }}</div> ...
Maintenant on obtient le nombre pour chaque catégorie :
Annonces à modérer
Les routes
On doit ajouter des routes pour la modération des annonces :
- accès à la liste
- approbation
- refus
Les voici :
Route::prefix('admin')->middleware('admin')->group(function () { ... Route::prefix('annonces')->group(function () { Route::get('/', 'AdminController@ads')->name('admin.ads'); Route::middleware('ajax')->group(function () { Route::post('approve/{ad}', 'AdminController@approve')->name('admin.approve'); Route::post('refuse', 'AdminController@refuse')->name('admin.refuse'); }); }); });
On avait déjà le groupe admin, on y inclus un groupe annonces. On voit que l’approbation et le refus se feront en ajax, on utilise le middleware qu’on a déjà créé.
AdRespository
Dans le repository on ajoute une méthode pour récupérer les annonces non actives paginées classées par date :
public function noActive($nbr) { return Ad::whereActive(false)->latest()->paginate($nbr); }
AdminController
Dans le contrôleur on utilise le repository pour envoyer les données dans la vue :
public function ads() { $adModeration = $this->adRepository->noActive(5); return view('admin.ads', compact('adModeration')); }
Vue admin.ads
On crée la vue ads :
@extends('layouts.admin') @section('content') @include('partials.message', ['url' => route('admin.refuse')]) @include('partials.alerts', ['title' => 'Annonces à modérer']) <div class="table-responsive"> <table class="table table-hover"> <thead class="thead-light"> <tr> <th scope="col">Titre</th> <th scope="col"></th> </tr> </thead> <tbody> @foreach ($adModeration as $ad) <tr id="{{ $ad->id }}"> <td>{{ $ad->title }}</td> <td class="float-right"> <a class="btn btn-primary btn-sm" href="{{ route('annonces.show', $ad->id) }}" target="_blank" role="button" data-toggle="tooltip" title="Voir l'annonce"><i class="fas fa-eye"></i></a> <a class="btn btn-success btn-sm" href="{{ route('admin.approve', $ad->id) }}" role="button" data-toggle="tooltip" title="Approuver l'annonce"><i class="fas fa-thumbs-up"></i></a> <i class="fas fa-spinner fa-pulse fa-lg" style="display: none"></i> <a class="btn btn-danger btn-sm" href="#" role="button" data-id="{{ $ad->id }}" data-toggle="tooltip" title="Refuser l'annonce"><i class="fas fa-thumbs-down"></i></a> </td> </tr> @endforeach </tbody> </table> </div> <div class="d-flex"> <div class="mx-auto"> {{ $adModeration->links() }} </div> </div> @endsection @section('script') @include('partials.script') @endsection
On fait appel à quelques vues partielles. On a déjà celle pour les messages, on va créer les deux autres :
Vue partielle alerts
Cette vue est destinée à afficher les alertes :
<div class="d-sm-flex align-items-center justify-content-between mb-4"> <h1 class="h3 mb-0 text-gray-800">{{ $title }}</h1> </div> @if(session()->has('status')) <div class="alert alert-success alert-dismissible fade show" role="alert"> {{ session('status') }} <button type="button" class="close" data-dismiss="alert" aria-label="Close"> <span aria-hidden="true">×</span> </button> </div> @endif <div class="alert alert-warning alert-dismissible fade d-none" role="alert"> Le serveur est inaccessible, veuillez retenter dans quelques minutes. <button type="button" class="close" aria-label="Close"> <span aria-hidden="true">×</span> </button> </div>
Vue partielle script
Dans cette vue on a le Javascript pour gérer les requêtes HTML, les alertes :
<script> $(() => { const toggleButtons = () => { $('#icon').toggle(); $('#buttons').toggle(); } $('.alert-success button').click(() => { $('.alert-success').addClass('d-none').removeClass('show'); }); $('.alert-warning button').click(() => { $('.alert-warning').addClass('d-none').removeClass('show'); }); $('.btn-success').click((e) => { e.preventDefault(); let that = $(e.currentTarget); that.hide(); that.closest('td').find('i.fa-spinner').show(); $.post(that.attr('href')) .done((data) => { document.location.reload(true); }) .fail(() => { $('.alert-warning').removeClass('d-none').addClass('show'); that.show(); that.closest('td').find('i.fa-spinner').hide(); }); }); $('.btn-danger').click((e) => { e.preventDefault(); $('#id').val($(e.currentTarget).attr('data-id')); $('#messageModal').modal(); }); $('#messageForm').submit((e) => { let that = e.currentTarget; e.preventDefault(); $('#message').removeClass('is-invalid'); $('.invalid-feedback').html(''); toggleButtons(); $.ajax({ method: $(that).attr('method'), url: $(that).attr('action'), data: $(that).serialize() }) .done((data) => { document.location.reload(true); }) .fail((data) => { toggleButtons(); if(data.status == 422) { $.each(data.responseJSON.errors, function (i, error) { $(document) .find('[name="' + i + '"]') .addClass('is-invalid') .next() .append(error[0]); }); } else { $('#messageModal').modal('hide'); $('.alert-warning').removeClass('d-none').addClass('show'); } }); }); }) </script>
Layout admin
Dans notre layout admin il faut ajouter le lien pour accéder à la vue des annonces à modérer (on n’avait pas renseigné le href) :
<li class="nav-item @if(request()->route()->getName() == 'admin.ads') active @endif"> <a class="nav-link" href="{{ route('admin.ads') }}"> <i class="fas fa-fw fa-question"></i> <span>A modérer</span></a> </li>
Le lien devient alors actif et en cliquant on arrive sur le page des annonces à modérer :
Pour chaque annonce on dispose de 3 icônes de commande pour :
- voir l’annonce
- approuver l’annonce
- refuser l’annonce
Le codage existait déjà pour la première action et nous n’avons donc à nous intéresser aux deux dernières.
Approuver une annonce
On va coder maintenant côté serveur pour l’approbation d’une annonce.
AdRepository
Approuver une annonce signifie mettre true dans le champ active. On crée donc une méthode dans le repository pour le faire :
public function approve($ad) { $ad->active = true; $ad->save(); }
Notification AdApprove
On va prévoir aussi une notification pour le rédacteur pour le prévenir que son annonce a été approuvée :
php artisan make:notification AdApprove
<?php namespace App\Notifications; use Illuminate\Bus\Queueable; use Illuminate\Notifications\Notification; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; use App\Models\Ad; class AdApprove extends Notification { use Queueable; protected $ad; public function __construct(Ad $ad) { $this->ad = $ad; } public function via($notifiable) { return ['mail']; } public function toMail($notifiable) { return (new MailMessage) ->line('Nous avons approuvé une annonce que vous avez déposée :') ->action('Voir votre annonce', route('annonces.show', $this->ad->id)) ->line("Merci d'utiliser notre site pour vos annonces !"); } public function toArray($notifiable) { return [ // ]; } }
AdminController
Il ne nous reste plus qu’à coder le contrôleur :
use App\Models\Ad; use App\Notifications\AdApprove; ... public function approve(Request $request, Ad $ad) { $this->adRepository->approve($ad); $request->session()->flash('status', "L'annonce a bien été approuvée et le rédacteur va être notifié."); $ad->notify(new AdApprove($ad)); return response()->json(['id' => $ad->id]); }
Fonctionnement
Voyons maintenant le fonctionnement pour l’approbation d’une annonce…
Dans la vue ads pour le lien d’approbation on a ce code :
<a class="btn btn-success btn-sm" href="{{ route('admin.approve', $ad->id) }}" role="button" data-toggle="tooltip" title="Approuver l'annonce"><i class="fas fa-thumbs-up"></i></a>
L’url générée est de la forme admin/annonces/approve/{annonce_id}. Comme on est en Ajax c’est le Javascript qui gère la requête :
$('.btn-success').click((e) => { e.preventDefault(); let that = $(e.currentTarget); that.hide(); that.closest('td').find('i.fa-spinner').show(); $.post(that.attr('href')) .done((data) => { document.location.reload(true); }) .fail(() => { $('.alert-warning').removeClass('d-none').addClass('show'); that.show(); that.closest('td').find('i.fa-spinner').hide(); }); });
Pendant la durée de la communication avec le serveur l’icône d’approbation est remplacée par une icône d’attente animée :
Dans le contrôleur on traite la requête avec ce code :
public function approve(Request $request, Ad $ad) { $this->adRepository->approve($ad); $request->session()->flash('status', "L'annonce a bien été approuvée et le rédacteur va être notifié."); $ad->notify(new AdApprove($ad)); return response()->json(['id' => $ad->id]); }
On approuve l’annonce, on prépare un message en flash session, on envoie la notification au rédacteur, on renvoie l’identifiant de l’annonce au client.
Si le serveur ne renvoie pas d’erreur la page est rechargée, ce qui actualise la liste des annonces, et l’alerte s’affiche :
Avec ce message :
Si le serveur retourne une erreur on affiche cette autre alerte :
Refuser une annonce
On va coder maintenant côté serveur pour le refus d’une annonce.
AdRepository
Refuser une annonce signifie la supprimer définitivement. On crée donc une méthode dans le repository pour le faire :
public function delete($ad) { $ad->delete(); }
Notification AdRefuse
On va prévoir aussi une notification pour le rédacteur pour le prévenir que son annonce a été refusée :
php artisan make:notification AdRefuse
<?php namespace App\Notifications; use Illuminate\Bus\Queueable; use Illuminate\Notifications\Notification; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; class AdRefuse extends Notification { use Queueable; protected $message; public function __construct($message) { $this->message = $message; } public function via($notifiable) { return ['mail']; } public function toMail($notifiable) { return (new MailMessage) ->line('Nous avons refusé une annonce que vous avez déposée pour la raison suivante :') ->line($this->message) ->line("Merci d'utiliser notre site pour vos annonces !"); } public function toArray($notifiable) { return [ // ]; } }
MessageRefuseRequest
Comme on va devoir envoyer un message à partir d’un formulaire on a besoin d’une Form Request :
php artisan make:request MessageRefuse
<?php namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; class MessageRefuse extends FormRequest { /** * Determine if the user is authorized to make this request. * * @return bool */ public function authorize() { return true; } /** * Get the validation rules that apply to the request. * * @return array */ public function rules() { return [ 'message' => ['required', 'string', 'max:500'], ]; } }
AdminController
Il ne nous reste plus qu’à coder le contrôleur :
use App\Notifications\ { AdApprove, AdRefuse }; use App\Http\Requests\MessageRefuse as MessageRefuseRequest; ... public function refuse(MessageRefuseRequest $request) { $ad = $this->adRepository->getById($request->id); $ad->notify(new AdRefuse($request->message)); $this->adRepository->delete($ad); $request->session()->flash('status', "L'annonce a bien été refusée et le rédacteur va être notifié."); return response()->json(['id' => $ad->id]); }
Fonctionnement
Voyons maintenant le fonctionnement pour l’approbation d’une annonce…
Dans la vue ads pour le lien de refus on a ce code :
<a class="btn btn-danger btn-sm" href="#" role="button" data-id="{{ $ad->id }}" data-toggle="tooltip" title="Refuser l'annonce"><i class="fas fa-thumbs-down"></i></a>
L’action va ouvrir une page modale :
$('.btn-danger').click((e) => { e.preventDefault(); $('#id').val($(e.currentTarget).attr('data-id')); $('#messageModal').modal(); });
En effet il faut expliquer au rédacteur pourquoi on refuse l’annonce.
La soumission est assurée par Javascript :
$('#messageForm').submit((e) => { let that = e.currentTarget; e.preventDefault(); $('#message').removeClass('is-invalid'); $('.invalid-feedback').html(''); toggleButtons(); $.ajax({ method: $(that).attr('method'), url: $(that).attr('action'), data: $(that).serialize() }) .done((data) => { document.location.reload(true); }) .fail((data) => { toggleButtons(); if(data.status == 422) { $.each(data.responseJSON.errors, function (i, error) { $(document) .find('[name="' + i + '"]') .addClass('is-invalid') .next() .append(error[0]); }); } else { $('#messageModal').modal('hide'); $('.alert-warning').removeClass('d-none').addClass('show'); } }); });
Le traitement de l’échec (fail) distingue deux cas :
- erreur de validation (code 422), dans ce cas on affiche l’erreur
- autre erreur du serveur, dans ce cas on efface la page modale et on affiche une alerte
La requête arrive dans la méthode refuse du contrôleur. La Form request assure la validation et on renvoie une erreur 422 avec le message de l’erreur au besoin :
Comme pour l’approbation de l’annonce on a des alertes de réussite ou d’échec.
Conclusion
Dans cette première partie de construciton de l’administration on a mis en place la structure de base et la page d’accueil. Nous avons aussi traité le cas de l’approbation et du refus des annonces. Dans le prochain article on terminera avec la gestion des annonces obsolètes et la modération des messages.
32 commentaires
zouboulba
est ce que quelqu’un a trouvé une solution pour « The GET method is not supported for this route. Supported methods: POST » ?
thijo
Bonsoir ,
quels sont les configurations à faire pour voir envoyer une email 🙂 j’ai fait comm le tutu mais j’arrive pas a voir l’email dans mailtrap 🙂 j’ai bien mis mes inputs de mailtrap dans fichier .env mais ca marche pas
merci d’avance 🙂
bestmomo
Salut,
Normalement il n’y a rien de plus à faire juste bien renseigner tout ça :
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=***
MAIL_PASSWORD=***
MAIL_FROM_ADDRESS=from@example.com
MAIL_FROM_NAME=Example
Dans Mailtrap il faut bien aller chercher les données pour Laravel dans leur menu déroulant.
thijo
Ca marche bien merci 😉
j’ai une autre question, j’ai ajouté un button avec class btn btn-warning pour demander à l’auteur de l’annonce de modifier son annonce. c’est la meme action que btn btn-danger dans le script ( j’ai cliqué le pop vient pour écrire un message et envoyer le lien de l’annonce à modifier ) . Dans la vue index je fais ca @include(‘partials.messageUpdate’, [‘url’ => route(‘admin.modifs’)])
@include(‘partials.message’, [‘url’ => route(‘admin.refuse’)])
@include(‘partials.alerts’, [‘title’ => ‘Annonces à valider’])
Cela fonctionne mais c’est celui déclaré en premier(admin.modifs) qui fonctionne et quand je cliqué sur le second(admin.refuse) ca déclenche une erreur No query results for model [App\Ads]. on dirait qu’il trouve plus l’id de l’Ad.
à note que les deux buttons ont le meme data-id data-id= »{{ $ads->id }} ».
J ai tout essayé de mon cote j arrive pas .) peut être ajouter un element dans le script
Help Me Pleaseeee ,
Merci d’avance
bestmomo
Salut,
Tu as bien ajouté le Javascript pour gérer cette action ?
thijo
Bonjour ,
je suis arrivé au moment d ‘approuver l’annoncé 😉 quand je clique sur le bouton like pour approuver j’ai l’url comme il admin/annonces/approve/{annonce_id}. mais il me sort l’erreur (‘The GET method is not supported for this route. Supported methods: POST.’) et dans mes routes c’est bien méthode post que j utilise
bestmomo
Bonjour,
Apparemment le Javascript ne fonctionne pas et le lien fonctionne de façon basique.
golli
bonjour
svp aidez moi
refuser l’annonces ne fonctionne pas il me dit « Le serveur est inaccessible, veuillez retenter dans quelques minutes. » et la validation fonctionne mais en me disant que le « Le serveur est inaccessible, veuillez retenter dans quelques minutes. » quand j’actualise il me dit que « L’annonce a bien été approuvée et le rédacteur va être notifié. »
bestmomo
Bonjour,
Il y a apparemment une erreur côté serveur, il faudrait regarder avec les outils du navigateur quelle est cette erreur.
golli
j’ai tout essayé tout mais toujours le méme message 🙁
golli
je pense un problem au niveau serveur mail
golli
failed to load resource: the server responded with a status of 500 (Internal Server Error) voila l’erreur au niveau navigateur
bestmomo
Tu dois avoir le détail de l’erreur dans les outils du navigateur.
golli
BONSOIR BESTMOMO
VOILA l’erreur:
il me dit : 500 (Internal Server Error)
elle vien du fichier sb-admin-2.js et de la ligne14624 » xhr.send( options.hasContent && options.data || null );
} catch ( e ) {«
bestmomo
Salut,
Si c’est une erreur serveur ça peut pas venir du Javascript, du moins pas directement. Il faut regarder le contenu de la réponse dans l’onglet Réseau (les appellations dépendent des navigateurs).
golli
oui c l’erreur dans l’onglet reseau
voila les etapes :
1 send // Do send the request (this may raise an exception)
xhr.send( options.hasContent && options.data || null );
2 ajax transport.send( requestHeaders, done );
$.ajax({
method: $(that).attr(‘method’),
url: $(that).attr(‘action’),
data: $(that).serialize()
})
3 ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle ||
handleObj.handler ).apply( matched.elem, args );
4 // Discard the second event of a jQuery.event.trigger() and
// when an event is called after a page has unloaded
return typeof jQuery !== « undefined » && jQuery.event.triggered !== e.type ?
jQuery.event.dispatch.apply( elem, arguments ) : undefined;
bestmomo
Oui mais ça me dit toujours pas ce que retourne le serveur…
golli
bonjour en mode développement chrome j’ai ça
Status Code: 500 Internal Server Error
bestmomo
Oui d’accord mais dans l’onglet « Réponse » tu dois avoir le détail de l’erreur, si tu es en mode DEBUG évidemment.
golli
voila la reponse de l’onglet reponse
{
« message »: « Expected response code 250 but got code \ »530\ », with message \ »530 5.7.1 Authentication required\r\n\ » »,
« exception »: « Swift_TransportException »,
« file »: « C:\\xampp\\mesannonces\\vendor\\swiftmailer\\swiftmailer\\lib\\classes\\Swift\\Transport\\AbstractSmtpTransport.php »,
« line »: 457,
« trace »: [
{
« file »: « C:\\xampp\\mesannonces\\vendor\\swiftmailer\\swiftmailer\\lib\\classes\\Swift\\Transport\\AbstractSmtpTransport.php »,
« line »: 341,
« function »: « assertResponseCode »,
« class »: « Swift_Transport_AbstractSmtpTransport »,
« type »: « -> »
},
{
…
{
« file »: « C:\\xampp\\mesannonces\\server.php »,
« line »: 21,
« function »: « require_once »
}
]
}
bestmomo
Salut,
Bon là on voit qu’il y a un erreur d’authentification SMTP (j’ai un peu raccourci ton message). Dans il faudrait vérifier le username et le mot de passe dans .env.
golli
bonsoir mon ami
toujours rien 🙁
il m’affiche cette erreur:
« message »: « Class ‘App\\Http\\Controllers\\MessageRefuse’ not found »,
« exception »: « Error »,
« file »: « C:\\xampp\\mesannonces\\app\\Http\\Controllers\\AdminController.php »,
« line »: 195,
« trace »: [
{
« function »: « MessageRefuse »,
« class »: « App\\Http\\Controllers\\AdminController »,
« type »: « -> »
},
mon code :
public function MessageRefuse(MessageRefuseRequest $request)
{
$message = $this->messagerepository->getById($request->id);
$ad = $this->messagerepository->getAd($message);
$ad->notify(new MessageRefuse($ad, $message, $request->$message));
$this->messagerepository->delete($message);
$request->session()->flash(‘status’, « Le message a bien été refusé et le rédacteur va être notifié. »);
return response()->json([‘id’ => $ad->id]);
}
bestmomo
Salut,
On dirait que c’est la classe App\Notifications\Messagerefuse qui n’est pas trouvée. Est-ce que cette classe existe et est-ce qu’il y a bien un use dans l’entête du contrôleur ?
golli
salut,
voila mon code :
public function MessageRefuse(MessageRefuseRequest $request)
{
$message = $this->messagerepository->getById($request->id);
$ad = $this->messagerepository->getAd($message);
$ad->notify(new MessageRefuse($ad, $message, $request->message));
$this->messagerepository->delete($message);
$request->session()->flash(‘status’, « Le message a bien été refusé et le rédacteur va être notifié. »);
return response()->json([‘id’ => $ad->id]);
}
bestmomo
Mais est-ce qu’il y a un use App\Notifications\Messagerefuse dans le contrôleur ?
golli
bonsoir
mon probleme maintenant
quand j’aprouve le message il l’envoi au proprietaire de l’annonce et non a l’expediteur dont j’ai remplie son adresse mail sans indiquer qui a envoyé le message c a dire il n’ya pas le mail de l’expediteur qui veux contacter l’annonceur
et quand je refuse le message c la même chose sauf normalement lémetteur reçois le refus et non l’annonceur !!!!! mais ici c le contraire le refus le reçois l’annonceur et non celui qui veux contacter ce dernier
merci pour votre aide mon ami
golli
bonjour mon ami
oui vous avez raison il manque le MessageRefuse
je vous tiendrai au courant si j’ai d’autre soucies
merciii beaucoup et svp s’il ya moyen de vous contacter par mail
mon mail : golliyoussef@gmail.com
mercii
Mody
Je vous prie d’agréer mes sincères salutations monsieur tes tutos ont étaient un véritable modèle et m’ont permis de faire des découvertes avec surtout de nouvelles découvertes.
j’ai eu seulement des petits bugs dus à l’installation des vues avec laravel.
Je suis vraiment ravi et c’est la dernière partie qui me reste concernant le site d’anonces
SN
En ce qui concerne l’approbation d’une annonce, on obtient cette erreur « The GET method is not supported for this route. Supported methods: POST »
bestmomo
Salut,
Apparemment le Javascript n’est pas actif parce que normalement le clic est intercepté par JQuery et c’est là que la requête est envoyée en POST.
bugtronik
Bonjour,
En ce qui concerne l’approbation ou le refus d’une annonce (pareil pour les messages) on obtient cette erreur
« Malformed UTF-8 characters, possibly incorrectly encoded » dans la console de débogage
J’ai vraiment tout essayer mais rien si vous pouvez ce serait vraiment cool, ce le seul truc qui me bloque
Et merci encore pour ce travail vous êtes super
lamine
Verifie bien ton serveur mail