Parcourir la source

gestion utilisateurs et genrs

garthh il y a 1 semaine
Parent
commit
e8090c95b8

+ 46 - 0
assets/controllers/admin_confirm_controller.js

@@ -0,0 +1,46 @@
+import { Controller } from '@hotwired/stimulus';
+
+/*
+ * Contrôleur Stimulus pour bouton de suppression avec confirmation.
+ * Sur le premier clic, le bouton devient "Confirmer" et son href est mis à jour.
+ */
+
+export default class extends Controller {
+    static values = {
+        baseUrl: String // Par exemple : "/items/delete/"
+    }
+
+    connect() {
+        console.log("Stimulus: bouton de suppression avec confirmation");
+        this.originalText = this.element.textContent;
+        this.confirmed = false;
+        this.element.addEventListener('click', this.handleClick.bind(this));
+    }
+
+    handleClick(event) {
+        if (!this.confirmed) {
+            event.preventDefault();
+
+            // Récupère l'ID depuis data-id
+            const id = this.element.dataset.id;
+
+            if (!id) {
+                console.error("Aucun data-id trouvé sur l'élément.");
+                return;
+            }
+
+            // Remplace le texte et modifie le lien
+            this.element.textContent = "Confirmer ?";
+            this.element.href = id;
+            this.confirmed = true;
+
+            // Optionnel : annule la confirmation après X secondes
+            setTimeout(() => {
+                this.element.textContent = this.originalText;
+                this.element.href = "#";
+                this.confirmed = false;
+            }, 5000);
+        }
+        // Sinon, clic confirmé => laisse le lien agir normalement
+    }
+}

+ 1 - 1
assets/controllers/datatables_controller.js

@@ -14,7 +14,7 @@ export default class extends Controller {
     }
 
     // Initialise DataTables
-    $(this.element).DataTable();
+    $(this.element).DataTable({ responsive: true });
 
     // Marque comme initialisé pour les reconnections futures
     this.element.dataset.datatableInitialized = 'true';

Fichier diff supprimé car celui-ci est trop grand
+ 2 - 2
assets/js/datatables.min.js


+ 3 - 0
config/services.yaml

@@ -18,3 +18,6 @@ services:
 
     # add more service definitions when explicit configuration is needed
     # please note that last definitions always *replace* previous ones
+    App\EventListener\LastLoginListener:
+        tags:
+            - { name: kernel.event_listener, event: security.interactive_login, method: onInteractiveLogin }

+ 31 - 0
migrations/Version20250725105030.php

