Creare un Ecommerce con Laravel, Inertia.js e React: parte 2, Utenti e Autorizzazioni

Creare un Ecommerce con Laravel, Inertia.js e React: parte 2, Utenti e Autorizzazioni

Nel post precedente abbiamo impostato il progetto andando ad installare alcuni tra pacchetti che ci serviranno durante lo sviluppo del nostro progetto.

In questo secondo post andremo a impostare le autorizzazioni, creeremo il layout base e inizieremo a gestire gli utenti.

Autorizzazioni

Iniziamo installando un pacchetto, basato a sua volta sul pacchetto spatie/laravel-permission, il quale ci permetterà di associare gli utenti a ruoli e permessi

composer require flixtechs-labs/laravel-authorizer

Per configurare il pacchetto lanciamo il comando

php artisan authorizer:setup

Infine aggiungiamo il trait HasRoles al modello user

use Spatie\Permission\Traits\HasRoles;
class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable, HasRoles;

Ora ci serve un modo per dare il ruolo di super admin ad un utente. Per fare ciò creiamo un comando custom

php artisan make:command MakeAdmin --command="make:admin"

Apriamo il file appena creato, che si trova in app/Console/Commands e aggiungiamo il seguente codice

<?php
namespace App\Console\Commands;
use App\Models\User;
use Illuminate\Console\Command;
use Spatie\Permission\Models\Role;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rules\Password;
class MakeAdmin extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'make:admin';
    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Crea un utente con accesso super admin';
    /**
     * Execute the console command.
     */
    /**
     * The function assigns a role to a user and displays a message indicating that the user now has
     * full access to the site.
     * 
     * @return either 0 or 1.
     */
    public function handle()
    {
        $user = $this->getUserInfo();
        if (!$user) {
            return 0;
        }
        $this->assignRole($user);
        $this->info(
            'L\'utente ' . $user->email . ' ha ora completo accesso al sito',
        );
        return 1;
    }
    /**
     * Get the user information from the user.
     *
     * @return bool|User
     */
    /**
     * The function `getUserInfo()` in PHP prompts the user for an email address, checks if a user with
     * that email exists, and if not, prompts for additional information to create a new user with
     * validation.
     * 
     * @return bool|User The function `getUserInfo()` returns either a `bool` value or an instance of
     * the `User` model.
     */
    public function getUserInfo(): bool|User
    {
        $email = $this->ask(
            'Qual è l\'indirizzo email dell\'utente che vuoi rendere admin?',
        );
        $user = User::where('email', $email)->first();
        if (!is_null($user)) {
            return $user;
        }
        $name = $this->ask('Qual è il suo nome?');
        $password = $this->secret(
            'Qual è la password dell\'utente che vuoi rendere admin?',
        );
        $passwordConfirmation = $this->secret(
            'Per favore conferma la password dell\'utente che vuoi rendere admin',
        );
        $validator = Validator::make(
            [
                'name' => $name,
                'email' => $email,
                'password' => $password,
                'password_confirmation' => $passwordConfirmation,
            ],
            [
                'name' => ['required', 'string', 'max:255'],
                'email' => [
                    'required',
                    'string',
                    'email',
                    'max:255',
                    'unique:users',
                ],
                'password' => [
                'required',
                'confirmed',
                Password::min(8)
                    ->letters()
                    ->symbols()
                ],
            ],
        );
        if ($validator->fails()) {
            $this->error('Operazione fallita. Per favore controlla gli errori qui sotto:');
            foreach ($validator->errors()->all() as $error) {
                $this->error($error);
            }
            return false;
        }
        return User::create([
            ...$validator->validated(),
            'password' => Hash::make($password),
        ]);
    }
    /**
     * Assign the super admin role to the user
     *
     * @param User $user
     * @return void
     */
    /**
     * The function assigns the "super admin" role to a user.
     * 
     * @param User user The "user" parameter is an instance of the User class. It represents a user in
     * the system to whom a role needs to be assigned.
     */
    public function assignRole(User $user): void
    {
        $role = Role::findOrCreate('super admin');
        $user->assignRole($role);
    }
}

