Laravel 5

Un site d’annonces – la page d’accueil

On va poursuivre la construction du site d’annonces en créant la page d’accueil qui va comporter une carte de France interactive avec toutes les régions. On va prévoir une barre de navigation supérieure pour le menu (accueil, connexion/déconnexion, profil, administration…). Il nous faudra aussi un bouton pour la création d’une annonce. Enfin on va remanier et franciser les vues de l’authentification.

Pour vous simplifier la vie vous pouvez télécharger le dossier complet pour le code de cet article.

Les routes

Les routes par défaut sont celles-ci :

Route::get('/', function () {
    return view('welcome');
});

Auth::routes();

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

La ligne Auth::routes(); crée toutes les routes de l’authentification. Si on fait un petit état des lieux avec php artisan route:list on a :

On voit une route api pour les users. On n’en a pas besoin et on peut la supprimer dans le fichier routes\api.php.

Pour les routes de l’authentification on a des désignations anglaises dans les urls, ça ne me convient pas vraiment, alors plutôt que d’utiliser la ligne génératrice on va détailler les routes :

// Authentication Routes...
Route::prefix('connexion')->group(function () {
    Route::get('/', 'Auth\LoginController@showLoginForm')->name('login');
    Route::post('/', 'Auth\LoginController@login');
});
Route::post('deconnexion', 'Auth\LoginController@logout')->name('logout');

// Registration Routes...
Route::prefix('enregistrement')->group(function () {
    Route::get('/', 'Auth\RegisterController@showRegistrationForm')->name('register');
    Route::post('/', 'Auth\RegisterController@register');
});

// Password Reset Routes...
Route::prefix('passe')->group(function () {
    Route::get('change', 'Auth\ForgotPasswordController@showLinkRequestForm')->name('password.request');
    Route::post('email', 'Auth\ForgotPasswordController@sendResetLinkEmail')->name('password.email');
    Route::get('change/{token}', 'Auth\ResetPasswordController@showResetForm')->name('password.reset');
    Route::post('change', 'Auth\ResetPasswordController@reset')->name('password.update');
});

C’est évidemment plus lourd au niveau codage mais au moins on gère les désignations comme on veut ! Faisons le point :

Voilà qui est déjà plus adapté aux français !

On va aussi supprimer les deux autres routes et se contenter d’une route pour l’accueil qui envoie directement une vue :

Route::view('/', 'home')->name('home');

Bon évidemment avec ça on a tout cassé et on arrive sur cette page :

Le style

Par défaut Laravel utilise Mix pour compiler les assets. On a le fichier webpack.mix.js avec ce code :

const mix = require('laravel-mix');

mix.js('resources/js/app.js', 'public/js')
   .sass('resources/sass/app.scss', 'public/css');

Mix permet d’utiliser Webpack de façon simplifié, ce qui est une bonne chose parce que c’est rapidement une usine à gaz selon ce qu’on veut faire.

Là on voit bien qu’on va chercher les fichiers sass et js dans les ressources et qu’on les compile en les envoyant dans des dossiers publics.

Dans le fichier resources\sass\app.css on a ce code :

// Fonts
@import url('https://fonts.googleapis.com/css?family=Nunito');

// Variables
@import 'variables';

// Bootstrap
@import '~bootstrap/scss/bootstrap';

.navbar-laravel {
  background-color: #fff;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.04);
}

On va chercher une police Google, on importe des variables pour Bootstrap, on importe aussi Bootstrap et enfin on crée des règles de style pour la barre de navigation.

On va changer ça pour styliser le site. Pour commencer il faut installer les librairies avec npm :

npm install

Là ça prend un certain temps parce qu’il y a beaucoup de librairies à charger…

Les librairies sont installées dans ce dossier :

Pour que nos modifications soient prises en compte en temps réel on exécute :

npm run watch

La compilation se passe bien, on continue…

On va changer le code dans resources\sass\app.css :

