Laravel

Un framework qui rend heureux

Voir cette catégorie
Vers le bas
Voir cette série
Mon CMS - Les pages
Jeudi 26 septembre 2024 16:51

Nous avons précédemment assuré la gestion complète des articles et des catégories. Dans le présent article, nous allons nous intéresser aux pages du CMS. Contrairement à un article, une page ne dépend d'aucune catégorie et concerne des données plus générales, par exemple les mentions légales ou la politique de confidentialité. Au niveau du codage, on va avoir quelque chose de proche de ce qu'on a vu pour la gestion des articles, mais en simplifié.

Pour rappel, la table des matières est ici.

Un composant pour le tableau des pages

On a à nouveau besoin d'un composant Volt pour gérer le tableau des pages et le code PHP qui va faire tout le traitement :

php artisan make:volt admin/pages/index --class

On va ajouter la route pour l'atteindre :

Route::middleware('auth')->group(function () {
	...
	Route::middleware(IsAdminOrRedac::class)->prefix('admin')->group(function () {
		...
		Route::middleware(IsAdmin::class)->group(function () {
			...
			Volt::route('/pages/index', 'admin.pages.index')->name('pages.index');

Et dans la foulée un item dans la barre latérale (admin.sidebar) :

@if (Auth::user()->isAdmin())
    <x-menu-sub title="{{ __('Pages') }}" icon="s-document">
        <x-menu-item title="{{ __('All pages') }}" link="{{ route('pages.index') }}" />
    </x-menu-sub>
@endif    

Une traduction :

"All pages": "Toutes les pages",

Et voici le code que je ne vais pas détailler étant donné qu'on a déjà vu tout ça largement dans les précédents articles :

<?php

use App\Models\Page;
use Livewire\Attributes\{Layout, Title};
use Livewire\Volt\Component;
use Livewire\WithPagination;
use Mary\Traits\Toast;

new #[Title('Pages'), Layout('components.layouts.admin')] 
class extends Component {
	use Toast, WithPagination;

	public function headers(): array
	{
		return [['key' => 'title', 'label' => __('Title')], ['key' => 'slug', 'label' => 'Slug'], ['key' => 'active', 'label' => __('Published')]];
	}

	public function deletePage(Page $page): void
	{
		$page->delete();
		$this->success(__('Page deleted'));
	}

	public function with(): array
	{
		return [
			'pages'   => Page::select('id', 'title', 'slug', 'active')->get(),
			'headers' => $this->headers(),
		];
	}
}; ?>

<div>
    <x-header title="{{ __('Pages') }}" separator progress-indicator >
        <x-slot:actions class="lg:hidden">
            <x-button icon="s-building-office-2" label="{{ __('Dashboard') }}" class="btn-outline"
                link="{{ route('admin') }}" />
            <x-button icon="c-document-plus" label="{{ __('Add a page') }}" class="btn-outline"
                link="#" />
        </x-slot:actions>
    </x-header>

    <x-card>
        <x-table striped :headers="$headers" :rows="$pages" link="#">
            @scope('cell_active', $page)
                @if ($page->active)
                    <x-icon name="o-check-circle" />
                @endif
            @endscope
            @scope('actions', $page)
                <x-popover>
                    <x-slot:trigger>
                        <x-button icon="o-trash" wire:click="deletePage({{ $page->id }})"
                            wire:confirm="{{ __('Are you sure to delete this page?') }}" spinner
                            class="text-red-500 btn-ghost btn-sm" />
                    </x-slot:trigger>
                    <x-slot:content class="pop-small">
                        @lang('Delete')
                    </x-slot:content>
                </x-popover>
            @endscope
        </x-table>
    </x-card>
</div>

Deux petites traductions :

"Add a page": "Ajouter une page",
"Are you sure to delete this page?": "Êtes-vous sûr de vouloir supprimer cette page ?",

Vérifiez que la suppression fonctionne avec le message d'alerte :

Créer une page

Maintenant que nous savons afficher la liste des pages et aussi les supprimer, pensons à pouvoir en créer. On a à nouveau besoin d'un composant Volt pour gérer le formulaire et le code PHP qui va faire tout le traitement :

php artisan make:volt admin/pages/create --class

On va ajouter la route pour l'atteindre (administrateurs) :

Route::middleware(IsAdmin::class)->group(function () {
	...
	Volt::route('/pages/index', 'admin.pages.index')->name('pages.index');
	Volt::route('/pages/create', 'admin.pages.create')->name('pages.create');
});

Et dans la foulée un item dans la barre latérale (admin.sidebar) :

@if (Auth::user()->isAdmin())
    <x-menu-sub title="{{ __('Pages') }}" icon="s-document">
        <x-menu-item title="{{ __('All pages') }}" link="{{ route('pages.index') }}" />
        <x-menu-item title="{{ __('Add a page') }}" link="{{ route('pages.create') }}" />
    </x-menu-sub>
@endif   

Le formulaire

Le formulaire sera commun pour la création et la modification, alors on crée un composant pour le gérer :

Avec ce code :

<x-card>
    <x-form wire:submit="save">
        <x-input type="text" wire:model="title" label="{{ __('Title') }}"
            placeholder="{{ __('Enter the title') }}" wire:change="$refresh" />
        <x-input type="text" wire:model="slug" label="{{ __('Slug') }}" /><br>
        <x-checkbox label="{{ __('Published') }}" wire:model="active" /><br>
        <x-editor wire:model="body" label="{{ __('Content') }}" :config="config('tinymce.config')"
            folder="{{ 'photos/' . now()->format('Y/m') }}" />
        <x-card title="{{ __('SEO') }}" shadow separator>
            <x-input placeholder="{{ __('Title') }}" wire:model="seo_title" hint="{{ __('Max 70 chars') }}" />
            <br>
            <x-textarea label="{{ __('META Description') }}" wire:model="meta_description"
                hint="{{ __('Max 160 chars') }}" rows="2" inline />
            <br>
            <x-textarea label="{{ __('META Keywords') }}" wire:model="meta_keywords"
                hint="{{ __('Keywords separated by comma') }}" rows="1" inline />
        </x-card>
        <x-slot:actions>
            <x-button label="{{ __('Save') }}" icon="o-paper-airplane" spinner="save" type="submit"
                class="btn-primary" />
        </x-slot:actions>
    </x-form>
</x-card>

On peut ainsi code le composant pages.create :

<?php

use App\Models\Page;
use illuminate\Support\Str;
use Livewire\Attributes\{Layout, Validate, Title};
use Livewire\Volt\Component;
use Mary\Traits\Toast;

new #[Title('Create Page'), Layout('components.layouts.admin')] 
class extends Component {
	use Toast;

	#[Validate('required|max:65000')]
	public string $body = '';

	#[Validate('required|max:255')]
	public string $title = '';

	#[Validate('required|max:255|unique:posts,slug|regex:/^[a-z0-9]+(?:-[a-z0-9]+)*$/')]
	public string $slug = '';

	#[Validate('required')]
	public bool $active = false;

	#[Validate('required|max:70')]
	public string $seo_title = '';

	#[Validate('required|max:160')]
	public string $meta_description = '';

	#[Validate('required|regex:/^[A-Za-z0-9-éèàù]{1,50}?(,[A-Za-z0-9-éèàù]{1,50})*$/')]
	public string $meta_keywords = '';

	public function updatedTitle($value): void
	{
		$this->generateSlug($value);

		$this->seo_title = $value;
	}

	public function save()
	{
		$data = $this->validate();
		Page::create($data);

		$this->success(__('Page added with success.'), redirectTo: '/admin/pages/index');
	}

	private function generateSlug(string $title): void
	{
		$this->slug = Str::of($title)->slug('-');
	}
}; ?>

<div>
    <x-header title="{{ __('Add a page') }}" separator progress-indicator>
        <x-slot:actions class="lg:hidden">
            <x-button icon="s-building-office-2" label="{{ __('Dashboard') }}" class="btn-outline"
                link="{{ route('admin') }}" />
        </x-slot:actions>
    </x-header>
    @include('livewire.admin.pages.page-form')
</div>

On retrouve pratiquement le même formulaire que les articles avec le choix des catégories et l'image en exergue en moins :

Il nous reste plus qu'à ajouter une traduction :

"Page added with success.": "Page ajoutée avec succès.",

Si vous avez bien suivi le processus, vous pouvez ajouter une page :

On va ajouter le lien de la création dans le bouton qu'on a prévu dans le tableau (pages.index) :

<x-button icon="c-document-plus" label="{{ __('Add a page') }}" class="btn-outline" link="{{ route('pages.create') }}" />

Modifier une page

Après la création, passons à la modification. On a à nouveau besoin d'un composant Volt pour gérer le formulaire et le code PHP qui va faire tout le traitement :

php artisan make:volt admin/pages/edit --class

Avec ce code :

<?php


use App\Models\Page;
use illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Livewire\Attributes\{Layout, Title};
use Livewire\Volt\Component;
use Mary\Traits\Toast;

new #[Title('Edit Page'), Layout('components.layouts.admin')] 
class extends Component {
	use Toast;

	public Page $page;
	public string $body             = '';
	public string $title            = '';
	public string $slug             = '';
	public bool $active             = false;
	public string $seo_title        = '';
	public string $meta_description = '';
	public string $meta_keywords    = '';

	public function mount(Page $page): void
	{
		$this->page = $page;
		$this->fill($this->page);
	}

	public function updatedTitle($value): void
	{
		$this->generateSlug($value);
	}

	public function save()
	{
		$data = $this->validate([
			'title'            => 'required|string|max:255',
			'body'             => 'required|max:65000',
			'active'           => 'required',
			'slug'             => ['required', 'string', 'max:255', 'regex:/^[a-z0-9]+(?:-[a-z0-9]+)*$/', Rule::unique('pages')->ignore($this->page->id)],
			'seo_title'        => 'required|max:70',
			'meta_description' => 'required|max:160',
			'meta_keywords'    => 'required|regex:/^[A-Za-z0-9-éèàù]{1,50}?(,[A-Za-z0-9-éèàù]{1,50})*$/',
		]);

		$this->page->update($data);

		$this->success(__('Page edited with success.'), redirectTo: '/admin/pages/index');
	}

	private function generateSlug(string $title): void
	{
		$this->slug = Str::of($title)->slug('-');
	}
}; ?>

<div>
    <x-header title="{{ __('Edit a page') }}" shadow separator progress-indicator>
        <x-slot:actions class="lg:hidden">
            <x-button icon="s-building-office-2" label="{{ __('Dashboard') }}" class="btn-outline"
                link="{{ route('admin') }}" />
        </x-slot:actions>
    </x-header>
    @include('livewire.admin.pages.page-form')
</div>

On prévoit deux traductions :

"Page edited with success.": "Page mise à jour avec succès.",
"Edit a page": "Modifier une page",

On ajoute la route :

Route::middleware(IsAdmin::class)->group(function () {
	...
	Volt::route('/pages/index', 'admin.pages.index')->name('pages.index');
	Volt::route('/pages/create', 'admin.pages.create')->name('pages.create');
	Volt::route('/pages/{page:slug}/edit', 'admin.pages.edit')->name('pages.edit');
});

On va ajouter le lien dans le tableau (pages.index) :

<x-table striped :headers="$headers" :rows="$pages" link="/admin/pages/{slug}/edit">

Il suffit donc de cliquer sur la ligne d'une page pour ouvrir le formulaire de modification :

Conclusion

Nous avons bien avancé dans la création du CMS puisque nous pouvons désormais gérer à la fois les articles et les pages. Dans la prochaine étape, on va s'intéresser aux utilisateurs.

Pour vous simplifier la vie, vous pouvez charger le projet dans son état à l’issue de ce chapitre.



Par bestmomo

Aucun commentaire

Article précédent : Mon CMS - Les catégories
Article suivant : Mon CMS - Les comptes