Questo comando chiede l'indirizzo email dell'utente che vogliamo rendere super admin, verifica la sua presenza o meno all'interno del database, se l'utente è presente gli assegna il ruolo altrimenti chiede i dati per creare il nuovo utente e infine assegna il ruolo.

Ora nell' AuthServiceProvider, all'interno della funzione boot andiamo a garantire l'accesso a ogni sezione dell'app a chi ha il ruolo di super admin

use Illuminate\Support\Facades\Gate;
...
...
public function boot(): void
{
     $this->registerPolicies();
      Gate::before(static function ($user, $ability) {
       return $user->hasRole('super admin') ? true : null;
    });
 }

Procediamo andando a registrare i middleware di spatie/laravel-permission nel file Kernel.php in app/http in questo modo

protected $middlewareAliases = [
       ...
        'role' => \Spatie\Permission\Middlewares\RoleMiddleware::class,
        'permission' => \Spatie\Permission\Middlewares\PermissionMiddleware::class,
        'role_or_permission' => \Spatie\Permission\Middlewares\RoleOrPermissionMiddleware::class,
   ];

Layout

Iniziamo creando il primo controller, che si occuperà di visualizzare la dashboard

php artisan make:controller Admin/DashboardController

Creiamo il metodo index all'interno del controller. Al momento limitiamoci a ritornare la pagina Dashboard

public function index() {
    return Inertia::render('Admin/Dashboard');
 }

Impostiamo la rotta nel file web.php

Route::prefix('admin')
    ->middleware(['auth', 'verified', 'permission:view admin dashboard'])
	->name('admin.')
    ->group(function () {
        Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard');
});

All'interno di resources/js/Pages creiamo una nuova cartella Admin e a l suo interno spostiamo il file Dashboard.jsx fornitoci da breeze. Cancelliamone il contenuto e, per ora, ritorniamo un semplice div

export default function Dashboard() {
    return <div>Dashboard</div>;
}

All'interno di resources/js/Layouts, breeze ci ha fornito un file AuthenticathedLayout.jsx; utilizzeremo questo file per creare il nostro layout delle pagine del pannello di gestione. Apriamo il file, cancelliamone il contenuto e sostituiamolo con il nostro layout

import { useState } from 'react';
import { Head, Link } from '@inertiajs/react';
import { GiHamburgerMenu } from 'react-icons/gi';
import { HiUserCircle } from 'react-icons/hi';
import { ImSwitch } from 'react-icons/im';
import { BsFire } from 'react-icons/bs';
import { useMediaQuery } from 'react-responsive';
import { usePopUpVisibility } from '@/Hooks/usePopUpVisibility';
import { AnimatePresence, motion } from 'framer-motion';
export default function AuthenticatedLayout({ user, children }) {
    const [sidebar, setSidebar] = useState(false);
    const isMobile = useMediaQuery({ maxWidth: 768 });
    const [popUpVisibility, togglePopUpVisibility] = usePopUpVisibility();
    return (
        <>
            <Head title="E-commerce | Pannello di Gestione" />
            <header>
                <nav className="fixed left-0 top-0 z-10 flex min-h-[100px] w-[100vw] items-center justify-end space-x-3 bg-violet-400 p-5 text-white shadow-lg">
                    <div className="">
                        <p className="text-lg">Benvenuto, {user.name}</p>
                    </div>
                    <div className="relative">
                        <HiUserCircle
                            onClick={togglePopUpVisibility}
                            className="h-10 w-10 text-white"
                        />
                        <AnimatePresence>
                            {popUpVisibility && (
                                <motion.div
                                    className="absolute -right-0 top-10 w-[200px] space-y-3 rounded-lg bg-violet-200 p-5 text-black shadow-lg"
                                    initial={{ opacity: 0 }}
                                    animate={{ opacity: 1 }}
                                    exit={{ opacity: 0 }}
                                    transition={{ duration: 0.3 }}
                                >
                                    <Link
                                        className="flex items-center text-lg transition-all duration-150 hover:font-bold"
                                        method="post"
                                        href={route('logout')}
                                        as="button"
                                    >
                                        <ImSwitch className="mr-3" />
                                        Logout
                                    </Link>
                                </motion.div>
                            )}
                        </AnimatePresence>
                    </div>
                    <GiHamburgerMenu
                        className="block text-2xl md:hidden"
                        onClick={() => setSidebar(!sidebar)}
                    />
                </nav>
            </header>
            <section className="h-screen w-full md:grid md:grid-cols-5">
                <aside className="hidden bg-violet-400 pt-40 text-xl text-white shadow-lg shadow-black md:col-span-1 md:inline-grid">
                    <div className="mx-auto flex w-2/6 flex-col items-start space-y-3">
                        <Link
                            className="flex items-center transition-all duration-150 hover:font-bold"
                            href={route('admin.dashboard')}
                        >
                            <BsFire className="mr-2" />
                            Dashboard
                        </Link>
                    </div>
                </aside>
                <main className="p-10 md:col-span-4">
                    {children}
                </main>
            </section>
            {/* Mobile menù */}
            <div
                className={`${
                    sidebar && isMobile
                        ? 'fixed top-[100px] h-screen w-full justify-center bg-violet-400 p-5'
                        : 'hidden'
                }`}
            >
                <div className="h-96 space-y-3 overflow-y-scroll px-10 scrollbar-thin scrollbar-track-slate-100 scrollbar-thumb-violet-700">
                    <Link
                    className="flex w-full items-center justify-center pt-10 text-2xl text-white transition-all duration-150 hover:bg-white hover:font-bold hover:text-black"
                    href={route('admin.dashboard')}
                	>
                    	<BsFire className="mr-2" />
                    	Dashboard
                	</Link>
                </div>	
            </div>
        </>
    );
}