// Fonts
@import url('https://fonts.googleapis.com/css?family=Sniglet');

// Bootstrap
@import '~bootstrap/scss/bootstrap';

html {
    font-size: 0.8rem;
}

@include media-breakpoint-up(sm) {
    html {
        font-size: 1rem;
    }
}

@include media-breakpoint-up(lg) {
    html {
        font-size: 1.4rem;
    }
}

body {
    background-color: #e47517;
    font-family: 'Sniglet', cursive;
}

.navbar {
    background-color: #e4751788;
}

.navbar-expand-md {
    @include font-size(1.4rem);
}

.navbar-expand {
    padding: 0;
    font-size: 0.8rem;
}

On supprime le fichier des variables pour ne garder que app.scss :

L’aspect a un peu changé :

Le Javascript

Au niveau de Javascript on a ces fichiers :

Là aussi on va faire un peu le ménage en ne gardant que app.js avec ce code :

window.Popper = require('popper.js').default;
window.$ = window.jQuery = require('jquery');
require('bootstrap');

Donc on charge Popper qui est nécessaire pour certains composants de Bootstrap qu’on va utiliser, on charge JQuery et Boostrap.

Au niveau des fichiers on n’a plus que ça :

On a supprimé également le composant de Vue.js. On en créera un dans un prochain article.

Le layout

Laravel est équipé d’un layout :

On y trouve la structure de base avec le chargement des librairies, une barre de navigation et un emplacement pour le contenu.

On va utiliser ce code :

<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- CSRF Token -->
    <meta name="csrf-token" content="{{ csrf_token() }}">

    <title>Les meilleures annonces</title>

    <!-- Styles -->
    <link href="{{ asset('css/app.css') }}" rel="stylesheet">
    @yield('css')
</head>
<body>
    <div>
        <nav class="navbar navbar-expand-md navbar-dark">
            <div class="container">
                <a class="navbar-brand" href="{{ url('/') }}">Les meilleures annonces</a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="{{ __('Toggle navigation') }}">
                    <span class="navbar-toggler-icon"></span>
                </button>

                <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
                            <li class="nav-item">
                                <a class="nav-link" href="{{ route('logout') }}"
                                    onclick="event.preventDefault(); document.getElementById('logout-form').submit();">
                                    Deconnexion
                                </a>
                                <form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">
                                    @csrf
                                </form>
                            </li>
                        @endguest
                    </ul>
                </div>
            </div>
        </nav>

        <main class="py-4">
            @yield('content')
        </main>

        <nav class="navbar navbar-expand fixed-bottom navbar-dark">
            <div class="navbar-nav ml-auto">
                <a class="nav-item nav-link " href="">Mentions légales</a>
                <a class="nav-item nav-link " href="">Politique de confidentialité</a>
            </div>
        </nav>

    </div>
    <script src="{{ asset('js/app.js') }}"></script>
    @yield('script')
</body>
</html>

J’ai apporté quelques petits changements d’apparence. Le texte de la barre de navigation est clair, ce qui me plait sur le fond orange foncé. J’ai aussi ajouté une barre fixe en bas de page pour les mentions légales et la politique de confidentialité.

La page d’accueil

C’est ici qu’on va avoir le plus de travail ! On veut une jolie carte de France interactive avec les régions et un bouton pour créer une annonce.

