Logomark

LARAVEL

Un framework qui rend heureux
Voir cette catégorie
Vers le bas
Flux et reflux
Dimanche 9 mars 2025 21:10

Avec l'arrivée de Laravel 12, de nouveaux kits de démarrage (starter kits) ont été introduits. Il est temps de dire adieu à Breeze et Jetstream, que nous commencions à bien maîtriser. Il est essentiel de suivre les évolutions et de s'adapter aux nouvelles technologies. Désormais, trois options s'offrent à nous : React, Vue et Livewire, des choix déjà disponibles avec Breeze.

Cependant, un point délicat se pose avec le kit Livewire : il impose l'utilisation de FluxUI, désignée comme la bibliothèque officielle de Livewire. Créée par le même développeur, cette bibliothèque est remarquable en termes de qualité et de fonctionnalités. Le problème réside dans ses tarifs : 149$ par projet, ou 299$ pour un nombre non illimité de projets. Si ces coûts peuvent être absorbés par des entreprises ou des projets commerciaux, ils deviennent prohibitifs pour les amateurs, les associations et ceux qui recherchent des solutions économiques, voire gratuites.

Mon blog a toujours mis en avant des solutions Open Source et gratuites, ce qui rend l'intégration de FluxUI problématique. Bien qu'une version gratuite limitée existe, elle ne répond pas aux besoins d'un développement complet. Utiliser des composants gratuits pour l'authentification et se retrouver limité par des composants payants pour d'autres parties du projet n'est pas viable. Une option permettant de choisir entre une version avec ou sans FluxUI aurait été plus judicieuse et aurait satisfait un plus large public.

Dans cet article, je vais vous expliquer comment remplacer FluxUI par Tailwind CSS dans le code généré par le kit Livewire, tout en conservant les fonctionnalités essentielles. Pour ce faire, je vais réutiliser des éléments de Breeze et les adapter au nouveau contexte. Je me concentrerai uniquement sur la partie authentification, sans modifier les pages de profil.

Edit : on dispose désormais de nouveaux starter kits communautaires avec la version 12.2 de Laravel.

Installation du kit

La première chose à faire est d'installer le kit.

Laravel :

laravel new reflux

On tombe sur une première question :

Là, on choisit livewire. On a ensuite cette question :

On choisit la première option laravel.

On nous demande ensuite quel système de test, on veut adopter. Choisissez ce que vous voulez. Vous n'avez plus qu'à attendre la fin de l'installation. Vous avez une dernière question :

Répondez yes. Et c'est terminé ! Vous arrivez sur la page d'accueil de Laravel, et vous remarquez la présence des deux nouveaux boutons de l'authentification :

Les composants

On a eu la création de plusieurs composants :

Il y en a un seul qui utilise Flux, c'est auth-header, avec ce code :

@props([
    'title',
    'description',
])

<div class="flex w-full flex-col text-center">
    <flux:heading size="xl">{{ $title }}</flux:heading>
    <flux:subheading>{{ $description }}</flux:subheading>
</div>

On le remplace par celui-ci :

@props(['title', 'description'])

<div class="flex w-full flex-col text-center">
    <h1 class="text-2xl font-bold">{{ $title }}</h1>
    <h2 class="text-lg text-sm text-gray-600 mt-2">{{ $description }}</h2>
</div>

Et on ajoute ces quatre composants, qui sont issus de Breeze et adaptés pour le nouveau contexte :

Voilà input-error :

@props(['messages'])

@if ($messages)
    <ul {{ $attributes->merge(['class' => 'text-sm text-red-600 space-y-1 mt-2']) }}>
        @foreach ((array) $messages as $message)
            <li>{{ $message }}</li>
        @endforeach
    </ul>
@endif

Le code d'input-label :

@props(['value'])

<label {{ $attributes->merge(['class' => 'block text-sm text-gray-700']) }}>
    {{ $value ?? $slot }}
</label>

Celui de text-input :

@props(['disabled' => false])

<input @disabled($disabled)
    {{ $attributes->merge(['class' => 'p-2 border-gray-500 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm block mt-1 w-full']) }}>

Et enfin celui de primary-button :

<button
    {{ $attributes->merge(['type' => 'submit', 'class' => 'w-full items-center px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-sm text-white tracking-widest hover:bg-gray-700 focus:bg-gray-700 active:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition ease-in-out duration-150']) }}>
    {{ $slot }}
</button>

L’enregistrement d’un utilisateur

