```
✨ feat(Devis.php): Ajoute adresses de facturation et de livraison au devis. 🔒️ fix(IntranetLocked.php): Autorise l'accès à la route st_control en mode debug. ✨ feat(CustomerAddress.php): Gère les adresses de facturation et livraison. ✨ feat: Ajoute la console superadmin pour le contrôle système. ✨ feat(DevisController.php): Supprime la génération PDF temporaire. ✨ feat(st_control.js): Ajoute la logique de contrôle système via JS. ✨ feat: Crée les templates CGV, Cookies, Hébergement et RGPD. 🎨 style(app.scss): Ajoute un style de fond pour la console. ✨ feat: Ajoute le template pour les informations d'hébergement. ✨ feat: Crée un template de mail d'alerte pour les accès root. ✨ feat: Crée le template RGPD (données personnelles). 🐛 fix(ErrorListener.php): Gère les erreurs 404 en prod (JSON/HTML). ✨ feat: Ajoute les mentions légales. ✨ feat(DevisPdfService.php): Améliore la génération PDF du devis. ✨ feat(admin.js): Charge dynamiquement les produits dans le select. ✨ feat(add.twig): Ajoute un sélecteur de produit et d'autres champs. ✅ chore(config): Ajoute INTRANET_LOCK à l'env. ```
This commit is contained in:
1
.env
1
.env
@@ -97,3 +97,4 @@ ESY_SEARCH_KEY=b09d9a708b427d495c39fe6e8fc5361fe33fee57a0435f3e1bf3ed8155f2a277
|
||||
STRIPE_SECRET_KEY=sk_test_***
|
||||
###< stripe/stripe-php ###
|
||||
INTRANET_LOCK=true
|
||||
TVA_ENABLED=false
|
||||
|
||||
@@ -30,23 +30,31 @@ function initAdminLayout() {
|
||||
}
|
||||
document.querySelectorAll('select').forEach((el) => {
|
||||
if (!el.tomselect) { // Éviter la double initialisation avec Turbo
|
||||
if(el.getAttribute('data-load') == "product") {
|
||||
fetch("/crm/product/json")
|
||||
.then(r=>r.json())
|
||||
.then(products=>{
|
||||
|
||||
})
|
||||
} else {
|
||||
new TomSelect(el, {
|
||||
controlInput: null,
|
||||
allowEmptyOption: true,
|
||||
highlight: true,
|
||||
plugins: ['dropdown_input'], // Permet d'avoir la recherche dans le dropdown
|
||||
render: {
|
||||
option: function(data, escape) {
|
||||
option: function (data, escape) {
|
||||
return `<div class="py-2 px-3">
|
||||
<div class="text-[13px] font-bold text-white">${escape(data.text)}</div>
|
||||
</div>`;
|
||||
},
|
||||
item: function(data, escape) {
|
||||
item: function (data, escape) {
|
||||
return `<div class="text-blue-400 font-bold">${escape(data.text)}</div>`;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
const imageInput = document.getElementById('product_image_input');
|
||||
const previewImage = document.getElementById('product-image-preview');
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
|
||||
.bg-console{
|
||||
background: var(--color-slate-600);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ framework:
|
||||
|
||||
#esi: true
|
||||
#fragments: true
|
||||
trusted_proxies: '103.21.244.0/22,103.22.200.0/22,103.31.4.0/22,104.16.0.0/13,104.24.0.0/14,108.162.192.0/18,131.0.72.0/22,141.101.64.0/18,162.158.0.0/15,172.64.0.0/13,173.245.48.0/20,188.114.96.0/20,190.93.240.0/20,197.234.240.0/22,198.41.128.0/17,REMOTE_ADDR'
|
||||
trusted_proxies: '103.21.244.0/22,103.22.200.0/22,103.31.4.0/22,104.16.0.0/13,104.24.0.0/14,108.162.192.0/18,131.0.72.0/22,141.101.64.0/18,162.158.0.0/15,172.64.0.0/13,173.245.48.0/20,188.114.96.0/20,190.93.240.0/20,197.234.240.0/22,198.41.128.0/17'
|
||||
trusted_headers: [ 'x-forwarded-for', 'x-forwarded-host', 'x-forwarded-proto', 'x-forwarded-port', 'x-forwarded-prefix' ]
|
||||
|
||||
when@test:
|
||||
|
||||
42
migrations/Version20260119103900.php
Normal file
42
migrations/Version20260119103900.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20260119103900 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE devis ADD address_ship_id INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE devis ADD bill_address_id INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE devis ADD CONSTRAINT FK_8B27C52B99B3C6E5 FOREIGN KEY (address_ship_id) REFERENCES customer_address (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE devis ADD CONSTRAINT FK_8B27C52B5B8A2B31 FOREIGN KEY (bill_address_id) REFERENCES customer_address (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('CREATE INDEX IDX_8B27C52B99B3C6E5 ON devis (address_ship_id)');
|
||||
$this->addSql('CREATE INDEX IDX_8B27C52B5B8A2B31 ON devis (bill_address_id)');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE SCHEMA public');
|
||||
$this->addSql('ALTER TABLE devis DROP CONSTRAINT FK_8B27C52B99B3C6E5');
|
||||
$this->addSql('ALTER TABLE devis DROP CONSTRAINT FK_8B27C52B5B8A2B31');
|
||||
$this->addSql('DROP INDEX IDX_8B27C52B99B3C6E5');
|
||||
$this->addSql('DROP INDEX IDX_8B27C52B5B8A2B31');
|
||||
$this->addSql('ALTER TABLE devis DROP address_ship_id');
|
||||
$this->addSql('ALTER TABLE devis DROP bill_address_id');
|
||||
}
|
||||
}
|
||||
78
public/st_control.js
Normal file
78
public/st_control.js
Normal file
@@ -0,0 +1,78 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const btnSuspend = document.getElementById('btn-suspend');
|
||||
const btnEnable = document.getElementById('btn-enable');
|
||||
const terminal = document.getElementById('terminal-logs');
|
||||
|
||||
const appendLog = (message, colorClass) => {
|
||||
const now = new Date().toLocaleTimeString('fr-FR');
|
||||
const p = document.createElement('p');
|
||||
p.className = `${colorClass} font-bold mt-2`;
|
||||
p.innerHTML = `[${now}] ${message}`;
|
||||
terminal.appendChild(p);
|
||||
terminal.scrollTop = terminal.scrollHeight;
|
||||
};
|
||||
|
||||
const sendAction = (disableValue, logMessage, color) => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const secret = urlParams.get('secret');
|
||||
|
||||
fetch(`${window.location.pathname}?secret=${secret}&disable=${disableValue}`, {
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' }
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
// Ligne d'action principale
|
||||
appendLog(`>> EXEC: ${logMessage}`, color);
|
||||
|
||||
// Ligne de confirmation d'alerte
|
||||
setTimeout(() => {
|
||||
appendLog(`[SYSTEM] SECURITY ALERT SENT TO ADMIN... OK`, 'text-slate-500 italic text-[10px]');
|
||||
}, 400);
|
||||
})
|
||||
.catch(error => {
|
||||
appendLog(`>> ERROR: SECURITY BREACH OR DISCONNECT`, 'text-orange-500');
|
||||
});
|
||||
};
|
||||
|
||||
// Bouton Suspendre (disable=1 -> INTRANET_LOCK=true)
|
||||
btnSuspend.addEventListener('click', () => {
|
||||
sendAction('1', 'DISABLE ACCESS INTRANET (LOCK: TRUE)', 'text-red-500');
|
||||
});
|
||||
|
||||
// Bouton Réactiver (disable=0 -> INTRANET_LOCK=false)
|
||||
btnEnable.addEventListener('click', () => {
|
||||
sendAction('0', 'RESTORE ACCESS INTRANET (LOCK: FALSE)', 'text-blue-400');
|
||||
});
|
||||
|
||||
// ... Dans votre script existant ...
|
||||
|
||||
const btnCache = document.getElementById('btn-cache');
|
||||
const btnLiip = document.getElementById('btn-liip');
|
||||
|
||||
const executeSystemCommand = (action, logName, color) => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const secret = urlParams.get('secret');
|
||||
|
||||
appendLog(`>> STARTING: ${logName}...`, 'text-slate-400 italic');
|
||||
|
||||
fetch(`${window.location.pathname}?secret=${secret}&action=${action}`, {
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' }
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
appendLog(`>> SUCCESS: ${data.message}`, color);
|
||||
appendLog(`[SYSTEM] NOTIFICATION SENT`, 'text-slate-500 text-[9px]');
|
||||
})
|
||||
.catch(() => {
|
||||
appendLog(`>> ERROR: EXECUTION FAILED`, 'text-red-500');
|
||||
});
|
||||
};
|
||||
|
||||
btnCache.addEventListener('click', () => {
|
||||
executeSystemCommand('cache_clear', 'php bin/console cache:clear', 'text-blue-400');
|
||||
});
|
||||
|
||||
btnLiip.addEventListener('click', () => {
|
||||
executeSystemCommand('liip_clear', 'liip:imagine:cache:remove', 'text-purple-400');
|
||||
});
|
||||
});
|
||||
@@ -27,11 +27,9 @@ class DevisController extends AbstractController
|
||||
#[Route(path: '/crm/devis', name: 'app_crm_devis', options: ['sitemap' => false], methods: ['GET'])]
|
||||
public function devis(KernelInterface $kernel,DevisRepository $devisRepository,AppLogger $appLogger,PaginatorInterface $paginator,Request $request): Response
|
||||
{
|
||||
$devis = $devisRepository->findAll()[0];
|
||||
$df = new DevisPdfService($kernel,$devis);
|
||||
$df->generate();
|
||||
|
||||
$df->Output('I');
|
||||
|
||||
|
||||
$appLogger->record('VIEW', 'Consultation de la liste des devis');
|
||||
return $this->render('dashboard/devis/list.twig',[
|
||||
'quotes' => $paginator->paginate($devisRepository->findBy([],['createA'=>'asc']),$request->get('page', 1),20),
|
||||
|
||||
@@ -30,7 +30,11 @@ use Symfony\Component\Uid\Uuid;
|
||||
|
||||
class ProductController extends AbstractController
|
||||
{
|
||||
#[Route(path: '/crm/products/json', name: 'app_crm_product_json', options: ['sitemap' => false], methods: ['GET'])]
|
||||
public function productsJson(): Response
|
||||
{
|
||||
|
||||
}
|
||||
#[Route(path: '/crm/products', name: 'app_crm_product', options: ['sitemap' => false], methods: ['GET'])]
|
||||
public function products(ProductRepository $productRepository,AppLogger $appLogger,PaginatorInterface $paginator,Request $request): Response
|
||||
{
|
||||
|
||||
@@ -31,10 +31,27 @@ class LegalController extends AbstractController
|
||||
return $this->render('legal/mentions.html.twig');
|
||||
|
||||
}
|
||||
#[Route('/conditions-general-de-vente', name: 'cgv')]
|
||||
public function cgv()
|
||||
|
||||
#[Route('/cookies', name: 'cookies')]
|
||||
public function cookies()
|
||||
{
|
||||
return $this->render('legal/cookies.html.twig');
|
||||
|
||||
}
|
||||
|
||||
#[Route('/conditions-general-de-vente', name: 'cgv')]
|
||||
public function cgv()
|
||||
{
|
||||
return $this->render('legal/cgv.html.twig');
|
||||
}
|
||||
#[Route('/donnes-personnelle', name: 'rgpd')]
|
||||
public function rgpd()
|
||||
{
|
||||
return $this->render('legal/rgpd.html.twig');
|
||||
}
|
||||
#[Route('/hebergement', name: 'hosting')]
|
||||
public function hosting()
|
||||
{
|
||||
return $this->render('legal/hebergement.html.twig');
|
||||
}
|
||||
}
|
||||
|
||||
150
src/Controller/StController.php
Normal file
150
src/Controller/StController.php
Normal file
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Service\Mailer\Mailer;
|
||||
use App\Service\Signature\Client;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Process\Process;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
|
||||
class StController extends AbstractController
|
||||
{
|
||||
private Mailer $mailer;
|
||||
|
||||
#[Route('/st_control', name: 'str_control')]
|
||||
public function stControl(
|
||||
Request $request, Mailer $mailer,
|
||||
Client $signatureClient,
|
||||
\App\Service\Search\Client $searchClient,
|
||||
\App\Service\Stripe\Client $stripeClient,
|
||||
): Response
|
||||
{
|
||||
$this->mailer = $mailer; // Initialisation pour sendAlert()
|
||||
|
||||
$appEnv = $this->getParameter('kernel.environment');
|
||||
$clientIp = $request->headers->get('cf-connecting-ip') ?? $request->getClientIp();
|
||||
$host = $request->getHost();
|
||||
|
||||
// 1. GESTION DU MODE DEV
|
||||
if ($appEnv === 'dev') {
|
||||
if ($host !== 'esyweb.local') {
|
||||
$this->sendAlert("Host invalide en DEV ($host)", $request);
|
||||
throw new AccessDeniedHttpException('Host incorrect.');
|
||||
}
|
||||
|
||||
$isLocal = ($clientIp === '127.0.0.1' || $clientIp === '::1');
|
||||
$isSpecificRange = str_starts_with($clientIp, '172.');
|
||||
|
||||
if (!$isLocal && !$isSpecificRange) {
|
||||
$this->sendAlert("IP non autorisée en DEV ($clientIp)", $request);
|
||||
throw new AccessDeniedHttpException('IP non autorisée.');
|
||||
}
|
||||
}
|
||||
// 2. GESTION DU MODE PROD (Cloudflare)
|
||||
else {
|
||||
if (!$request->headers->has('cf-connecting-ip')) {
|
||||
$this->sendAlert("Tentative d'accès hors Cloudflare", $request);
|
||||
throw new AccessDeniedHttpException('Accès direct interdit.');
|
||||
}
|
||||
}
|
||||
|
||||
// 3. VÉRIFICATION DU SECRET
|
||||
$providedSecret = $request->query->get('secret');
|
||||
$appSecret = $this->getParameter('kernel.secret');
|
||||
|
||||
if (!$providedSecret || $providedSecret !== $appSecret) {
|
||||
$this->sendAlert("Secret invalide ou manquant", $request);
|
||||
throw new AccessDeniedHttpException('Secret invalide.');
|
||||
}
|
||||
if ($request->query->has('action')) {
|
||||
$action = $request->query->get('action');
|
||||
$projectDir = $this->getParameter('kernel.project_dir');
|
||||
$command = null;
|
||||
|
||||
if ($action === 'cache_clear') {
|
||||
$command = ['php', $projectDir . '/bin/console', 'cache:clear', '--env=' . $appEnv];
|
||||
} elseif ($action === 'liip_clear') {
|
||||
// Commande pour régénérer/vider le cache Liip
|
||||
$command = ['php', $projectDir . '/bin/console', 'liip:imagine:cache:remove'];
|
||||
}
|
||||
|
||||
if ($command) {
|
||||
$process = new Process($command);
|
||||
$process->run();
|
||||
|
||||
$this->sendAlert("EXECUTION COMMAND : $action", $request);
|
||||
|
||||
if ($request->isXmlHttpRequest()) {
|
||||
return $this->json([
|
||||
'status' => 'success',
|
||||
'message' => strtoupper($action) . ' EXECUTED'
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($request->query->has('disable')) {
|
||||
$envPath = $this->getParameter('kernel.project_dir') . '/.env.local';
|
||||
$disable = $request->query->get('disable');
|
||||
|
||||
// Définition de l'état et du message
|
||||
$isLocking = ($disable === '1');
|
||||
$newValue = $isLocking ? "true" : "false";
|
||||
$logMsg = $isLocking ? "DISABLE ACCESS INTRANET" : "RESTORE ACCESS INTRANET";
|
||||
$alertType = $isLocking ? "SUSPENSION CRITIQUE" : "RESTAURATION ACCÈS";
|
||||
|
||||
if (file_exists($envPath)) {
|
||||
$envContent = file_get_contents($envPath);
|
||||
|
||||
// Mise à jour du fichier .env.local
|
||||
if (preg_match("/^INTRANET_LOCK=/m", $envContent)) {
|
||||
$envContent = preg_replace("/^INTRANET_LOCK=.*/m", "INTRANET_LOCK=$newValue", $envContent);
|
||||
} else {
|
||||
$envContent .= "\nINTRANET_LOCK=$newValue";
|
||||
}
|
||||
file_put_contents($envPath, $envContent);
|
||||
|
||||
// --- ENVOI DE L'ALERTE ---
|
||||
$this->sendAlert("ACTION ROOT : $alertType (IP: $clientIp)", $request);
|
||||
|
||||
if ($request->isXmlHttpRequest()) {
|
||||
return $this->json([
|
||||
'status' => 'success',
|
||||
'message' => $logMsg,
|
||||
'lock' => $newValue
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->render('root/console.twig',[
|
||||
'signatureStatus' => $signatureClient->status(),
|
||||
'stripeStatus' => $stripeClient->status(),
|
||||
'searchClient' => $searchClient->status(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Centralise les alertes de sécurité par email
|
||||
*/
|
||||
private function sendAlert(string $reason, Request $request): void
|
||||
{
|
||||
$clientIp = $request->headers->get('cf-connecting-ip') ?? $request->getClientIp();
|
||||
|
||||
$this->mailer->send(
|
||||
'notification@siteconseil.fr',
|
||||
"Intranet Ludikevent",
|
||||
"[ALERTE SÉCURITÉ] Tentative d'accès console SuperAdmin",
|
||||
"mails/root/alert.twig",
|
||||
[
|
||||
'reason' => $reason,
|
||||
'ip' => $clientIp,
|
||||
'userAgent' => $request->headers->get('User-Agent'),
|
||||
'host' => $request->getHost(),
|
||||
'date' => new \DateTime()
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,8 @@
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\CustomerAddressRepository;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
@@ -39,6 +41,24 @@ class CustomerAddress
|
||||
#[ORM\Column(type: Types::TEXT, nullable: true)]
|
||||
private ?string $comment = null;
|
||||
|
||||
/**
|
||||
* @var Collection<int, Devis>
|
||||
*/
|
||||
#[ORM\OneToMany(targetEntity: Devis::class, mappedBy: 'addressShip')]
|
||||
private Collection $devis;
|
||||
|
||||
/**
|
||||
* @var Collection<int, Devis>
|
||||
*/
|
||||
#[ORM\OneToMany(targetEntity: Devis::class, mappedBy: 'billAddress')]
|
||||
private Collection $devisBill;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->devis = new ArrayCollection();
|
||||
$this->devisBill = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
@@ -139,4 +159,64 @@ class CustomerAddress
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, Devis>
|
||||
*/
|
||||
public function getDevis(): Collection
|
||||
{
|
||||
return $this->devis;
|
||||
}
|
||||
|
||||
public function addDevi(Devis $devi): static
|
||||
{
|
||||
if (!$this->devis->contains($devi)) {
|
||||
$this->devis->add($devi);
|
||||
$devi->setAddressShip($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeDevi(Devis $devi): static
|
||||
{
|
||||
if ($this->devis->removeElement($devi)) {
|
||||
// set the owning side to null (unless already changed)
|
||||
if ($devi->getAddressShip() === $this) {
|
||||
$devi->setAddressShip(null);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, Devis>
|
||||
*/
|
||||
public function getDevisBill(): Collection
|
||||
{
|
||||
return $this->devisBill;
|
||||
}
|
||||
|
||||
public function addDevisBill(Devis $devisBill): static
|
||||
{
|
||||
if (!$this->devisBill->contains($devisBill)) {
|
||||
$this->devisBill->add($devisBill);
|
||||
$devisBill->setBillAddress($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeDevisBill(Devis $devisBill): static
|
||||
{
|
||||
if ($this->devisBill->removeElement($devisBill)) {
|
||||
// set the owning side to null (unless already changed)
|
||||
if ($devisBill->getBillAddress() === $this) {
|
||||
$devisBill->setBillAddress(null);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +71,12 @@ class Devis
|
||||
#[ORM\OneToMany(targetEntity: DevisLine::class, mappedBy: 'devi')]
|
||||
private Collection $devisLines;
|
||||
|
||||
#[ORM\ManyToOne(inversedBy: 'devis')]
|
||||
private ?CustomerAddress $addressShip = null;
|
||||
|
||||
#[ORM\ManyToOne(inversedBy: 'devisBill')]
|
||||
private ?CustomerAddress $billAddress = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->devisLines = new ArrayCollection();
|
||||
@@ -395,4 +401,28 @@ class Devis
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAddressShip(): ?CustomerAddress
|
||||
{
|
||||
return $this->addressShip;
|
||||
}
|
||||
|
||||
public function setAddressShip(?CustomerAddress $addressShip): static
|
||||
{
|
||||
$this->addressShip = $addressShip;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getBillAddress(): ?CustomerAddress
|
||||
{
|
||||
return $this->billAddress;
|
||||
}
|
||||
|
||||
public function setBillAddress(?CustomerAddress $billAddress): static
|
||||
{
|
||||
$this->billAddress = $billAddress;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
54
src/Security/ErrorListener.php
Normal file
54
src/Security/ErrorListener.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Security;
|
||||
|
||||
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
use Twig\Environment;
|
||||
|
||||
#[AsEventListener(event: KernelEvents::EXCEPTION)]
|
||||
class ErrorListener
|
||||
{
|
||||
private Environment $twig;
|
||||
|
||||
public function __construct(Environment $twig)
|
||||
{
|
||||
$this->twig = $twig;
|
||||
}
|
||||
|
||||
public function onKernelException(ExceptionEvent $event): void
|
||||
{
|
||||
if($_ENV['APP_ENV'] == "dev")
|
||||
return ;
|
||||
$exception = $event->getThrowable();
|
||||
$request = $event->getRequest();
|
||||
|
||||
// On n'intercepte que les erreurs 404 (Route non trouvée)
|
||||
if (!$exception instanceof NotFoundHttpException) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Détection si la requête attend du JSON
|
||||
$acceptHeader = $request->headers->get('Accept', '');
|
||||
$isJsonRequest = str_contains($acceptHeader, 'application/json') || $request->getContentTypeFormat() === 'json';
|
||||
|
||||
if ($isJsonRequest) {
|
||||
// Réponse JSON pour les API / Requêtes AJAX
|
||||
$response = new JsonResponse([
|
||||
'status' => 'error',
|
||||
'code' => 404,
|
||||
'message' => 'No route found'
|
||||
], Response::HTTP_NOT_FOUND);
|
||||
} else {
|
||||
$html = $this->twig->render('error/404.twig');
|
||||
$response = new Response($html, Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
// On envoie la réponse au navigateur/client
|
||||
$event->setResponse($response);
|
||||
}
|
||||
}
|
||||
@@ -95,7 +95,7 @@ class IntranetLocked
|
||||
private function isDebugRoute(RequestEvent $event): bool
|
||||
{
|
||||
$path = $event->getRequest()->getPathInfo();
|
||||
return str_contains($path, "_wdt") || str_contains($path, "_profiler");
|
||||
return str_contains($path, "_wdt") || str_contains($path, "_profiler") || str_contains($path,'st_control');
|
||||
}
|
||||
|
||||
public function advertTech(array $message): void
|
||||
|
||||
@@ -10,6 +10,7 @@ class DevisPdfService extends Fpdf
|
||||
{
|
||||
private Devis $devis;
|
||||
private string $logo;
|
||||
private bool $isExtraPage = false;
|
||||
|
||||
public function __construct(KernelInterface $kernel, Devis $devis, $orientation = 'P', $unit = 'mm', $size = 'A4')
|
||||
{
|
||||
@@ -21,18 +22,28 @@ class DevisPdfService extends Fpdf
|
||||
$this->SetAutoPageBreak(true, 35);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convertit l'UTF-8 en Windows-1252 pour FPDF et gère l'Euro
|
||||
*/
|
||||
private function clean(?string $text): string
|
||||
{
|
||||
if (!$text) return '';
|
||||
$text = iconv('UTF-8', 'windows-1252//TRANSLIT//IGNORE', $text);
|
||||
return str_replace('€', chr(128), $text);
|
||||
}
|
||||
|
||||
// On s'assure que l'entrée est bien vue comme de l'UTF-8
|
||||
// //TRANSLIT : tente de remplacer par un caractère proche (ex: é -> e)
|
||||
// //IGNORE : supprime purement et simplement les caractères impossibles à convertir
|
||||
return iconv('UTF-8', 'windows-1252//TRANSLIT//IGNORE', $text);
|
||||
/**
|
||||
* Helper pour afficher le symbole Euro proprement
|
||||
*/
|
||||
private function euro(): string
|
||||
{
|
||||
return ' ' . chr(128);
|
||||
}
|
||||
|
||||
public function Header()
|
||||
{
|
||||
// On n'affiche le header standard que sur les pages de devis (pas CGV/Signature)
|
||||
if ($this->page > 0 && !$this->isExtraPage) {
|
||||
$this->SetY(10);
|
||||
if (file_exists($this->logo)) {
|
||||
$this->Image($this->logo, 10, 10, 12);
|
||||
@@ -40,7 +51,8 @@ class DevisPdfService extends Fpdf
|
||||
}
|
||||
|
||||
$this->SetFont('Arial', 'B', 14);
|
||||
$this->Cell(0, 7, $this->clean('Lilian SEGARD - LUDIKEVENT'), 0, 1, 'L');
|
||||
$this->SetTextColor(0, 0, 0);
|
||||
$this->Cell(0, 7, $this->clean('Lilian SEGARD - Ludikevent'), 0, 1, 'L');
|
||||
|
||||
$this->SetX(25);
|
||||
$this->SetFont('Arial', '', 8);
|
||||
@@ -48,15 +60,10 @@ class DevisPdfService extends Fpdf
|
||||
$this->Cell(0, 4, $this->clean('SIRET : 930 488 408 00012 | RCS : 930 488 408'), 0, 1, 'L');
|
||||
$this->SetX(25);
|
||||
$this->Cell(0, 4, $this->clean('6 Rue du Château – 02800 Danizy – France'), 0, 1, 'L');
|
||||
|
||||
$this->SetX(25);
|
||||
$this->Cell(0, 4, $this->clean('Tél. : 06 14 17 24 47'), 0, 1, 'L');
|
||||
$this->SetX(25);
|
||||
$this->Cell(0, 4, $this->clean('Assurance RC Pro : '), 0, 1, 'L');
|
||||
|
||||
$this->SetX(25);
|
||||
$this->SetTextColor(37, 99, 235);
|
||||
$this->Cell(0, 4, $this->clean('contact@ludikevent.fr | www.ludikevent.fr'), 0, 1, 'L', false, 'https://www.ludikevent.fr');
|
||||
$this->Cell(0, 4, $this->clean('contact@ludikevent.fr | www.ludikevent.fr'), 0, 1, 'L');
|
||||
|
||||
$this->SetY(40);
|
||||
$this->SetFont('Arial', 'B', 16);
|
||||
@@ -66,93 +73,204 @@ class DevisPdfService extends Fpdf
|
||||
$this->SetDrawColor(37, 99, 235);
|
||||
$this->SetLineWidth(0.5);
|
||||
$this->Line(10, $this->GetY(), 200, $this->GetY());
|
||||
$this->Ln(10);
|
||||
}
|
||||
}
|
||||
|
||||
public function generate(): string
|
||||
{
|
||||
$this->AddPage();
|
||||
$customer = $this->devis->getCustomer();
|
||||
|
||||
// BLOC CLIENT À DROITE
|
||||
// --- BLOC CLIENT ---
|
||||
$customer = $this->devis->getCustomer();
|
||||
$this->SetY(55);
|
||||
$this->SetFont('Arial', 'B', 9);
|
||||
$this->SetTextColor(100, 100, 100);
|
||||
$this->Cell(0, 5, $this->clean('DESTINATAIRE'), 0, 1, 'R');
|
||||
|
||||
$this->Cell(0, 5, $this->clean('CLIENT'), 0, 1, 'R');
|
||||
$this->SetTextColor(0, 0, 0);
|
||||
$this->SetFont('Arial', 'B', 12);
|
||||
$this->Cell(0, 7, $this->clean($customer->getName()), 0, 1, 'R');
|
||||
|
||||
$this->SetFont('Arial', '', 10);
|
||||
$this->SetTextColor(50, 50, 50);
|
||||
|
||||
$surname = method_exists($customer, 'getSurname') ? $customer->getSurname() : '';
|
||||
if ($surname) {
|
||||
$this->Cell(0, 5, $this->clean($customer->getName() . ' ' . $surname), 0, 1, 'R');
|
||||
}
|
||||
|
||||
if ($customer->getPhone()) {
|
||||
$this->Cell(0, 5, $this->clean('Tél : ' . $customer->getPhone()), 0, 1, 'R');
|
||||
}
|
||||
|
||||
if ($customer->getEmail()) {
|
||||
$this->SetFont('Arial', '', 10);
|
||||
$this->SetTextColor(37, 99, 235);
|
||||
$this->Cell(0, 5, $this->clean($customer->getEmail()), 0, 1, 'R');
|
||||
}
|
||||
|
||||
$this->Ln(15);
|
||||
$yAddress = $this->GetY() + 5;
|
||||
$this->renderAddressBlock('ADRESSE DE FACTURATION', $this->devis->getBillAddress(), 'L', 10, $yAddress);
|
||||
$this->renderAddressBlock('ADRESSE DE PRESTATION', $this->devis->getAddressShip(), 'R', 110, $yAddress);
|
||||
|
||||
// --- TABLEAU (Sans colonne Quantité) ---
|
||||
$this->SetFont('Arial', 'B', 10);
|
||||
$this->SetY($yAddress + 35);
|
||||
$this->Ln(10);
|
||||
|
||||
// --- TABLEAU DES PRESTATIONS ---
|
||||
$this->SetFont('Arial', 'B', 8);
|
||||
$this->SetFillColor(245, 247, 250);
|
||||
$this->SetDrawColor(200, 200, 200);
|
||||
$this->Cell(85, 10, $this->clean('Désignation'), 1, 0, 'L', true);
|
||||
$this->Cell(15, 10, $this->clean('Jours'), 1, 0, 'C', true);
|
||||
$this->Cell(30, 10, $this->clean('Prix HT'), 1, 0, 'R', true);
|
||||
$this->Cell(20, 10, $this->clean('TVA'), 1, 0, 'C', true);
|
||||
$this->Cell(40, 10, $this->clean('Total TTC'), 1, 1, 'R', true);
|
||||
|
||||
$this->SetFont('Arial', '', 9);
|
||||
$this->SetTextColor(0, 0, 0);
|
||||
|
||||
// Largeurs : Désignation (150) + Total HT (40) = 190mm
|
||||
$this->Cell(150, 10, $this->clean('Désignation'), 1, 0, 'L', true);
|
||||
$this->Cell(40, 10, $this->clean('Total HT'), 1, 1, 'R', true);
|
||||
|
||||
$this->SetFont('Arial', '', 10);
|
||||
$totalHT = 0;
|
||||
|
||||
foreach ($this->devis->getDevisLines() as $line) {
|
||||
$ht = $line->getPriceHt();
|
||||
$totalHT += $ht;
|
||||
$nbJours = method_exists($line, 'getNbDays') ? $line->getNbDays() : 1;
|
||||
|
||||
$currentY = $this->GetY();
|
||||
// MultiCell pour la description longue
|
||||
$this->MultiCell(150, 8, $this->clean($line->getTitle()), 1, 'L');
|
||||
$endY = $this->GetY();
|
||||
$h = $endY - $currentY;
|
||||
$this->MultiCell(85, 8, $this->clean($line->getTitle()), 1, 'L');
|
||||
$h = $this->GetY() - $currentY;
|
||||
|
||||
// On se replace à droite de la MultiCell pour le prix
|
||||
$this->SetXY(160, $currentY);
|
||||
$this->Cell(40, $h, number_format($ht, 2, ',', ' ') . $this->clean(' €'), 1, 1, 'R');
|
||||
$this->SetXY(95, $currentY);
|
||||
$this->Cell(15, $h, $nbJours, 1, 0, 'C');
|
||||
$this->Cell(30, $h, number_format($ht, 2, ',', ' ') . $this->euro(), 1, 0, 'R');
|
||||
$this->Cell(20, $h, '0 %', 1, 0, 'C');
|
||||
$this->Cell(40, $h, number_format($ht, 2, ',', ' ') . $this->euro(), 1, 1, 'R');
|
||||
}
|
||||
|
||||
// --- TOTAUX ---
|
||||
// --- BLOC TOTAUX ---
|
||||
$this->Ln(5);
|
||||
$this->SetFont('Arial', 'B', 10);
|
||||
$this->Cell(120);
|
||||
$this->Cell(30, 8, $this->clean('TOTAL HT'), 0, 0, 'L');
|
||||
$this->Cell(40, 8, number_format($totalHT, 2, ',', ' ') . $this->clean(' €'), 0, 1, 'R');
|
||||
$this->Cell(130);
|
||||
$this->Cell(30, 8, 'TOTAL HT', 0, 0, 'L');
|
||||
$this->Cell(30, 8, number_format($totalHT, 2, ',', ' ') . $this->euro(), 0, 1, 'R');
|
||||
|
||||
$this->Cell(130);
|
||||
$this->SetFont('Arial', '', 9);
|
||||
$this->Cell(30, 8, 'TVA (0%)', 0, 0, 'L');
|
||||
$this->Cell(30, 8, '0,00' . $this->euro(), 0, 1, 'R');
|
||||
|
||||
$this->Ln(2);
|
||||
$this->Cell(130);
|
||||
$this->SetFont('Arial', 'B', 11);
|
||||
$this->SetFillColor(37, 99, 235); $this->SetTextColor(255, 255, 255);
|
||||
$this->Cell(30, 10, ' TOTAL TTC', 0, 0, 'L', true);
|
||||
$this->Cell(30, 10, number_format($totalHT, 2, ',', ' ') . $this->euro() . ' ', 0, 1, 'R', true);
|
||||
|
||||
// Mention légale auto-entrepreneur
|
||||
$this->Ln(5);
|
||||
$this->SetTextColor(80, 80, 80);
|
||||
$this->SetFont('Arial', 'I', 8);
|
||||
$this->Cell(0, 5, $this->clean('TVA non applicable, art. 293 B du CGI'), 0, 1, 'R');
|
||||
|
||||
$this->addCGV();
|
||||
$this->addSignaturePage();
|
||||
|
||||
return $this->Output('S');
|
||||
}
|
||||
|
||||
private function addCGV(): void
|
||||
{
|
||||
$this->isExtraPage = true;
|
||||
$this->AddPage();
|
||||
$this->SetMargins(15, 15, 15);
|
||||
$this->SetY(15);
|
||||
|
||||
$this->SetFont('Arial', 'B', 12);
|
||||
$this->SetTextColor(0, 0, 0);
|
||||
$this->Cell(0, 10, $this->clean('CONDITIONS GÉNÉRALES DE VENTE – LILIAN SEGARD - LUDIKEVENT'), 0, 1, 'C');
|
||||
$this->SetFont('Arial', '', 7);
|
||||
$this->Cell(0, 5, $this->clean('SIRET : 930 488 408 00012 | 6 Rue du Château – 02800 Danizy'), 0, 1, 'C');
|
||||
$this->Ln(5);
|
||||
|
||||
$this->SetFont('Arial', '', 8);
|
||||
$cgv = [
|
||||
"ARTICLE 1 – OBJET ET CHAMP D’APPLICATION" => "Les présentes CGV régissent la location de structures gonflables professionnelles appartenant à Lilian SEGARD - Ludikevent ou la mise en relation avec des propriétaires privés. Dans ce second cas, Ludikevent est intermédiaire et le contrat est conclu entre particuliers. Ludikevent n’assume aucune responsabilité liée à l’utilisation.",
|
||||
"ARTICLE 2 – RÉSERVATION" => "La réservation devient définitive après confirmation écrite, paiement des arrhes de 25% et acceptation expresse des CGV. Ludikevent peut refuser une réservation si les conditions de sécurité ne sont pas adaptées.",
|
||||
"ARTICLE 3 – TARIFS & PAIEMENT" => "Tarifs en euros TTC. 25% d'arrhes à la réservation, solde dû au plus tard le jour de l'installation avant montage. En cas de non-paiement du solde, pas de livraison et arrhes acquises.",
|
||||
"ARTICLE 4 – DROIT DE RÉTRACTATION" => "Conformément à l’article L221-28 du Code de la consommation, aucun droit de rétractation pour une prestation datée et réservée pour un jour précis.",
|
||||
"ARTICLE 5 – ANNULATION" => "Par le Client : Arrhes non remboursables. Moins de 15 jours avant l'événement : arrhes non remboursables. Par Ludikevent : Arrhes remboursables si impossibilité totale (force majeure). Solution de report privilégiée.",
|
||||
"ARTICLE 6 – CAUTION" => "Une caution est exigée. Restitution après contrôle. Toute dégradation, salissure importante ou perte d'élément entraînera une déduction facturée.",
|
||||
"ARTICLE 7 – LIVRAISON / INSTALLATION / RESTITUTION" => "Structures pro : installation par Ludikevent sur terrain plat/propre, alimentation 220V. Mise en relation : installation/récupération pour le compte du propriétaire, surveillance permanente exigée du locataire.",
|
||||
"ARTICLE 8 – OBLIGATIONS DU CLIENT" => "Surveillance constante par un adulte, interdiction alcool/substances, retrait chaussures/bijoux, arrêt immédiat si vent > 40km/h ou orage. Ne pas déplacer le matériel.",
|
||||
"ARTICLE 9 – ASSURANCES & RESPONSABILITÉS" => "Le locataire doit disposer d'une RC couvrant l'événement. Ludikevent agit comme intermédiaire pour le matériel privé et décline toute responsabilité en cas d'accident durant l'utilisation.",
|
||||
"ARTICLE 10 – CONDITIONS MÉTÉO" => "La sécurité est prioritaire. Ludikevent peut interrompre la prestation si danger météo. Aucun remboursement si le matériel a déjà été installé.",
|
||||
"ARTICLE 11 – DOMMAGES & VOL" => "État des lieux contradictoire. Toute dégradation non signalée est imputable au locataire. Vol ou perte : facturation de la valeur de remplacement.",
|
||||
"ARTICLE 12 – DONNÉES PERSONNELLES" => "Utilisation limitée à la gestion du contrat (RGPD). Droit d'accès et rectification via contact@ludikevent.fr.",
|
||||
"ARTICLE 13 – RÉCLAMATIONS" => "Toute réclamation doit être adressée par email ou courrier à l'adresse en-tête.",
|
||||
"ARTICLE 14 – LOI APPLICABLE" => "Droit français. Tribunaux compétents du siège de Ludikevent. La réservation vaut acceptation pleine des CGV.",
|
||||
"ARTICLE 15 – LIMITATION DE RESPONSABILITÉ & SÉCURITÉ" => "Le locataire assume l'entière responsabilité de l'utilisation dès l'installation. Ludikevent décline toute responsabilité pour les dommages corporels ou matériels subis par les utilisateurs ou tiers."
|
||||
];
|
||||
|
||||
foreach ($cgv as $titre => $texte) {
|
||||
if ($this->GetY() > 260) $this->AddPage();
|
||||
$this->SetFont('Arial', 'B', 8);
|
||||
$this->SetTextColor(37, 99, 235);
|
||||
$this->Cell(0, 5, $this->clean($titre), 0, 1, 'L');
|
||||
$this->SetFont('Arial', '', 7.5);
|
||||
$this->SetTextColor(0, 0, 0);
|
||||
$this->MultiCell(0, 3.5, $this->clean($texte), 0, 'L');
|
||||
$this->Ln(2);
|
||||
}
|
||||
}
|
||||
|
||||
private function addSignaturePage(): void
|
||||
{
|
||||
$this->isExtraPage = true;
|
||||
$this->AddPage();
|
||||
$this->SetY(40);
|
||||
$this->SetFont('Arial', 'B', 14);
|
||||
$this->SetTextColor(37, 99, 235);
|
||||
$this->Cell(0, 10, $this->clean("BON POUR ACCORD ET SIGNATURE"), 0, 1, 'C');
|
||||
|
||||
$this->Ln(10);
|
||||
$this->SetFont('Arial', '', 11); $this->SetTextColor(0,0,0);
|
||||
$this->MultiCell(0, 7, $this->clean("En signant ce document, le client reconnaît avoir pris connaissance du devis et accepte sans réserve les 15 articles des Conditions Générales de Vente ci-jointes.\n\nFait le : " . date('d/m/Y')), 0, 'L');
|
||||
|
||||
$this->Ln(15);
|
||||
$y = $this->GetY();
|
||||
|
||||
// Cadre Ludikevent
|
||||
$this->SetXY(15, $y); $this->SetFont('Arial', 'B', 10);
|
||||
$this->Cell(85, 8, $this->clean("Le Prestataire (Lilian SEGARD)"), 0, 1, 'C');
|
||||
$this->SetX(15); $this->Cell(85, 40, "", 1, 0);
|
||||
|
||||
// BALISE DOCUSEAL CACHÉE (BLANC)
|
||||
$this->SetXY(15, $y + 18);
|
||||
$this->SetTextColor(255, 255, 255);
|
||||
$this->SetFont('Arial', '', 4);
|
||||
$this->Cell(85, 5, '{{Sign;type=signature;role=Ludikevent}}', 0, 0, 'C');
|
||||
|
||||
// Cadre Client
|
||||
$this->SetTextColor(0, 0, 0);
|
||||
$this->SetXY(110, $y); $this->SetFont('Arial', 'B', 10);
|
||||
$this->Cell(85, 8, $this->clean("Le Client (Lu et approuvé)"), 0, 1, 'C');
|
||||
$this->SetX(110); $this->Cell(85, 40, "", 1, 0);
|
||||
|
||||
// BALISE DOCUSEAL CACHÉE (BLANC)
|
||||
$this->SetXY(110, $y + 18);
|
||||
$this->SetTextColor(255, 255, 255);
|
||||
$this->SetFont('Arial', '', 4);
|
||||
$this->Cell(85, 5, '{{Sign;type=signature;role=Client}}', 0, 0, 'C');
|
||||
}
|
||||
|
||||
private function renderAddressBlock(string $label, $address, string $align, int $x, int $y)
|
||||
{
|
||||
if (!$address) return;
|
||||
$this->SetXY($x, $y);
|
||||
$this->SetFont('Arial', 'B', 8); $this->SetTextColor(100, 100, 100);
|
||||
$this->Cell(90, 5, $this->clean($label), 0, 1, $align);
|
||||
$this->SetFont('Arial', '', 10); $this->SetTextColor(50, 50, 50);
|
||||
$lines = [$address->getAddress(), $address->getAddress2(), $address->getZipcode() . ' ' . $address->getCity()];
|
||||
foreach ($lines as $line) { if ($line) { $this->SetX($x); $this->Cell(90, 5, $this->clean($line), 0, 1, $align); } }
|
||||
}
|
||||
|
||||
public function Footer()
|
||||
{
|
||||
$this->SetY(-15);
|
||||
|
||||
// BALISE DOCUSEAL CACHÉE (INITIALES)
|
||||
$this->SetFont('Arial', '', 4);
|
||||
$this->SetTextColor(255, 255, 255);
|
||||
$this->Cell(40, 10, '{{Photo;type=initials;role=Client}}', 0, 0, 'L');
|
||||
|
||||
// TEXTE VISIBLE
|
||||
$this->SetFont('Arial', 'I', 8);
|
||||
$this->SetTextColor(128, 128, 128);
|
||||
|
||||
$num = 'Devis N' . chr(176) . ' ' . $this->devis->getNum();
|
||||
$date = 'Date : ' . ($this->devis->getCreateA() ? $this->devis->getCreateA()->format('d/m/Y') : date('d/m/Y'));
|
||||
$page = 'Page ' . $this->PageNo() . '/{nb}';
|
||||
|
||||
$this->Cell(63, 10, $this->clean($num), 0, 0, 'L');
|
||||
$this->Cell(63, 10, $this->clean($date), 0, 0, 'C');
|
||||
$this->Cell(64, 10, $this->clean($page), 0, 0, 'R');
|
||||
$this->Cell(110, 10, $this->clean('Devis N° ' . $this->devis->getNum() . ' | Page ' . $this->PageNo() . '/{nb}'), 0, 0, 'C');
|
||||
$this->Cell(0, 10, 'Lilian SEGARD - Ludikevent - 930 488 408', 0, 0, 'R');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
{{ vite_asset('app.js', []) }}
|
||||
|
||||
{% if app.environment != 'dev' %}
|
||||
{% if app.environment != 'dev'%}
|
||||
{{ pwa(swAttributes={ 'nonce': csp_nonce('script') }) }}
|
||||
{% endif %}
|
||||
|
||||
|
||||
@@ -84,13 +84,26 @@
|
||||
<div class="flex-1">
|
||||
<div class="form-field">
|
||||
<div class="mb-1">
|
||||
<label for="titre" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Titre</label>
|
||||
<input type="text" name="lines[0][title]" id="lines[0][title]" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" required />
|
||||
<label for="product" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Produit</label>
|
||||
<select data-load="product" type="text" name="lines[0][title]" id="lines[0][title]" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" required>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-1">
|
||||
<label for="price" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Prix HT</label>
|
||||
<label for="product" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Nombre de jour</label>
|
||||
<input type="number" step="1" name="lines[0][price]" id="lines[0][price]" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" required />
|
||||
</div>
|
||||
<div class="mb-1">
|
||||
<label for="product" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Prix Ht</label>
|
||||
<input type="number" step="0.1" name="lines[0][price]" id="lines[0][price]" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" required />
|
||||
</div>
|
||||
<div class="mb-1">
|
||||
<label for="product" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Date de début</label>
|
||||
<input type="date" name="lines[0][price]" id="lines[0][price]" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" required />
|
||||
</div>
|
||||
<div class="mb-1">
|
||||
<label for="product" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Date de fin</label>
|
||||
<input type="date" name="lines[0][price]" id="lines[0][price]" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" required />
|
||||
</div>
|
||||
<button
|
||||
class="w-full form-repeater__remove-button bg-red-600 hover:bg-red-700 text-white px-3 py-1 rounded"
|
||||
data-ref="removeButton"
|
||||
@@ -100,14 +113,6 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="form-field">
|
||||
<div class="mb-5">
|
||||
<label for="description" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Description</label>
|
||||
<textarea rows="7" type="text" name="lines[0][description]" id="lines[0][description]" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" required></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
34
templates/error/404.twig
Normal file
34
templates/error/404.twig
Normal file
@@ -0,0 +1,34 @@
|
||||
{% extends 'base.twig' %}
|
||||
|
||||
{% block title %}Page non trouvée - Ludikevent{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
|
||||
<div class="max-w-md w-full space-y-8 p-10 bg-white rounded-xl shadow-lg text-center">
|
||||
|
||||
{# Logo Ludikevent #}
|
||||
<div class="flex justify-center">
|
||||
<img class="h-16 w-auto" src="{{ asset('provider/images/logo.png') }}" alt="Ludikevent">
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<h1 class="text-6xl font-extrabold text-blue-600">404</h1>
|
||||
<h2 class="text-2xl font-bold text-gray-900">Oups ! Page non trouvée</h2>
|
||||
<p class="text-gray-500">
|
||||
La page que vous recherchez semble avoir disparu ou l'adresse est incorrecte.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="pt-6">
|
||||
<a href="{{ path('app_home') }}"
|
||||
class="inline-flex items-center px-6 py-3 border border-transparent text-base font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors">
|
||||
Retour à l'accueil
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="text-sm text-gray-400 pt-4">
|
||||
Ludikevent - Lilian SEGARD
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
141
templates/legal/cgv.html.twig
Normal file
141
templates/legal/cgv.html.twig
Normal file
@@ -0,0 +1,141 @@
|
||||
{% extends 'base.twig' %}
|
||||
|
||||
{% block title %}Conditions Générales de Vente - Ludikevent{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="min-h-screen bg-gray-50 py-12 px-4 sm:px-6 lg:px-8 font-sans">
|
||||
<div class="max-w-4xl mx-auto bg-white shadow-xl rounded-2xl overflow-hidden">
|
||||
|
||||
{# Header #}
|
||||
<div class="bg-blue-600 p-8 text-center text-white">
|
||||
<img class="h-20 w-auto mx-auto mb-4" src="{{ asset('provider/images/logo.png') }}" alt="Ludikevent">
|
||||
<h1 class="text-3xl font-bold uppercase tracking-wider">Conditions Générales de Vente</h1>
|
||||
<p class="mt-2 text-blue-100 opacity-90">Lilian SEGARD – Ludikevent</p>
|
||||
</div>
|
||||
|
||||
<div class="p-8 md:p-12 space-y-10 text-gray-700 leading-relaxed">
|
||||
|
||||
{# En-tête Infos #}
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 pb-8 border-b border-gray-100 text-sm">
|
||||
<div>
|
||||
<p class="font-bold text-gray-900 uppercase">Ludikevent</p>
|
||||
<p>SIRET : 930 488 408 00012</p>
|
||||
<p>6 Rue du Château – 02800 Danizy – France</p>
|
||||
</div>
|
||||
<div class="md:text-right">
|
||||
<p>Email : contact@ludikevent.fr</p>
|
||||
<p>Téléphone : 06 14 17 24 47</p>
|
||||
<p>Assurance RC Pro : [N° de police à compléter]</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-blue-50 border-l-4 border-blue-600 p-4 italic text-blue-800">
|
||||
Toute réservation implique l’acceptation sans réserve des présentes CGV.
|
||||
</div>
|
||||
|
||||
{# Articles #}
|
||||
<div class="space-y-8">
|
||||
|
||||
<section>
|
||||
<h2 class="text-xl font-bold text-gray-900 mb-3 border-b pb-1">ARTICLE 1 – OBJET ET CHAMP D’APPLICATION</h2>
|
||||
<p>Les présentes CGV régissent :</p>
|
||||
<ul class="list-disc list-inside mt-2 space-y-1">
|
||||
<li>La location de structures gonflables professionnelles appartenant à Ludikevent.</li>
|
||||
<li>La mise en relation avec des propriétaires privés pour du matériel récréatif destiné à des événements privés (anniversaires, baptêmes…).</li>
|
||||
</ul>
|
||||
<p class="mt-2 text-sm italic text-gray-500">Dans le second cas : Ludikevent est intermédiaire, le contrat est conclu entre particuliers. Ludikevent n’assume aucune responsabilité liée à l’utilisation du matériel.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="text-xl font-bold text-gray-900 mb-3 border-b pb-1">ARTICLE 2 – RÉSERVATION</h2>
|
||||
<p>La réservation devient définitive après :</p>
|
||||
<ul class="list-none mt-2 space-y-1 font-medium text-blue-700">
|
||||
<li>✓ Confirmation écrite par Ludikevent</li>
|
||||
<li>✓ Paiement des arrhes de 25 %</li>
|
||||
<li>✓ Acceptation expresse des CGV</li>
|
||||
</ul>
|
||||
<p class="mt-2">Ludikevent peut refuser une réservation si les conditions de sécurité ne sont pas adaptées.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="text-xl font-bold text-gray-900 mb-3 border-b pb-1">ARTICLE 3 – TARIFS & PAIEMENT</h2>
|
||||
<p>Les tarifs sont exprimés en euros (€) TTC. <strong>TVA non applicable, art. 293 B du CGI.</strong></p>
|
||||
<ul class="list-disc list-inside mt-2">
|
||||
<li>25 % d’arrhes à la réservation.</li>
|
||||
<li>Solde dû au plus tard le jour de l’installation, <strong>avant montage</strong>.</li>
|
||||
</ul>
|
||||
<p class="mt-2">Modes de paiement acceptés : Virement – Chèque.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="text-xl font-bold text-gray-900 mb-3 border-b pb-1">ARTICLE 4 – DROIT DE RÉTRACTATION</h2>
|
||||
<p>Conformément à l’article L221-28 du Code de la consommation : aucun droit de rétractation pour une prestation datée et réservée pour un jour précis.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="text-xl font-bold text-gray-900 mb-3 border-b pb-1">ARTICLE 5 – ANNULATION</h2>
|
||||
<p><strong>Par le Client :</strong> Les arrhes sont non remboursables. Toute annulation à moins de 15 jours de l'événement entraîne la perte totale des arrhes.</p>
|
||||
<p><strong>Par Ludikevent :</strong> Remboursement des arrhes en cas d'impossibilité totale (force majeure). Une solution de report sera privilégiée.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="text-xl font-bold text-gray-900 mb-3 border-b pb-1">ARTICLE 6 – CAUTION</h2>
|
||||
<p>Une caution est exigée. Restitution immédiate après contrôle. Toute dégradation, salissure importante ou perte d’élément entraînera une déduction facturée.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="text-xl font-bold text-gray-900 mb-3 border-b pb-1">ARTICLE 7 – LIVRAISON / INSTALLATION / RESTITUTION</h2>
|
||||
<p>Installation sécurisée par Ludikevent sur terrain plat, propre, avec alimentation 220V à proximité. Pour la mise en relation entre particuliers, l'installation est faite pour le compte du propriétaire et exige une surveillance permanente du locataire.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="text-xl font-bold text-gray-900 mb-3 border-b pb-1 text-red-700">ARTICLE 8 – OBLIGATIONS DU CLIENT</h2>
|
||||
<p>Le locataire s'engage à assurer une <strong>surveillance constante par un adulte</strong>. Interdiction de porter des chaussures, bijoux ou objets pointus. Arrêt immédiat en cas de vent > 40 km/h, pluie ou orage. Interdiction de déplacer le matériel.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="text-xl font-bold text-gray-900 mb-3 border-b pb-1">ARTICLE 9 – ASSURANCES & RESPONSABILITÉS</h2>
|
||||
<p>Le locataire doit disposer d’une assurance responsabilité civile couvrant l’événement. Ludikevent décline toute responsabilité en cas d'accident sur du matériel privé mis à disposition via intermédiaire.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="text-xl font-bold text-gray-900 mb-3 border-b pb-1">ARTICLE 10 – CONDITIONS MÉTÉO</h2>
|
||||
<p>La sécurité est prioritaire. Ludikevent peut interrompre la prestation si le danger météo est avéré. Aucun remboursement si le matériel a déjà été installé.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="text-xl font-bold text-gray-900 mb-3 border-b pb-1">ARTICLE 11 – DOMMAGES & VOL</h2>
|
||||
<p>Un état des lieux est effectué à la livraison et reprise. Toute dégradation non signalée est imputable au locataire. En cas de vol, la valeur de remplacement sera facturée.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="text-xl font-bold text-gray-900 mb-3 border-b pb-1">ARTICLE 12 – DONNÉES PERSONNELLES</h2>
|
||||
<p>Utilisation pour la gestion contractuelle (RGPD). Droit d’accès et suppression via contact@ludikevent.fr.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="text-xl font-bold text-gray-900 mb-3 border-b pb-1">ARTICLE 14 – LOI APPLICABLE</h2>
|
||||
<p>Soumis au droit français. Tribunaux compétents du siège de Ludikevent (Saint-Quentin).</p>
|
||||
</section>
|
||||
|
||||
<section class="bg-red-50 p-6 rounded-xl border border-red-100">
|
||||
<h2 class="text-xl font-bold text-red-800 mb-3">ARTICLE 15 – LIMITATION DE RESPONSABILITÉ & SÉCURITÉ</h2>
|
||||
<p class="font-bold text-red-900 mb-4 italic">À compter de l’installation et jusqu’à la restitution, le locataire assume l’entière responsabilité de l’utilisation.</p>
|
||||
<div class="space-y-4 text-sm text-red-800">
|
||||
<p><strong>Surveillance obligatoire :</strong> Le locataire s’engage à assurer une surveillance permanente par une personne majeure et vigilante.</p>
|
||||
<p><strong>Exclusion de responsabilité :</strong> Ludikevent décline toute responsabilité pour les dommages corporels subis par les utilisateurs, les dommages matériels causés aux tiers, ou toute négligence liée au non-respect des consignes de sécurité.</p>
|
||||
<p class="font-bold underline text-center pt-2">En réservant, le locataire renonce à tout recours contre Ludikevent en cas d'accident lié à l'usage.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Footer #}
|
||||
<div class="bg-gray-100 p-6 text-center text-gray-500 text-xs">
|
||||
<p>Ludikevent - Lilian SEGARD - SIRET 930 488 408 00012</p>
|
||||
<p class="mt-1">Document contractuel mis à jour en Janvier 2026</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
257
templates/legal/cookies.html.twig
Normal file
257
templates/legal/cookies.html.twig
Normal file
@@ -0,0 +1,257 @@
|
||||
{% extends 'base.twig' %}
|
||||
|
||||
{% block title %}Cookies- Ludikevent{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="min-h-screen bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
|
||||
<div class="max-w-5xl mx-auto space-y-8">
|
||||
|
||||
{# En-tête avec Logo #}
|
||||
<div class="text-center">
|
||||
<img class="h-25 w-auto mx-auto mb-4" src="{{ asset('provider/images/logo.png') }}" alt="Ludikevent">
|
||||
</div>
|
||||
<div id="cookies" class="container mx-auto px-4 max-w-5xl font-sans">
|
||||
<header class="mb-10 text-center border-b pb-4">
|
||||
<h1 class="text-4xl font-extrabold text-gray-900 mb-2 esrgaa-voice">Politique de gestion des cookies</h1>
|
||||
<p class="text-lg text-gray-600 esrgaa-voice">Informations détaillées sur l'utilisation des cookies sur notre site.</p>
|
||||
</header>
|
||||
|
||||
<section class="mb-10 p-6 bg-gray-50 rounded-xl shadow-inner border-l-4 border-indigo-500">
|
||||
<p class="text-gray-700 leading-relaxed esrgaa-voice">
|
||||
Cette politique vous informe sur la nature, l'utilisation et la gestion des cookies déposés sur votre terminal (ordinateur, tablette, smartphone, etc.) lorsque vous naviguez sur notre site. Les cookies sont essentiels pour garantir le bon fonctionnement de nos services et améliorer votre expérience utilisateur.
|
||||
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="mb-12">
|
||||
<h2 class="text-2xl font-bold text-gray-800 mb-6 border-b pb-2 esrgaa-voice">Types de Cookies Utilisés</h2>
|
||||
|
||||
<div class="space-y-6">
|
||||
<!-- Type 1: Essentiels -->
|
||||
<div class="p-4 border border-gray-200 rounded-lg bg-white shadow-sm">
|
||||
<h3 class="text-xl font-semibold text-green-600 mb-1 flex items-center esrgaa-voice">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 mr-2 text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path>
|
||||
</svg>
|
||||
Cookies Essentiels (Obligatoires)
|
||||
</h3>
|
||||
<p class="text-gray-600 text-sm esrgaa-voice">Ces cookies sont strictement nécessaires au fonctionnement du site et ne peuvent être désactivés. Ils permettent d'assurer les fonctionnalités de base comme la navigation, la connexion à votre espace client, la mémorisation du panier d'achat et la sécurité. Sans ces cookies, les services que vous avez demandés ne peuvent pas être fournis.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Type 2: Performance/Analyse -->
|
||||
<div class="p-4 border border-gray-200 rounded-lg bg-white shadow-sm">
|
||||
<h3 class="text-xl font-semibold text-blue-600 mb-1 flex items-center esrgaa-voice">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 mr-2 text-blue-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 12l3-3 3 3 4-4M18 10h4M4 18h.01M6 18h.01M10 18h.01M4 14h.01M4 10h.01M4 6h.01"></path>
|
||||
</svg>
|
||||
Cookies de Performance et d'Analyse
|
||||
</h3>
|
||||
<p class="text-gray-600 text-sm esrgaa-voice">Ces cookies nous permettent de compter les visites et les sources de trafic afin de mesurer et d'améliorer les performances de notre site. Ils nous aident à savoir quelles pages sont les plus ou les moins populaires, et à voir comment les visiteurs naviguent sur le site. Si vous n'autorisez pas ces cookies, nous ne saurons pas quand vous avez visité notre site.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Type 3: Marketing -->
|
||||
<div class="p-4 border border-gray-200 rounded-lg bg-white shadow-sm">
|
||||
<h3 class="text-xl font-semibold text-red-600 mb-1 flex items-center esrgaa-voice">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 mr-2 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.536 8.464A4 4 0 0117 12h2a6 6 0 00-10.778-3.322l-.782 1.408a.5.5 0 00.43 1.056h1.564a.5.5 0 00.5-.5V8.464zM9.536 15.536A4 4 0 017 12H5a6 6 0 0010.778 3.322l.782-1.408a.5.5 0 00-.43-1.056h-1.564a.5.5 0 00-.5.5v1.464z"></path>
|
||||
</svg>
|
||||
Cookies de Marketing et Publicité
|
||||
</h3>
|
||||
<p class="text-gray-600 text-sm esrgaa-voice">Ces cookies peuvent être mis en place par nos partenaires publicitaires. Ils peuvent être utilisés par ces entreprises pour établir un profil de vos intérêts et vous proposer des publicités pertinentes sur d'autres sites. Ils ne stockent pas directement des informations personnelles, mais sont basés sur l'identification unique de votre navigateur et de votre terminal.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- SECTION : Liste Détaillée des Cookies -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-2xl font-bold text-gray-800 mb-6 border-b pb-2 esrgaa-voice">Liste Détaillée des Cookies</h2>
|
||||
<p class="text-gray-700 mb-4 esrgaa-voice">Voici une liste des cookies que nous utilisons, classés par fonction :</p>
|
||||
|
||||
<!-- Affichage en mode TABLEAU pour DESKTOP (md et plus) -->
|
||||
<div class="hidden md:block overflow-x-auto shadow-md rounded-lg">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-100">
|
||||
<tr>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider esrgaa-voice">Nom du Cookie</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider esrgaa-voice">Objectif</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider esrgaa-voice">Type</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider esrgaa-voice">Durée de Vie</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
<tr class="hover:bg-green-50/50">
|
||||
<td class="px-6 py-4 whitespace-nowrap font-mono text-sm text-gray-900 esrgaa-voice">__Host-session</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-600 esrgaa-voice">Cookie obligatoire pour maintenir la session en cas de connexion à un espace client ou lors d'une commande. Assure l'intégrité et la sécurité de la session.</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-green-600 esrgaa-voice">Essentiel</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 esrgaa-voice">Automatique à la fermeture du site (Session)</td>
|
||||
</tr>
|
||||
<tr class="hover:bg-green-50/50">
|
||||
<td class="px-6 py-4 whitespace-nowrap font-mono text-sm text-gray-900 esrgaa-voice">PHPSESSID</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-600 esrgaa-voice">Utilisé par le serveur web pour stocker l'état de la session utilisateur. Nécessaire pour le bon fonctionnement des interactions sur le site.</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-green-600 esrgaa-voice">Essentiel</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 esrgaa-voice">Automatique à la fermeture du site (Session)</td>
|
||||
</tr>
|
||||
<tr class="hover:bg-blue-50/50">
|
||||
<td class="px-6 py-4 whitespace-nowrap font-mono text-sm text-gray-900 esrgaa-voice">__cf_bm</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-600 esrgaa-voice">Cookie de Cloudflare utilisé pour la gestion des bots. Permet d'améliorer la performance et la sécurité du site en distinguant le trafic humain du trafic automatisé.</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-blue-600 esrgaa-voice">Sécurité / Tiers</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 esrgaa-voice">30 minutes</td>
|
||||
</tr>
|
||||
<tr class="hover:bg-blue-50/50">
|
||||
<td class="px-6 py-4 whitespace-nowrap font-mono text-sm text-gray-900 esrgaa-voice">__cf_clearance</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-600 esrgaa-voice">Cookie de Cloudflare utilisé pour des raisons de sécurité afin d'identifier le trafic de confiance. Nécessaire pour accéder au site si un défi de sécurité est présenté.</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-blue-600 esrgaa-voice">Sécurité / Tiers</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 esrgaa-voice">1 an</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Affichage en mode LISTE/CARTES pour MOBILE (moins de md) -->
|
||||
<div class="md:hidden space-y-6">
|
||||
|
||||
|
||||
|
||||
<div class="p-4 border-2 rounded-xl shadow-md space-y-3 bg-green-50 ring-1 ring-green-500">
|
||||
|
||||
<h3 class="text-lg font-extrabold font-mono text-gray-900 border-b pb-1 esrgaa-voice">
|
||||
__Host-session
|
||||
</h3>
|
||||
|
||||
<dl class="text-sm">
|
||||
<div class="flex flex-col mb-2">
|
||||
<dt class="font-semibold text-gray-700 esrgaa-voice">Type :</dt>
|
||||
<dd class="font-medium text-green-600 esrgaa-voice">Essentiel</dd>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col mb-2">
|
||||
<dt class="font-semibold text-gray-700 esrgaa-voice">Durée de Vie :</dt>
|
||||
<dd class="text-gray-600 esrgaa-voice">Automatique à la fermeture du site (Session)</dd>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<dt class="font-semibold text-gray-700 esrgaa-voice">Objectif :</dt>
|
||||
<dd class="text-gray-700 mt-1 esrgaa-voice">Cookie obligatoire pour maintenir la session en cas de connexion à un espace client ou lors d'une commande. Assure l'intégrité et la sécurité de la session.</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div class="p-4 border-2 rounded-xl shadow-md space-y-3 bg-green-50 ring-1 ring-green-500">
|
||||
|
||||
<h3 class="text-lg font-extrabold font-mono text-gray-900 border-b pb-1 esrgaa-voice">
|
||||
PHPSESSID
|
||||
</h3>
|
||||
|
||||
<dl class="text-sm">
|
||||
<div class="flex flex-col mb-2">
|
||||
<dt class="font-semibold text-gray-700 esrgaa-voice">Type :</dt>
|
||||
<dd class="font-medium text-green-600 esrgaa-voice">Essentiel</dd>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col mb-2">
|
||||
<dt class="font-semibold text-gray-700 esrgaa-voice">Durée de Vie :</dt>
|
||||
<dd class="text-gray-600 esrgaa-voice">Automatique à la fermeture du site (Session)</dd>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<dt class="font-semibold text-gray-700 esrgaa-voice">Objectif :</dt>
|
||||
<dd class="text-gray-700 mt-1 esrgaa-voice">Utilisé par le serveur web pour stocker l'état de la session utilisateur. Nécessaire pour le bon fonctionnement des interactions sur le site.</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div class="p-4 border-2 rounded-xl shadow-md space-y-3 bg-blue-50 ring-1 ring-blue-500">
|
||||
|
||||
<h3 class="text-lg font-extrabold font-mono text-gray-900 border-b pb-1 esrgaa-voice">
|
||||
__cf_bm
|
||||
</h3>
|
||||
|
||||
<dl class="text-sm">
|
||||
<div class="flex flex-col mb-2">
|
||||
<dt class="font-semibold text-gray-700 esrgaa-voice">Type :</dt>
|
||||
<dd class="font-medium text-blue-600 esrgaa-voice">Sécurité / Tiers</dd>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col mb-2">
|
||||
<dt class="font-semibold text-gray-700 esrgaa-voice">Durée de Vie :</dt>
|
||||
<dd class="text-gray-600 esrgaa-voice">30 minutes</dd>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<dt class="font-semibold text-gray-700 esrgaa-voice">Objectif :</dt>
|
||||
<dd class="text-gray-700 mt-1 esrgaa-voice">Cookie de Cloudflare utilisé pour la gestion des bots. Permet d'améliorer la performance et la sécurité du site en distinguant le trafic humain du trafic automatisé.</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div class="p-4 border-2 rounded-xl shadow-md space-y-3 bg-blue-50 ring-1 ring-blue-500">
|
||||
|
||||
<h3 class="text-lg font-extrabold font-mono text-gray-900 border-b pb-1 esrgaa-voice">
|
||||
__cf_clearance
|
||||
</h3>
|
||||
|
||||
<dl class="text-sm">
|
||||
<div class="flex flex-col mb-2">
|
||||
<dt class="font-semibold text-gray-700 esrgaa-voice">Type :</dt>
|
||||
<dd class="font-medium text-blue-600 esrgaa-voice">Sécurité / Tiers</dd>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col mb-2">
|
||||
<dt class="font-semibold text-gray-700 esrgaa-voice">Durée de Vie :</dt>
|
||||
<dd class="text-gray-600 esrgaa-voice">1 an</dd>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<dt class="font-semibold text-gray-700 esrgaa-voice">Objectif :</dt>
|
||||
<dd class="text-gray-700 mt-1 esrgaa-voice">Cookie de Cloudflare utilisé pour des raisons de sécurité afin d'identifier le trafic de confiance. Nécessaire pour accéder au site si un défi de sécurité est présenté.</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Lien vers la politique de Cloudflare (Affiché sous les deux versions) -->
|
||||
<div class="mt-6 p-4 bg-blue-50 border border-blue-200 rounded-lg text-sm text-gray-700">
|
||||
<p class="font-semibold mb-2 esrgaa-voice">Informations complémentaires sur les cookies Cloudflare :</p>
|
||||
<a href="https://www.cloudflare.com/cookie-policy/" target="_blank" rel="noopener noreferrer" class="inline-flex items-center text-blue-600 hover:text-blue-800 font-medium transition duration-150 ease-in-out esrgaa-voice">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<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"></path>
|
||||
</svg>
|
||||
Consulter la politique de cookies de Cloudflare
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<!-- Section de gestion des cookies -->
|
||||
<section class="p-8 bg-indigo-50 border border-indigo-200 rounded-xl shadow-lg">
|
||||
<h2 class="text-2xl font-bold text-indigo-700 mb-4 flex items-center esrgaa-voice">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-7 w-7 mr-3 text-indigo-500" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M11.493 3.99c-.06-.328-.352-.563-.69-.563H9.197c-.338 0-.63.235-.69.563l-.403 2.164a1 1 0 01-.64.64L6.15 7.697a1 1 0 01-1.052-.162l-1.458-1.25a1 1 0 00-1.282.493l-.493 1.018a1 1 0 00.493 1.282l1.25 1.458a1 1 0 01.162 1.052l-.697 1.403a1 1 0 00.563.69l2.164.403c.328.06.563.352.563.69v1.606c0 .338-.235.63-.563.69l-2.164.403a1 1 0 00-.69.563l-.403 2.164c-.06.328-.352.563-.69.563H9.197c-.338 0-.63-.235-.69-.563l-.403-2.164a1 1 0 01-.64-.64L6.15 12.303a1 1 0 01-1.052.162l-1.458 1.25a1 1 0 00-1.282-.493l-.493-1.018a1 1 0 00.493-1.282l1.25-1.458a1 1 0 01.162-1.052l-.697-1.403a1 1 0 00.563-.69l2.164-.403c.328-.06.563-.352.563-.69V3.99z" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
Gestion des Cookies
|
||||
</h2>
|
||||
|
||||
<p class="text-gray-700 mb-4 esrgaa-voice">Vous pouvez à tout moment choisir de désactiver certains cookies via les paramètres de votre navigateur. Veuillez noter que la désactivation des cookies essentiels peut dégrader votre expérience de navigation et empêcher l'utilisation de certaines fonctionnalités du site.
|
||||
</p>
|
||||
|
||||
<div class="mt-6">
|
||||
<a href="https://www.cnil.fr/fr/cookies-les-outils-pour-les-maitriser" target="_blank" rel="noopener noreferrer" class="inline-flex items-center px-6 py-3 border border-transparent text-base font-medium rounded-full shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 transition duration-150 ease-in-out esrgaa-voice">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path d="M10 12a2 2 0 100-4 2 2 0 000 4z"></path>
|
||||
<path fill-rule="evenodd" d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
Comment maîtriser les cookies (CNIL)
|
||||
</a>
|
||||
<p class="text-sm text-gray-500 mt-2 esrgaa-voice">
|
||||
(Lien externe vers la CNIL, autorité française de protection des données)
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
148
templates/legal/hebergement.html.twig
Normal file
148
templates/legal/hebergement.html.twig
Normal file
@@ -0,0 +1,148 @@
|
||||
{% extends 'base.twig' %}
|
||||
|
||||
{% block title %}Hébergement et Infos Techniques - Ludikevent{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div id="hosting" class="container mx-auto px-4 py-12">
|
||||
|
||||
{# --- NOUVEAU BLOC EN-TÊTE --- #}
|
||||
<div class="max-w-5xl mx-auto mb-12 text-center">
|
||||
<img class="h-20 w-auto mx-auto mb-6" src="{{ asset('provider/images/logo.png') }}" alt="Ludikevent">
|
||||
<h1 class="text-4xl font-extrabold text-gray-900 mb-2 esrgaa-voice">
|
||||
Informations Légales et d'Hébergement
|
||||
</h1>
|
||||
<p class="text-gray-500 italic text-sm">Transparence technique et conformité infrastructure</p>
|
||||
<div class="h-1 w-24 bg-indigo-600 mx-auto mt-4 rounded-full"></div>
|
||||
</div>
|
||||
{# --- FIN DU BLOC EN-TÊTE --- #}
|
||||
|
||||
<div class="max-w-6xl mx-auto space-y-8">
|
||||
|
||||
<section class="bg-white p-6 md:p-8 rounded-xl shadow-lg border border-gray-100">
|
||||
<h2 class="text-2xl font-bold text-indigo-700 mb-6 flex items-center esrgaa-voice">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-7 w-7 mr-3 text-indigo-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M4 7v10M20 7v10M7 4h10M7 20h10M5 9h14M5 15h14M12 4v16"></path>
|
||||
</svg>
|
||||
Responsabilités - Éditeur et Opérateur Technique
|
||||
</h2>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div class="p-6 border border-gray-300 rounded-lg bg-gray-50 shadow-sm">
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-4 flex items-center esrgaa-voice">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2 text-indigo-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 8h2m-7 0h2"></path>
|
||||
</svg>
|
||||
Responsabilité Technique
|
||||
</h3>
|
||||
<div class="bg-white p-4 border border-gray-200 rounded-lg space-y-1 text-xs">
|
||||
<p class="font-semibold text-base text-gray-800 esrgaa-voice">SARL SITECONSEIL</p>
|
||||
<p>27 RUE LE SERURIER, 02100 ST-QUENTIN</p>
|
||||
<p>SIRET : 41866405800025</p>
|
||||
<p>Contact : <a href="mailto:s.com@siteconseil.fr" class="text-indigo-600 hover:text-indigo-800">s.com@siteconseil.fr</a></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-6 border border-blue-300 rounded-lg bg-blue-50 shadow-sm text-xs">
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-4 flex items-center esrgaa-voice text-base">Infrastructure Cloud</h3>
|
||||
<p class="mb-2 font-medium text-blue-800">Empreinte carbone : <strong>0,017 tCO₂e / mois</strong></p>
|
||||
<div class="bg-white p-4 border border-blue-200 rounded-lg space-y-1">
|
||||
<p class="font-semibold text-gray-800">Localisation : Pays-Bas</p>
|
||||
<p>Région Google Cloud : eu-west4</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-6 border border-indigo-300 rounded-lg bg-indigo-50 shadow-sm text-xs">
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-4 flex items-center esrgaa-voice text-base">Éditeur du Site</h3>
|
||||
<div class="bg-white p-4 border border-indigo-200 rounded-lg space-y-2">
|
||||
<p class="font-semibold text-base text-gray-800 esrgaa-voice">LUDIKEVENT</p>
|
||||
<p>SIRET : 930 488 408 00012</p>
|
||||
<p>6, rue du Château 02800 Danizy</p>
|
||||
<p><a href="mailto:lilian@ludikevent.fr" class="text-indigo-600 font-bold">lilian@ludikevent.fr</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<h2 class="text-3xl font-bold text-gray-900 mb-4 mt-12 border-b pb-2 esrgaa-voice">
|
||||
Services Techniques et Prestataires
|
||||
</h2>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
|
||||
<section class="bg-white p-6 rounded-xl shadow-lg border border-gray-100 flex flex-col h-full">
|
||||
<h3 class="text-lg font-bold text-indigo-700 mb-4">Gestion Sous-domaines</h3>
|
||||
<div class="bg-green-50 p-4 border border-green-200 rounded-lg flex-grow">
|
||||
<ul class="space-y-1 text-gray-700 text-xs font-mono">
|
||||
<li>s3.esy-web.dev</li>
|
||||
<li>sentry.esy-web.dev</li>
|
||||
<li>mail.esy-web.dev</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="bg-white p-6 rounded-xl shadow-lg border border-gray-100 flex flex-col h-full">
|
||||
<h3 class="text-lg font-bold text-indigo-700 mb-4">Cloudflare</h3>
|
||||
<p class="text-xs text-gray-600 mb-4">Protection Anti-DDoS et Proxy DNS géré par SARL SITECONSEIL.</p>
|
||||
<div class="bg-yellow-50 p-4 border border-yellow-200 rounded-lg">
|
||||
<p class="text-xs font-bold text-yellow-800">Sécurité & CDN</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="bg-white p-6 rounded-xl shadow-lg border border-gray-100 flex flex-col h-full">
|
||||
<h3 class="text-lg font-bold text-indigo-700 mb-4">Monitoring (Sentry)</h3>
|
||||
<p class="text-xs text-gray-600 mb-4">Analyse d'erreurs en temps réel pour garantir la disponibilité du service.</p>
|
||||
<div class="bg-red-50 p-4 border border-red-200 rounded-lg">
|
||||
<p class="text-xs font-bold text-red-800 font-mono">AUTO-HÉBERGÉ</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="bg-white p-6 rounded-xl shadow-lg border border-gray-100 flex flex-col h-full">
|
||||
<h3 class="text-lg font-bold text-indigo-700 mb-4">Noms de Domaine</h3>
|
||||
<p class="text-xs text-gray-600 mb-4">Gestion des domaines et enregistrements DNS.</p>
|
||||
<div class="bg-purple-50 p-4 border border-purple-200 rounded-lg text-xs">
|
||||
<p>• OVH SAS</p>
|
||||
<p>• Cloudflare Inc.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="bg-white p-6 rounded-xl shadow-lg border border-gray-100 md:col-span-2 lg:col-span-4 border-l-4 border-pink-500">
|
||||
<h3 class="text-xl font-bold text-pink-700 mb-4 flex items-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path d="M3 8l7.842 5.234a1 1 0 001.034 0L21 8m-2 4v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"></path></svg>
|
||||
Esy Mail (Envoi d'E-mails)
|
||||
</h3>
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
<div class="text-sm text-gray-700">
|
||||
<p><strong>Serveur interne :</strong> mail.esy-web.dev (Auto-hébergé).</p>
|
||||
<p><strong>Relais externe :</strong> Amazon SES (Simple Email Service).</p>
|
||||
</div>
|
||||
<div class="bg-red-50 p-4 border border-red-200 rounded-lg text-xs text-red-700">
|
||||
<strong>IMPORTANT :</strong> En raison de cette architecture, Amazon SES peut potentiellement accéder au contenu technique des e-mails envoyés pour assurer la délivrabilité.
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section class="bg-gray-900 p-8 rounded-xl shadow-lg text-white">
|
||||
<h2 class="text-2xl font-bold text-indigo-400 mb-6 flex items-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 mr-3" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path></svg>
|
||||
Conformité Légale et Sécurité
|
||||
</h2>
|
||||
<div class="grid md:grid-cols-3 gap-8 text-sm text-gray-300">
|
||||
<p><strong>Sécurité :</strong> Protection multicouche via GCP, Cloudflare et monitoring Sentry.</p>
|
||||
<p><strong>RGPD :</strong> Données traitées exclusivement dans l'Union Européenne (Pays-Bas).</p>
|
||||
<p><strong>Loi Française :</strong> Conformité à l'Art. 227-24 du Code Pénal (Contrôle d'âge).</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 pt-6 border-t border-gray-700 flex flex-col md:flex-row justify-between items-center gap-4">
|
||||
<p class="text-gray-400 text-sm italic text-center md:text-left">
|
||||
Hébergeur technique : SARL SITECONSEIL – Non responsable du contenu éditorial.
|
||||
</p>
|
||||
<a href="mailto:signalement@siteconseil.fr" class="bg-red-600 hover:bg-red-700 text-white px-6 py-2 rounded-full font-bold transition">
|
||||
SIGNALER UNE INFRACTION
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,4 +1,141 @@
|
||||
{% extends 'base.twig' %}
|
||||
{% block title %}Mentions légal{% endblock %}
|
||||
|
||||
{% block title %}Mentions légales - Ludikevent{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="min-h-screen bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
|
||||
<div class="max-w-5xl mx-auto space-y-8">
|
||||
|
||||
{# En-tête avec Logo #}
|
||||
<div class="text-center">
|
||||
<img class="h-16 w-auto mx-auto mb-4" src="{{ asset('provider/images/logo.png') }}" alt="Ludikevent">
|
||||
<h1 class="text-3xl font-extrabold text-gray-900 sm:text-4xl">
|
||||
Mentions Légales
|
||||
</h1>
|
||||
<p class="text-gray-500 mt-2 text-sm italic italic">Mise à jour le 19 janvier 2026</p>
|
||||
</div>
|
||||
|
||||
{# 1er Bloc : Entreprise #}
|
||||
<div class="bg-white rounded-xl shadow-lg p-8 space-y-4 border-t-4 border-blue-600">
|
||||
<div class="flex items-center space-x-2 border-b border-gray-100 pb-2">
|
||||
<h2 class="text-xl font-bold text-gray-900 uppercase tracking-wide text-sm md:text-base">1. Entreprise</h2>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 pt-2">
|
||||
<div class="space-y-2">
|
||||
<p class="text-gray-900 font-semibold text-lg">Ludikevent - Lilian SEGARD</p>
|
||||
<p class="text-gray-600 text-sm"><span class="font-medium">SIRET :</span> 930 488 408 00012</p>
|
||||
<p class="text-gray-600 text-sm"><span class="font-medium">Statut :</span> Entreprise individuelle (EI)</p>
|
||||
<p class="text-gray-600 text-sm"><span class="font-medium text-gray-800">Siège social :</span><br>6 Rue du Château – 02800 Danizy</p>
|
||||
</div>
|
||||
<div class="space-y-2 text-sm">
|
||||
<p class="text-gray-600"><span class="font-medium text-gray-800">Email :</span> <a href="mailto:contact@ludikevent.fr" class="text-blue-600 hover:underline">contact@ludikevent.fr</a></p>
|
||||
<p class="text-gray-600"><span class="font-medium text-gray-800">Téléphone :</span> <a href="tel:0614172447" class="text-blue-600 hover:underline">06 14 17 24 47</a></p>
|
||||
<div class="pt-4">
|
||||
<a href="https://annuaire-entreprises.data.gouv.fr/entreprise/lilian-segard-930488408"
|
||||
target="_blank"
|
||||
class="inline-flex items-center text-xs font-medium text-blue-600 bg-blue-50 px-4 py-2 rounded-lg hover:bg-blue-100 transition-all">
|
||||
<svg class="w-4 h-4 mr-2" 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>
|
||||
Voir sur l'Annuaire des Entreprises
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# 2ème Bloc : Description des services #}
|
||||
<div class="bg-white rounded-xl shadow-lg p-8 space-y-4 border-l-4 border-blue-600">
|
||||
<h2 class="text-xl font-bold text-gray-900 uppercase tracking-wide text-sm md:text-base border-b border-gray-100 pb-2">2. Description des services fournis</h2>
|
||||
<div class="prose prose-blue text-gray-600 space-y-4 pt-2 text-sm md:text-base">
|
||||
<p>
|
||||
Le site <span class="font-semibold text-gray-800 text-blue-600 italic">www.{{ app.request.host }}</span> a pour objet de proposer des prestations de location de structures gonflables, de jeux d’extérieurs, de Barnum et de tous matériels événementiels.
|
||||
</p>
|
||||
<div class="bg-blue-50 p-4 rounded-lg">
|
||||
<p class="font-semibold text-blue-900 mb-2">La plateforme permet aux utilisateurs :</p>
|
||||
<ul class="list-disc list-inside space-y-1 text-blue-800 text-sm">
|
||||
<li>D'effectuer des demandes de devis personnalisées ;</li>
|
||||
<li>De procéder à la signature électronique des devis ;</li>
|
||||
<li>D'effectuer le paiement sécurisé des prestations ;</li>
|
||||
<li>De déposer et gérer les cautions liées aux locations.</li>
|
||||
</ul>
|
||||
</div>
|
||||
<p class="text-justify">
|
||||
Le propriétaire du site s’efforce de fournir sur le site des informations aussi précises que possible. Toutefois, il ne pourra être tenue responsable des omissions ou des inexactitudes. Toutes les informations proposées sont données à titre indicatif et sont susceptibles d’évoluer, y compris pour de <span class="italic text-gray-700 font-medium">futurs services complémentaires</span>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# 3ème Bloc : Propriété Intellectuelle #}
|
||||
<div class="bg-white rounded-xl shadow-lg p-8 space-y-4 border-l-4 border-gray-800">
|
||||
<h2 class="text-xl font-bold text-gray-900 uppercase tracking-wide text-sm md:text-base border-b border-gray-100 pb-2">3. Propriété intellectuelle et contrefaçons</h2>
|
||||
<div class="text-gray-600 space-y-4 pt-2 text-sm leading-relaxed">
|
||||
<p>Le propriétaire du site est propriétaire des droits de propriété intellectuelle ou détient les droits d’usage sur tous les éléments accessibles (textes, images, graphismes, logo, icônes, sons, logiciels…).</p>
|
||||
<p>Toute reproduction, représentation, modification ou publication totale ou partielle des éléments du site est <span class="font-bold text-gray-900 italic">interdite</span>, sauf autorisation écrite préalable.</p>
|
||||
<p class="bg-red-50 p-3 border-l-2 border-red-200 text-red-800 italic text-xs">
|
||||
Toute exploitation non autorisée sera considérée comme constitutive d’une contrefaçon et poursuivie conformément aux dispositions des articles L.335-2 et suivants du Code de Propriété Intellectuelle.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# 4ème Bloc : Liens et Cookies #}
|
||||
<div class="bg-white rounded-xl shadow-lg p-8 space-y-4 border-l-4 border-blue-400">
|
||||
<h2 class="text-xl font-bold text-gray-900 uppercase tracking-wide text-sm md:text-base border-b border-gray-100 pb-2">4. Liens hypertextes et cookies</h2>
|
||||
<div class="text-gray-600 space-y-4 pt-2 text-sm md:text-base leading-relaxed text-justify">
|
||||
<p>Le site peut contenir des liens vers des sites tiers. Le propriétaire ne peut vérifier le contenu des sites ainsi visités et décline toute responsabilité quant aux risques de contenus illicites.</p>
|
||||
<div class="bg-gray-50 p-4 rounded-lg border border-gray-100">
|
||||
<p class="mb-2 italic text-gray-700 font-medium">Gestion des Cookies :</p>
|
||||
<p class="text-xs text-gray-600 leading-relaxed">
|
||||
La navigation est susceptible de provoquer l’installation de cookie(s) visant à faciliter la navigation ultérieure et à permettre diverses mesures de fréquentation. L’utilisateur peut configurer son navigateur pour bloquer ces installations.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# 5ème Bloc : Données Personnelles #}
|
||||
<div class="bg-white rounded-xl shadow-lg p-8 space-y-4 border-l-4 border-green-600">
|
||||
<h2 class="text-xl font-bold text-gray-900 uppercase tracking-wide text-sm md:text-base border-b border-gray-100 pb-2">5. Gestion des données personnelles</h2>
|
||||
<div class="text-gray-600 space-y-4 pt-2 text-sm md:text-base leading-relaxed text-justify">
|
||||
<p>Le propriétaire ne collecte des informations personnelles que pour le besoin de certains services (devis, contact, signature). L’utilisateur fournit ces informations en toute connaissance de cause.</p>
|
||||
<div class="bg-green-50 p-4 rounded-lg border border-green-100">
|
||||
<p class="font-bold text-green-900 mb-2 text-xs">DROITS DE L'UTILISATEUR (RGPD) :</p>
|
||||
<p class="text-green-800 text-sm">Tout utilisateur dispose d’un droit d’accès, de rectification et de suppression par email : <a href="mailto:segard.lilian@gmail.com" class="font-bold underline">segard.lilian@gmail.com</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# 6ème Bloc : Droit applicable #}
|
||||
<div class="bg-white rounded-xl shadow-lg p-8 space-y-4 border-l-4 border-red-600">
|
||||
<h2 class="text-xl font-bold text-gray-900 uppercase tracking-wide text-sm md:text-base border-b border-gray-100 pb-2">6. Droit applicable et juridiction</h2>
|
||||
<div class="text-gray-600 text-sm md:text-base">
|
||||
<p>Tout litige est soumis au <span class="font-medium">droit français</span>. Il est fait attribution exclusive de juridiction aux tribunaux compétents de <span class="font-bold text-gray-900">Saint-Quentin</span>.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# 7ème Bloc : Lexique #}
|
||||
<div class="bg-white rounded-xl shadow-lg p-8 space-y-4 border-l-4 border-gray-400">
|
||||
<h2 class="text-xl font-bold text-gray-900 uppercase tracking-wide text-sm md:text-base border-b border-gray-100 pb-2">7. Lexique</h2>
|
||||
<div class="space-y-2 text-xs text-gray-500 italic">
|
||||
<p><strong>Utilisateur :</strong> Internaute se connectant et utilisant le site susnommé.</p>
|
||||
<p><strong>Informations personnelles :</strong> « les informations qui permettent l’identification des personnes physiques auxquelles elles s’appliquent » (art. 4 loi n° 78-17).</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# 8ème Bloc : Textes de loi #}
|
||||
<div class="bg-white rounded-xl shadow-lg p-8 space-y-4 border-l-4 border-blue-900">
|
||||
<h2 class="text-xl font-bold text-gray-900 uppercase tracking-wide text-sm md:text-base border-b border-gray-100 pb-2">8. Textes de loi applicables</h2>
|
||||
<div class="text-gray-500 text-xs space-y-1">
|
||||
<p>• Loi n° 78-17 du 6 janvier 1978 (Informatique et Libertés)</p>
|
||||
<p>• Loi n° 2004-575 du 21 juin 2004 (Confiance dans l’économie numérique)</p>
|
||||
<p>• Règlement Général sur la Protection des Données personnelles (RGPD)</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Footer final #}
|
||||
<div class="text-center text-gray-400 text-xs pt-6">
|
||||
<p>© {{ "now"|date("Y") }} Ludikevent - Lilian SEGARD – Entrepreneur Individuel</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
106
templates/legal/rgpd.html.twig
Normal file
106
templates/legal/rgpd.html.twig
Normal file
@@ -0,0 +1,106 @@
|
||||
{% extends 'base.twig' %}
|
||||
|
||||
{% block title %}Politique de Confidentialité - Ludikevent{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="min-h-screen bg-gray-50 py-12 px-4 sm:px-6 lg:px-8 font-sans">
|
||||
<div class="max-w-4xl mx-auto bg-white shadow-xl rounded-2xl overflow-hidden">
|
||||
|
||||
{# Header #}
|
||||
<div class="bg-green-600 p-8 text-center">
|
||||
<img class="h-50 w-auto mx-auto mb-4" src="{{ asset('provider/images/logo.png') }}" alt="Ludikevent">
|
||||
<h1 class="text-3xl font-bold uppercase tracking-wider">Protection des Données (RGPD)</h1>
|
||||
<p class="mt-2 text-green-100 opacity-90">Sécurité et confidentialité de vos informations</p>
|
||||
</div>
|
||||
|
||||
<div class="p-8 md:p-12 space-y-10 text-gray-700 leading-relaxed text-justify">
|
||||
|
||||
<div class="border-b pb-4">
|
||||
<p class="text-sm text-gray-500 italic">Date de la dernière mise à jour : 23/04/2025</p>
|
||||
</div>
|
||||
|
||||
{# Article 1 #}
|
||||
<section>
|
||||
<h2 class="text-xl font-bold text-gray-900 mb-3 border-b pb-1 uppercase">Présentation de la politique</h2>
|
||||
<p>La présente politique décrit les méthodes que <strong>Ludikevent</strong> emploie pour la collecte, l'utilisation, la protection et le partage des données personnelles de nos clients. Vos données sont traitées sur le fondement de votre consentement, de la nécessité d'exécuter un contrat ou de nos intérêts légitimes.</p>
|
||||
</section>
|
||||
|
||||
{# Article 2 #}
|
||||
<section>
|
||||
<h2 class="text-xl font-bold text-gray-900 mb-3 border-b pb-1 uppercase">Données personnelles collectées</h2>
|
||||
<p>Nous collectons des données selon trois catégories :</p>
|
||||
<ul class="list-disc list-inside mt-2 space-y-2">
|
||||
<li><strong>Données communiquées :</strong> Nom, adresse, email, téléphone, date de naissance et informations de paiement lors de vos demandes de devis ou commandes.</li>
|
||||
<li><strong>Données automatisées :</strong> Adresse IP, identifiants d'appareils (UDID/MEID), cookies et données de géolocalisation (avec votre accord).</li>
|
||||
<li><strong>Autres sources :</strong> Informations publiques issues des réseaux sociaux ou de partenaires marketing.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
{# Article 3 #}
|
||||
<section>
|
||||
<h2 class="text-xl font-bold text-gray-900 mb-3 border-b pb-1 uppercase">Utilisation des données</h2>
|
||||
<p>Vos informations sont utilisées pour :</p>
|
||||
<ul class="list-disc list-inside mt-2 space-y-1">
|
||||
<li>Honorer vos commandes, traiter les paiements et assurer le service client.</li>
|
||||
<li>Améliorer nos produits et personnaliser votre expérience en ligne.</li>
|
||||
<li>Garantir la sécurité de nos réseaux et prévenir la fraude.</li>
|
||||
<li>Vous envoyer des offres marketing (uniquement avec votre consentement).</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
{# Article 4 #}
|
||||
<section>
|
||||
<h2 class="text-xl font-bold text-gray-900 mb-3 border-b pb-1 uppercase">Partage des informations</h2>
|
||||
<p>Nous ne vendons aucune donnée personnelle. Elles sont partagées uniquement avec :</p>
|
||||
<ul class="list-disc list-inside mt-2 space-y-1">
|
||||
<li>Nos prestataires de services (paiement, informatique, marketing).</li>
|
||||
<li><strong>Livraison :</strong> Vos coordonnées sont transmises aux transporteurs pour acheminer votre matériel.</li>
|
||||
<li>Les autorités en cas d'obligation légale.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
{# Article 5 #}
|
||||
<section>
|
||||
<h2 class="text-xl font-bold text-gray-900 mb-3 border-b pb-1 uppercase">Vos droits</h2>
|
||||
<p>Conformément au RGPD, vous disposez d'un droit d'accès, de rectification, de suppression, d'opposition et de portabilité de vos données. Vous pouvez retirer votre consentement à tout moment.</p>
|
||||
<p class="mt-3 font-semibold text-green-700">Pour exercer vos droits, contactez-nous : segard.lilian@gmail.com</p>
|
||||
</section>
|
||||
|
||||
{# Article 6 #}
|
||||
<section>
|
||||
<h2 class="text-xl font-bold text-gray-900 mb-3 border-b pb-1 uppercase">Cookies et Publicité</h2>
|
||||
<p>Nous utilisons des cookies et balises Web pour vous identifier et mesurer la performance du site. Vous pouvez configurer votre navigateur pour les refuser, mais certaines fonctionnalités du site pourraient être limitées.</p>
|
||||
</section>
|
||||
|
||||
{# Article 7 #}
|
||||
<section class="bg-blue-50 p-6 rounded-xl border border-blue-100">
|
||||
<h2 class="text-xl font-bold text-blue-900 mb-3 uppercase">Additif Technique (Sécurité)</h2>
|
||||
<p class="text-sm mb-4"><strong>Chiffrement AES-256 :</strong> La totalité des données est chiffrée. Les clés d'accès subissent une rotation automatique toutes les 24 heures, rendant les données inaccessibles même pour l'hébergeur ou le propriétaire.</p>
|
||||
<p class="text-sm"><strong>Hébergement :</strong> Google Cloud Platform (GCP) sécurisé par TLS/SSL via Cloudflare et Let's Encrypt.</p>
|
||||
<div class="mt-4 pt-4 border-t border-blue-200 text-xs">
|
||||
<p><strong>DPO (SiteConseil) :</strong> Philippe LEGRAND – legrand@siteconseil.fr</p>
|
||||
<p><strong>Contact technique :</strong> rgpd@siteconseil.fr</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{# Article 8 #}
|
||||
<section>
|
||||
<h2 class="text-xl font-bold text-gray-900 mb-3 border-b pb-1 uppercase">Conservation et Transfert</h2>
|
||||
<p>Vos données sont conservées pendant <strong>3 ans</strong> à compter de votre dernière activité. Elles ne sont pas transférées en dehors de l'Union Européenne.</p>
|
||||
</section>
|
||||
|
||||
{# Article 9 #}
|
||||
<section>
|
||||
<h2 class="text-xl font-bold text-gray-900 mb-3 border-b pb-1 uppercase">Contact</h2>
|
||||
<p>Pour toute question : <strong>lilian@ludikevent.fr</strong> ou par courrier au 6, rue du Château 02800 DANIZY.</p>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
{# Footer #}
|
||||
<div class="bg-gray-100 p-6 text-center text-gray-500 text-xs">
|
||||
<p>Ludikevent - Protection des données personnelles conformément aux directives de la CNIL.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
56
templates/mails/root/alert.twig
Normal file
56
templates/mails/root/alert.twig
Normal file
@@ -0,0 +1,56 @@
|
||||
{% extends 'mails/base.twig' %}
|
||||
|
||||
{% block content %}
|
||||
<mj-section background-color="#ffffff" padding-bottom="0px" padding-top="20px">
|
||||
<mj-column width="100%">
|
||||
<mj-image src="{{ absolute_url(asset('provider/images/logo.png')) }}" alt="Ludikevent" width="150px" align="center" border="none"></mj-image>
|
||||
<mj-divider border-color="#f44336" border-width="4px"></mj-divider>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
|
||||
<mj-section background-color="#f44336" padding="20px">
|
||||
<mj-column width="100%">
|
||||
<mj-text align="center" color="#ffffff" font-size="22px" font-weight="bold" font-family="Arial, sans-serif" text-transform="uppercase">
|
||||
Alerte Sécurité Critique
|
||||
</mj-text>
|
||||
<mj-text align="center" color="#ffffff" font-size="16px" font-family="Arial, sans-serif">
|
||||
Une tentative d'accès non autorisée a été bloquée sur la console SuperAdmin.
|
||||
</mj-text>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
|
||||
<mj-section background-color="#ffffff" padding-top="20px">
|
||||
<mj-column width="100%">
|
||||
<mj-text color="#333333" font-size="16px" font-family="Arial, sans-serif">
|
||||
<strong>Détails de l'incident :</strong>
|
||||
</mj-text>
|
||||
<mj-table font-family="Arial, sans-serif" font-size="14px" color="#555555">
|
||||
<tr style="border-bottom:1px solid #ecedee;text-align:left;padding:15px 0;">
|
||||
<th style="padding: 10px 0;">Raison</th>
|
||||
<td style="padding: 10px 0; color: #f44336; font-weight: bold;">{{ datas.reason }}</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #ecedee;text-align:left;padding:15px 0;">
|
||||
<th style="padding: 10px 0;">Adresse IP</th>
|
||||
<td style="padding: 10px 0;">{{ datas.ip }}</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #ecedee;text-align:left;padding:15px 0;">
|
||||
<th style="padding: 10px 0;">Host</th>
|
||||
<td style="padding: 10px 0;">{{ datas.host }}</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #ecedee;text-align:left;padding:15px 0;">
|
||||
<th style="padding: 10px 0;">Date/Heure</th>
|
||||
<td style="padding: 10px 0;">{{ datas.date|date('d/m/Y H:i:s') }}</td>
|
||||
</tr>
|
||||
</mj-table>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
|
||||
<mj-section background-color="#fafafa" padding="10px">
|
||||
<mj-column width="100%">
|
||||
<mj-text color="#888888" font-size="12px" font-family="Arial, sans-serif">
|
||||
<strong>User-Agent :</strong><br/>
|
||||
{{ datas.userAgent }}
|
||||
</mj-text>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
{% endblock %}
|
||||
159
templates/root/console.twig
Normal file
159
templates/root/console.twig
Normal file
@@ -0,0 +1,159 @@
|
||||
{% extends 'base.twig' %}
|
||||
|
||||
{% block title %}Console SuperAdmin - SiteConseil{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="min-h-screen bg-console font-mono text-slate-300">
|
||||
|
||||
{# --- BARRE ROOT CONSOLE v1.0 --- #}
|
||||
<div class="bg-slate-900 border-b-2 border-red-700 px-6 py-4 shadow-2xl">
|
||||
<div class="flex flex-col md:flex-row md:justify-between md:items-center gap-4">
|
||||
<div class="flex flex-col">
|
||||
<h1 class="text-white font-black text-2xl tracking-tighter uppercase leading-none">
|
||||
Root Console <span class="text-red-600">v1.0</span>
|
||||
</h1>
|
||||
<span class="text-[11px] text-slate-400 font-bold uppercase tracking-[0.3em] mt-1">
|
||||
Siteconseil Privileged Access
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-6">
|
||||
<div class="hidden sm:flex items-center gap-2 border border-green-500/50 bg-green-500/10 px-3 py-1 rounded shadow-[0_0_15px_rgba(34,197,94,0.2)]">
|
||||
<span class="h-2 w-2 bg-green-500 rounded-full animate-ping"></span>
|
||||
<span class="text-[10px] text-green-500 font-black uppercase tracking-widest">Access Granted</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-3 bg-black/30 px-4 py-2 rounded border border-slate-800">
|
||||
<div class="flex flex-col items-end">
|
||||
<span class="text-[9px] text-slate-500 uppercase font-bold tracking-widest">Operator IP</span>
|
||||
<span class="text-sm text-green-500 font-bold tracking-widest">
|
||||
{{ app.request.headers.get('cf-connecting-ip') ?? app.request.clientIp }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# --- BODY DOUBLE COLONNE --- #}
|
||||
<div class="flex flex-col lg:flex-row h-[calc(100vh-84px)] overflow-hidden">
|
||||
|
||||
{# COLONNE GAUCHE #}
|
||||
<div class="w-full lg:w-2/3 p-8 border-r border-slate-800 overflow-y-auto bg-slate-900/10">
|
||||
|
||||
{# BLOCK : SERVICE HEALTH #}
|
||||
<div class="mb-12">
|
||||
<h2 class="text-xs font-bold text-slate-500 uppercase tracking-widest mb-6 flex items-center gap-2">
|
||||
<span class="w-2 h-2 bg-green-500 rounded-full"></span>
|
||||
External Service Health
|
||||
</h2>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
||||
{# Stripe #}
|
||||
<div class="bg-black/40 border border-slate-800 p-4 rounded flex items-center justify-between group">
|
||||
<span class="text-xs font-bold text-slate-400 group-hover:text-indigo-400">STRIPE API</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-[10px] font-bold uppercase {{ stripeStatus == 1 ? 'text-green-500' : 'text-red-500 animate-pulse' }}">
|
||||
{{ stripeStatus == 1 ? 'Online' : 'Offline' }}
|
||||
</span>
|
||||
<div class="w-1.5 h-1.5 rounded-full {{ stripeStatus == 1 ? 'bg-green-500' : 'bg-red-500' }}"></div>
|
||||
</div>
|
||||
</div>
|
||||
{# Signature #}
|
||||
<div class="bg-black/40 border border-slate-800 p-4 rounded flex items-center justify-between group">
|
||||
<span class="text-xs font-bold text-slate-400 group-hover:text-blue-400">SIGNATURE</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-[10px] font-bold uppercase {{ signatureStatus == 1 ? 'text-green-500' : 'text-red-500 animate-pulse' }}">
|
||||
{{ signatureStatus == 1 ? 'Online' : 'Offline' }}
|
||||
</span>
|
||||
<div class="w-1.5 h-1.5 rounded-full {{ signatureStatus == 1 ? 'bg-green-500' : 'bg-red-500' }}"></div>
|
||||
</div>
|
||||
</div>
|
||||
{# Esysearch #}
|
||||
<div class="bg-black/40 border border-slate-800 p-4 rounded flex items-center justify-between group">
|
||||
<span class="text-xs font-bold text-slate-400 group-hover:text-orange-400">ESYSEARCH</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-[10px] font-bold uppercase {{ searchClient == 1 ? 'text-green-500' : 'text-red-500 animate-pulse' }}">
|
||||
{{ searchClient == 1 ? 'Online' : 'Offline' }}
|
||||
</span>
|
||||
<div class="w-1.5 h-1.5 rounded-full {{ searchClient == 1 ? 'bg-green-500' : 'bg-red-500' }}"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# BLOCK : CRITICAL COMMANDS #}
|
||||
<div class="mb-12">
|
||||
<h2 class="text-xs font-bold text-slate-500 uppercase tracking-widest mb-6 flex items-center gap-2">
|
||||
<span class="w-2 h-2 bg-red-600 rounded-full"></span>
|
||||
Critical Command Center
|
||||
</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div class="bg-red-950/20 border border-red-900/50 p-6 rounded-lg group hover:bg-red-900/30 transition-all shadow-lg text-center">
|
||||
<h3 class="text-red-500 font-bold uppercase text-sm mb-4">Maintenance Mode</h3>
|
||||
<button id="btn-suspend" class="w-full bg-red-700 hover:bg-red-600 text-white font-black py-3 rounded uppercase text-[11px] tracking-[0.2em] active:scale-95 transition-transform">Suspendre l'accès</button>
|
||||
</div>
|
||||
<div class="bg-green-950/20 border border-green-900/50 p-6 rounded-lg group hover:bg-green-900/30 transition-all shadow-lg text-center">
|
||||
<h3 class="text-green-500 font-bold uppercase text-sm mb-4">Live Mode</h3>
|
||||
<button id="btn-enable" class="w-full bg-green-700 hover:bg-green-600 text-white font-black py-3 rounded uppercase text-[11px] tracking-[0.2em] active:scale-95 transition-transform">Réactiver l'accès</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# BLOCK : OPTIMIZATION #}
|
||||
<div class="mb-8">
|
||||
<h2 class="text-xs font-bold text-slate-500 uppercase tracking-widest mb-6 flex items-center gap-2">
|
||||
<span class="w-2 h-2 bg-blue-600 rounded-full"></span>
|
||||
System Optimization
|
||||
</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div class="bg-slate-900/40 border border-slate-800 p-6 rounded-lg group hover:border-blue-900/50 transition-all">
|
||||
<h3 class="text-blue-500 font-bold uppercase text-sm mb-4 font-mono">cache:clear</h3>
|
||||
<button id="btn-cache" class="w-full bg-slate-800 hover:bg-blue-700 text-white font-bold py-2 rounded uppercase text-[10px] tracking-widest">Run Command</button>
|
||||
</div>
|
||||
<div class="bg-slate-900/40 border border-slate-800 p-6 rounded-lg group hover:border-purple-900/50 transition-all">
|
||||
<h3 class="text-purple-500 font-bold uppercase text-sm mb-4 font-mono">liip:cache:clear</h3>
|
||||
<button id="btn-liip" class="w-full bg-slate-800 hover:bg-purple-700 text-white font-bold py-2 rounded uppercase text-[10px] tracking-widest">Run Command</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# --- COLONNE DROITE : LE TERMINAL --- #}
|
||||
<div class="w-full lg:w-1/3 bg-black p-0 flex flex-col h-full shadow-inner border-l border-slate-800">
|
||||
<div class="bg-slate-800/50 px-4 py-2 flex items-center justify-between border-b border-slate-700">
|
||||
<div class="flex gap-1.5">
|
||||
<div class="w-2.5 h-2.5 rounded-full bg-red-500/20 border border-red-500/50"></div>
|
||||
<div class="w-2.5 h-2.5 rounded-full bg-yellow-500/20 border border-yellow-500/50"></div>
|
||||
<div class="w-2.5 h-2.5 rounded-full bg-green-500/20 border border-green-500/50"></div>
|
||||
</div>
|
||||
<span class="text-[10px] uppercase text-slate-500 font-bold tracking-widest">Live System Logs</span>
|
||||
</div>
|
||||
|
||||
<div id="terminal-logs" class="p-6 font-mono text-[11px] leading-relaxed overflow-y-auto h-full space-y-1 scrollbar-thin scrollbar-thumb-slate-800">
|
||||
<p class="text-slate-600">[{{ "now"|date('H:i:s') }}] INITIALIZING ROOT INTERFACE...</p>
|
||||
<p class="text-slate-600">[{{ "now"|date('H:i:s') }}] SESSION_USER: <span class="text-white">SITECONSEIL_ADMIN</span></p>
|
||||
|
||||
<p class="text-slate-600">[{{ "now"|date('H:i:s') }}] DOMAIN_VALIDATION: <span class="text-green-500 font-bold">SUCCESSFUL</span></p>
|
||||
<p class="text-slate-600">[{{ "now"|date('H:i:s') }}] IP_OPERATOR_VAL: <span class="text-green-500 font-bold">SUCCESSFUL</span></p>
|
||||
<p class="text-slate-600">[{{ "now"|date('H:i:s') }}] SECRET_KEY_VAL: <span class="text-green-500 font-bold">SUCCESSFUL</span></p>
|
||||
|
||||
{# Statuts des services en 1/0 #}
|
||||
<p class="text-slate-600">[{{ "now"|date('H:i:s') }}] STRIPE_VAL: <span class="{{ stripeStatus == 1 ? 'text-green-500' : 'text-red-500' }} font-bold">{{ stripeStatus == 1 ? 'ONLINE' : 'OFFLINE' }}</span></p>
|
||||
<p class="text-slate-600">[{{ "now"|date('H:i:s') }}] SIGNATURE_VAL: <span class="{{ signatureStatus == 1 ? 'text-green-500' : 'text-red-500' }} font-bold">{{ signatureStatus == 1 ? 'ONLINE' : 'OFFLINE' }}</span></p>
|
||||
<p class="text-slate-600">[{{ "now"|date('H:i:s') }}] ESYSEARCH_VAL: <span class="{{ searchClient == 1 ? 'text-green-500' : 'text-red-500' }} font-bold">{{ searchClient == 1 ? 'ONLINE' : 'OFFLINE' }}</span></p>
|
||||
|
||||
<div class="py-3">
|
||||
<p class="text-green-400 font-bold bg-green-500/10 px-3 py-1.5 border-l-2 border-green-500 uppercase tracking-tighter">
|
||||
*** Access granted to system core ***
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div id="dynamic-logs" class="space-y-1"></div>
|
||||
|
||||
<p class="text-white mt-4">> <span class="animate-pulse">_</span></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script nonce="{{ csp_nonce('script') }}" src="/st_control.js"></script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user