All'interno del layout utilizziamo usePopUpVisibility che è uno hook custom; creiamo una nuova cartella all'interno di resources/js chiamata Hooks al cui interno creiamo un file usePopUpVisibility.js

import { useState } from 'react';
export const usePopUpVisibility = () => {
    const [popUpVisibility, setPopUpVisibility] = useState(false);
    const togglePopUpVisibility = () => {
        setPopUpVisibility(!popUpVisibility);
    };
    return [popUpVisibility, togglePopUpVisibility];
};

Andiamo a modificare il file Dashboard.jsx per inserirlo all'interno del layout

import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import { Head } from '@inertiajs/react';
export default function Dashboard({ auth }) {
    return (
        <AuthenticatedLayout user={auth.user}>
            <Head title="Dashboard" />
            <div className="pt-[100px]">Dashboard</div>
        </AuthenticatedLayout>
    );
}

Spostiamoci all'interno del RouteServiceProvider e modifichiamo la costante HOME in 

public const HOME = '/admin/dashboard';

Possiamo ora creare il nostro primo utente con il ruolo di super admin

$ php artisan make:admin

E visitando la nostra app, dopo esserci loggati verremo reindirizzati alla dashboard.

Gestione Utenti

Per quanto riguarda gli utenti vogliamo avere le classiche funzioni CRUD per poter gestire la creazione, la modifica e l'eliminazione delle entità.

Cominciamo creando il controller con le relative request di salvataggio e aggiornamento, impostiamo le rotte nel file web.php e andiamo a impostare il metodo index nel controller

php artisan make:controller Admin/UserController -m User  -R
Route::resource('users', UserController::class);
public function index()
{
    $users = User::with('roles')
        ->orderBy('id', 'desc')
        ->paginate(10);
    $users->getCollection()->transform(function ($user) {
        $user->formatted_created_at = $user->created_at->format('d-m-Y H:i:s');
        return $user;
    });
    return Inertia::render('Admin/UsersIndex', compact('users'));
}

In questo metodo andiamo a recuperare una lista degli utenti caricando anche la relazione dei ruoli associati all'utente, questa relazione ci viene fornita dal pacchetto spatie permissions. 

In seguito iteriamo attraverso la collezione degli utenti e per ciascun utente, andiamo ad aggiungere un nuovo attributo per presentare la data nel formato giorno-mese-anno ora:minuti:secondi alla vista. Infine ritorniamo la vista.

A questo punto possiamo creare la pagina Admin/UsersIndex.jsx in cui andremo a visualizzare una tabella contenente la lista degli utenti, e i relativi tasti per creare, modificare ed eliminare la risorsa.