@@ -0,0 +1,31 @@
+<?php
+
+declare(strict_types=1);
+
+namespace DoctrineMigrations;
+
+use Doctrine\DBAL\Schema\Schema;
+use Doctrine\Migrations\AbstractMigration;
+
+/**
+ * Auto-generated Migration: Please modify to your needs!
+ */
+final class Version20250725105030 extends AbstractMigration
+{
+    public function getDescription(): string
+    {
+        return '';
+    }
+
+    public function up(Schema $schema): void
+    {
+        // this up() migration is auto-generated, please modify it to your needs
+        $this->addSql('CREATE TABLE genre (id INT AUTO_INCREMENT NOT NULL, genre VARCHAR(255) NOT NULL, slug VARCHAR(255) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
+    }
+
+    public function down(Schema $schema): void
+    {
+        // this down() migration is auto-generated, please modify it to your needs
+        $this->addSql('DROP TABLE genre');
+    }
+}

+ 31 - 0
migrations/Version20250725105242.php

@@ -0,0 +1,31 @@
+<?php
+
+declare(strict_types=1);
+
+namespace DoctrineMigrations;
+
+use Doctrine\DBAL\Schema\Schema;
+use Doctrine\Migrations\AbstractMigration;
+
+/**
+ * Auto-generated Migration: Please modify to your needs!
+ */
+final class Version20250725105242 extends AbstractMigration
+{
+    public function getDescription(): string
+    {
+        return '';
+    }
+
+    public function up(Schema $schema): void
+    {
+        // this up() migration is auto-generated, please modify it to your needs
+        $this->addSql('ALTER TABLE genre CHANGE id id BINARY(16) NOT NULL COMMENT \'(DC2Type:uuid)\'');
+    }
+
+    public function down(Schema $schema): void
+    {
+        // this down() migration is auto-generated, please modify it to your needs
+        $this->addSql('ALTER TABLE genre CHANGE id id INT AUTO_INCREMENT NOT NULL');
+    }
+}

+ 100 - 0
src/Controller/Admin/GenreController.php

@@ -0,0 +1,100 @@
+<?php
+
+namespace App\Controller\Admin;
+
+use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\Routing\Requirement\Requirement;
+use Symfony\Component\Routing\Attribute\Route;
+use Doctrine\ORM\EntityManagerInterface;
+use Symfony\Component\String\Slugger\AsciiSlugger;
+
+use App\Entity\Genre;
+use App\Form\GenreType;
+use App\Repository\GenreRepository;
+
+final class GenreController extends AbstractController
+{
+    /*
+     * Lister tous les genres
+     */
+    #[Route('/admin/genre', name: 'app_admin_genre', methods: ['GET'])]
+    public function index(GenreRepository $repository): Response
+    {
+        $genres = $repository->findAll();
+
+        return $this->render('admin/genre/index.html.twig', [
+            'genres' => $genres,
+        ]);
+    }
+
+    /*
+     * Supprimer un genre
+     */
+    #[Route('/admin/genre/{id}/delete', name: 'app_admin_genre_delete', requirements: ['id' => Requirement::UUID_V7], methods: ['GET'])]
+    public function delete(?Genre $genre, EntityManagerInterface $manager): Response
+    {
+        $manager->remove($genre);
+        $manager->flush();
+        $this->addFlash('success', 'Genre supprimé avec succès.');
+        return $this->redirectToRoute('app_admin_genre');
+    }
+
+    /* 
+     * Modifier un genre
+     */
+    #[Route('/admin/genre/{id}/edit', name: 'app_admin_genre_edit', requirements: ['id' => Requirement::UUID_V7], methods: ['GET', 'POST'])]
+    public function edit(?Genre $genre, Request $request, EntityManagerInterface $manager): Response
+    {
+        $form = $this->createForm(GenreType::class, $genre);
+
+        $form->handleRequest($request);
+        if ($form->isSubmitted() && $form->isValid()) {
+            // Aucun slug n'a été proposé par l'utilisateur
+            if (!$form->get('slug')->getData()) {
+                $slugger = new AsciiSlugger('fr_FR');
+                $genre->setSlug($slugger->slug($genre->getGenre()));
+            }
+            $manager->persist($genre);
+            $manager->flush();
+
+            $this->addFlash('success', 'Genre ajouté à la base de données avec succès.');
+            return $this->redirectToRoute('app_admin_genre');
+        }
+
+        return $this->render('admin/genre/edit.html.twig', [
+            'form' => $form,
+            'genre' => $genre,
+        ]);
+    }
+
+    /* 
+     * Ajouter un genre
+     */
+    #[Route('/admin/genre/add', name: 'app_admin_genre_add', methods: ['GET', 'POST'])]
+    public function add(Request $request, EntityManagerInterface $manager): Response
+    {
+        $genre = new Genre();
+        $form = $this->createForm(GenreType::class, $genre);
+
+        $form->handleRequest($request);
+        if ($form->isSubmitted() && $form->isValid()) {
+            // Aucun slug n'a été proposé par l'utilisateur
+            if (!$form->get('slug')->getData()) {
+                $slugger = new AsciiSlugger('fr_FR');
+                $genre->setSlug($slugger->slug($genre->getGenre()));
+            }
+            $manager->persist($genre);
+            $manager->flush();
+
+            $this->addFlash('success', 'Genre ajouté à la base de données avec succès.');
+            return $this->redirectToRoute('app_admin_genre');
+        }
+
+        return $this->render('admin/genre/edit.html.twig', [
+            'form' => $form,
+        ]);
+    }
+}
+

+ 6 - 18
src/Controller/Admin/UserController.php

@@ -43,25 +43,13 @@ final class UserController extends AbstractController
     /*
      * Supprimer un utilisateur
      */
-    #[Route('/admin/user/{id}/delete', name: 'app_admin_user_delete', requirements: ['id' => Requirement::UUID_V7], methods: ['GET', 'POST'])]
-    public function delete(?User $user, Request $request, EntityManagerInterface $manager): Response
+    #[Route('/admin/user/{id}/delete', name: 'app_admin_user_delete', requirements: ['id' => Requirement::UUID_V7], methods: ['GET'])]
+    public function delete(?User $user, EntityManagerInterface $manager): Response
     {
-        $form = $this->createFormBuilder(FormType::class)->getForm();
-        $form->handleRequest($request);
-
-        // Suppression de l'utilisateur si le formulaire a été soumis
-        if ($form->isSubmitted() && $form->isValid()) {
-            $manager->remove($user);
-            $manager->flush();
-            $this->addFlash('success', 'Utilisateur supprimé avec succès.');
-            return $this->redirectToRoute('app_admin_user');
-        }
-
-        // Affichage du formualaire de confirmation
-        return $this->render('admin/user/delete.html.twig', [
-            'form' => $form,
-            'user' => $user
-        ]);
+        $manager->remove($user);
+        $manager->flush();
+        $this->addFlash('success', 'Utilisateur supprimé avec succès.');
+        return $this->redirectToRoute('app_admin_user');
     }
 
     /*

+ 15 - 0
src/Controller/RegistrationController.php

@@ -97,7 +97,22 @@ class RegistrationController extends AbstractController
 
         $this->addFlash('success', 'Un nouvel email de confirmation a été envoyé à votre adresse email.');
         return $this->redirectToRoute('app_login');
+    }
+
+    #[Route('/admin/user/{id}/resend', name: 'app_admin_user_resend_verification_email', requirements: ['id' => Requirement::UUID_V7], methods: ['GET'])]
+    public function adminUserResendVerificationEmail(User $user, TranslatorInterface $translator): Response
+    {
+        $this->emailVerifier->sendEmailConfirmation('app_verify_email', $user,
+            (new TemplatedEmail())
+                ->from(new Address($_ENV['CONTACT_EMAIL'], $_ENV['CONTACT_NAME']))
+                ->to((string) $user->getEmail())
+                ->subject('Merci de confirmer votre adresse email')
+                ->htmlTemplate('registration/confirmation_email.html.twig')
+                ->textTemplate('registration/confirmation_email.txt.twig')
+        );
 
+        $this->addFlash('success', 'Un nouvel email de confirmation a été envoyé à cet utilisateur.');
+        return $this->redirectToRoute('app_admin_user');
     }
 
     #[Route('/verify/email', name: 'app_verify_email')]

+ 62 - 0
src/Entity/Genre.php

@@ -0,0 +1,62 @@
+<?php
+
+namespace App\Entity;
+
+use App\Repository\GenreRepository;
+use Doctrine\ORM\Mapping as ORM;
+use Doctrine\DBAL\Types\Types;
+use Symfony\Bridge\Doctrine\Types\UuidType;
+use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
+use Symfony\Component\Uid\Uuid;
+
+#[ORM\Entity(repositoryClass: GenreRepository::class)]
+class Genre
+{
+    #[ORM\Id]
+    #[ORM\Column(type: UuidType::NAME, unique: true)]
+    #[ORM\GeneratedValue(strategy: 'CUSTOM')]
+    #[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')]
+    private ?Uuid $id = null;
+
+    #[ORM\Column(length: 255)]
+    private ?string $genre = null;
+
+    #[ORM\Column(length: 255)]
+    private ?string $slug = null;
+
+    public function getId(): ?Uuid
+    {
+        return $this->id;
+    }
+
+    public function setId(Uuid $id): static
+    {
+        $this->id = $id;
+
+        return $this;
+    }
+
+    public function getGenre(): ?string
+    {
+        return $this->genre;
+    }
+
+    public function setGenre(string $genre): static
+    {
+        $this->genre = $genre;
+
+        return $this;
+    }
+
+    public function getSlug(): ?string
+    {
+        return $this->slug;
+    }
+
+    public function setSlug(string $slug): static
+    {
+        $this->slug = $slug;
+
+        return $this;
+    }
+}

+ 1 - 1
src/Enum/Exceptions.php

@@ -4,7 +4,7 @@ namespace App\Enum;
 
 enum Exceptions: string
 {
-    case NOT_VERIFIED_USER = 'Merci de confirmer avant votre courriel en cliquant sur le lien reçu.';
+    case NOT_VERIFIED_USER = 'Nous venons de vous adresser un mail de confirmation. Merci de confirmer votre adresse en cliquant sur le lien reçu.';
 }
 
 ?>

+ 32 - 0
src/EventListener/LastLoginListener.php

@@ -0,0 +1,32 @@
+<?php
+
+namespace App\EventListener;
+
+use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
+use Doctrine\ORM\EntityManagerInterface;
+use App\Entity\User;
+use DateTimeImmutable;
+
+class LastLoginListener
+{
+    private EntityManagerInterface $em;
+
+    public function __construct(EntityManagerInterface $entityManager)
+    {
+        $this->entityManager = $entityManager;
+    }
+
+    public function onInteractiveLogin(InteractiveLoginEvent $event): void
+    {
+        $user = $event->getAuthenticationToken()->getUser();
+
+        if (!$user instanceof User) {
+            return;
+        }
+
+        $user->setLastLogin();
+
+        // Pas besoin de persist si Doctrine gère déjà l'entité
+        $this->entityManager->flush();
+    }
+}

+ 33 - 0
src/Form/GenreType.php

@@ -0,0 +1,33 @@
+<?php
+
+namespace App\Form;
+
+use App\Entity\Genre;
+use Symfony\Component\Form\AbstractType;
+use Symfony\Component\Form\FormBuilderInterface;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+class GenreType extends AbstractType
+{
+    public function buildForm(FormBuilderInterface $builder, array $options): void
+    {
+        $builder
+            ->add('genre', null, [
+                'label' => 'Genre',
+                'required' => true,
+                ])
+            ->add('slug', null, [
+                'label' => 'Slug',
+                'help' => 'Laissez vide pour le générer automatiquement',
+                'required' => false,
+            ])
+        ;
+    }
+
+    public function configureOptions(OptionsResolver $resolver): void
+    {
+        $resolver->setDefaults([
+            'data_class' => Genre::class,
+        ]);
+    }
+}

+ 2 - 1
src/Form/UserType.php

@@ -58,7 +58,8 @@ class UserType extends AbstractType
                 'invalid_message' => 'Les mots de passe ne sont pas identiques.',
                 'type' => PasswordType::class,
                 'first_options' => [
-                    'label' => 'Mot de passe (laissez vide pour ne pas changer)',
+                    'label' => 'Mot de passe',
+                    'help' => 'Vous mettez à jour un compte ? laissez vide pour ne pas changer',
                     'toggle' => true,
                     'attr' => ['autocomplete' => 'new-password'],
                     'constraints' => [

+ 43 - 0
src/Repository/GenreRepository.php

@@ -0,0 +1,43 @@
+<?php
+
+namespace App\Repository;
+
+use App\Entity\Genre;
+use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
+use Doctrine\Persistence\ManagerRegistry;
+
+/**
+ * @extends ServiceEntityRepository<Genre>
+ */
+class GenreRepository extends ServiceEntityRepository
+{
+    public function __construct(ManagerRegistry $registry)
+    {
+        parent::__construct($registry, Genre::class);
+    }
+
+    //    /**
+    //     * @return Genre[] Returns an array of Genre objects
+    //     */
+    //    public function findByExampleField($value): array
+    //    {
+    //        return $this->createQueryBuilder('g')
+    //            ->andWhere('g.exampleField = :val')
+    //            ->setParameter('val', $value)
+    //            ->orderBy('g.id', 'ASC')
+    //            ->setMaxResults(10)
+    //            ->getQuery()
+    //            ->getResult()
+    //        ;
+    //    }
+
+    //    public function findOneBySomeField($value): ?Genre
+    //    {
+    //        return $this->createQueryBuilder('g')
+    //            ->andWhere('g.exampleField = :val')
+    //            ->setParameter('val', $value)
+    //            ->getQuery()
+    //            ->getOneOrNullResult()
+    //        ;
+    //    }
+}

+ 4 - 0
src/Security/UserChecker.php

@@ -21,14 +21,18 @@ class UserChecker implements UserCheckerInterface
 
     public function checkPostAuth(UserInterface $user): void
     {
+        // Si utilisateur n'existe pas, on ferme.
         if (!$user instanceof AppUser) {
             return;
         }
 
+        // Sinon, on passe à la suite en contrôlant si le compte est actif
         if ($user instanceof AppUser) {
             if (!$user->isVerified()) {
+                
                 throw new AuthenticationException(ExceptionsMsg::NOT_VERIFIED_USER->value);
             }
+
         }
     }
 } 

