Files
ludikevent_crm/templates/dashboard/base.twig
Serreau Jovann 890da18c15 ```
 feat(Stripe): Intègre Stripe pour la gestion des paiements et les webhooks

Ajoute Stripe pour la synchronisation des clients et la configuration des webhooks.
Crée une commande pour synchroniser les clients locaux avec Stripe.
Ajoute un champ customerId à l'entité Customer.
```
2026-01-16 13:15:42 +01:00

157 lines
11 KiB
Twig

<!DOCTYPE html>
<html lang="fr" class="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Administration{% endblock %} — Intranet Ludikevent</title>
{{ vite_asset('admin.js', {}) }}
{% if app.environment != 'dev' %}
{{ pwa() }}
{% endif %}
</head>
<body class="bg-slate-50 dark:bg-[#0f172a] text-slate-900 dark:text-slate-200 antialiased overflow-hidden font-sans">
<div class="flex h-screen overflow-hidden">
{# SIDEBAR #}
<aside id="sidebar" class="fixed inset-y-0 left-0 z-40 w-72 bg-white dark:bg-[#1e293b] border-r border-slate-200 dark:border-slate-800 lg:translate-x-0 transition-all duration-300 ease-in-out">
<div class="flex items-center px-8 h-20 border-b border-slate-100 dark:border-slate-800">
<div class="flex items-center space-x-3">
<div class="w-8 h-8 bg-blue-600 rounded-lg flex items-center justify-center shadow-lg shadow-blue-500/30">
<span class="text-white font-bold text-xl font-serif">L</span>
</div>
<span class="text-lg font-bold tracking-tight text-slate-800 dark:text-white uppercase italic">Intranet <span class="text-blue-600 not-italic">Ludikevent</span></span>
</div>
</div>
<nav class="flex flex-col p-6 space-y-8 h-[calc(100vh-80px)] overflow-y-auto custom-scrollbar">
<div>
<p class="px-4 mb-4 text-[10px] font-semibold text-slate-400 dark:text-slate-500 uppercase tracking-[0.2em]">Menu Principal</p>
<div class="space-y-1">
{% macro nav_link(path, label, icon_svg, current_route) %}
{% set isActive = app.request.get('_route') == current_route %}
<a href="{{ path }}" class="flex items-center space-x-3 px-4 py-3 rounded-xl transition-all duration-200 group {{ isActive ? 'bg-blue-600 text-white shadow-lg shadow-blue-500/30' : 'hover:bg-slate-100 dark:hover:bg-slate-800 text-slate-600 dark:text-slate-400' }}">
<svg class="w-5 h-5 {{ isActive ? 'text-white' : 'text-slate-400 group-hover:text-blue-500' }}" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">{{ icon_svg|raw }}</svg>
<span class="font-semibold text-sm">{{ label }}</span>
</a>
{% endmacro %}
{% import _self as menu %}
{{ menu.nav_link(path('app_crm'), 'Dashboard', '<path d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path>', 'app_crm') }}
{{ menu.nav_link(path('app_crm_customer'), 'Clients', '<path d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>', 'app_clients') }}
</div>
</div>
<div>
<p class="px-4 mb-4 text-[10px] font-semibold text-slate-400 dark:text-slate-500 uppercase tracking-[0.2em]">Configuration</p>
<details class="group" {{ (app.request.get('_route') matches '/^app_crm_administrateur/' or app.request.get('_route') == 'app_crm_audit_logs') ? 'open' }}>
<summary class="list-none w-full flex items-center justify-between px-4 py-3 rounded-xl hover:bg-slate-100 dark:hover:bg-slate-800 text-slate-600 dark:text-slate-400 transition-all duration-200 cursor-pointer">
<div class="flex items-center space-x-3">
<svg class="w-5 h-5 text-slate-400 group-hover:text-blue-500" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37a1.724 1.724 0 002.572-1.065z"></path></svg>
<span class="font-semibold text-sm">Paramètres</span>
</div>
<svg class="w-4 h-4 transition-transform duration-300 arrow-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg>
</summary>
<div class="mt-2 space-y-1">
<a href="{{ path('app_crm_administrateur') }}" class="block px-12 py-2 text-sm hover:text-blue-600 transition-colors">Gestion Admins</a>
<a href="{{ path('app_crm_audit_logs') }}" class="block px-12 py-2 text-sm hover:text-blue-600 transition-colors">Audit Logs</a>
<a href="{{ path('app_crm_backup') }}" class="block px-12 py-2 text-sm hover:text-blue-600 transition-colors">Sauvegarde</a>
</div>
</details>
</div>
</nav>
</aside>
{# MAIN CONTENT #}
<main class="flex-1 flex flex-col min-w-0 lg:ml-72 bg-slate-50 dark:bg-[#0f172a] h-screen overflow-y-auto custom-scrollbar relative">
{# HEADER #}
{# HEADER CORRIGÉ #}
<header class="h-20 flex items-center justify-between px-8 bg-white dark:bg-[#1e293b] border-b border-slate-200 dark:border-slate-800 sticky top-0 z-30 gap-6">
{# Barre de recherche parfaitement alignée #}
<div class="flex-1 max-w-2xl relative">
<form action="{{ path('app_crm_search') }}" method="GET" class="flex items-center">
{# Input avec padding droit suffisant pour le bouton #}
<input required type="text" name="q" placeholder="Recherche rapide..."
class="w-full pl-2 py-3 bg-slate-50 dark:bg-slate-900/50 border border-slate-200 dark:border-slate-700 rounded-2xl text-sm focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all outline-none text-slate-700 dark:text-slate-200">
{# Bouton ancré à droite SANS chevauchement #}
<button type="submit" class="ml-2 px-5 py-2 bg-blue-600 text-white text-[10px] font-bold uppercase tracking-widest rounded-xl hover:bg-blue-700 transition-colors shadow-md">
Chercher
</button>
</form>
</div>
{# Profil utilisateur #}
<div class="flex items-center gap-2">
{# Zone Profil cliquable #}
<a href="{{ path('app_crm_profils') }}"
class="flex items-center space-x-3 px-4 py-2 bg-slate-50 dark:bg-slate-900/50 rounded-2xl border border-slate-200 dark:border-slate-700 shrink-0 hover:bg-slate-100 dark:hover:bg-slate-800 transition-all group">
<div class="w-8 h-8 rounded-full bg-blue-600 flex items-center justify-center text-white font-bold text-xs shadow-lg shadow-blue-500/20 group-hover:scale-105 transition-transform">
{{ app.user.firstName|first|upper }}
</div>
<div class="text-left hidden sm:block">
<p class="text-xs font-bold text-slate-800 dark:text-white leading-none mb-0.5">{{ app.user.firstName }}</p>
<span class="text-[8px] text-blue-500 font-bold uppercase tracking-widest opacity-70 group-hover:opacity-100 transition-opacity">Mon Compte</span>
</div>
</a>
{# Bouton Déconnexion séparé #}
<a href="{{ path('app_logout') }}"
title="Déconnexion"
class="w-10 h-10 flex items-center justify-center bg-red-500/10 hover:bg-red-500 text-red-500 hover:text-white border border-red-500/20 rounded-xl transition-all duration-300">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
</svg>
</a>
</div>
</header>
{# CONTENT #}
<div class="p-6 md:p-10 page-transition w-full">
<div class="flex items-end justify-between mb-10 pb-8 border-b border-slate-200 dark:border-slate-800/50">
<div>
<p class="text-blue-600 font-bold text-[10px] uppercase tracking-[0.4em] mb-2">Ludikevent Intranet</p>
<h1 class="text-4xl font-extrabold text-slate-900 dark:text-white">
{% block title_header %}{{ block('title') }}{% endblock %}
</h1>
</div>
<div class="flex items-center space-x-3">
{% block actions %}{% endblock %}
</div>
</div>
{# MESSAGE D'ERREUR STRIPE #}
{% if syncStripe().state == false %}
<div class="mb-8 flex items-center p-6 backdrop-blur-xl bg-rose-500/5 border border-rose-500/20 rounded-[2rem] shadow-xl shadow-rose-500/5 animate-in fade-in slide-in-from-top-4 duration-500">
<div class="flex-shrink-0 w-12 h-12 rounded-2xl bg-rose-500/10 border border-rose-500/20 flex items-center justify-center text-rose-500 mr-5">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
</div>
<div>
<h4 class="text-[10px] font-black text-rose-500 uppercase tracking-[0.2em] mb-1">Erreur de synchronisation Stripe</h4>
<p class="text-sm text-slate-400 font-medium leading-relaxed italic">
"{{ syncStripe().message }}"
</p>
</div>
</div>
{% endif %}
<div class="w-full">
{% block body %}{% endblock %}
</div>
</div>
</main>
</div>
</body>
</html>