ajout dropdown de deconnection

This commit is contained in:
Ploush 2026-03-25 23:39:39 +01:00
parent 50360a590b
commit c14507a716
7 changed files with 199 additions and 16 deletions

View file

@ -13,6 +13,8 @@
/* Backgrounds de navigation */
--nav-bg-light: rgba(255, 255, 255, 0.5);
--nav-bg-dark: rgba(12, 10, 18, 0.4);
--nav-bg-light-so: rgba(255, 255, 255, 0.82);
--nav-bg-dark-so: rgba(12, 10, 18, 0.8);
/* Couleurs de texte */
--text-light: #1a1625;
@ -30,7 +32,7 @@
/* Effets de blur */
--blur-sm: 8px;
--blur-md: 20px;
--blur-md: 25px;
/* Durées de transition */
--duration-fast: 0.15s; /* Transforms rapides */

View file

@ -73,13 +73,7 @@ header nav {
flex-wrap: nowrap;
}
/* ── LIENS DE NAVIGATION — style calqué sur .login-btn ─────────── */
/*
"all: unset" neutralise Bootstrap 5 entièrement sur .nav-link,
puis on réapplique exactement ce qu'on veut.
Les !important sur padding/border/background garantissent
qu'aucune règle Bootstrap résiduelle ne peut l'écraser.
*/
/* ── LIENS DE NAVIGATION ───────────────────────────────────────── */
header nav .nav-link {
all: unset;
@ -90,7 +84,7 @@ header nav .nav-link {
cursor: pointer;
text-decoration: none !important;
/* Dimensions — identiques au .login-btn */
/* Dimensions */
padding: 0.55rem 1.1rem !important;
border-radius: 10px !important;
border: 1px solid var(--border-light) !important;
@ -153,6 +147,80 @@ header nav .nav-link.active:hover {
header nav .nav-link::before,
header nav .nav-link::after { display: none !important; }
/* ── LIENS DROPDOWN ─────────── */
header .header-dropdown .dropdown-link {
all: unset;
display: inline-flex !important;
align-items: center !important;
gap: 0.45rem;
white-space: nowrap;
cursor: pointer;
text-decoration: none !important;
/* Dimensions */
padding: 0.55rem 1.1rem !important;
border-radius: 10px !important;
border: 1px solid var(--border-light) !important;
background: transparent !important;
/* Typographie */
font-family: 'Syne', sans-serif !important;
font-weight: 600 !important;
font-size: 0.875rem !important;
letter-spacing: 0.03em !important;
text-transform: uppercase !important;
color: var(--muted-light) !important;
transition:
color var(--duration-normal) ease,
background var(--duration-normal) ease,
border-color var(--duration-normal) ease,
box-shadow var(--duration-normal) ease,
transform var(--duration-fast) ease !important;
}
[data-theme="dark"] header .header-dropdown .dropdown-link {
border-color: var(--border-dark) !important;
color: var(--muted-dark) !important;
}
/* Hover */
header .header-dropdown .dropdown-link:hover {
color: var(--violet) !important;
background: rgba(0, 0, 0, 0.05) !important;
border-color: rgba(0, 0, 0, 0.15) !important;
transform: translateY(-1px) !important;
box-shadow: none !important;
}
[data-theme="dark"] header .header-dropdown .dropdown-link:hover {
color: var(--violet-light) !important;
background: rgba(255, 255, 255, 0.07) !important;
border-color: rgba(255, 255, 255, 0.15) !important;
}
/* Actif — fond violet léger + bordure violet */
header .header-dropdown .dropdown-link.active {
color: var(--violet) !important;
background: var(--pill-bg-light) !important;
border-color: rgba(124, 58, 237, 0.30) !important;
font-weight: 700 !important;
}
[data-theme="dark"] header .header-dropdown .dropdown-link.active {
color: var(--violet-light) !important;
background: var(--pill-bg-dark) !important;
border-color: rgba(167, 139, 250, 0.30) !important;
}
/* Actif + hover — glow subtil */
header .header-dropdown .dropdown-link.active:hover {
box-shadow: 0 4px 16px rgba(124, 58, 237, 0.20) !important;
}
/* Pas de pseudo-élément soulignement */
header .header-dropdown .dropdown-link::before,
header .header-dropdown .dropdown-link::after { display: none !important; }
/* ── ACTIONS (droite) ──────────────────────────────────────────── */
.header-actions {
display: flex;
@ -223,14 +291,15 @@ header nav .nav-link::after { display: none !important; }
}
/* ── DROPDOWN CONNEXION ────────────────────────────────────────── */
.login-wrapper { position: relative; }
.header-wrapper { position: relative; }
.login-dropdown {
.header-dropdown {
z-index: 100;
position: absolute;
top: calc(100% + 10px);
right: 0;
width: 260px;
background: var(--nav-bg-light);
background: var(--nav-bg-light-so);
backdrop-filter: blur(var(--blur-md));
-webkit-backdrop-filter: blur(var(--blur-md));
border: 1px solid var(--border-light);
@ -242,12 +311,12 @@ header nav .nav-link::after { display: none !important; }
gap: 0.75rem;
animation: dropdown-in var(--duration-normal) ease;
}
[data-theme="dark"] .login-dropdown {
background: var(--nav-bg-dark);
[data-theme="dark"] .header-dropdown {
background: var(--nav-bg-dark-so);
border-color: var(--border-dark);
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.4);
}
.login-dropdown.open { display: flex; }
.header-dropdown.open { display: flex; }
@keyframes dropdown-in {
from { opacity: 0; transform: translateY(-6px); }

View file

@ -0,0 +1,6 @@
# Load routes from all controllers in src/Controller/
controllers:
resource: ../../src/Controller/
type: attribute

View file

@ -0,0 +1,40 @@
<?php
namespace App\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ControllerEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
use Twig\Environment;
class TwigGlobalSubscriber implements EventSubscriberInterface
{
public function __construct(
private readonly Environment $twig,
private readonly AuthenticationUtils $authUtils,
) {}
public function onKernelController(ControllerEvent $event): void
{
if (!$event->isMainRequest()) {
return;
}
try {
$this->twig->addGlobal('error', $this->authUtils->getLastAuthenticationError());
$this->twig->addGlobal('last_username', $this->authUtils->getLastUsername());
} catch (\LogicException) {
// Route hors pare-feu (assets, profiler...) — on ignore
$this->twig->addGlobal('error', null);
$this->twig->addGlobal('last_username', '');
}
}
public static function getSubscribedEvents(): array
{
return [
KernelEvents::CONTROLLER => 'onKernelController',
];
}
}

View file

@ -38,7 +38,11 @@
{# ── Droite : Actions (thème + connexion) ─────────────────── #}
<div class="header-actions">
{% include 'partials/_theme_toggle.html.twig' %}
{% include 'partials/_login_dropdown.html.twig' %}
{% if is_granted('ROLE_USER') %}
{% include 'partials/_logout_dropdown.html.twig' %}
{% else %}
{% include 'partials/_login_dropdown.html.twig' %}
{% endif %}
</div>
</header>

View file

@ -0,0 +1,62 @@
{# ══════════════════════════════════════════════════════════════
templates/partials/_login_dropdown.html.twig
Bouton + dropdown formulaire de connexion
══════════════════════════════════════════════════════════════ #}
<div class="header-wrapper" id="loginWrapper"
data-controller="header-dropdown"
data-header-dropdown-target="wrapper">
{# Bouton déclencheur #}
<button class="login-btn" id="loginToggle"
aria-haspopup="true" aria-expanded="false"
data-header-dropdown-target="toggle">
{% if app.user.uriPp is not null %}
<img src="{{ asset(app.user.uri_pp) }}" alt="Photo de profil de {{ app.user.pseudo }}" class="profile-pic">
{% else %}
<svg width="15" height="15" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>
<circle cx="12" cy="7" r="4"/>
</svg>
{% endif %}
{{ app.user.pseudo }}
</button>
{# Dropdown #}
<div class="header-dropdown" id="headerDropdown"
role="dialog" aria-label="Formulaire de connexion"
data-header-dropdown-target="dropdown">
<span class="dropdown-title">Bonjour {{ app.user.pseudo }}</span>
{% if is_granted("ROLE_ADMIN") %}
<a href="{{ path('app_accueil') }}"
class="dropdown-link {{ current_route == 'app_home' ? 'active' }}">
Administration
</a>
{% endif %}
<a href="{{ path('app_accueil') }}"
class="dropdown-link {{ current_route == 'app_home' ? 'active' }}">
Parametre
</a>
<form action="{{ path('app_logout') }}" method="post" >
<input type="hidden"
name="_csrf_token"
value="{{ csrf_token('authenticate') }}">
{# Retenir la page courante pour y revenir en cas d'erreur #}
<input type="hidden"
name="_failure_path"
value="{{ app.request.uri }}">
<button class="submit-btn" type="submit">Se déconnecter</button>
</form>
</div>
</div>