La façon la plus élégante de gérer ça est de créer une image en SVG. Pour ceux qui ne se sentent pas trop à l’aise avec le SVG j’ai écrit une série d’articles sur le sujet. J’ai dessiné une carte schématisée avec Inkscape pour l’intégrer dans la vue. Voilà le code pour la vue resources\views\home.blade.php :

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row">
        <div class="col-12 col-md-3 col-lg-3">
            <a class="btn btn-primary" href="" role="button">Déposer une annonce</a>
        </div>
        <div class="col-12 col-md-9 col-lg-9">
            <svg xmlns:svg="http://www.w3.org/2000/svg" viewBox="0 0 900 900">
                <a href="">
                    <path class="base" data-toggle="tooltip" title="Occitanie"
                        d="m 428.3,774.7 68.28824,1.41765 L 494.4,720.6 l 52.5,-38.8 14.8,7 25.1,-26 0.9,-38.1 -37.5,-8.3 -9.7,-29.4 -34.6,-24.3 -15.42353,24.04118 -15.61765,-21.68824 -21.22941,26.62353 -22.55294,-3.91765 -2.51176,-23.22941 -34.12942,-3.54706 L 351.5,638.6 293.45294,646.81765 302.82353,749.64706 356.4,744.2 Z"/>
                </a>
                <a href="">
                    <path class="base" data-toggle="tooltip" title="Pays de la Loire"
                        d="M 268.2,432.3 229.2353,441.52941 182.9,401.6 162.2,343.4 231.37059,312.44118 247.29412,255.67647 356.5,281.5 l -20.8,46.1 -30.75882,38.22353 -56.11765,14.82353 z""/>
                </a>
                <a href="">
                    <path class="base" data-toggle="tooltip" title="Bretagne"
                        d="M 226.05882,309.17647 157.23529,338.29411 41.82353,307.05882 20.647059,288 48.176471,281.64706 16.941177,259.94117 25.8,247 123.35294,218.64706 143.47059,248.29411 207.5,243.2 242,252.5 Z""/>
                </a>
                <a href="">
                    <path class="base" data-toggle="tooltip" title="Centre-Val de Loire"
                        d="m 313.41177,364.7647 c 9,7.41177 71.31764,68.52942 71.31764,68.52942 L 441.04118,427.20588 490.2,398.1 487.95294,290.12941 388.1513,230.74351 362.62353,241.30588 362.1,283.3 342,329.82353 Z"/>
                </a>
                <a href="">
                    <path class="base" data-toggle="tooltip" title="Normandie"
                        d="m 323.71176,174.21765 -88.65294,2.60588 -9.52941,-29.11765 -36,-1.58824 25.19191,92.05393 141.17868,35.3929 0.7,-36.86447 33.52419,-16.52616 22.53911,-31.1701 2.80632,-42.7111 L 392.6,117.1 312.35294,151.41176 Z"/>
                </a>
                <a href="">
                    <path class="base" data-toggle="tooltip" title="Île-de-France"
                        d="M 397.58824,227.11764 415,194 l 79,6.6 20.1,26 -1.1,37.7 -29.3,16.7 z"/>
                </a>
                <a href="">
                    <path class="base" data-toggle="tooltip" title="Hauts-de-France"
                        d="m 459.5,28.1 -55.3,18.4 -3.4,39.5 -3.53336,26.02312 23.75689,29.84747 -3.0264,46.31423 76.47889,6.53412 20.30295,23.76216 46.87398,-86.33992 L 558.3,99 Z"/>
                </a>
                <a href="">
                    <path class="base" data-toggle="tooltip" title="Grand Est"
                        d="M 563.57853,103.93079 635.69174,156.40927 800,212.3 774.52942,246.70588 757.9,336.4 l -19.6,7.6 -12.21765,-32.78235 -64.04787,-12.25792 -36.27715,30.39069 -38.03513,-33.68543 -43.84789,1.1548 -25.04824,-32.27372 0.18819,-41.30026 48.92475,-91.74037 z/"">
                </a>
                <a href="">
                    <path class="base" data-toggle="tooltip" title="Bourgogne-Franche-Comté"
                        d="m 665.06471,305.92941 56.52353,11.18823 10.88823,27.94118 -71.77058,99.11765 L 635.1,442.5 l -4.6043,-15.10589 -25.49168,-3.77814 -15.22208,26.95233 -46.4355,-3.2994 7.80392,-22.04274 -55.53596,-28.13453 -0.1772,-114.99224 20.02314,-11.68867 26.14333,32.71916 47.7414,0.78345 38.12551,35.23961 z"/>
                </a>
                <a href="">
                    <path class="base" data-toggle="tooltip" title="Auvergne-Rhône-Alpes"
                        d="M 535.45882,450.42353 593.6,455.2 608.82353,429.88235 625.7,431.6 l 5.35883,17.87059 36,1.05882 36,-21.17647 30.70588,101.11765 L 632.9,608.1 l 12.8,20.3 -13.5,2.3 -45.6,-16.1 -30.18823,-2.6 -11.11765,-29.64706 -39.13122,-29.42816 -16.90577,21.26103 -15.41509,-17.33867 -22.25617,29.48934 -17.33293,-4.9306 1.98236,-19.69999 30.04875,-93.6762 -19.98993,-33.38263 45.80543,-30.84615 49.88761,25.00432 z"/>
                </a>
                <a href="">
                    <path class="base" data-toggle="tooltip" title="Provence-Alpes-Côte d'Azur"
                        d="m 594,625.23529 c 9,4.76471 41.29412,12.70589 41.29412,12.70589 l 18,-3.17648 -12.08824,-25.51764 68.20589,-54.42353 24.35294,26.47059 L 726.4,621.1 769.76471,636.88235 696.8,716.5 676.3,726.7 567,690.35294 592.94118,664.41176 Z"/>
                </a>
                <a href="">
                    <path class="base" data-toggle="tooltip" title="Corse"
                        d="m 773.65294,695.94118 5.7,72.5 -18.2,63.7 -19.2,-6.7 -24.6,-65.20589 12.17648,-29.11764 L 765,715.23529 l 1.05883,-20.64706 z"/>
                </a>
                <a href="">
                    <path class="base" data-toggle="tooltip" title="Nouvelle-Aquitaine"
                        d="m 224.35882,446.83529 c 9,4.76471 51.35295,-11.64705 51.35295,-11.64705 l -15.35294,-49.76472 48.79411,-13.87058 75.36142,69.83442 53.72682,-6.19912 20.62104,34.50899 -27.50339,87.78513 -40.71765,-4.66471 L 349.3,630.54117 285.86471,643.45294 293.62353,745.47059 205.3,694.6 214.82942,628.95294 235.1874,506.12438 Z"/>
                </a>
            </svg>
        </div>
    </div>
