templates/header.html.twig line 1

Open in your IDE?
  1. <header class="bg-white border-b border-gray-200 dark:bg-gray-800 dark:border-gray-700">
  2.     <nav class="max-w-screen-xl flex flex-wrap items-center justify-around mx-auto p-4">
  3.         <!-- Logo -->
  4.         <a href="{{ path('app_home') }}" class="flex items-center space-x-3">
  5.         
  6.             <span class="self-center text-2xl italic font-semibold whitespace-nowrap dark:text-white">🛠️ MatchMeuble</span>
  7.         </a>
  8.         <!-- Navigation Desktop -->
  9.         <div class="hidden md:flex md:items-center md:space-x-8">
  10.             <a href="{{ path('app_home') }}"
  11.                 class="text-gray-900 hover:text-blue-700 px-3 py-2 text-sm font-medium dark:text-white dark:hover:text-blue-500">
  12.                 Accueil
  13.             </a>
  14.             <a href="{{ path('app_annonces') }}"
  15.                 class="text-gray-900 hover:text-blue-700 px-3 py-2 text-sm font-medium dark:text-white dark:hover:text-blue-500">
  16.                 Annonces <sup class="text-gradient italic text-xs">New</sup>
  17.             </a>
  18.             {% if app.user %}
  19.             <a href="{{ path('app_favorites') }}"
  20.                 class="text-gray-900 hover:text-blue-700 px-3 py-2 text-sm font-medium dark:text-white dark:hover:text-blue-500">
  21.                 Mes favoris
  22.             </a>
  23.             {% endif %}
  24.             <a href="/#pricing"
  25.                 class="text-gray-900 hover:text-blue-700 px-3 py-2 text-sm font-medium dark:text-white dark:hover:text-blue-500">
  26.                 Tarifs
  27.             </a>
  28.             <a href="/#faq"
  29.                 class="text-gray-900 hover:text-blue-700 px-3 py-2 text-sm font-medium dark:text-white dark:hover:text-blue-500">
  30.                 Faq
  31.             </a>
  32.             <a href="/#blog"
  33.                 class="text-gray-900 hover:text-blue-700 px-3 py-2 text-sm font-medium dark:text-white dark:hover:text-blue-500">
  34.                 Blog
  35.             </a>
  36.         </div>
  37.         <!-- Boutons CTA Desktop -->
  38.                 <div class="hidden md:flex items-center space-x-3">
  39.             {% if app.user %}
  40.                                     <div class="relative">
  41.                                         <button id="notifDropdownButton" data-dropdown-toggle="notifDropdown" class="relative px-3 py-2 rounded-lg border border-gray-200 dark:border-gray-700">
  42.                                             🔔
  43.                                             <span id="notifBadge" class="hidden absolute -top-1 -right-1 text-xs font-semibold text-white bg-red-600 rounded-full px-1.5 py-0.5">0</span>
  44.                                         </button>
  45.                                         <div id="notifDropdown" class="z-20 hidden bg-white divide-y divide-gray-100 rounded-lg shadow w-80 dark:bg-gray-700">
  46.                                             <div class="flex items-center justify-between p-2">
  47.                                                 <span class="text-sm font-medium text-gray-700 dark:text-gray-200">Notifications</span>
  48.                                                 <button id="notifMarkAll" class="text-xs text-primary-700 hover:underline dark:text-primary-400">Tout marquer comme lu</button>
  49.                                             </div>
  50.                                             <div class="max-h-80 overflow-auto" id="notifList">
  51.                                                 <div class="p-3 text-sm text-gray-500 dark:text-gray-400">Chargement...</div>
  52.                                             </div>
  53.                                         </div>
  54.                                     </div>
  55.             <a href="#"
  56.                 class="text-gray-900 bg-white border border-gray-300 hover:bg-gray-100 focus:ring-4 focus:ring-gray-100 font-medium rounded-lg text-sm px-4 py-2 dark:bg-gray-800 dark:text-white dark:border-gray-600 dark:hover:bg-gray-700">
  57.                 Mon compte
  58.             </a>
  59.             <a href="{{ path('app_annonces') }}"
  60.                 class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-4 py-2 dark:bg-blue-600 dark:hover:bg-blue-700">
  61.                 Voir les annonces
  62.             </a>
  63.             {% else %}
  64.             <a href="{{ path('app_login') }}"
  65.                 class="text-gray-900 bg-white border border-gray-300 hover:bg-gray-100 focus:ring-4 focus:ring-gray-100 font-medium rounded-lg text-sm px-4 py-2 dark:bg-gray-800 dark:text-white dark:border-gray-600 dark:hover:bg-gray-700">
  66.                 Connexion
  67.             </a>
  68.             <a href="#"
  69.                 class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-4 py-2 dark:bg-blue-600 dark:hover:bg-blue-700">
  70.                 Essayer gratuitement
  71.             </a>
  72.             {% endif %}
  73.         </div>
  74.         <!-- Bouton Mobile -->
  75.         <button type="button"
  76.             class="md:hidden inline-flex items-center p-2 w-10 h-10 justify-center text-sm text-gray-500 rounded-lg hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600"
  77.             id="mobile-menu-button">
  78.             <span class="sr-only">Ouvrir le menu</span>
  79.             <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  80.                 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16">
  81.                 </path>
  82.             </svg>
  83.         </button>
  84.         <!-- Menu Mobile -->
  85.         <div class="hidden w-full md:hidden" id="mobile-menu">
  86.             <ul class="flex flex-col font-medium mt-4 rounded-lg bg-gray-50 dark:bg-gray-800 dark:border-gray-700">
  87.             {% if is_granted('ROLE_ADMIN') %}
  88.             <a href="{{ path('admin') }}"
  89.                 class="text-gray-900 hover:text-blue-700 px-3 py-2 text-sm font-medium dark:text-white dark:hover:text-blue-500">
  90.                 Administration
  91.             </a>
  92.             {% endif %}
  93.             <a href="{{ path('myAccount') }}"
  94.                 class="text-gray-900 hover:text-blue-700 px-3 py-2 text-sm font-medium dark:text-white dark:hover:text-blue-500">
  95.                 Mon compte
  96.             </a>
  97.             {% if app.user %}
  98.             <a href="{{ path('app_favorites') }}"
  99.                 class="text-gray-900 hover:text-blue-700 px-3 py-2 text-sm font-medium dark:text-white dark:hover:text-blue-500">
  100.                 Mes favoris
  101.             </a>
  102.             {% endif %}
  103.             <a href="{{ path('abonnement') }}"
  104.                 class="text-gray-900 hover:text-blue-700 px-3 py-2 text-sm font-medium dark:text-white dark:hover:text-blue-500">
  105.                 Abonnements <sup class="text-gradient italic text-xs">New</sup>
  106.             </a>
  107.             <a href="{{ path('abonnement') }}"
  108.                 class="text-gray-900 hover:text-blue-700 px-3 py-2 text-sm font-medium dark:text-white dark:hover:text-blue-500">
  109.                 Abonnements <sup class="text-gradient italic text-xs">New</sup>
  110.             </a>
  111.             <a href="{{ path('app_logout') }}"
  112.                 class="text-gray-900 hover:text-blue-700 px-3 py-2 text-sm font-medium dark:text-white dark:hover:text-blue-500">
  113.                 Déconnexion
  114.             </a>
  115.                 {% if app.user %}
  116.                 <li>
  117.                     <a href="{{ path('quote_index') }}"
  118.                         class="block py-2 px-3 text-gray-900 rounded hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700">
  119.                         Mes Devis
  120.                     </a>
  121.                 </li>
  122.                 {% endif %}
  123.                 <li>
  124.                     <a href="#"
  125.                         class="block py-2 px-3 text-gray-900 rounded hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700">
  126.                         Blog
  127.                     </a>
  128.                 </li>
  129.             </ul>
  130.             <!-- Boutons CTA Mobile -->
  131.             <div class="flex flex-col space-y-2 mt-4 px-3 pb-3">
  132.                 {% if app.user %}
  133.                 <a href="{{ path('app_account') }}"
  134.                     class="text-gray-900 bg-white border border-gray-300 hover:bg-gray-100 focus:ring-4 focus:ring-gray-100 font-medium rounded-lg text-sm px-4 py-2 text-center dark:bg-gray-800 dark:text-white dark:border-gray-600 dark:hover:bg-gray-700">
  135.                     Tableau de bord
  136.                 </a>
  137.                 <a href="{{ path('app_annonces') }}"
  138.                     class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-4 py-2 text-center dark:bg-blue-600 dark:hover:bg-blue-700">
  139.                     Annonces
  140.                 </a>
  141.                 {% else %}
  142.                 <a href="{{ path('app_login') }}"
  143.                     class="text-gray-900 bg-white border border-gray-300 hover:bg-gray-100 focus:ring-4 focus:ring-gray-100 font-medium rounded-lg text-sm px-4 py-2 text-center dark:bg-gray-800 dark:text-white dark:border-gray-600 dark:hover:bg-gray-700">
  144.                     Connexion
  145.                 </a>
  146.                 <a href="{{ path('app_annonces') }}"
  147.                     class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-4 py-2 text-center dark:bg-blue-600 dark:hover:bg-blue-700">
  148.                     Annonces
  149.                 </a>
  150.                 {% endif %}
  151.             </div>
  152.         </div>
  153.     </nav>
  154. </header>
  155. <script>
  156.     // Script pour le menu mobile
  157.     document.addEventListener('DOMContentLoaded', function () {
  158.         const mobileMenuButton = document.getElementById('mobile-menu-button');
  159.         const mobileMenu = document.getElementById('mobile-menu');
  160.         if (mobileMenuButton && mobileMenu) {
  161.             mobileMenuButton.addEventListener('click', function () {
  162.                 mobileMenu.classList.toggle('hidden');
  163.             });
  164.         }
  165.     });
  166. </script>
  167. <script>
  168.     // Lightweight fetch of notifications when opening the dropdown
  169.         document.addEventListener('DOMContentLoaded', () => {
  170.             const btn = document.getElementById('notifDropdownButton');
  171.             const list = document.getElementById('notifList');
  172.             const badge = document.getElementById('notifBadge');
  173.             const markAll = document.getElementById('notifMarkAll');
  174.             const csrf = '{{ csrf_token('notif') }}';
  175.             if (!btn || !list || !badge) return;
  176.             async function refreshCount() {
  177.                 try {
  178.                     const res = await fetch('{{ path('app_notifications_count') }}', { headers: { 'X-Requested-With': 'XMLHttpRequest' } });
  179.                     const data = await res.json();
  180.                     const c = (data && typeof data.count === 'number') ? data.count : 0;
  181.                     if (c > 0) {
  182.                         badge.textContent = c;
  183.                         badge.classList.remove('hidden');
  184.                     } else {
  185.                         badge.classList.add('hidden');
  186.                     }
  187.                 } catch {}
  188.             }
  189.                 async function loadList() {
  190.                 try {
  191.                     const res = await fetch('{{ path('app_notifications') }}', { headers: { 'X-Requested-With': 'XMLHttpRequest' } });
  192.                     const data = await res.json();
  193.                     list.innerHTML = '';
  194.                     if (!data || data.length === 0) {
  195.                         list.innerHTML = '<div class="p-3 text-sm text-gray-500 dark:text-gray-400">Aucune notification</div>';
  196.                     } else {
  197.                                     data.forEach(n => {
  198.                                 const el = document.createElement('div');
  199.                                 el.className = 'p-3 text-sm text-gray-800 dark:text-gray-200 border-b border-gray-100 dark:border-gray-600';
  200.                                 el.dataset.id = n.id;
  201.                                 el.innerHTML = `
  202.                                     <div class="flex items-start justify-between gap-2">
  203.                                         <div class="flex items-start gap-2">
  204.                                             ${n.lu ? '' : '<span class="mt-1 inline-block w-2 h-2 rounded-full bg-red-500"></span>'}
  205.                                             <span>${n.message}</span>
  206.                                         </div>
  207.                                                 <div class="flex items-center gap-2">
  208.                                                     ${n.lu ? '' : '<button class="notif-mark text-xs text-primary-700 hover:underline dark:text-primary-400">Marquer comme lu</button>'}
  209.                                                     <button class="notif-del text-xs text-red-700 hover:underline dark:text-red-400" title="Supprimer" aria-label="Supprimer">
  210.                                                         🗑️
  211.                                                     </button>
  212.                                                 </div>
  213.                                     </div>
  214.                                 `;
  215.                             list.appendChild(el);
  216.                         });
  217.                     }
  218.                 } catch (e) {
  219.                     list.innerHTML = '<div class="p-3 text-sm text-red-600">Erreur de chargement</div>';
  220.                 }
  221.             }
  222.             btn.addEventListener('click', async () => {
  223.                 await loadList();
  224.             });
  225.             if (markAll) {
  226.                 markAll.addEventListener('click', async (e) => {
  227.                     e.preventDefault();
  228.                     try {
  229.                         const res = await fetch('{{ path('app_notifications_read') }}', {
  230.                             method: 'POST',
  231.                             headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest', 'X-CSRF-TOKEN': csrf },
  232.                             body: JSON.stringify({ all: true })
  233.                         });
  234.                         if (res.ok) {
  235.                             await loadList();
  236.                             await refreshCount();
  237.                         }
  238.                     } catch {}
  239.                 });
  240.             }
  241.                 // Event delegation for per-notification "mark as read"
  242.                 list.addEventListener('click', async (e) => {
  243.                     const btn = e.target.closest('.notif-mark');
  244.                     if (!btn) return;
  245.                     const container = btn.closest('[data-id]');
  246.                     const id = container?.dataset?.id;
  247.                     if (!id) return;
  248.                     try {
  249.                         const res = await fetch('{{ path('app_notifications_read') }}', {
  250.                             method: 'POST',
  251.                             headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest', 'X-CSRF-TOKEN': csrf },
  252.                             body: JSON.stringify({ ids: [parseInt(id, 10)] })
  253.                         });
  254.                         if (res.ok) {
  255.                             await loadList();
  256.                             await refreshCount();
  257.                         }
  258.                     } catch {}
  259.                 });
  260.                     list.addEventListener('click', async (e) => {
  261.                         const btn = e.target.closest('.notif-del');
  262.                         if (!btn) return;
  263.                         const container = btn.closest('[data-id]');
  264.                         const id = container?.dataset?.id;
  265.                         if (!id) return;
  266.                         try {
  267.                             const res = await fetch('{{ path('app_notifications_delete', { id: 0 }) }}'.replace('/0', '/' + id), {
  268.                                 method: 'DELETE',
  269.                                 headers: { 'X-Requested-With': 'XMLHttpRequest', 'X-CSRF-TOKEN': csrf },
  270.                             });
  271.                             if (res.ok) {
  272.                                 await loadList();
  273.                                 await refreshCount();
  274.                             }
  275.                         } catch {}
  276.                     });
  277.             refreshCount();
  278.             setInterval(refreshCount, 30000);
  279.         });
  280. </script>