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.
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,
];
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.
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.
© 2024 Fabio Angelici
P.I. 03929520124
Privacy policy Cookie policy