```
✨ feat(Product): Ajoute méthode json pour sérialiser les données du produit. ✨ feat(analytics): Intègre suivi Umami pour catalogue, contact et produits. ✨ feat(caddy): Ajoute header Cloudflare et script UTM, améliore config PHP. ✨ feat(nelmio): Autorise tools-security.esy-web.dev dans CSP. ✨ feat(template): Ajoute suivi Umami sur pages catalogue, contact et produit. ```
This commit is contained in:
@@ -14,20 +14,36 @@ intranet.ludikevent.fr, signature.ludikevent.fr, reservation.ludikevent.fr {
|
||||
@noindex_hosts host intranet.ludikevent.fr signature.ludikevent.fr
|
||||
header @noindex_hosts X-Robots-Tag "noindex, nofollow"
|
||||
|
||||
# SUPPRIME le noindex sur la réservation (au cas où PHP ou un autre bloc l'ajouterait)
|
||||
@index_host host reservation.ludikevent.fr
|
||||
header @index_host -X-Robots-Tag
|
||||
|
||||
handle_path /utm_reserve.js {
|
||||
redir https://tools-security.esy-web.dev/script.js
|
||||
}
|
||||
|
||||
# --- BLOC HEADER AVEC CSP ---
|
||||
header {
|
||||
X-Content-Type-Options "nosniff"
|
||||
X-Frame-Options "DENY"
|
||||
Referrer-Policy "strict-origin-when-cross-origin"
|
||||
|
||||
# Injection des headers Cloudflare pour PHP
|
||||
# Cela permet à PHP de les lire via $_SERVER['HTTP_CF_CONNECTING_IP'] etc.
|
||||
CF-Connecting-IP {header.CF-Connecting-IP}
|
||||
CF-IPCountry {header.CF-IPCountry}
|
||||
CF-RegionCode {header.CF-RegionCode}
|
||||
CF-IPCity {header.CF-IPCity}
|
||||
X-Real-IP {remote_host}
|
||||
}
|
||||
|
||||
# --- PHP FASTCGI ---
|
||||
# Ici, Caddy transmet automatiquement tous les headers définis ci-dessus au socket PHP
|
||||
php_fastcgi unix//run/php/php8.3-fpm.sock {
|
||||
read_timeout 300s
|
||||
write_timeout 300s
|
||||
dial_timeout 100s
|
||||
|
||||
# Optionnel : Forcer explicitement certains paramètres FastCGI si nécessaire
|
||||
env REMOTE_ADDR {header.CF-Connecting-IP}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import './reserve.scss';
|
||||
import * as Sentry from "@sentry/browser";
|
||||
import {UtmEvent,UtmAccount} from "./tools/UtmEvent.js";
|
||||
import * as Turbo from "@hotwired/turbo"
|
||||
|
||||
// --- INITIALISATION SENTRY ---
|
||||
@@ -103,6 +104,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
initLoader();
|
||||
initMobileMenu();
|
||||
initCatalogueSearch();
|
||||
customElements.define('utm-event',UtmEvent)
|
||||
customElements.define('utm-account',UtmAccount)
|
||||
|
||||
});
|
||||
|
||||
document.addEventListener('turbo:load', () => {
|
||||
|
||||
58
assets/tools/UtmEvent.js
Normal file
58
assets/tools/UtmEvent.js
Normal file
@@ -0,0 +1,58 @@
|
||||
export class UtmAccount extends HTMLElement {
|
||||
connectedCallback() {
|
||||
if (typeof umami === 'undefined') {
|
||||
console.warn('Umami script non détecté.');
|
||||
return;
|
||||
}
|
||||
const umamiScript = document.querySelector('script[data-website-id]');
|
||||
const websiteId = umamiScript ? umamiScript.getAttribute('data-website-id') : null;
|
||||
umami.identify('user_'+this.getAttribute('id'), { name: this.getAttribute('name'), email: this.getAttribute('email') });
|
||||
|
||||
}
|
||||
}
|
||||
export class UtmEvent extends HTMLElement {
|
||||
connectedCallback() {
|
||||
// On attend un court instant pour s'assurer qu'umami est chargé
|
||||
// ou on vérifie s'il existe déjà
|
||||
if (typeof umami === 'undefined') {
|
||||
console.warn('Umami script non détecté.');
|
||||
return;
|
||||
}
|
||||
|
||||
const event = this.getAttribute('event');
|
||||
const dataRaw = this.getAttribute('data');
|
||||
|
||||
// Extraction dynamique du website-id depuis le script existant
|
||||
const umamiScript = document.querySelector('script[data-website-id]');
|
||||
const websiteId = umamiScript ? umamiScript.getAttribute('data-website-id') : null;
|
||||
|
||||
if (!websiteId) {
|
||||
console.error('Impossible de trouver le data-website-id umami.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (event === "view_catalogue") {
|
||||
umami.track('Affichage du catalogue');
|
||||
}
|
||||
if (event === "view_contact") {
|
||||
umami.track('Affichage du page contact');
|
||||
umami.track('Affichage du page contact');
|
||||
}
|
||||
|
||||
if (event === "view_product" && dataRaw) {
|
||||
const data = JSON.parse(dataRaw);
|
||||
|
||||
// Umami track accepte soit un nom seul,
|
||||
// soit un objet complet pour des propriétés personnalisées
|
||||
umami.track({
|
||||
website: websiteId,
|
||||
name:'Affichage produit',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Erreur lors du tracking Umami:', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,7 @@ nelmio_security:
|
||||
- "https://auth.esy-web.dev"
|
||||
- "https://cloudflareinsights.com"
|
||||
- "https://challenges.cloudflare.com"
|
||||
- "https://tools-security.esy-web.dev"
|
||||
frame-src:
|
||||
- "'self'"
|
||||
- "https://chat.esy-web.dev"
|
||||
|
||||
@@ -30,6 +30,9 @@
|
||||
output stdout
|
||||
format json
|
||||
}
|
||||
handle_path /utm_reserve.js {
|
||||
redir https://tools-security.esy-web.dev/script.js
|
||||
}
|
||||
handle_path /ts.js {
|
||||
redir https://widget.trustpilot.com/bootstrap/v5/tp.widget.bootstrap.min.js
|
||||
}
|
||||
|
||||
@@ -80,6 +80,15 @@ class Product
|
||||
return$s->slugify($this->id."-".$this->name);
|
||||
}
|
||||
|
||||
public function json()
|
||||
{
|
||||
return json_encode([
|
||||
'id' => $this->id,
|
||||
'ref' => $this->ref,
|
||||
'name' => $this->name,
|
||||
]);
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
|
||||
@@ -81,13 +81,21 @@
|
||||
{# --- PWA (Configuré via manifest.json) --- #}
|
||||
{% if app.environment != 'dev' %}
|
||||
{{ pwa(swAttributes={ 'nonce': csp_nonce('script') }) }}
|
||||
<script data-host-url="https://tools-security.esy-web.dev" nonce="{{ csp_nonce('script') }}" defer src="/utm_reserve.js" data-website-id="38d713c3-3923-4791-875a-dfe5f45372c3"></script>
|
||||
{% else %}
|
||||
<script data-host-url="https://tools-security.esy-web.dev" nonce="{{ csp_nonce('script') }}" defer src="/utm_reserve.js" data-website-id="bc640e0d-43fb-4c3a-bb17-1ac01cec9643"></script>
|
||||
|
||||
{% endif %}
|
||||
|
||||
{{ vite_asset('reserve.js',{}) }}
|
||||
{% block stylesheets %}{% endblock %}
|
||||
|
||||
|
||||
</head>
|
||||
<body class="bg-gray-50 text-gray-900 font-sans antialiased min-h-screen flex flex-col">
|
||||
|
||||
{% if is_granted('ROLE_USER') %}
|
||||
<utm-account id="{{ app.user.id }}" email="{{ app.user.email }}" name="{{ app.user.username }}"></utm-account>
|
||||
{% endif %}
|
||||
{# --- NAVIGATION --- #}
|
||||
<nav class="sticky top-0 z-50 bg-white/80 backdrop-blur-md border-b border-gray-100">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
}
|
||||
{% endblock %}
|
||||
{% block body %}
|
||||
<utm-event event="view_catalogue"></utm-event>
|
||||
<div class="min-h-screen bg-gray-50/50 font-sans antialiased pb-20">
|
||||
|
||||
{# --- HEADER --- #}
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
}
|
||||
{% endblock %}
|
||||
{% block body %}
|
||||
<utm-event event="view_contact"></utm-event>
|
||||
|
||||
<div class="min-h-screen bg-gray-50/50 font-sans antialiased pb-20">
|
||||
|
||||
{# --- HEADER --- #}
|
||||
|
||||
@@ -1,23 +1,27 @@
|
||||
{% extends 'revervation/base.twig' %}
|
||||
|
||||
{% block title %}{{ product.name }} - Location Ludikevent{% endblock %}
|
||||
|
||||
{% block breadcrumb_json %}
|
||||
,{
|
||||
"@type": "ListItem",
|
||||
"position": 1,
|
||||
"name": "Catalogue",
|
||||
"item": "{{ path('reservation_catalogue') }}"
|
||||
"item": "{{ absolute_url(path('reservation_catalogue')) }}"
|
||||
},{
|
||||
"@type": "ListItem",
|
||||
"position": 2
|
||||
"name": "Catalogue",
|
||||
"item": "{{ path('reservation_product_show',{id:product.id}) }}"
|
||||
"position": 2,
|
||||
"name": "{{ product.name }}",
|
||||
"item": "{{ absolute_url(path('reservation_product_show',{id:product.id})) }}"
|
||||
}
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<utm-event event="view_product" data="{{ product.json }}"></utm-event>
|
||||
|
||||
<div class="min-h-screen bg-white font-sans antialiased">
|
||||
{# --- HEADER --- #}
|
||||
|
||||
{# --- NAVIGATION / BREADCRUMB --- #}
|
||||
<div class="max-w-7xl mx-auto pt-16 pb-8 px-4 text-center">
|
||||
<nav class="flex justify-center space-x-4 text-[10px] mb-8 uppercase tracking-[0.3em] font-black italic">
|
||||
<a href="{{ url('reservation') }}" class="text-slate-400 hover:text-blue-600 transition">ACCUEIL</a>
|
||||
@@ -27,8 +31,9 @@
|
||||
<span class="text-amber-500 underline decoration-2 underline-offset-4">{{ product.name }}</span>
|
||||
</nav>
|
||||
</div>
|
||||
{# --- FIL D'ARIANE / RETOUR --- #}
|
||||
<div class="max-w-7xl mx-auto px-4 pt-8">
|
||||
|
||||
{# --- BOUTON RETOUR --- #}
|
||||
<div class="max-w-7xl mx-auto px-4 pt-4">
|
||||
<a href="{{ path('reservation_catalogue') }}" class="group inline-flex items-center gap-3 text-[10px] font-black uppercase tracking-[0.2em] text-slate-400 hover:text-blue-600 transition-colors">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 transform group-hover:-translate-x-2 transition-transform" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
|
||||
@@ -40,7 +45,7 @@
|
||||
<main class="max-w-7xl mx-auto px-4 py-12 md:py-20">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-16 md:gap-24 items-start">
|
||||
|
||||
{# --- COLONNE GAUCHE : IMAGE --- #}
|
||||
{# --- COLONNE GAUCHE : VISUEL --- #}
|
||||
<div class="sticky top-24">
|
||||
<div class="relative overflow-hidden rounded-[4rem] bg-slate-50 aspect-[4/5] shadow-2xl">
|
||||
{% if product.imageName %}
|
||||
@@ -48,53 +53,65 @@
|
||||
alt="{{ product.name }}"
|
||||
class="w-full h-full object-cover">
|
||||
{% else %}
|
||||
{# FALLBACK : Image par défaut si vide #}
|
||||
<div class="h-full flex flex-col items-center justify-center p-12 text-center">
|
||||
<img src="{{ asset('provider/images/favicon.png') }}"
|
||||
alt="Ludik Event"
|
||||
class="w-75 h-75 object-contain opacity-50 group-hover:opacity-100 group-hover:scale-110 transition-all duration-500 grayscale group-hover:grayscale-0">
|
||||
<div class="h-full flex flex-col items-center justify-center p-12 text-center opacity-20">
|
||||
<img src="{{ asset('provider/images/favicon.png') }}" alt="Ludik Event" class="w-40 h-40">
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Badge catégorie flottant #}
|
||||
<div class="absolute top-8 left-8">
|
||||
<span class="bg-blue-600 text-white px-6 py-2 rounded-2xl text-[10px] font-black uppercase tracking-widest shadow-xl">
|
||||
{{ product.category }}
|
||||
</span>
|
||||
<span class="bg-blue-600 text-white px-6 py-2 rounded-2xl text-[10px] font-black uppercase tracking-widest shadow-xl">
|
||||
{{ product.category }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# --- COLONNE DROITE : INFOS --- #}
|
||||
{# --- COLONNE DROITE : CONTENU --- #}
|
||||
<div class="flex flex-col h-full py-4">
|
||||
<div class="mb-10">
|
||||
<span class="text-[12px] font-black text-slate-300 uppercase tracking-[0.4em] mb-4 block italic">
|
||||
Référence : {{ product.ref }}
|
||||
</span>
|
||||
<h1 class="text-6xl md:text-8xl font-black text-slate-900 uppercase italic tracking-tighter leading-[0.85] mb-8">
|
||||
<div class="mb-12">
|
||||
<span class="text-[12px] font-black text-slate-300 uppercase tracking-[0.4em] mb-4 block italic">
|
||||
Référence : {{ product.ref }}
|
||||
</span>
|
||||
<h1 class="text-6xl md:text-8xl font-black text-slate-900 uppercase italic tracking-tighter leading-[0.85] mb-10">
|
||||
{{ product.name }}
|
||||
</h1>
|
||||
|
||||
<div class="flex items-baseline gap-4">
|
||||
<span class="text-5xl font-black text-blue-600 italic">{{ product.priceDay }}€</span>
|
||||
<span class="text-sm font-bold text-slate-400 uppercase tracking-widest italic">TTC / Journée</span>
|
||||
<div class="space-y-6">
|
||||
{# Prix principal #}
|
||||
<div class="flex items-baseline gap-4">f
|
||||
<span class="text-6xl font-black text-blue-600 italic">{{ product.priceDay }}€</span>
|
||||
<span class="text-sm font-bold text-slate-400 uppercase tracking-widest italic">La première journée</span>
|
||||
</div>
|
||||
|
||||
{# Prix supplémentaire #}
|
||||
<div class="flex items-center gap-3 bg-slate-50 self-start px-5 py-3 rounded-2xl border border-slate-100 w-fit">
|
||||
<span class="text-2xl font-black text-slate-900 italic">+ {{ product.priceSup }}€</span>
|
||||
<span class="text-[10px] font-black text-slate-400 uppercase tracking-widest italic">Par journée supplémentaire</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# --- DESCRIPTION / CARACTERISTIQUES --- #}
|
||||
<div class="border-t border-slate-100 pt-10 mb-12">
|
||||
<p class="text-xl text-slate-600 leading-relaxed italic font-medium">
|
||||
Louez ce produit pour vos événements. Qualité professionnelle, sécurité certifiée et plaisir garanti.
|
||||
</p>
|
||||
|
||||
<div class="grid grid-cols-2 gap-6 mt-10">
|
||||
<div class="bg-slate-50 p-6 rounded-[2rem] border border-slate-100">
|
||||
<span class="block text-[9px] font-black text-slate-400 uppercase tracking-widest mb-1">Âge conseillé</span>
|
||||
<div class="grid grid-cols-1 gap-8">
|
||||
{# Badge Âge #}
|
||||
<div class="bg-slate-50 p-6 rounded-[2rem] border border-slate-100 flex items-center justify-between">
|
||||
<span class="text-[10px] font-black text-slate-400 uppercase tracking-widest">Âge conseillé</span>
|
||||
<span class="text-lg font-black text-slate-900 uppercase italic">{{ product.category }}</span>
|
||||
</div>
|
||||
<div class="bg-slate-50 p-6 rounded-[2rem] border border-slate-100">
|
||||
<span class="block text-[9px] font-black text-slate-400 uppercase tracking-widest mb-1">Disponibilité</span>
|
||||
<span class="text-lg font-black text-emerald-500 uppercase italic">En stock ⚡</span>
|
||||
|
||||
{# SÉLECTEUR DE PÉRIODE --- #}
|
||||
<div class="bg-blue-50/50 p-8 rounded-[3rem] border border-blue-100">
|
||||
<span class="block text-[10px] font-black text-blue-600 uppercase tracking-[0.2em] mb-6 italic text-center">Planifiez votre événement</span>
|
||||
<div class="flex flex-col sm:flex-row gap-4">
|
||||
<div class="flex-1">
|
||||
<label class="block text-[8px] font-black uppercase text-slate-400 mb-2 ml-4">Date de début</label>
|
||||
<input type="date" class="w-full bg-white border-none rounded-2xl px-5 py-4 text-sm font-bold text-slate-900 focus:ring-2 focus:ring-blue-600 shadow-sm">
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<label class="block text-[8px] font-black uppercase text-slate-400 mb-2 ml-4">Date de fin</label>
|
||||
<input type="date" class="w-full bg-white border-none rounded-2xl px-5 py-4 text-sm font-bold text-slate-900 focus:ring-2 focus:ring-blue-600 shadow-sm">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -103,10 +120,11 @@
|
||||
<div class="mt-auto">
|
||||
<a href="{{ path('reservation_contact', {id: product.id}) }}"
|
||||
class="flex items-center justify-center w-full py-8 bg-slate-900 text-white rounded-[2.5rem] font-black uppercase text-[12px] tracking-[0.3em] hover:bg-blue-600 transition-all shadow-2xl hover:scale-[1.02] active:scale-95">
|
||||
Réserver maintenant
|
||||
Vérifier la disponibilité
|
||||
</a>
|
||||
|
||||
<p class="text-center mt-6 text-[9px] font-black text-slate-300 uppercase tracking-widest italic">
|
||||
Réponse rapide sous 24h • Ludikevent
|
||||
Devis gratuit • Ludikevent • Qualité Pro
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -114,55 +132,46 @@
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{# --- SECTION SUGGESTIONS DISCRETE --- #}
|
||||
{# --- SECTION SUGGESTIONS --- #}
|
||||
<section class="max-w-7xl mx-auto px-4 py-20 border-t border-slate-100">
|
||||
<div class="flex items-end justify-between mb-12">
|
||||
<section class="max-w-7xl mx-auto px-4 py-24 border-t border-slate-100 mt-12">
|
||||
<div class="flex flex-col md:flex-row md:items-end justify-between gap-6 mb-16">
|
||||
<div>
|
||||
<span class="text-[10px] font-black text-blue-600 uppercase tracking-[0.3em] mb-2 block italic">Vous pourriez aussi aimer</span>
|
||||
<h2 class="text-4xl md:text-5xl font-black text-slate-900 uppercase italic tracking-tighter leading-none">D'autres <span class="text-blue-600">idées ?</span></h2>
|
||||
<span class="text-[10px] font-black text-blue-600 uppercase tracking-[0.3em] mb-3 block italic">Vous pourriez aussi aimer</span>
|
||||
<h2 class="text-4xl md:text-6xl font-black text-slate-900 uppercase italic tracking-tighter leading-none">D'autres <span class="text-blue-600">idées ?</span></h2>
|
||||
</div>
|
||||
<a href="{{ path('reservation_catalogue') }}" class="hidden md:block text-[10px] font-black uppercase tracking-widest border-b-2 border-blue-600 pb-1 hover:text-blue-600 transition-colors">
|
||||
Voir tout le catalogue
|
||||
<a href="{{ path('reservation_catalogue') }}" class="inline-flex items-center gap-2 text-[10px] font-black uppercase tracking-widest border-b-2 border-slate-900 pb-1 hover:text-blue-600 hover:border-blue-600 transition-all">
|
||||
Tout le catalogue
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{# Grille de suggestions (on réutilise le style épuré) #}
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-8">
|
||||
<div class="grid grid-cols-2 lg:grid-cols-4 gap-8">
|
||||
{% for other in otherProducts %}
|
||||
<div class="group transition-all duration-500">
|
||||
<a href="{{ path('reservation_product_show', {id: other.id}) }}" class="block">
|
||||
|
||||
{# Image miniature #}
|
||||
<div class="relative overflow-hidden rounded-[2.5rem] bg-slate-50 aspect-square mb-4 shadow-sm group-hover:shadow-xl transition-all duration-700">
|
||||
<a href="{{ path('reservation_product_show', {id: (other.slug)}) }}" class="block">
|
||||
<div class="relative overflow-hidden rounded-[2.5rem] bg-slate-50 aspect-square mb-6 shadow-sm group-hover:shadow-2xl transition-all duration-700">
|
||||
{% if other.imageName %}
|
||||
<img src="{{ vich_uploader_asset(other,'imageFile') | imagine_filter('webp') }}"
|
||||
alt="{{ other.name }}"
|
||||
class="w-full h-full object-cover transform group-hover:scale-110 transition-transform duration-1000">
|
||||
{% else %}
|
||||
<img src="{{ asset('provider/images/favicon.png') | imagine_filter('webp') }}"
|
||||
alt="{{ product.name }}"
|
||||
class="w-full h-full object-cover opacity-50 transform group-hover:scale-110 transition-transform duration-1000">
|
||||
|
||||
<div class="w-full h-full flex items-center justify-center opacity-10">
|
||||
<img src="{{ asset('provider/images/favicon.png') }}" class="w-16">
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="absolute top-3 right-3 bg-white/90 backdrop-blur-md px-3 py-1 rounded-xl shadow-sm">
|
||||
<p class="text-slate-900 font-black text-[11px] italic leading-none">{{ other.priceDay }}€</p>
|
||||
<div class="absolute top-4 right-4 bg-white/95 backdrop-blur-md px-3 py-1.5 rounded-xl shadow-sm">
|
||||
<p class="text-slate-900 font-black text-[12px] italic leading-none">{{ other.priceDay }}€</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Infos miniatures #}
|
||||
<div class="px-2">
|
||||
<span class="text-[8px] font-black text-slate-300 uppercase tracking-widest mb-1 block italic">{{ other.category }}</span>
|
||||
<h3 class="text-sm font-black text-slate-900 uppercase italic tracking-tighter leading-tight group-hover:text-blue-600 transition-colors line-clamp-1">
|
||||
<h3 class="text-lg font-black text-slate-900 uppercase italic tracking-tighter leading-tight group-hover:text-blue-600 transition-colors line-clamp-1">
|
||||
{{ other.name }}
|
||||
</h3>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
{% else %}
|
||||
{# Cas où il n'y a pas d'autres produits à suggérer #}
|
||||
<p class="col-span-full text-slate-400 italic text-sm">Plus de surprises arrivent bientôt...</p>
|
||||
<p class="col-span-full text-center text-slate-400 italic text-sm py-12">Plus de surprises arrivent bientôt...</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user