import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import { BiHomeAlt2 } from 'react-icons/bi';
import { Head, Link, router } from '@inertiajs/react';
import { Pagination } from 'flowbite-react';

export default function UsersIndex({ auth, users }) {
    const confirmationHandler = (username) => {
        const confirmed = window.confirm(
            `Stai per eliminare l'utente ${username}. Sei sicuro di voler procedere?`,
        );
        if (!confirmed) {
            return false;
        }
    };

    return (
        <AuthenticatedLayout user={auth.user}>
            <Head title="Lista Utenti" />

            <h1 className="pt-[100px] text-center text-6xl">Lista Utenti</h1>
            <div className="py-5 text-center">
                <Link
                    className="rounded-lg bg-sky-500 px-5 py-2 text-white shadow-lg transition-all duration-150 hover:bg-sky-600"
                    href={route('admin.users.create')}
                >
                    Crea Utente
                </Link>
            </div>
            <div className="flex space-x-2 py-5">
                <Link
                    className="flex items-center underline transition-all duration-150 hover:text-red-500"
                    href={route('admin.dashboard')}
                >
                    <BiHomeAlt2 /> Dashboard
                </Link>
                {'>'}
                <Link
                    className="underline transition-all duration-150 hover:text-red-500"
                    href={route('admin.users.index')}
                >
                    Utenti
                </Link>
                {'>'}
                <p>Lista Utenti</p>
            </div>

            <div className="">
                <table className="block w-full border-separate border-spacing-4 overflow-x-scroll md:table md:overflow-x-hidden">
                    <thead className="bg-gray-200">
                        <tr className="text-center">
                            <th>Nome</th>
                            <th>Email</th>
                            <th>Ruolo</th>
                            <th>Registrato il</th>
                            <th>Azioni</th>
                        </tr>
                    </thead>
                    <tbody>
                        {users.data.map((user) => (
                            <tr key={user.id} className="text-center">
                                <td>{user.name}</td>
                                <td>{user.email}</td>
                                <td>
                                    {user.roles.length > 0 ? (
                                        user.roles.map((role) => (
                                            <p key={role.id}>{role.name}</p>
                                        ))
                                    ) : (
                                        <p>Nessuno</p>
                                    )}
                                </td>
                                <td>{user.formatted_created_at}</td>
                                {user.id !== auth.user.id ? (
                                    <td className="flex flex-col space-y-3 md:flex-row md:justify-center md:space-x-3 md:space-y-0">
                                        <Link
                                            className="rounded-lg bg-emerald-500 px-5 py-2 text-white shadow-lg transition-all duration-150 hover:bg-emerald-600"
                                            href={route(
                                                'admin.users.edit',
                                                user,
                                            )}
                                        >
                                            Modifica
                                        </Link>
                                        <Link
                                            className="rounded-lg bg-red-500 px-5 py-2 text-white shadow-lg transition-all duration-150 hover:bg-red-600"
                                            method="delete"
                                            as="button"
                                            onBefore={() =>
                                                confirmationHandler(user.name)
                                            }
                                            href={route(
                                                'admin.users.destroy',
                                                user,
                                            )}
                                        >
                                            Elimina
                                        </Link>
                                    </td>
                                ) : (
                                    <td className="flex flex-col space-y-3 md:flex-row md:justify-center md:space-x-3 md:space-y-0">
                                        <Link
                                            className="cursor-not-allowed rounded-lg bg-emerald-500 px-5 py-2 text-white opacity-50 shadow-lg"
                                            href="#"
                                        >
                                            Modifica
                                        </Link>

                                        <button
                                            disabled
                                            className="cursor-not-allowed rounded-lg bg-red-500 px-5 py-2 text-white opacity-50 shadow-lg "
                                        >
                                            Elimina
                                        </button>
                                    </td>
                                )}
                            </tr>
                        ))}
                    </tbody>
                </table>
                <div className="mt-5 text-center">
                    <Pagination
                        currentPage={users.current_page}
                        layout="pagination"
                        nextLabel="Successivo"
                        previousLabel="Precedente"
                        onPageChange={(newPage) => {
                            router.visit(users.links[newPage].url);
                        }}
                        showIcons
                        totalPages={users.last_page}
                    />
                </div>
            </div>
        </AuthenticatedLayout>
    );
}

