```
✨ feat(dashboard): Ajoute l'intégration d'Umami pour les statistiques du site. and fix erro return line
```
This commit is contained in:
2
.env
2
.env
@@ -100,3 +100,5 @@ STRIPE_SECRET_KEY=sk_test_***
|
||||
INTRANET_LOCK=true
|
||||
TVA_ENABLED=false
|
||||
MAINTENANCE_ENABLED=false
|
||||
UMAMI_USER=api
|
||||
UMAMI_PASSWORD=Analytics_8962@
|
||||
|
||||
@@ -2,37 +2,80 @@
|
||||
|
||||
namespace App\Controller\Dashboard;
|
||||
|
||||
use App\Controller\EntityManagerInterface;
|
||||
use App\Entity\Account;
|
||||
use App\Entity\AccountResetPasswordRequest;
|
||||
use App\Form\RequestPasswordConfirmType;
|
||||
use App\Form\RequestPasswordRequestType;
|
||||
use App\Repository\AccountRepository;
|
||||
use App\Repository\CustomerRepository;
|
||||
use App\Repository\DevisRepository;
|
||||
use App\Repository\ProductRepository;
|
||||
use App\Service\ResetPassword\Event\ResetPasswordConfirmEvent;
|
||||
use App\Service\ResetPassword\Event\ResetPasswordEvent;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
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 Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
|
||||
use Symfony\Contracts\Cache\CacheInterface;
|
||||
use Symfony\Contracts\Cache\ItemInterface;
|
||||
|
||||
class HomeController extends AbstractController
|
||||
{
|
||||
#[Route(path: '/crm', name: 'app_crm', options: ['sitemap' => false], methods: ['GET','POST'])]
|
||||
public function crm(ProductRepository $productRepository,CustomerRepository $customerRepository,DevisRepository $devisRepository): Response
|
||||
{
|
||||
return $this->render('dashboard/home.twig',[
|
||||
#[Route(path: '/crm', name: 'app_crm', options: ['sitemap' => false], methods: ['GET'])]
|
||||
public function crm(
|
||||
ProductRepository $productRepository,
|
||||
CustomerRepository $customerRepository,
|
||||
DevisRepository $devisRepository,
|
||||
HttpClientInterface $httpClient,
|
||||
CacheInterface $cache
|
||||
): Response {
|
||||
|
||||
$websiteId = "38d713c3-3923-4791-875a-dfe5f45372c3";
|
||||
$baseUrl = "https://tools-security.esy-web.dev/api";
|
||||
|
||||
// 1. Récupération sécurisée du Token (Cache 2h)
|
||||
$token = $cache->get('umami_token', function (ItemInterface $item) use ($httpClient, $baseUrl) {
|
||||
$item->expiresAfter(7200);
|
||||
|
||||
$response = $httpClient->request('POST', "$baseUrl/auth/login", [
|
||||
'json' => [
|
||||
'username' => $_ENV['UMAMI_USER'],
|
||||
'password' => $_ENV['UMAMI_PASSWORD'],
|
||||
]
|
||||
]);
|
||||
|
||||
return $response->toArray()['token'] ?? null;
|
||||
});
|
||||
|
||||
// 2. Récupération des Stats (Cache 15 min pour la fluidité du CRM)
|
||||
$stats = $cache->get('umami_stats_24h', function (ItemInterface $item) use ($httpClient, $baseUrl, $token, $websiteId) {
|
||||
$item->expiresAfter(900); // 15 minutes
|
||||
|
||||
if (!$token) return ['visitors' => 0, 'views' => 0];
|
||||
|
||||
try {
|
||||
$startAt = (time() - (24 * 3600)) * 1000;
|
||||
$endAt = time() * 1000;
|
||||
|
||||
|
||||
$response = $httpClient->request('GET', "$baseUrl/websites/$websiteId/stats", [
|
||||
'headers' => ['Authorization' => "Bearer $token"],
|
||||
'query' => [
|
||||
'startAt' => $startAt,
|
||||
'endAt' => $endAt,
|
||||
],
|
||||
]);
|
||||
|
||||
$data = $response->toArray();
|
||||
return [
|
||||
'visitors' => $data['visitors'] ?? 0,
|
||||
'views' => $data['pageviews'] ?? 0
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
return ['visitors' => 0, 'views' => 0];
|
||||
}
|
||||
});
|
||||
|
||||
return $this->render('dashboard/home.twig', [
|
||||
'product' => $productRepository->count(),
|
||||
'devis_wait_sign' => $devisRepository->waitSign(),
|
||||
'customers' => $customerRepository->count(),
|
||||
'nbVisitor' => $stats['visitors'],
|
||||
'nbView' => $stats['views'],
|
||||
'statview' => "https://tools-security.esy-web.dev/websites/$websiteId"
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,17 +62,17 @@ class ProductType extends AbstractType
|
||||
|
||||
->add('dimW',NumberType::class,[
|
||||
'label' => 'Largeur',
|
||||
'required' => true,
|
||||
'required' => false,
|
||||
'html5' => true,
|
||||
])
|
||||
->add('dimP',NumberType::class,[
|
||||
'label' => 'Longeur',
|
||||
'required' => true,
|
||||
'required' => false,
|
||||
'html5' => true,
|
||||
])
|
||||
->add('dimH',NumberType::class,[
|
||||
'label' => 'Hauteur',
|
||||
'required' => true,
|
||||
'required' => false,
|
||||
'html5' => true,
|
||||
])
|
||||
->add('imageFile',FileType::class,[
|
||||
|
||||
@@ -42,10 +42,10 @@
|
||||
|
||||
{{ 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_product'), 'Produits', '<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') }}
|
||||
{{ menu.nav_link(path('app_crm_contrats'), 'Contrat de location', '<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') }}
|
||||
{{ menu.nav_link(path('app_crm_facture'), 'Facture', '<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') }}
|
||||
{{ menu.nav_link(path('app_crm_devis'), 'Devis', '<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') }}
|
||||
{{ 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') }}
|
||||
{# {{ menu.nav_link(path('app_crm_contrats'), 'Contrat de location', '<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') }}#}
|
||||
{# {{ menu.nav_link(path('app_crm_facture'), 'Facture', '<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') }}#}
|
||||
{# {{ menu.nav_link(path('app_crm_devis'), 'Devis', '<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') }}#}
|
||||
{# {{ 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>
|
||||
|
||||
|
||||
@@ -3,57 +3,98 @@
|
||||
{% block title %}Tableau de bord{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="space-y-8">
|
||||
{# Grille principale avec effet Glass #}
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div class="min-h-screen p-4 md:p-8 space-y-10">
|
||||
|
||||
<div class="relative group overflow-hidden bg-white/40 dark:bg-slate-800/40 backdrop-blur-xl border border-white/20 dark:border-slate-700/50 rounded-[2.5rem] p-8 shadow-2xl transition-all hover:scale-[1.02] duration-300">
|
||||
<div class="absolute -right-4 -top-4 w-24 h-24 bg-blue-500/10 rounded-full blur-2xl group-hover:bg-blue-500/20 transition-colors"></div>
|
||||
|
||||
<div class="relative flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-[10px] font-black text-blue-600 dark:text-blue-400 uppercase tracking-[0.3em] mb-1">Inventaire</p>
|
||||
<h2 class="text-5xl font-extrabold text-slate-900 dark:text-white tracking-tight">{{ product }}</h2>
|
||||
<p class="text-sm font-medium text-slate-500 dark:text-slate-400 mt-2">Produits actifs</p>
|
||||
</div>
|
||||
<div class="w-16 h-16 bg-blue-600/10 border border-blue-600/20 rounded-2xl flex items-center justify-center text-blue-600 shadow-inner">
|
||||
<svg class="w-8 h-8" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />
|
||||
{# --- HEADER STATS (GLASS PILLS) --- #}
|
||||
<div class="flex flex-wrap gap-4 items-center mb-8">
|
||||
<div class="flex flex-col md:flex-row md:items-center gap-4 w-full md:w-auto">
|
||||
{# Titre / Lien vers le site #}
|
||||
<div class="flex items-center gap-2 px-2">
|
||||
<h1 class="text-xl font-black italic text-slate-900 dark:text-white uppercase tracking-tighter">
|
||||
Analytics
|
||||
</h1>
|
||||
<a href="{{ statview }}" target="_blank" class="group flex items-center gap-2 bg-slate-900/5 dark:bg-white/5 px-4 py-2 rounded-xl border border-slate-200/50 dark:border-white/10 hover:bg-blue-600 transition-all duration-300">
|
||||
<span class="text-[10px] font-bold text-slate-500 group-hover:text-white transition-colors uppercase tracking-widest">
|
||||
reservation.ludikevent.fr
|
||||
</span>
|
||||
<svg class="w-3 h-3 text-slate-400 group-hover:text-white transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3">
|
||||
{# Badge Visiteurs #}
|
||||
<div class="backdrop-blur-md bg-white/40 dark:bg-slate-900/20 border border-white/40 dark:border-white/10 px-6 py-2.5 rounded-full shadow-lg flex items-center gap-3">
|
||||
<div class="w-2 h-2 bg-emerald-500 rounded-full animate-pulse"></div>
|
||||
<span class="text-[11px] font-black uppercase tracking-widest text-slate-600 dark:text-slate-300">
|
||||
<span class="text-slate-900 dark:text-white text-sm">{{ nbVisitor }}</span> Visiteurs <span class="hidden md:inline text-slate-400">(-24h)</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{# Badge Vues #}
|
||||
<div class="backdrop-blur-md bg-white/40 dark:bg-slate-900/20 border border-white/40 dark:border-white/10 px-6 py-2.5 rounded-full shadow-lg flex items-center gap-3">
|
||||
<svg class="w-4 h-4 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
|
||||
</svg>
|
||||
<span class="text-[11px] font-black uppercase tracking-widest text-slate-600 dark:text-slate-300">
|
||||
<span class="text-slate-900 dark:text-white text-sm">{{ nbView }}</span> Vues <span class="hidden md:inline text-slate-400">(-24h)</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# --- GRILLE PRINCIPALE --- #}
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
|
||||
{# Carte Inventaire #}
|
||||
<div class="group relative">
|
||||
<div class="absolute inset-0 bg-gradient-to-br from-blue-600/20 to-transparent blur-3xl opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
|
||||
<div class="relative overflow-hidden backdrop-blur-2xl bg-white/30 dark:bg-slate-800/40 border border-white/20 dark:border-white/5 rounded-[3rem] p-8 shadow-[0_20px_50px_rgba(0,0,0,0.1)] transition-all duration-500 hover:-translate-y-2">
|
||||
<div class="flex items-start justify-between">
|
||||
<div>
|
||||
<p class="text-[10px] font-black text-blue-600 dark:text-blue-400 uppercase tracking-[0.4em] mb-4 italic">Inventaire</p>
|
||||
<h2 class="text-7xl font-black text-slate-900 dark:text-white tracking-tighter italic leading-none">{{ product }}</h2>
|
||||
<p class="text-[11px] font-bold text-slate-500 dark:text-slate-400 mt-4 uppercase tracking-wider">Produits actifs en ligne</p>
|
||||
</div>
|
||||
<div class="w-14 h-14 bg-gradient-to-br from-blue-500 to-blue-700 rounded-2xl flex items-center justify-center text-white shadow-[0_10px_20px_rgba(59,130,246,0.3)]">
|
||||
<svg class="w-7 h-7" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" /></svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative group overflow-hidden bg-white/40 dark:bg-slate-800/40 backdrop-blur-xl border border-white/20 dark:border-slate-700/50 rounded-[2.5rem] p-8 shadow-2xl transition-all hover:scale-[1.02] duration-300">
|
||||
<div class="absolute -right-4 -top-4 w-24 h-24 bg-amber-500/10 rounded-full blur-2xl group-hover:bg-amber-500/20 transition-colors"></div>
|
||||
|
||||
<div class="relative flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-[10px] font-black text-amber-600 dark:text-amber-400 uppercase tracking-[0.3em] mb-1">Devis</p>
|
||||
<h2 class="text-5xl font-extrabold text-slate-900 dark:text-white tracking-tight">{{ devis_wait_sign }}</h2>
|
||||
<p class="text-sm font-medium text-slate-500 dark:text-slate-400 mt-2">Devis en attends de signature</p>
|
||||
</div>
|
||||
<div class="w-16 h-16 bg-amber-600/10 border border-amber-600/20 rounded-2xl flex items-center justify-center text-amber-600 shadow-inner">
|
||||
<svg class="w-8 h-8" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10" />
|
||||
</svg>
|
||||
{# Carte Devis #}
|
||||
<div class="group relative">
|
||||
<div class="absolute inset-0 bg-gradient-to-br from-amber-600/20 to-transparent blur-3xl opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
|
||||
<div class="relative overflow-hidden backdrop-blur-2xl bg-white/30 dark:bg-slate-800/40 border border-white/20 dark:border-white/5 rounded-[3rem] p-8 shadow-[0_20px_50px_rgba(0,0,0,0.1)] transition-all duration-500 hover:-translate-y-2">
|
||||
<div class="flex items-start justify-between">
|
||||
<div>
|
||||
<p class="text-[10px] font-black text-amber-600 dark:text-amber-400 uppercase tracking-[0.4em] mb-4 italic">En attente</p>
|
||||
<h2 class="text-7xl font-black text-slate-900 dark:text-white tracking-tighter italic leading-none">{{ devis_wait_sign }}</h2>
|
||||
<p class="text-[11px] font-bold text-slate-500 dark:text-slate-400 mt-4 uppercase tracking-wider">Devis à faire signer</p>
|
||||
</div>
|
||||
<div class="w-14 h-14 bg-gradient-to-br from-amber-500 to-orange-600 rounded-2xl flex items-center justify-center text-white shadow-[0_10px_20px_rgba(245,158,11,0.3)]">
|
||||
<svg class="w-7 h-7" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10" /></svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative group overflow-hidden bg-white/40 dark:bg-slate-800/40 backdrop-blur-xl border border-white/20 dark:border-slate-700/50 rounded-[2.5rem] p-8 shadow-2xl transition-all hover:scale-[1.02] duration-300">
|
||||
<div class="absolute -right-4 -top-4 w-24 h-24 bg-emerald-500/10 rounded-full blur-2xl group-hover:bg-emerald-500/20 transition-colors"></div>
|
||||
|
||||
<div class="relative flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-[10px] font-black text-emerald-600 dark:text-emerald-400 uppercase tracking-[0.3em] mb-1">Base de données</p>
|
||||
<h2 class="text-5xl font-extrabold text-slate-900 dark:text-white tracking-tight">{{ customers }}</h2>
|
||||
<p class="text-sm font-medium text-slate-500 dark:text-slate-400 mt-2">Clients totaux</p>
|
||||
</div>
|
||||
<div class="w-16 h-16 bg-emerald-600/10 border border-emerald-600/20 rounded-2xl flex items-center justify-center text-emerald-600 shadow-inner">
|
||||
<svg class="w-8 h-8" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.501 20.118a7.5 7.5 0 0114.998 0A17.933 17.933 0 0112 21.75c-2.676 0-5.216-.584-7.499-1.632z" />
|
||||
</svg>
|
||||
{# Carte Clients #}
|
||||
<div class="group relative">
|
||||
<div class="absolute inset-0 bg-gradient-to-br from-emerald-600/20 to-transparent blur-3xl opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
|
||||
<div class="relative overflow-hidden backdrop-blur-2xl bg-white/30 dark:bg-slate-800/40 border border-white/20 dark:border-white/5 rounded-[3rem] p-8 shadow-[0_20px_50px_rgba(0,0,0,0.1)] transition-all duration-500 hover:-translate-y-2">
|
||||
<div class="flex items-start justify-between">
|
||||
<div>
|
||||
<p class="text-[10px] font-black text-emerald-600 dark:text-emerald-400 uppercase tracking-[0.4em] mb-4 italic">BDD Clients</p>
|
||||
<h2 class="text-7xl font-black text-slate-900 dark:text-white tracking-tighter italic leading-none">{{ customers }}</h2>
|
||||
<p class="text-[11px] font-bold text-slate-500 dark:text-slate-400 mt-4 uppercase tracking-wider">Fiches clients uniques</p>
|
||||
</div>
|
||||
<div class="w-14 h-14 bg-gradient-to-br from-emerald-500 to-teal-600 rounded-2xl flex items-center justify-center text-white shadow-[0_10px_20px_rgba(16,185,129,0.3)]">
|
||||
<svg class="w-7 h-7" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.501 20.118a7.5 7.5 0 0114.998 0A17.933 17.933 0 0112 21.75c-2.676 0-5.216-.584-7.499-1.632z" /></svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -141,7 +141,7 @@
|
||||
|
||||
{# --- DESCRIPTION --- #}
|
||||
<div class="prose prose-slate prose-lg max-w-none mb-12 text-slate-600 leading-relaxed text-center md:text-left">
|
||||
{{ product.description|raw }}
|
||||
{{ product.description|nl2br|raw }}
|
||||
</div>
|
||||
|
||||
{# --- DIMENSIONS (Version Responsive) --- #}
|
||||
|
||||
Reference in New Issue
Block a user