On utilise ce composant Volt :

On remplace la partie HTML avec celle-ci :

<div class="flex flex-col gap-6">
    <x-auth-header :title="__('Create an account')" :description="__('Enter your details below to create your account')" />

    <!-- Session Status -->
    <x-auth-session-status class="text-center" :status="session('status')" />

    <form wire:submit="register" class="flex flex-col gap-6">
        <!-- Name -->
        <div>
            <x-input-label for="name" :value="__('Name')" />
            <x-text-input 
                wire:model="name" 
                id="name" 
                type="text" 
                name="name" 
                required
                placeholder="{{ __('Full name') }}" 
                autofocus 
                autocomplete="name" 
            />
            <x-input-error :messages="$errors->get('name')" />
        </div>

        <!-- Email Address -->
        <div>
            <x-input-label for="email" :value="__('Email address')" />
            <x-text-input 
                wire:model="email" 
                id="email" 
                type="email" 
                name="email" 
                placeholder="email@example.com" 
                required 
                autocomplete="email" 
            />
            <x-input-error :messages="$errors->get('email')" />
        </div>

        <!-- Password -->
        <div>
            <x-input-label for="password" :value="__('Password')" />
            <x-text-input 
                wire:model="password" 
                id="password" 
                type="password" 
                name="password" 
                placeholder="{{ __('Password') }}" 
                required 
                autocomplete="new-password" 
            />
            <x-input-error :messages="$errors->get('password')" />
        </div>

        <!-- Confirm Password -->
        <div>
            <x-input-label for="password_confirmation" :value="__('Confirm password')" />
            <x-text-input 
                wire:model="password_confirmation" 
                id="password_confirmation" 
                type="password" 
                name="password_confirmation" 
                placeholder="{{ __('Confirm password') }}" 
                required 
                autocomplete="new-password" 
            />
            <x-input-error :messages="$errors->get('password_confirmation')" />
        </div>

        <x-primary-button>
            {{ __('Create account') }}
        </x-primary-button>
    </form>

    <div class="space-x-1 text-center text-sm text-zinc-600 dark:text-zinc-400">
        <a class="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
            href="{{ route('login') }}" wire:navigate>
            {{ __('Already have an account?') }}
        </a>
    </div>
</div>

On retrouve le même visuel :

Avec les messages de validation :

Je ne suis pas allé jusqu'à reproduire la petite icône.

La connexion d’un utilisateur

La connexion utilise ce composant Volt :

On va aussi changer la partie HTML :

<div class="flex flex-col gap-6">
    <x-auth-header :title="__('Log in to your account')" :description="__('Enter your email and password below to log in')" />

    <!-- Session Status -->
    <x-auth-session-status class="text-center" :status="session('status')" />

    <form wire:submit="login" class="flex flex-col gap-6">
        <!-- Email Address -->
        <div class="mt-4">
            <x-input-label for="email" :value="__('Email address')" />
            <x-text-input 
                wire:model="email" 
                id="email" 
                type="email" 
                name="email" 
                placeholder="email@example.com"
                required 
                autofocus 
                autocomplete="email" 
            />
            <x-input-error :messages="$errors->get('email')" />
        </div>

        <!-- Password -->
        <div class="relative">
            <x-input-label for="password" :value="__('Password')" />
            <x-text-input 
                wire:model="password" 
                id="password" 
                type="password" 
                name="password" 
                placeholder="{{ __('Password') }}" 
                required 
                autocomplete="current-password" 
            />
            <x-input-error :messages="$errors->get('password')" />
            @if (Route::has('password.request'))
                <a href="{{ route('password.request') }}" class="absolute right-0 top-0 text-sm underline">
                    {{ __('Forgot your password?') }}
                </a>
            @endif
        </div>

        <!-- Remember Me -->
        <div>
            <label for="remember" class="inline-flex items-center">
                <input 
                    wire:model="remember" 
                    id="remember" 
                    type="checkbox"
                    class="rounded border-gray-300 text-indigo-600 shadow-sm focus:ring-indigo-500" 
                    name="remember"
                >
                <span class="ms-2 text-sm text-gray-600">{{ __('Remember me') }}</span>
            </label>
        </div>

        <x-primary-button>
            {{ __('Log in') }}
        </x-primary-button>
    </form>

    @if (Route::has('register'))
        <div class="space-x-1 text-center text-sm text-zinc-600 dark:text-zinc-400">
            <a class="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
                href="{{ route('register') }}" wire:navigate>
                {{ __('Don\'t have an account?') }}
            </a>
        </div>
    @endif