Ora dobbiamo generare i codici di autorizzazione con i comandi

php artisan authorizer:permissions:generate -m User

php artisan authorizer:policies:generate -m User  --force

Verrà generata una nuova cartella Policies con un file UserPolicy.php e i permessi per creare, aggiornare e vedere gli utenti.

Indichiamo al controller di autorizzare tutte le azioni aggiungendo un costruttore

public function __construct()
{
    $this->authorizeResource(User::class, 'user');
}

Definiamo i metodi del controller partendo con create

public function create()
{
    return Inertia::render('Admin/UserForm');
}

Continuiamo con la store

public function store(StoreUserRequest $request)
{
    $data = $request->validated();
    $data['password'] = bcrypt($data['password']);
    $userName = $request->name;
    User::create($data);
        
    return to_route('admin.users.index')->with('message', 'Utente ' .$userName. ' creato con successo.');
}

Proseguiamo con la edit

public function edit(User $user)
{
    return Inertia::render('Admin/UserForm', compact('user'));
}

Update

public function update(UpdateUserRequest $request, User $user)
{
    $data = $request->validated();
    if (isset($data['password'])) {
        $data['password'] = bcrypt($data['password']);
    }
        
    $user->update($data);
    $userName = $user->name;

    return to_route('admin.users.index')->with('message', 'L\'Utente ' .$userName. ' è stato aggiornato con successo.');
}

E infine destroy

public function destroy(User $user)
{
    $userName = $user->name;
    $user->delete();

    return redirect(route('admin.users.index'), 303)->with('message', 'L\'Utente ' .$userName. ' è stato rimosso con successo.');

}

Terminato con il controller occupiamoci delle request per la validazione; Partiamo con la StoreUserRequest ricordandoci di impostare il metodo authorize a true

public function rules(): array
{
    return [
        'name' => 'required|string|max:20',
        'email' => 'required|email|unique:users,email',
        'password' => [
            'required',
            'confirmed',
            Password::min(8)
                ->letters()
                ->symbols()
        ]
    ];
}

public function messages()
{
    return [
        'name.required' => 'Il campo Nome Utente è richiesto.',
        'name.max' => 'Il Nome Utente può avere una lunghezza massima di 20 caratteri.',
        'email.required' => 'Il campo Email è richiesto.',
        'email.unique' => 'L\'indirizzo email inserito esiste già.',
        'password.required' => 'Il campo Password è richiesto.',
        'password.confirmed' => 'Le Password non corrispondono',
        'password.min' => 'La Password deve contenere almeno 8 caratteri.',
    ];
}

Facciamo lo stesso con la UpdateUserRequest

public function rules(): array
{
    return [
        'name' => 'required|string|max:20',
        'email' => 'required|email|unique:users,email,'.$this->id,
        'password' => [
            'confirmed',
            Password::min(8)
                ->letters()
                ->symbols()
        ]
    ];
}

public function messages()
{
    return [
        'name.required' => 'Il campo Nome Utente è richiesto.',
        'name.max' => 'Il Nome Utente può avere una lunghezza massima di 20 caratteri.',
        'email.required' => 'Il campo Email è richiesto.',
        'email.unique' => 'L\'Indirizzo Email inserito esiste già.',
        'password.confirmed' => 'Le Password non corrispondono',
        'password.min' => 'La Password deve contenere almeno 8 caratteri.',
    ];
}

Procediamo andando a creare la pagina Admin/UserForm.jsx che utilizzeremo sia per la creazione che per l'aggiornamento degli utenti

 import { useEffect } from 'react';
import { Head, Link, useForm } from '@inertiajs/react';
import TextInput from '@/Components/TextInput';
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import InputLabel from '@/Components/InputLabel';
import InputError from '@/Components/InputError';
import PrimaryButton from '@/Components/PrimaryButton';
import PasswordInput from '@/Components/PasswordInput';
import { BiHomeAlt2 } from 'react-icons/bi';

