From 79f463d280b6dfbc7e451f4f81b076ac166caa9d Mon Sep 17 00:00:00 2001 From: Andil Date: Sun, 23 Aug 2026 10:35:33 +0000 Subject: [PATCH] feat: integrate consent-based Google Analytics --- Jenkinsfile | 11 ++-- frontend/Dockerfile | 3 ++ frontend/src/components/CookieBanner.jsx | 54 ++++++++++++++++--- frontend/src/services/analytics.js | 33 ++++++++++++ .../test/fonctionnel/CookieBanner.test.jsx | 2 +- 5 files changed, 91 insertions(+), 12 deletions(-) create mode 100644 frontend/src/services/analytics.js diff --git a/Jenkinsfile b/Jenkinsfile index 6831941..b33ce04 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -49,6 +49,7 @@ pipeline { env.DEPLOY_NETWORK = 'tiptop-dev' env.FRONTEND_API_URL = "https://api-dev.dsp5-archi-o24a-4-5-g4.fr" + env.FRONTEND_GA_ID = "" } else if (env.BRANCH_NAME.startsWith('release-')) { env.DEPLOY_ENABLED = 'true' @@ -59,6 +60,7 @@ pipeline { env.DEPLOY_NETWORK = 'tiptop-preprod' env.FRONTEND_API_URL = "https://api-preprod.dsp5-archi-o24a-4-5-g4.fr" + env.FRONTEND_GA_ID = "" } else if (env.BRANCH_NAME == 'main') { env.DEPLOY_ENABLED = 'true' @@ -69,6 +71,7 @@ pipeline { env.DEPLOY_NETWORK = 'tiptop-prod' env.FRONTEND_API_URL = "https://api.dsp5-archi-o24a-4-5-g4.fr" + env.FRONTEND_GA_ID = "G-4FTNTPJDRJ" } else { env.DEPLOY_ENABLED = 'false' @@ -78,6 +81,7 @@ pipeline { env.COMPOSE_PROJECT = '' env.DEPLOY_NETWORK = '' env.FRONTEND_API_URL = '' + env.FRONTEND_GA_ID = "" } echo "Branche : ${env.BRANCH_NAME}" @@ -229,9 +233,10 @@ pipeline { backend docker build \ - --build-arg VITE_API_URL="${FRONTEND_API_URL}" \ - -t "${FRONTEND_IMAGE}:${IMAGE_TAG}" \ - frontend + --build-arg VITE_API_URL="${FRONTEND_API_URL}" \ + --build-arg VITE_GA_ID="${FRONTEND_GA_ID}" \ + -t "${FRONTEND_IMAGE}:${IMAGE_TAG}" \ + frontend ''' } } diff --git a/frontend/Dockerfile b/frontend/Dockerfile index f5e98df..a3816af 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -8,7 +8,10 @@ RUN npm ci COPY . . ARG VITE_API_URL +ARG VITE_GA_ID + ENV VITE_API_URL=${VITE_API_URL} +ENV VITE_GA_ID=${VITE_GA_ID} RUN npm run build diff --git a/frontend/src/components/CookieBanner.jsx b/frontend/src/components/CookieBanner.jsx index e68ed36..1eef4f0 100644 --- a/frontend/src/components/CookieBanner.jsx +++ b/frontend/src/components/CookieBanner.jsx @@ -1,40 +1,78 @@ -import { useState } from 'react' -import { Link } from 'react-router-dom' +import { useEffect, useState } from 'react' +import { Link, useLocation } from 'react-router-dom' +import { initAnalytics, trackPageView } from '../services/analytics' export default function CookieBanner() { - const [visible, setVisible] = useState(() => !localStorage.getItem('cookieConsent')) + const location = useLocation() - if (!visible) return null + const [consent, setConsent] = useState(() => + localStorage.getItem('cookieConsent') + ) + + const visible = consent === null + + // Si le consentement avait déjà été donné lors d'une précédente visite, + // Analytics est initialisé au chargement de l'application. + useEffect(() => { + if (consent !== 'accepted') return + + initAnalytics() + trackPageView(location.pathname + location.search) + }, [consent, location.pathname, location.search]) const handleChoice = (choice) => { localStorage.setItem('cookieConsent', choice) - setVisible(false) + setConsent(choice) + + if (choice === 'accepted') { + initAnalytics() + trackPageView(location.pathname + location.search) + } } + if (!visible) return null + return (
+
-

Ce site utilise des cookies

+

+ Gestion des cookies +

+

- Nous utilisons des cookies essentiels au fonctionnement du site. En continuant votre navigation, vous acceptez leur utilisation conformément à notre{' '} - politique de confidentialité. + Les cookies strictement nécessaires au fonctionnement du site sont + utilisés automatiquement. Avec votre accord, nous utilisons également + Google Analytics afin de mesurer l'audience et améliorer le site. Vous + pouvez accepter ou refuser ces cookies statistiques. Consultez notre{' '} + + politique de confidentialité + .

+
+
+
) diff --git a/frontend/src/services/analytics.js b/frontend/src/services/analytics.js new file mode 100644 index 0000000..32aa2ea --- /dev/null +++ b/frontend/src/services/analytics.js @@ -0,0 +1,33 @@ +const GA_ID = import.meta.env.VITE_GA_ID + +export function initAnalytics() { + if (!GA_ID || window.gtag) return + + const script = document.createElement('script') + script.async = true + script.src = `https://www.googletagmanager.com/gtag/js?id=${GA_ID}` + document.head.appendChild(script) + + window.dataLayer = window.dataLayer || [] + + window.gtag = function gtag() { + window.dataLayer.push(arguments) + } + + window.gtag('js', new Date()) + + // Les page_view sont envoyées explicitement lors des changements de route React. + window.gtag('config', GA_ID, { + send_page_view: false, + }) +} + +export function trackPageView(path) { + if (!GA_ID || !window.gtag) return + + window.gtag('event', 'page_view', { + page_path: path, + page_location: window.location.href, + page_title: document.title, + }) +} diff --git a/frontend/src/test/fonctionnel/CookieBanner.test.jsx b/frontend/src/test/fonctionnel/CookieBanner.test.jsx index 2e4a139..23f112c 100644 --- a/frontend/src/test/fonctionnel/CookieBanner.test.jsx +++ b/frontend/src/test/fonctionnel/CookieBanner.test.jsx @@ -8,7 +8,7 @@ describe('CookieBanner', () => { it("s'affiche quand aucun choix n'a été fait", () => { renderWithProviders() - expect(screen.getByText(/ce site utilise des cookies/i)).toBeInTheDocument() + expect(screen.getByText(/gestion des cookies/i)).toBeInTheDocument() }) it('enregistre le consentement et disparaît quand on accepte', async () => {