</div>

On retrouve le même visuel :

La vérification de l'email

Cette action est gérée par ce composant Volt :

Là aussi on remanie la partie HTML :

<div class="mt-4 flex flex-col gap-6">
    <p class="text-center text-sm">
        {{ __('Please verify your email address by clicking on the link we just emailed to you.') }}
    </p>

    @if (session('status') == 'verification-link-sent')
        <p class="text-center text-sm !dark:text-green-400 !text-green-600">
            {{ __('A new verification link has been sent to the email address you provided during registration.') }}
        </p>
    @endif

    <div class="flex flex-col items-center justify-between space-y-3">
        <x-primary-button wire:click="sendVerification" >
            {{ __('Resend verification email') }}
        </x-primary-button>

        <div class="space-x-1 text-center text-sm text-zinc-600 dark:text-zinc-400">
            <a class="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 cursor-pointer" wire:click="logout">
                {{ __('Log out') }}
            </a>
        </div>
    </div>
</div>

Et on retrouve notre page :

L'oubli du mot de passe

La demande

Cette action est gérée par ce composant Volt :

Là aussi, on change le HTML :

<div class="flex flex-col gap-6">
    <x-auth-header :title="__('Forgot password')" :description="__('Enter your email to receive a password reset link')" />

    <!-- Session Status -->
    <x-auth-session-status class="text-center" :status="session('status')" />

    <form wire:submit="sendPasswordResetLink" class="flex flex-col gap-6">
        <!-- Email Address -->
        <div class="mt-4">
            <x-input-label for="email" :value="__('Email address')" />
            <x-text-input 
                wire:model="email" 
                id="email" 
                type="email" 
                name="email" 
                placeholder="email@example.com" 
                required 
                autofocus 
            />
            <x-input-error :messages="$errors->get('email')" />
        </div>

        <x-primary-button>
            {{ __('Email password reset link') }}
        </x-primary-button>
    </form>

    <div class="space-x-1 text-center text-sm text-zinc-400">
        {{ __('Or, return to') }}
        <a class="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
            href="{{ route('login') }}" wire:navigate>
            {{ __('log in') }}
        </a>
    </div>
</div>

Et voilà notre formulaire :

Le renouvellement

On utilise ce composant Volt :

On change la partie HTML :

<div class="flex flex-col gap-6">
    <x-auth-header :title="__('Reset password')" :description="__('Please enter your new password below')" />

    <!-- Session Status -->
    <x-auth-session-status class="text-center" :status="session('status')" />

    <form wire:submit="resetPassword" class="flex flex-col gap-6">
        <!-- Email Address -->
        <div>
            <x-input-label for="email" :value="__('Email')" />
            <x-text-input 
                wire:model="email" 
                id="email" 
                type="email" 
                name="email" 
                required 
                autocomplete="email" 
            />
            <x-input-error :messages="$errors->get('email')" />
        </div>

        <!-- Password -->
        <div>
            <x-input-label for="password" :value="__('Password')" />
            <x-text-input 
                wire:model="password" 
                id="password" 
                type="password" 
                name="password" 
                placeholder="{{ __('Password') }}" 
                required 
                autocomplete="new-password" 
            />
            <x-input-error :messages="$errors->get('password')" />
        </div>

        <!-- Confirm Password -->
        <div>
            <x-input-label for="password_confirmation" :value="__('Confirm password')" />
            <x-text-input 
                wire:model="password_confirmation" 
                id="password_confirmation" 
                type="password" 
                name="password_confirmation" 
                placeholder="{{ __('Confirm password') }}" 
                required 
                autocomplete="new-password" 
            />
            <x-input-error :messages="$errors->get('password_confirmation')" />
        </div>

        <x-primary-button>
            {{ __('Reset password') }}
        </x-primary-button>
    </form>
</div>

Et voilà l'aspect du formulaire :

Conclusion

Il est relativement simple de se passer de Flux dans ce kit. Vous pouvez adopter n'importe quel framework CSS ou une autre bibliothèque de composants. La gestion du profil et le changement de mot de passe peuvent être traités de manière similaire. La démarche consiste à définir les outils que vous souhaitez utiliser pour votre frontend, puis à adapter le code généré par le kit afin d'assurer une apparence homogène pour votre application.



Par bestmomo

Nombre de commentaires : 4