</div>
@endsection

@section('script')

<script>
    $(function(){
        $('[data-toggle="tooltip"]').tooltip();
    });
</script>
@endsection

Avec cet aspect :

Mais en noir elle est pas vraiment jolie, d’autre part on a aucune différence au survol. On va donc ajouter ces règles de style dans app.scss :

.base {
    fill:#003dd4;
    cursor: pointer;
    -webkit-transition: fill .5s ease-out;
    -moz-transition: fill .5s ease-out;
    -o-transition: fill .5s ease-out;
    transition: fill .5s ease-out;
}

.base:hover {
    fill: #bd4;
}

N’oubliez pas de compiler !

Maintenant les régions sont bleues et au survol passent en jaune. On a aussi un tooltip avec le nom de la région :

Les vues de l’authentification

On va aussi apporter des petites modifications aux vues de l’authentification. Vous avez peut-être remarqué qu’il n’y a plus le lien de l’enregistrement dans le menu. On va surtout franciser tous les textes.

La connexion

On va franciser la vue login et ajouter un lien pour l’enregistrement :

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card bg-light">
                <div class="card-header">Connexion
                    <a class="btn btn-link float-right" href="{{ route('register') }}">
                            Pas encore de compte ?
                    </a>
                </div>

                <div class="card-body">
                    <form method="POST" action="{{ route('login') }}">
                        @csrf

                        <div class="form-group row">
                            <label for="email" class="col-md-4 col-form-label text-md-right">Adresse email</label>

                            <div class="col-md-6">
                                <input id="email" type="email" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="{{ old('email') }}" required autofocus>

                                @if ($errors->has('email'))
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $errors->first('email') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group row">
                            <label for="password" class="col-md-4 col-form-label text-md-right">Mot de passe</label>

                            <div class="col-md-6">
                                <input id="password" type="password" class="form-control{{ $errors->has('password') ? ' is-invalid' : '' }}" name="password" required>

                                @if ($errors->has('password'))
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $errors->first('password') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group row">
                            <div class="col-md-6 offset-md-4">
                                <div class="form-check">
                                    <input class="form-check-input" type="checkbox" name="remember" id="remember" {{ old('remember') ? 'checked' : '' }}>

                                    <label class="form-check-label" for="remember">
                                        Se rappeler de moi
                                    </label>
                                </div>
                            </div>
                        </div>

                        <div class="form-group row mb-0">
                            <div class="col-md-8 offset-md-4">
                                <button type="submit" class="btn btn-primary">
                                    Connexion
                                </button>

                                @if (Route::has('password.request'))
                                    <a class="btn btn-link" href="{{ route('password.request') }}">
                                        Mot de passe oublié ?
                                    </a>
                                @endif
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

