diff --git a/TASK_CHECKUP.md b/TASK_CHECKUP.md index 4890119..31ec134 100644 --- a/TASK_CHECKUP.md +++ b/TASK_CHECKUP.md @@ -130,6 +130,22 @@ - [x] Ajouter des tests pour AuditService, ExportService, InvoiceService - [x] Ajouter des tests pour les webhooks Stripe (payment_failed, charge.refunded) +### Réseaux sociaux & Partage +#### Page détail événement (public) +- [x] Bouton partage X (Twitter) : lien pré-rempli avec titre + URL événement +- [x] Bouton partage Facebook : lien share dialog avec URL événement +- [x] Bouton partage Instagram : copier le lien (Instagram ne supporte pas le share URL direct) +- [x] Bouton partage TikTok : copier le lien (TikTok ne supporte pas le share URL direct) +- [x] Bouton copier le lien de l'événement + +#### Interface organisateur (/mon-compte) +- [x] Bouton partage X (Twitter) pour chaque événement dans la liste +- [x] Bouton partage Facebook pour chaque événement +- [x] Bouton copier le lien pour chaque événement +- [x] Bouton QR code événement : génération QR code PNG téléchargeable +- [x] Page /mon-compte/evenement/{id}/qrcode : route avec Endroid QR Code +- [x] Afficher les liens réseaux sociaux de l'orga (facebook, instagram, twitter, tiktok) sur la page événement (déjà en place via _social_icons.html.twig) + ### Infrastructure - [x] Configurer les crons pour les backups automatiques (DB + uploads, toutes les 30 min, rétention 1 jour) - [x] Ajouter le monitoring des queues Messenger (commande + cron toutes les heures + email admin) diff --git a/assets/app.js b/assets/app.js index cf6bed1..08ec54b 100644 --- a/assets/app.js +++ b/assets/app.js @@ -10,6 +10,7 @@ import { initBilletDesigner } from "./modules/billet-designer.js" import { initCommissionCalculator } from "./modules/commission-calculator.js" import { initCart } from "./modules/cart.js" import { initStripePayment } from "./modules/stripe-payment.js" +import { initShare } from "./modules/share.js" document.addEventListener('DOMContentLoaded', () => { initMobileMenu() @@ -23,6 +24,7 @@ document.addEventListener('DOMContentLoaded', () => { initCommissionCalculator() initCart() initStripePayment() + initShare() document.querySelectorAll('[data-confirm]').forEach(form => { form.addEventListener('submit', (e) => { diff --git a/assets/modules/share.js b/assets/modules/share.js new file mode 100644 index 0000000..c7cb157 --- /dev/null +++ b/assets/modules/share.js @@ -0,0 +1,12 @@ +export function initShare() { + document.querySelectorAll('[data-share-copy]').forEach(btn => { + btn.addEventListener('click', () => { + const url = btn.dataset.shareCopy + globalThis.navigator.clipboard.writeText(url).then(() => { + const original = btn.innerHTML + btn.innerHTML = '' + setTimeout(() => { btn.innerHTML = original }, 1500) + }) + }) + }) +} diff --git a/src/Controller/AccountController.php b/src/Controller/AccountController.php index f17ef9a..c045fd0 100644 --- a/src/Controller/AccountController.php +++ b/src/Controller/AccountController.php @@ -26,6 +26,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; use Symfony\Component\Routing\Attribute\Route; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Security\Http\Attribute\IsGranted; #[IsGranted('ROLE_USER')] @@ -1059,6 +1060,37 @@ class AccountController extends AbstractController return $this->redirectToRoute('app_account', ['tab' => 'events']); } + #[Route('/mon-compte/evenement/{id}/qrcode', name: 'app_account_event_qrcode', methods: ['GET'])] + public function eventQrCode(Event $event, UrlGeneratorInterface $urlGenerator): Response + { + $this->denyAccessUnlessGranted('ROLE_ORGANIZER'); + + /** @var User $user */ + $user = $this->getUser(); + if ($event->getAccount()->getId() !== $user->getId()) { + throw $this->createAccessDeniedException(); + } + + $eventUrl = $urlGenerator->generate('app_event_detail', [ + 'orgaSlug' => $user->getSlug(), + 'id' => $event->getId(), + 'eventSlug' => $event->getSlug(), + ], UrlGeneratorInterface::ABSOLUTE_URL); + + $qrCode = (new \Endroid\QrCode\Builder\Builder( + writer: new \Endroid\QrCode\Writer\PngWriter(), + data: $eventUrl, + encoding: new \Endroid\QrCode\Encoding\Encoding('UTF-8'), + size: 400, + margin: 20, + ))->build(); + + return new Response($qrCode->getString(), 200, [ + 'Content-Type' => 'image/png', + 'Content-Disposition' => 'attachment; filename="qrcode-'.$event->getSlug().'.png"', + ]); + } + /** @codeCoverageIgnore Test helper, not used in production */ #[Route('/mon-compte/test-payout', name: 'app_account_test_payout', methods: ['POST'])] public function testPayout(EntityManagerInterface $em): Response diff --git a/templates/account/index.html.twig b/templates/account/index.html.twig index c478690..248ac53 100644 --- a/templates/account/index.html.twig +++ b/templates/account/index.html.twig @@ -301,8 +301,21 @@ {% endif %}