export default function UserForm({ auth, user }) {
    const { data, setData, post, put, processing, errors, reset } = useForm(
        user
            ? {
                  id: user.id,
                  name: user.name,
                  email: user.email,
                  password: user.password,
                  password_confirmation: '',
              }
            : {
                  name: '',
                  email: '',
                  password: '',
                  password_confirmation: '',
              },
    );

    useEffect(() => {
        return () => {
            reset('password', 'password_confirmation');
        };
    }, []);

    const submit = (event) => {
        event.preventDefault();
        if (user) {
            put(route('admin.users.update', user));
            return;
        }
        post(route('admin.users.store'));
    };

    return (
        <AuthenticatedLayout user={auth.user}>
            {user ? (
                <div className="pt-[100px]">
                    <Head title="Modifica Utente" />
                    <h1 className="mt-5 pb-5 text-center text-4xl font-bold">
                        Stai modificando l'utente: {user.name}
                    </h1>
                </div>
            ) : (
                <div className="pt-[100px]">
                    <Head title="Crea Utente" />
                    <h1 className="mt-5 pb-5 text-center text-4xl font-bold">
                        Nuovo Utente
                    </h1>
                </div>
            )}
            <div className="flex space-x-2 py-5">
                <Link
                    className="flex items-center underline transition-all duration-150 hover:text-red-500"
                    href={route('admin.dashboard')}
                >
                    <BiHomeAlt2 /> Dashboard
                </Link>
                {'>'}
                <Link
                    className="underline transition-all duration-150 hover:text-red-500"
                    href={route('admin.users.index')}
                >
                    Utenti
                </Link>
                {'>'}
                {user ? <p>Modifica Utente</p> : <p>Crea Utente</p>}
            </div>
            <form
                className="mt-10 rounded-lg bg-white p-10 shadow-lg"
                onSubmit={submit}
            >
                <div>
                    <InputLabel
                        className={`text-xl ${
                            errors.name ? 'text-red-500' : ''
                        }`}
                        htmlFor="name"
                        value="Nome"
                    />

                    <TextInput
                        id="name"
                        name="name"
                        value={data.name}
                        className={`mt-1 block w-full ${
                            errors.name ? 'border-red-500' : ''
                        } focus:bg-emerald-200`}
                        autoComplete="username"
                        onChange={(event) =>
                            setData('name', event.target.value)
                        }
                        required
                    />

                    <InputError
                        className="mt-2 text-xl"
                        message={errors.name}
                    />
                </div>

                <div className="mt-4">
                    <InputLabel
                        className={`text-xl ${
                            errors.email ? 'text-red-500' : ''
                        }`}
                        htmlFor="email"
                        value="Indirizzo Email"
                    />

                    <TextInput
                        id="email"
                        type="email"
                        name="email"
                        value={data.email}
                        className={`mt-1 block w-full ${
                            errors.email ? 'border-red-500' : ''
                        } focus:bg-emerald-200`}
                        autoComplete="email"
                        onChange={(event) =>
                            setData('email', event.target.value)
                        }
                        required
                    />

                    <InputError
                        className="mt-2 text-xl"
                        message={errors.email}
                    />
                </div>

                <div className="mt-4">
                    <InputLabel
                        className={`text-xl ${
                            errors.password ? 'text-red-500' : ''
                        }`}
                        htmlFor="password"
                        value="Password"
                    />

                    <PasswordInput
                        handleChange={(event) => {
                            setData('password', event.target.value);
                        }}
                        autoComplete="new-password"
                        placeholder=""
                        className={`${
                            errors.password ? 'border-red-500' : ''
                        } focus:bg-emerald-200`}
                    />

                    <InputError
                        className="mt-2 text-xl"
                        message={errors.password}
                    />
                </div>

                <div className="mt-4">
                    <InputLabel
                        className={`text-xl ${
                            errors.password_confirmation ? 'text-red-500' : ''
                        }`}
                        htmlFor="password_confirmation"
                        value="Conferma Password"
                    />

                    <PasswordInput
                        handleChange={(event) => {
                            setData(
                                'password_confirmation',
                                event.target.value,
                            );
                        }}
                        autoComplete="new-password"
                        placeholder=""
                        className={`${
                            errors.password_confirmation ? 'border-red-500' : ''
                        } focus:bg-emerald-200`}
                    />

                    <InputError
                        className="mt-2 text-xl"
                        message={errors.password_confirmation}
                    />
                </div>
                <div className="mt-4 text-center">
                    <PrimaryButton
                        className="ml-4 mt-5 bg-sky-400 px-6 py-3 text-[20px] hover:bg-sky-500"
                        disabled={processing}
                    >
                        {user ? 'Modifica Utente' : 'Crea Utente'}
                    </PrimaryButton>
                </div>
            </form>
        </AuthenticatedLayout>
    );
}