+ 47 - 0
templates/admin/genre/edit.html.twig

@@ -0,0 +1,47 @@
+{% extends 'bulma.html.twig' %}
+
+{% block title %}Administration > Genre > Éditer{% endblock %}
+
+{% block content %}
+
+    {{ form_errors(form) }}
+    {{ form_start(form) }}
+
+        <div class="field is-horizontal">
+            <div class="field-label is-normal">
+            {{ form_label(form.genre, null, {'label_attr': {'class': 'label'}}) }}
+            </div>
+            <div class="field-body">
+            {{ form_widget(form.genre, {'attr': {'class': 'input'}}) }}
+            </div>
+        </div>
+        
+        <div class="field is-horizontal">
+            <div class="field-label is-normal">
+            {{ form_label(form.slug, null, {'label_attr': {'class': 'label'}}) }}         
+            </div>  
+            <div class="field-body">
+                <div class="field is-expanded">
+                    <p>{{ form_widget(form.slug, {'attr': {'class': 'input'}}) }}</p>
+                    {# <span class="icon is-danger">{{ ux_icon('bi:exclamation-circle-fill') }}</span>  #}
+                    <small class="help">{{ form_help(form.slug) }}</small>
+                </div>
+            </div>
+        </div>
+
+
+        {{ form_widget(form) }}
+
+        <div class="field is-horizontal">
+            <div class="field-label is-normal"></div>
+            <div class="field-body">
+                <div class="control">
+        <button class="button is-primary" type="submit">Envoyer</button></div>
+        <div class='control'>
+        <a href="{{ path('app_admin_genre') }}" class="button">Annuler</a>
+        </div>
+        </div>
+        </div>
+    {{ form_end(form) }}
+
+{% endblock %}

+ 52 - 0
templates/admin/genre/index.html.twig

@@ -0,0 +1,52 @@
+{% extends 'bulma.html.twig' %}
+
+{% block title %}Administration > Genres{% endblock %}
+
+{% block content %}
+
+    <nav class="breadcrumb has-arrow-separator" aria-label="breadcrumbs">
+      <ul>
+        <li><a href="{{ path('app_main') }}">Accueil</a></li>
+        <li><a href="{{ path('app_admin') }}">Administration</a></li>
+        <li class="is-active"><a href="{{ path('app_admin_genre') }}">Gestion des genres</a></li>
+      </ul>
+    </nav>
+
+    <div class="block">
+        <h1 class="title">Gestion des genres</h1>
+        <p class="subtitle">Liste des genres et thèmes de jeux de rôles enregistrés dans l'application.</p>
+    </div>
+
+    <div class="block">
+        <div class="is-grouped">
+            <a class="button is-primary" href="{{ path('app_admin_genre_add') }}">Ajouter un genre</a>
+        </div>
+    </div>
+
+    <div class="block">
+        <table id="datatable" {{ stimulus_controller('datatables') }} class="table is-striped is-hoverable is-fullwidth">
+            <thead>
+            <tr>
+                <th>Slug</th>
+                <th>Genre</th>
+                <th>Actions</th>
+            </tr>
+            </thead>
+            <tbody>
+            {% for genre in genres %}
+                <tr>
+                    <td><a href="{{ path('app_admin_genre_edit', {id: genre.id}) }}">{{ genre.slug }}</a></td>
+                    <td>{{ genre.genre }}</td>
+                    <td>
+                        <a class="button" href="{{ path('app_admin_genre_edit', {id: genre.id}) }}">Éditer</a>
+                        <a class="button is-danger" data-id="{{ path('app_admin_genre_delete', {'id': genre.id})}}" href="#" {{ stimulus_controller('admin_confirm') }}>Supprimer</a>
+                    </td>
+                </tr>
+            {% endfor %}
+            </tbody>
+
+        </table>
+    </div>
+
+
+{% endblock %}

+ 40 - 3
templates/admin/index.html.twig

@@ -4,10 +4,27 @@
 
 {% block content %}
 
+    <nav class="breadcrumb has-arrow-separator" aria-label="breadcrumbs">
+      <ul>
+        <li><a href="{{ path('app_main') }}">Accueil</a></li>
+        <li class="is-active"><a href="{{ path('app_admin') }}">Administration</a></li>
+      </ul>
+    </nav>
+
   <section class="section">
+
+
+
     <div class="container">
       <div class="columns">
         <!-- Colonne 1 -->
+        <div class="column">
+          <div class="box">
+            <h2 class="title is-4">Application</h2>
+            <p>Configuration de l'application.</p>
+            <p><a href="#">Gérer l'application</a></p>
+          </div>
+        </div>
         <div class="column">
           <div class="box">
             <h2 class="title is-4">Utilisateurs</h2>
@@ -24,11 +41,31 @@
           </div>
         </div>
         <!-- Colonne 3 -->
+
+      </div>
+            <div class="columns">
+        <!-- Colonne 1 -->
         <div class="column">
           <div class="box">
-            <h2 class="title is-4">Utilisateurs</h2>
-            <p>Gérer les comptes utilisateurs et les droits.</p>
-            <p><a href="{{ path('app_admin_user') }}">Gérer les utilisateurs</a></p>
+            <h2 class="title is-4">Genres</h2>
+            <p>Gérer les genres et thématiques de jeux.</p>
+            <p><a href="{{ path('app_admin_genre') }}">Gérer les genres</a></p>
+          </div>
+        </div>
+        <!-- Colonne 2 -->
+        <div class="column">
+          <div class="box">
+            <h2 class="title is-4">Jeux</h2>
+            <p>Gérer les jeux et les ludothèques de l'association.</p>
+            <p><a href="#">Gérer les jeux</a></p>
+          </div>
+        </div>
+        <!-- Colonne 3 -->
+        <div class="column">
+          <div class="box">
+            <h2 class="title is-4">Meneurs et meneuses de jeux</h2>
+            <p>Gérer les meneurs et meneuses de jeux.</p>
+            <p><a href="#">Gérer les MJ</a></p>
           </div>
         </div>
       </div>

+ 0 - 16
templates/admin/user/delete.html.twig

@@ -1,16 +0,0 @@
-{% extends 'bulma.html.twig' %}
-
-{% block title %}Administration > Utilisateurs > Supprimer{% endblock %}
-
-{% block content %}
-            
-            {{ form_start(form) }}
-            {{ form_widget(form) }}
-            <button class="button is-danger" type="submit">Supprimer l'utilisateur</button>
-            <a class="button" href="{{ path('app_admin_user') }}">Annuler</a>
-            {{ form_end(form) }}
-        </div>
-    </div>
-</div>
-
-{% endblock %}

+ 107 - 2
templates/admin/user/edit.html.twig

@@ -6,11 +6,116 @@
 
     {{ form_errors(form) }}
     {{ form_start(form) }}
+
+        <div class="field is-horizontal">
+            <div class="field-label is-normal">
+            {{ form_label(form.firstName, null, {'label_attr': {'class': 'label'}}) }}
+            </div>
+            <div class="field-body">
+            {{ form_widget(form.firstName, {'attr': {'class': 'input'}}) }}
+            </div>
+        </div>
+        
+        <div class="field is-horizontal">
+            <div class="field-label is-normal">
+            {{ form_label(form.lastName, null, {'label_attr': {'class': 'label'}}) }}
+            </div>
+            <div class="field-body">
+            {{ form_widget(form.lastName, {'attr': {'class': 'input'}}) }}
+            </div>
+        </div>
+        
+        <div class="field is-horizontal">
+            <div class="field-label is-normal">
+            {{ form_label(form.phone, null, {'label_attr': {'class': 'label'}}) }}         
+            </div>  
+            <div class="field-body">
+                <div class="field is-expanded">
+                    <p>{{ form_widget(form.phone, {'attr': {'class': 'input'}}) }}</p>
+                    {# <span class="icon is-danger">{{ ux_icon('bi:exclamation-circle-fill') }}</span>  #}
+                    {% if form_errors(form.phone) %}<small class="help is-danger">{{ form_errors(form.phone) }}</small>{% endif %}
+                    <small class="help">{{ form_help(form.phone) }}</small>
+                </div>
+            </div>
+        </div>
+
+        <div class="field is-horizontal">
+            <div class="field-label is-normal">
+            {{ form_label(form.email, null, {'label_attr': {'class': 'label'}}) }}         
+            </div>  
+            <div class="field-body">
+                <div class="field is-expanded">
+                    <p>{{ form_widget(form.email, {'attr': {'class': 'input'}}) }}</p>
+                    {# <span class="icon is-danger">{{ ux_icon('bi:exclamation-circle-fill') }}</span>  #}
+                    {% if form_errors(form.email) %}<small class="help is-danger">{{ form_errors(form.email) }}</small>{% endif %}
+                    <small class="help">{{ form_help(form.email) }}</small>
+                </div>
+            </div>
+        </div>
+
+        <div class="field is-horizontal">
+            <div class="field-label is-normal">
+                {{ form_label(form.isVerified, null, {'label_attr': {'class': 'label'}}) }}
+            </div>  
+            <div class="field-body">
+                <div class="field is-horizontal">
+                    <p>{{ form_widget(form.isVerified, {'attr': {'class': 'checkbox'}}) }}
+                    &nbsp;<small class="is-help">{{ form_help(form.isVerified) }}</small></p>
+                </div>
+            </div>
+        </div>
+        
+        <div class="field is-horizontal">
+            <div class="field-label is-normal">
+                {{ form_label(form.roles, null, {'label_attr': {'class': 'label'}}) }}
+            </div>  
+            <div class="field-body">
+                <div class="field is-horizontal">
+                    {{ form_widget(form.roles, {'attr': {'class': 'checkbox'}}) }}
+                </div>
+            </div>
+        </div>
+
+        <div class="field is-horizontal">
+            <div class="field-label is-normal">
+            {{ form_label(form.newPassword.first, null, {'label_attr': {'class': 'label'}}) }}         
+            </div>  
+            <div class="field-body">
+                <div class="field is-expanded">
+                    <p>{{ form_widget(form.newPassword.first, {'attr': {'class': 'input'}}) }}</p>
+
+                    {% if form_errors(form.newPassword.first) %}<small class="help is-danger">{{ form_errors(form.newPassword.first) }}</small>{% endif %}
+                    <small class="help">{{ form_help(form.newPassword.first) }}</small>
+                </div>
+            </div>
+        </div>
+        
+        <div class="field is-horizontal">
+            <div class="field-label is-normal">
+            {{ form_label(form.newPassword.second, null, {'label_attr': {'class': 'label'}}) }}         
+            </div>  
+            <div class="field-body">
+                <div class="field is-expanded">
+                    <p>{{ form_widget(form.newPassword.second, {'attr': {'class': 'input'}}) }}</p>
+
+                    {% if form_errors(form.newPassword.second) %}<small class="help is-danger">{{ form_errors(form.newPassword.second) }}</small>{% endif %}
+                    <small class="help">{{ form_help(form.newPassword.second) }}</small>
+                </div>
+            </div>
+        </div>
+
         {{ form_widget(form) }}
-        <div class="block">
-        <button class="button is-primary" type="submit">Envoyer</button>
+
+        <div class="field is-horizontal">
+            <div class="field-label is-normal"></div>
+            <div class="field-body">
+                <div class="control">
+        <button class="button is-primary" type="submit">Envoyer</button></div>
+        <div class='control'>
         <a href="{{ path('app_admin_user') }}" class="button">Annuler</a>
         </div>
+        </div>
+        </div>
     {{ form_end(form) }}
 
 {% endblock %}

+ 15 - 3
templates/admin/user/index.html.twig

@@ -4,10 +4,22 @@
 
 {% block content %}
 
+    <nav class="breadcrumb has-arrow-separator" aria-label="breadcrumbs">
+      <ul>
+        <li><a href="{{ path('app_main') }}">Accueil</a></li>
+        <li><a href="{{ path('app_admin') }}">Administration</a></li>
+        <li class="is-active"><a href="{{ path('app_admin_user') }}">Gestion des utilisateurs</a></li>
+      </ul>
+    </nav>
+
+    <div class="block">
+        <h1 class="title">Gestion des utilisateurs</h1>
+        <p class="subtitle">Liste des utilisateurs enregistrés dans l'application.</p>
+    </div>
+
     <div class="block">
         <div class="is-grouped">
             <a class="button is-primary" href="{{ path('app_admin_user_add') }}">Ajouter un utilisateur</a>
-            <a class="button" href="{{ path('app_admin') }}">Retour</a>
         </div>
     </div>
 
@@ -27,13 +39,13 @@
             {% for user in users %}
                 <tr>
                     <td><a href="{{ path('app_admin_user_edit', {id: user.id}) }}">{{ user.firstname }} {{ user.lastname }}</a></td>
-                    <td><span class="icon-text">{{ user.email }}{% if user.isVerified %}<span class="icon"><twig:ux:icon name="bi:check2-circle" /></span>{% else %}<a href="{{ path('app_resend_verification_email', {id: user.id}) }}" title="Renvoyer l'email de confirmation"><span class="icon"><twig:ux:icon name="bi:arrow-clockwise" /></span></a>{% endif %}</span></td>
+                    <td><span class="icon-text">{{ user.email }}{% if user.isVerified %}<span class="icon"><twig:ux:icon name="bi:check2-circle" /></span>{% else %}<a href="{{ path('app_admin_user_resend_verification_email', {id: user.id}) }}" title="Renvoyer l'email de confirmation"><span class="icon"><twig:ux:icon name="bi:arrow-clockwise" /></span></a>{% endif %}</span></td>
                     <td>{{ component('Role', {roles: user.roles}) }}</td>
                     <td>{{ user.lastLogin ? user.lastLogin|date('d/m/Y H:i:s', app_timezone) : 'Jamais' }}</td>
                     <td>{{ user.lastUpdate ? user.lastUpdate|date('d/m/Y H:i:s', app_timezone) : 'Jamais' }}</td>
                     <td>
                         <a class="button" href="{{ path('app_admin_user_edit', {id: user.id}) }}">Éditer</a>
-                        <a class="button is-danger" href="{{ path('app_admin_user_delete', {id: user.id}) }}">Supprimer</a>
+                        <a class="button is-danger" data-id="{{ path('app_admin_user_delete', {'id': user.id})}}" href="#" {{ stimulus_controller('admin_confirm') }}>Supprimer</a>
                     </td>
                 </tr>
             {% endfor %}

+ 1 - 1
templates/reset_password/request.html.twig

@@ -12,7 +12,7 @@
         {{ form_row(requestForm.email) }}
         <div>
             <small>
-                Entrez votre adresse de courrile pour recevoir un lien de réinitialisation de votre mot de passe.
+                Entrez votre adresse de courriel pour recevoir un lien de réinitialisation de votre mot de passe.
                 Si vous ne recevez pas de courriel, vérifiez votre dossier de spam ou contactez
             </small>
         </div>

+ 14 - 2
templates/reset_password/reset.html.twig

@@ -1,12 +1,24 @@
-{% extends 'base.html.twig' %}
+{% extends 'bulma.html.twig' %}
 
 {% block title %}Réinitialisez votre mot de passe{% endblock %}
 
 {% block content %}
-    <h1>Réinitialisez votre mot de passe</h1>
 
+    <div class="content">
+<div class="container">
+  <div class="columns is-centered">
+    <div class="column is-5">
+      <div class="box">
     {{ form_start(resetForm) }}
+          <h1 class="title is-4 has-text-centered">Réinitialisez votre mot de passe</h1>
+
         {{ form_row(resetForm.plainPassword) }}
         <button class="btn btn-primary">Reset password</button>
     {{ form_end(resetForm) }}
+</div>
+</div>
+</div>
+</div>
+    </div>
+
 {% endblock %}

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff