浏览代码

gestion utilisateurs et genrs

garthh 1 周之前
父节点
当前提交
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
     // Initialise DataTables
-    $(this.element).DataTable();
+    $(this.element).DataTable({ responsive: true });
 
 
     // Marque comme initialisé pour les reconnections futures
     // Marque comme initialisé pour les reconnections futures
     this.element.dataset.datatableInitialized = 'true';
     this.element.dataset.datatableInitialized = 'true';

文件差异内容过多而无法显示
+ 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
     # add more service definitions when explicit configuration is needed
     # please note that last definitions always *replace* previous ones
     # 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
      * 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.');
         $this->addFlash('success', 'Un nouvel email de confirmation a été envoyé à votre adresse email.');
         return $this->redirectToRoute('app_login');
         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')]
     #[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
 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.',
                 'invalid_message' => 'Les mots de passe ne sont pas identiques.',
                 'type' => PasswordType::class,
                 'type' => PasswordType::class,
                 'first_options' => [
                 '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,
                     'toggle' => true,
                     'attr' => ['autocomplete' => 'new-password'],
                     'attr' => ['autocomplete' => 'new-password'],
                     'constraints' => [
                     '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
     public function checkPostAuth(UserInterface $user): void
     {
     {
+        // Si utilisateur n'existe pas, on ferme.
         if (!$user instanceof AppUser) {
         if (!$user instanceof AppUser) {
             return;
             return;
         }
         }
 
 
+        // Sinon, on passe à la suite en contrôlant si le compte est actif
         if ($user instanceof AppUser) {
         if ($user instanceof AppUser) {
             if (!$user->isVerified()) {
             if (!$user->isVerified()) {
+                
                 throw new AuthenticationException(ExceptionsMsg::NOT_VERIFIED_USER->value);
                 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 %}
 {% 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">
   <section class="section">
+
+
+
     <div class="container">
     <div class="container">
       <div class="columns">
       <div class="columns">
         <!-- Colonne 1 -->
         <!-- 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="column">
           <div class="box">
           <div class="box">
             <h2 class="title is-4">Utilisateurs</h2>
             <h2 class="title is-4">Utilisateurs</h2>
@@ -24,11 +41,31 @@
           </div>
           </div>
         </div>
         </div>
         <!-- Colonne 3 -->
         <!-- Colonne 3 -->
+
+      </div>
+            <div class="columns">
+        <!-- Colonne 1 -->
         <div class="column">
         <div class="column">
           <div class="box">
           <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>
         </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_errors(form) }}
     {{ form_start(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) }}
         {{ 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>
         <a href="{{ path('app_admin_user') }}" class="button">Annuler</a>
         </div>
         </div>
+        </div>
+        </div>
     {{ form_end(form) }}
     {{ form_end(form) }}
 
 
 {% endblock %}
 {% endblock %}

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

@@ -4,10 +4,22 @@
 
 
 {% block content %}
 {% 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="block">
         <div class="is-grouped">
         <div class="is-grouped">
             <a class="button is-primary" href="{{ path('app_admin_user_add') }}">Ajouter un utilisateur</a>
             <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>
     </div>
     </div>
 
 
@@ -27,13 +39,13 @@
             {% for user in users %}
             {% for user in users %}
                 <tr>
                 <tr>
                     <td><a href="{{ path('app_admin_user_edit', {id: user.id}) }}">{{ user.firstname }} {{ user.lastname }}</a></td>
                     <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>{{ component('Role', {roles: user.roles}) }}</td>
                     <td>{{ user.lastLogin ? user.lastLogin|date('d/m/Y H:i:s', app_timezone) : 'Jamais' }}</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>{{ user.lastUpdate ? user.lastUpdate|date('d/m/Y H:i:s', app_timezone) : 'Jamais' }}</td>
                     <td>
                     <td>
                         <a class="button" href="{{ path('app_admin_user_edit', {id: user.id}) }}">Éditer</a>
                         <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>
                     </td>
                 </tr>
                 </tr>
             {% endfor %}
             {% endfor %}

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

@@ -12,7 +12,7 @@
         {{ form_row(requestForm.email) }}
         {{ form_row(requestForm.email) }}
         <div>
         <div>
             <small>
             <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
                 Si vous ne recevez pas de courriel, vérifiez votre dossier de spam ou contactez
             </small>
             </small>
         </div>
         </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 title %}Réinitialisez votre mot de passe{% endblock %}
 
 
 {% block content %}
 {% 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) }}
     {{ form_start(resetForm) }}
+          <h1 class="title is-4 has-text-centered">Réinitialisez votre mot de passe</h1>
+
         {{ form_row(resetForm.plainPassword) }}
         {{ form_row(resetForm.plainPassword) }}
         <button class="btn btn-primary">Reset password</button>
         <button class="btn btn-primary">Reset password</button>
     {{ form_end(resetForm) }}
     {{ form_end(resetForm) }}
+</div>
+</div>
+</div>
+</div>
+    </div>
+
 {% endblock %}
 {% endblock %}

部分文件因为文件数量过多而无法显示