L’enregistrement

Là aussi francisation et on ajoute une case à cocher pour l’acceptation de la politique de confidentialité pour la vue register :

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card bg-light">
                <div class="card-header">Enregistrement</div>

                <div class="card-body">
                    <form method="POST" action="{{ route('register') }}">
                        @csrf

                        <div class="form-group row">
                            <label for="name" class="col-md-4 col-form-label text-md-right">Nom</label>

                            <div class="col-md-6">
                                <input id="name" type="text" class="form-control{{ $errors->has('name') ? ' is-invalid' : '' }}" name="name" value="{{ old('name') }}" required autofocus>

                                @if ($errors->has('name'))
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $errors->first('name') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group row">
                            <label for="email" class="col-md-4 col-form-label text-md-right">Adresse email</label>

                            <div class="col-md-6">
                                <input id="email" type="email" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="{{ old('email') }}" required>

                                @if ($errors->has('email'))
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $errors->first('email') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group row">
                            <label for="password" class="col-md-4 col-form-label text-md-right">Mot de passe</label>

                            <div class="col-md-6">
                                <input id="password" type="password" class="form-control{{ $errors->has('password') ? ' is-invalid' : '' }}" name="password" required>

                                @if ($errors->has('password'))
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $errors->first('password') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group row">
                            <label for="password-confirm" class="col-md-4 col-form-label text-md-right">Confirmation</label>

                            <div class="col-md-6">
                                <input id="password-confirm" type="password" class="form-control" name="password_confirmation" required>
                            </div>
                        </div>

                        <div class="form-group row mb-0">
                            <div class="col-md-6 offset-md-4">
                                <button type="submit" class="btn btn-primary">
                                    Enregistrement
                                </button>
                            </div>
                        </div>

                        <br>

                        <div class="custom-control custom-checkbox">
                            <input type="checkbox" class="custom-control-input" id="ok" required>
                            <label class="custom-control-label" for="ok">J
                                'accepte les termes et conditions de la politique de confidentialité.
                            </label>
                        </div>

                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

Autres vues et contrôleurs

C’est la même chose pour les vues de la vérification de l’email et du renouvellement du mot de passe. Je ne vais pas alourdir l’article avec ce code que vous pouvez trouver dans le dossier à téllécharger.

Dans les contrôleurs de l’authentification (LoginController, RegisterController, ResetPasswordController, VerificationController) on va aussi changer la redirection :

protected $redirectTo = '/';

On va aussi supprimer HomeController qu’on ne va pas utiliser.

Conclusion

On a bien avancé et maintenant on a une page d’accueil ! d’autre part els vues de l’authentifications ont été francisées et adaptées. On a aussi commencé à styliser le site pour lui donner un aspect sympathique. Rendez-vous au prochain article où on va créer l’intendance pour visualiser les annonces.

 

 

Print Friendly, PDF & Email

16 commentaires

Laisser un commentaire