Nel form utilizziamo un componente PasswordInput.jsx che ci permette di nascondere o visualizzare la password inserita, andiamo a crearlo all'interno della cartella Components

import { usePasswordVisibility } from "../Hooks/usePasswordVisibility";
import EyeIcon from "./EyeIcon";

export default function PasswordInput(props) {
    const [passwordIsVisible, togglePasswordVisibility] =
        usePasswordVisibility();

    return (
        <div className="relative flex">
            <input
                onChange={props.handleChange ? props.handleChange : () => {}}
                className={`focus:border-indigo-500 focus:ring-indigo-500 w-full p-3 text-lg rounded-md shadow-lg ${props.className}`}
                type={passwordIsVisible ? "text" : "password"}
                autoComplete={props.autoComplete}
                placeholder={props.placeholder}
            />
            <EyeIcon
                passwordIsVisible={passwordIsVisible}
                togglePasswordVisibility={togglePasswordVisibility}
            />
        </div>
    );
}

Creiamo un altro componente EyeIcon.jsx

import { AiOutlineEye, AiOutlineEyeInvisible } from "react-icons/ai";

export default function EyeIcon({
    passwordIsVisible,
    togglePasswordVisibility,
}) {
    return (
        <>
            {passwordIsVisible ? (
                <AiOutlineEyeInvisible
                    onClick={togglePasswordVisibility}
                    className="absolute right-[4%] top-[28%] cursor-pointer text-2xl"
                />
            ) : (
                <AiOutlineEye
                    onClick={togglePasswordVisibility}
                    className="absolute right-[4%] top-[28%] cursor-pointer text-2xl"
                />
            )}
        </>
    );
}

Come ultimo passaggio andiamo a creare all'interno della cartella Hooks, l'hook custom usePasswordVisibility.js

import { useMemo, useState } from "react";

export const usePasswordVisibility = (initialState = false) => {
    const [passwordIsVisible, setPasswordIsVisible] = useState(initialState);

    const togglePasswordVisibility = () => {
        setPasswordIsVisible((prevState) => !prevState);
    };

    const memoizedState = useMemo(() => {
        return [passwordIsVisible, togglePasswordVisibility];
    }, [passwordIsVisible]);

    return memoizedState;
};

Andiamo ad inserire i link per gli utenti all'interno della sidebar e del menù mobile in AuthenticatedLayout.jsx

import { AiOutlineUser } from 'react-icons/ai';
...
...
<Link
    className="flex items-center transition-all duration-150 hover:font-bold"
    href={route('admin.users.index')}
>
    <AiOutlineUser className="mr-2" />
    Utenti
</Link>
...
...
...
<Link
    className="flex w-full items-center justify-center pt-10 text-2xl text-white transition-all duration-150 hover:bg-white hover:font-bold hover:text-black"
    href={route('admin.users.index')}
>
    <AiOutlineUser className="mr-2" />
    Utenti
</Link>

Ora abbiamo la possibilità di creare e modificare gli utenti.
Nel prossimo post faremo un po' di refactoring, dato che il form per la gestione degli utenti ha diverso codice ripetuto, aggiungeremo le notifiche a schermo quando si completa un operazione e ci occuperemo dei ruoli e dei permessi.

Se vuoi creare il tuo sito web contattami per un preventivo gratuito

© 2024 Fabio Angelici

P.I. 03929520124

Privacy policy Cookie policy Hostinger Referral Badge