```
✨ feat(profils): Ajoute la page de gestion de profil avec formulaires d'édition.
```
This commit is contained in:
87
src/Controller/ProfilsController.php
Normal file
87
src/Controller/ProfilsController.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Dto\Profils\DtoForm;
|
||||
use App\Dto\Profils\DtoPassword;
|
||||
use App\Dto\Profils\DtoPasswordForm;
|
||||
use App\Dto\Profils\DtoProfils;
|
||||
use App\Entity\Account;
|
||||
use App\Entity\AccountResetPasswordRequest;
|
||||
use App\Entity\Sub;
|
||||
use App\Form\RequestPasswordConfirmType;
|
||||
use App\Form\RequestPasswordRequestType;
|
||||
use App\Repository\SubRepository;
|
||||
use App\Service\Mailer\Mailer;
|
||||
use App\Service\ResetPassword\Event\ResetPasswordConfirmEvent;
|
||||
use App\Service\ResetPassword\Event\ResetPasswordEvent;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
|
||||
use Twig\Environment;
|
||||
|
||||
class ProfilsController extends AbstractController
|
||||
{
|
||||
|
||||
#[Route(path: '/profils', name: 'app_profile', options: ['sitemap' => false], methods: ['POST','GET'])]
|
||||
public function notificationSub(Request $request,UserPasswordHasherInterface $userPasswordHasher,EntityManagerInterface $entityManager,Mailer $mailer): Response
|
||||
{
|
||||
if(!$this->isGranted('ROLE_USER'))
|
||||
return $this->render('error/forbidden.twig',[
|
||||
'no_index' =>true
|
||||
]);
|
||||
[$formProfils,$response] = $this->handleProfils($this->getUser(),$request,$entityManager,$mailer);
|
||||
if(!is_null($response)){
|
||||
return $response;
|
||||
}
|
||||
[$formPassword,$response] = $this->handlePassword($this->getUser(),$request,$userPasswordHasher,$entityManager,$mailer);
|
||||
if(!is_null($response)){
|
||||
return $response;
|
||||
}
|
||||
return $this->render('profis/index.twig', [
|
||||
'formProfils' => $formProfils,
|
||||
'formPassword' => $formPassword,
|
||||
'formDelete' => '',
|
||||
'no_index'
|
||||
]);
|
||||
}
|
||||
|
||||
private function handleProfils(?\Symfony\Component\Security\Core\User\UserInterface $getUser, Request $request, EntityManagerInterface $entityManager, Mailer $mailer)
|
||||
{
|
||||
$oldEmail = $getUser->getEmail();
|
||||
$dto = new DtoProfils($getUser);
|
||||
$form = $this->createForm(DtoForm::class,$dto);
|
||||
$form->handleRequest($request);
|
||||
if($form->isSubmitted() && $form->isValid()){
|
||||
$getUser->setEmail($dto->getEmail());
|
||||
$getUser->setUsername($dto->getUsername());
|
||||
$entityManager->persist($getUser);
|
||||
$entityManager->flush();
|
||||
return [$form->createView(),$this->redirectToRoute('app_profile',['updateProfils'=>true])];
|
||||
}
|
||||
return [$form->createView(),null];
|
||||
}
|
||||
|
||||
private function handlePassword(?\Symfony\Component\Security\Core\User\UserInterface $getUser, Request $request, UserPasswordHasherInterface $userPasswordHasher, EntityManagerInterface $entityManager, Mailer $mailer)
|
||||
{
|
||||
$dto = new DtoPassword();
|
||||
$form = $this->createForm(DtoPasswordForm::class,$dto);
|
||||
$form->handleRequest($request);
|
||||
if($form->isSubmitted() && $form->isValid()){
|
||||
$getUser->setPassword($userPasswordHasher->hashPassword($dto->getPassword(), $getUser));
|
||||
$entityManager->persist($getUser);
|
||||
$entityManager->flush();
|
||||
return [$form->createView(),$this->redirectToRoute('app_profile',['updatePassword'=>true])];
|
||||
}
|
||||
return [$form->createView(),null];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
34
src/Dto/Profils/DtoForm.php
Normal file
34
src/Dto/Profils/DtoForm.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Dto\Profils;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\EmailType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class DtoForm extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder
|
||||
->add('email',EmailType::class,[
|
||||
'label' => 'profils.email.label',
|
||||
'attr' => [
|
||||
'placeholder' => 'profils.email.placeholder',
|
||||
]
|
||||
])
|
||||
->add('username',TextType::class,[
|
||||
'label' => 'profils.username.label',
|
||||
'attr' => [
|
||||
'placeholder' => 'profils.username.placeholder',
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefault('data_class',DtoProfils::class);
|
||||
}
|
||||
}
|
||||
24
src/Dto/Profils/DtoPassword.php
Normal file
24
src/Dto/Profils/DtoPassword.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Dto\Profils;
|
||||
|
||||
class DtoPassword
|
||||
{
|
||||
private $password = "";
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPassword(): string
|
||||
{
|
||||
return $this->password;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $password
|
||||
*/
|
||||
public function setPassword(string $password): void
|
||||
{
|
||||
$this->password = $password;
|
||||
}
|
||||
}
|
||||
27
src/Dto/Profils/DtoPasswordForm.php
Normal file
27
src/Dto/Profils/DtoPasswordForm.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Dto\Profils;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class DtoPasswordForm extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder
|
||||
->add('password',RepeatedType::class,[
|
||||
'type' => PasswordType::class,
|
||||
'first_options' => ['label' => 'profils.password.1','attr'=>['placeholder'=>'profils.password_holder.1']],
|
||||
'second_options' => ['label' => 'profils.password.2','attr'=>['placeholder'=>'profils.password_holder.2']],
|
||||
]);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefault('data_class',DtoPassword::class);
|
||||
}
|
||||
}
|
||||
50
src/Dto/Profils/DtoProfils.php
Normal file
50
src/Dto/Profils/DtoProfils.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Dto\Profils;
|
||||
|
||||
use App\Entity\Account;
|
||||
|
||||
class DtoProfils
|
||||
{
|
||||
private ?string $username;
|
||||
private ?string $email;
|
||||
|
||||
public function __construct(Account $account)
|
||||
{
|
||||
$this->username = $account->getUsername();
|
||||
$this->email = $account->getEmail();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getEmail(): ?string
|
||||
{
|
||||
return $this->email;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getUsername(): ?string
|
||||
{
|
||||
return $this->username;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string|null $email
|
||||
*/
|
||||
public function setEmail(?string $email): void
|
||||
{
|
||||
$this->email = $email;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $username
|
||||
*/
|
||||
public function setUsername(?string $username): void
|
||||
{
|
||||
$this->username = $username;
|
||||
}
|
||||
}
|
||||
@@ -123,9 +123,18 @@
|
||||
</button>
|
||||
|
||||
<div class="relative group">
|
||||
<a href="{{ path('app_login') }}" class="p-2 border-2 border-gray-900 hover:bg-gray-900 hover:text-white transition-colors">
|
||||
<i class="fas fa-user-circle"></i>
|
||||
</a>
|
||||
{% if app.user %}
|
||||
<a href="{{ path('app_profile') }}"
|
||||
class="p-2 border-2 border-gray-900 bg-indigo-600 text-white shadow-[3px_3px_0px_#000] hover:shadow-none hover:translate-x-[2px] hover:translate-y-[2px] transition-all flex items-center justify-center">
|
||||
<i class="fas fa-user-check"></i>
|
||||
</a>
|
||||
{% else %}
|
||||
{# État DÉCONNECTÉ : Style neutre d'origine #}
|
||||
<a href="{{ path('app_login') }}"
|
||||
class="p-2 border-2 border-gray-900 bg-white text-gray-900 hover:bg-gray-900 hover:text-white transition-all flex items-center justify-center">
|
||||
<i class="fas fa-user-circle"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<button id="mobileMenuButton" class="lg:hidden p-2 border-2 border-gray-900 bg-yellow-400">
|
||||
|
||||
81
templates/error/forbidden.twig
Normal file
81
templates/error/forbidden.twig
Normal file
@@ -0,0 +1,81 @@
|
||||
{% extends 'base.twig' %}
|
||||
|
||||
{# --- METADATA --- #}
|
||||
{% block title %}{{ 'error.forbidden'|trans }}{% endblock %}
|
||||
|
||||
{# --- BODY --- #}
|
||||
{% block body %}
|
||||
<div class="min-h-screen bg-[#f0f0f0] flex items-center justify-center p-4 italic overflow-hidden relative">
|
||||
|
||||
{# Éléments de décorations en arrière-plan (Style Interface de jeu) #}
|
||||
<div class="absolute top-10 left-10 text-gray-200 text-9xl font-black select-none pointer-events-none opacity-50">403</div>
|
||||
<div class="absolute bottom-10 right-10 text-gray-200 text-9xl font-black select-none pointer-events-none opacity-50">ERROR</div>
|
||||
|
||||
<div class="max-w-2xl w-full relative z-10">
|
||||
<div class="bg-white border-8 border-gray-900 p-8 md:p-12 shadow-[16px_16px_0px_#ef4444] relative">
|
||||
|
||||
{# Badge d'alerte #}
|
||||
<div class="absolute -top-6 left-6 bg-red-600 text-white px-6 py-2 font-black uppercase tracking-tighter border-4 border-gray-900 skew-x-[-15deg] shadow-[4px_4px_0px_#000]">
|
||||
{{ 'error.forbidden.alert'|trans|default('CRITICAL_ACCESS_ERROR') }}
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<h1 class="text-6xl md:text-8xl font-black text-gray-900 uppercase leading-none tracking-tighter mb-6">
|
||||
{{ 'error.forbidden.title'|trans|default('403') }}<span class="text-red-600">.</span>
|
||||
</h1>
|
||||
|
||||
<p class="text-xl md:text-2xl font-bold text-gray-800 uppercase italic mb-8 border-l-8 border-red-600 pl-6 leading-tight">
|
||||
{{ 'error.forbidden.description'|trans|default('Accès refusé. Vous tentez d\'entrer dans une zone sécurisée.') }}
|
||||
</p>
|
||||
|
||||
<div class="flex flex-col sm:flex-row gap-6">
|
||||
{# Bouton Retour Accueil #}
|
||||
<a href="{{ path('app_home') }}" class="group relative inline-block">
|
||||
<div class="absolute inset-0 bg-gray-900 translate-x-2 translate-y-2 group-hover:translate-x-0 group-hover:translate-y-0 transition-transform"></div>
|
||||
<div class="relative bg-indigo-600 text-white px-8 py-4 font-black uppercase italic tracking-widest border-4 border-gray-900 flex items-center justify-center gap-3">
|
||||
<i class="fas fa-home"></i>
|
||||
{{ 'error.go_home'|trans|default('RETOUR_HOME') }}
|
||||
</div>
|
||||
</a>
|
||||
|
||||
{# Bouton Login (si déconnecté) #}
|
||||
{% if not app.user %}
|
||||
<a href="{{ path('app_login') }}" class="group relative inline-block">
|
||||
<div class="absolute inset-0 bg-gray-900 translate-x-2 translate-y-2 group-hover:translate-x-0 group-hover:translate-y-0 transition-transform"></div>
|
||||
<div class="relative bg-yellow-400 text-black px-8 py-4 font-black uppercase italic tracking-widest border-4 border-gray-900 flex items-center justify-center gap-3">
|
||||
<i class="fas fa-sign-in-alt"></i>
|
||||
{{ 'login.title'|trans|default('CONNEXION') }}
|
||||
</div>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Footer technique #}
|
||||
<div class="mt-12 pt-6 border-t-4 border-gray-100 flex justify-between items-center">
|
||||
<span class="text-[10px] font-black uppercase text-gray-400 tracking-[0.2em] italic">// SYSTEM_LOG: FORBIDDEN_ENTRY</span>
|
||||
<div class="flex gap-2">
|
||||
<div class="w-3 h-3 bg-red-500 border-2 border-black"></div>
|
||||
<div class="w-3 h-3 bg-gray-900 border-2 border-black"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* Petit effet de glitch sur le titre au survol */
|
||||
h1:hover {
|
||||
animation: glitch 0.3s cubic-bezier(.25,.46,.45,.94) both infinite;
|
||||
}
|
||||
|
||||
@keyframes glitch {
|
||||
0% { transform: translate(0) }
|
||||
20% { transform: translate(-3px, 3px) }
|
||||
40% { transform: translate(-3px, -3px) }
|
||||
60% { transform: translate(3px, 3px) }
|
||||
80% { transform: translate(3px, -3px) }
|
||||
100% { transform: translate(0) }
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
178
templates/profis/index.twig
Normal file
178
templates/profis/index.twig
Normal file
@@ -0,0 +1,178 @@
|
||||
{% extends 'base.twig' %}
|
||||
|
||||
{# --- METADATA --- #}
|
||||
{% block title %}{{ 'profils_title'|trans }}{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="min-h-screen bg-[#fbfbfb] py-8 md:py-16 px-4 italic">
|
||||
<div class="max-w-6xl mx-auto">
|
||||
|
||||
<div class="flex flex-col lg:flex-row gap-8 md:gap-12">
|
||||
|
||||
{# --- NAVIGATION TAGS (SIDEBAR) --- #}
|
||||
<aside class="lg:w-1/4">
|
||||
<nav class="flex flex-col gap-3 sticky top-8">
|
||||
|
||||
{# --- SECTION NAVIGATION STANDARD --- #}
|
||||
|
||||
{# Item: Profils #}
|
||||
<a href="#general" class="group relative inline-block w-full">
|
||||
<div class="absolute inset-0 bg-black translate-x-1 translate-y-1"></div>
|
||||
<div class="relative bg-indigo-600 text-white px-4 py-3 font-black uppercase text-sm border-2 border-black flex items-center gap-3">
|
||||
<i class="fas fa-user-circle"></i> {{ 'nav.profile'|trans|default('Profils') }}
|
||||
</div>
|
||||
</a>
|
||||
|
||||
|
||||
{# Item: Epage #}
|
||||
<a href="#" class="group relative inline-block w-full">
|
||||
<div class="absolute inset-0 bg-black translate-x-1 translate-y-1 group-hover:translate-x-0 group-hover:translate-y-0 transition-all"></div>
|
||||
<div class="relative bg-white text-black px-4 py-3 font-black uppercase text-sm border-2 border-black flex items-center gap-3">
|
||||
<i class="fas fa-globe"></i> {{ 'nav.epage'|trans|default('Epage') }}
|
||||
</div>
|
||||
</a>
|
||||
|
||||
{# Item: Organisateur #}
|
||||
<a href="#" class="group relative inline-block w-full">
|
||||
<div class="absolute inset-0 bg-black translate-x-1 translate-y-1 group-hover:translate-x-0 group-hover:translate-y-0 transition-all"></div>
|
||||
<div class="relative bg-white text-black px-4 py-3 font-black uppercase text-sm border-2 border-black flex items-center gap-3">
|
||||
<i class="fas fa-calendar-star"></i> {{ 'nav.event'|trans|default('Organisateur') }}
|
||||
</div>
|
||||
</a>
|
||||
|
||||
{# --- SECTION ADMINISTRATION (Conditionnelle) --- #}
|
||||
{% if is_granted('ROLE_ADMIN') %}
|
||||
<div class="mt-4 pt-4 border-t-2 border-dashed border-gray-300">
|
||||
<a href="{{ path('admin_dashboard') }}"
|
||||
data-turbo="false"
|
||||
class="group relative inline-block w-full">
|
||||
<div class="absolute inset-0 bg-black translate-x-1 translate-y-1 group-hover:translate-x-0 group-hover:translate-y-0 transition-all"></div>
|
||||
<div class="relative bg-yellow-400 text-black px-4 py-3 font-black uppercase text-sm border-2 border-black flex items-center gap-3">
|
||||
<i class="fas fa-user-shield"></i> {{ 'nav.admin'|trans|default('Administration') }}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# --- SECTION ACTIONS COMPTE --- #}
|
||||
<div class="mt-8 space-y-3">
|
||||
{# Logout #}
|
||||
<a href="{{ path('app_logout') }}" class="group relative inline-block w-full">
|
||||
<div class="absolute inset-0 bg-black translate-x-1 translate-y-1 group-hover:translate-x-0 group-hover:translate-y-0 transition-all"></div>
|
||||
<div class="relative bg-gray-200 text-black px-4 py-3 font-black uppercase text-sm border-2 border-black flex items-center gap-3">
|
||||
<i class="fas fa-sign-out-alt"></i> {{ 'nav.logout'|trans|default('Déconnexion') }}
|
||||
</div>
|
||||
</a>
|
||||
|
||||
{# Suppression (Alerte) #}
|
||||
<a href="#" class="group relative inline-block w-full">
|
||||
<div class="absolute inset-0 bg-red-600 translate-x-1 translate-y-1 group-hover:translate-x-0 group-hover:translate-y-0 transition-all"></div>
|
||||
<div class="relative bg-white text-red-600 px-4 py-3 font-black uppercase text-[10px] border-2 border-black flex items-center gap-3">
|
||||
<i class="fas fa-trash-alt"></i> {{ 'nav.delete'|trans|default('Supprimer mon compte') }}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
{# --- CONTENU PRINCIPAL --- #}
|
||||
<main class="lg:w-3/4 space-y-12">
|
||||
|
||||
{# Section Profils #}
|
||||
<section id="general" class="scroll-mt-8">
|
||||
<div class="bg-white border-4 border-black p-6 md:p-10 shadow-[12px_12px_0px_#4f46e5]">
|
||||
<h2 class="text-3xl font-black uppercase italic mb-8 border-b-4 border-gray-100 pb-4">
|
||||
{{ 'form.section.general'|trans|default('Informations Profil') }}
|
||||
</h2>
|
||||
{% if app.request.query.has('updateProfils') %}
|
||||
<div class="mb-8 animate-in fade-in slide-in-from-top-4 duration-300">
|
||||
<div class="relative inline-block w-full">
|
||||
{# L'ombre rigide en arrière-plan #}
|
||||
<div class="absolute inset-0 bg-black translate-x-2 translate-y-2"></div>
|
||||
|
||||
{# Le contenu du message #}
|
||||
<div class="relative bg-green-400 border-4 border-black p-4 flex items-center gap-4">
|
||||
{# Icône de succès avec un petit effet de rebond #}
|
||||
<div class="bg-black text-green-400 w-10 h-10 flex items-center justify-center shrink-0">
|
||||
<i class="fas fa-check-double text-xl"></i>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<span class="text-[10px] font-black uppercase tracking-[0.2em] text-black/60 leading-none mb-1">
|
||||
System.Notification
|
||||
</span>
|
||||
<span class="font-black uppercase italic text-gray-900 leading-tight">
|
||||
{{ 'form.section.update_profils_complete'|trans }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{# Bouton pour fermer (optionnel) #}
|
||||
<button onclick="this.parentElement.parentElement.remove()" class="ml-auto hover:scale-110 transition-transform">
|
||||
<i class="fas fa-times text-black"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{{ form_start(formProfils, {'attr': {'class': 'space-y-6'}}) }}
|
||||
{{ form_row(formProfils.username) }}
|
||||
{{ form_row(formProfils.email) }}
|
||||
<button type="submit" class="bg-indigo-600 text-white px-8 py-3 font-black uppercase border-2 border-black shadow-[4px_4px_0px_#000] hover:shadow-none hover:translate-x-1 hover:translate-y-1 transition-all">
|
||||
{{ 'form.button.save'|trans|default('Sauvegarder') }}
|
||||
</button>
|
||||
{{ form_end(formProfils) }}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{# Section Sécurité #}
|
||||
<section id="security" class="scroll-mt-8">
|
||||
<div class="bg-white border-4 border-black p-6 md:p-10 shadow-[12px_12px_0px_#eab308]">
|
||||
<h2 class="text-3xl font-black uppercase italic mb-8 border-b-4 border-gray-100 pb-4">
|
||||
{{ 'form.section.security'|trans }}
|
||||
</h2>
|
||||
{% if app.request.query.has('updatePassword') %}
|
||||
<div class="mb-8 animate-in fade-in slide-in-from-top-4 duration-300">
|
||||
<div class="relative inline-block w-full">
|
||||
{# L'ombre rigide en arrière-plan #}
|
||||
<div class="absolute inset-0 bg-black translate-x-2 translate-y-2"></div>
|
||||
|
||||
{# Le contenu du message #}
|
||||
<div class="relative bg-green-400 border-4 border-black p-4 flex items-center gap-4">
|
||||
{# Icône de succès avec un petit effet de rebond #}
|
||||
<div class="bg-black text-green-400 w-10 h-10 flex items-center justify-center shrink-0">
|
||||
<i class="fas fa-check-double text-xl"></i>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<span class="text-[10px] font-black uppercase tracking-[0.2em] text-black/60 leading-none mb-1">
|
||||
System.Notification
|
||||
</span>
|
||||
<span class="font-black uppercase italic text-gray-900 leading-tight">
|
||||
{{ 'form.section.update_password_complete'|trans }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{# Bouton pour fermer (optionnel) #}
|
||||
<button onclick="this.parentElement.parentElement.remove()" class="ml-auto hover:scale-110 transition-transform">
|
||||
<i class="fas fa-times text-black"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{{ form_start(formPassword, {'attr': {'class': 'space-y-6'}}) }}
|
||||
{{ form_row(formPassword.password.first) }}
|
||||
{{ form_row(formPassword.password.second) }}
|
||||
<button type="submit" class="bg-yellow-400 text-black px-8 py-3 font-black uppercase border-2 border-black shadow-[4px_4px_0px_#000] hover:shadow-none hover:translate-x-1 hover:translate-y-1 transition-all">
|
||||
{{ 'form.button.password'|trans|default('Mettre à jour le mot de passe') }}
|
||||
</button>
|
||||
{{ form_end(formPassword) }}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -911,6 +911,11 @@ safespace:
|
||||
|
||||
# Formulaire de candidature
|
||||
form:
|
||||
# --- LABELS DES CHAMPS ---
|
||||
|
||||
|
||||
# --- BOUTONS ---
|
||||
|
||||
choices:
|
||||
gender:
|
||||
not_specified: "Non spécifié"
|
||||
@@ -959,7 +964,12 @@ form:
|
||||
role: "Quel rôle souhaitez-vous occuper ?"
|
||||
section:
|
||||
social: "Réseaux & Portfolio"
|
||||
general: "INFORMATIONS PROFIL"
|
||||
security: "SÉCURITÉ & PASSWORD"
|
||||
update_profils_complete: "SYNCHRONISATION DU PROFIL RÉUSSIE !"
|
||||
button:
|
||||
save: "SAUVEGARDER LES MODIFS"
|
||||
password: "UPDATE PASSWORD"
|
||||
submit: "Envoyer ma candidature"
|
||||
|
||||
# Messages de succès / erreur (Optionnel pour vos contrôleurs)
|
||||
@@ -1143,3 +1153,41 @@ Adresse e-mail: Adresse e-mail
|
||||
Sujet: Sujet
|
||||
Téléphone (facultatif): Téléphone (facultatif)
|
||||
Votre message: Votre message
|
||||
# translations/messages.fr.yaml
|
||||
|
||||
error:
|
||||
go_home: "RETOUR AU QG"
|
||||
forbidden:
|
||||
title: "403"
|
||||
alert: "ERREUR_ACCÈS_CRITIQUE"
|
||||
description: "ACCÈS REFUSÉ. CETTE ZONE EST RÉSERVÉE AUX MEMBRES AUTORISÉS OU NÉCESSITE DES PERMISSIONS SPÉCIALES."
|
||||
# Si vous voulez personnaliser davantage
|
||||
sub_text: "VOTRE TENTATIVE D'INTRUSION A ÉTÉ LOGUÉE DANS LE SYSTÈME."
|
||||
|
||||
login:
|
||||
title: "CONNEXION"
|
||||
|
||||
# --- NAVIGATION DU PROFIL ---
|
||||
nav:
|
||||
profile: "PROFIL"
|
||||
security: "SÉCURITÉ"
|
||||
epage: "EPAGE"
|
||||
event: "ORGANISATEUR"
|
||||
admin: "PANNEAU ADMIN"
|
||||
logout: "QUITTER LA SESSION"
|
||||
delete: "SUPPRIMER LE COMPTE"
|
||||
|
||||
# --- TITRES ET META ---
|
||||
profils_title: "MON DASHBOARD"
|
||||
profils_description: "GÉREZ VOS INFORMATIONS PERSONNELLES ET LA SÉCURITÉ DE VOTRE COMPTE."
|
||||
|
||||
# --- SECTIONS DES FORMULAIRES ---
|
||||
|
||||
|
||||
# --- PAGES D'ERREURS ---
|
||||
profils.username.label: "NOM D'UTILISATEUR"
|
||||
profils.email.label: "ADRESSE E-MAIL"
|
||||
profils.password.1: "MOT DE PASSE"
|
||||
profils.password_holder.1: "MOT DE PASSE"
|
||||
profils.password.2: "CONFIRMER LE MOT DE PASSE"
|
||||
profils.password_holder.2: "CONFIRMER LE MOT DE PASSE"
|
||||
|
||||
Reference in New Issue
Block a user