/* ads-consent.js — Google Ads tag, gated behind a consent banner. Page-side only. * * Page: /odoo-black-friday/. Companion to /static/black-friday.js (which is untouched). * The tag ID and the conversion label are read from the banner element's data-* attributes * (#adsConsent[data-ads-id][data-ads-send-to]), so this file stays generic and reusable and * the page holds the one copy of the config. Without those attributes the script stays inert. * * WHY A GATE: the Google Ads tag is NOT cookieless — it sets the advertising cookies * _gcl_au / _gcl_aw. So nothing loads and no request fires until the visitor accepts. * The default is DENY. Declining genuinely prevents the tag from loading. * * WHAT THE CONVERSION IS: a CLICK PROXY, not a sale. It fires when a buy form on this page * is submitted. The real purchase is measured on the checkout host (20.odoo4projects.com) * by odoo-dev. See wiki/concepts/conversion-tracking-contract. Never label it a sale. * * SAFETY: a conversion tag must never swallow a buy. On submit we fire the event and submit * the form from the event callback, with a hard timeout that submits anyway. With JavaScript * off the native POST forms submit untouched (no banner, no conversion, no error). */ (function () { "use strict"; var STORE_KEY = "o4p_ads_consent"; // "granted" | "denied" var SUBMIT_FALLBACK_MS = 700; // hard cap: submit even if the tag never answers var cfg = null; // { id, sendTo } — read from the banner, never hard-coded /* ---- the visitor's stored choice ---- */ function readChoice() { try { return window.localStorage.getItem(STORE_KEY); } catch (e) { return null; } } function saveChoice(value) { try { window.localStorage.setItem(STORE_KEY, value); } catch (e) { /* private mode / storage disabled: treat as no choice, banner stays up */ } } function isGranted() { return readChoice() === "granted"; } /* ---- cookie removal (only used to revoke a prior grant) ---- */ function clearCookie(name) { var host = window.location.hostname; var bare = host.replace(/^www\./, ""); var paths = ["/", "/odoo-black-friday/"]; var domains = ["", "; domain=" + host, "; domain=." + bare]; for (var i = 0; i < paths.length; i++) { for (var j = 0; j < domains.length; j++) { document.cookie = name + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=" + paths[i] + domains[j]; } } } function clearAdsCookies() { clearCookie("_gcl_au"); clearCookie("_gcl_aw"); } /* ---- the tag: loaded ONLY on consent ---- */ function loadGtag() { if (!cfg || window.__o4pAdsLoaded) return; window.__o4pAdsLoaded = true; window.dataLayer = window.dataLayer || []; window.gtag = window.gtag || function () { window.dataLayer.push(arguments); }; window.gtag("js", new Date()); window.gtag("config", cfg.id); var s = document.createElement("script"); s.async = true; s.src = "https://www.googletagmanager.com/gtag/js?id=" + cfg.id; document.head.appendChild(s); } /* ---- the conversion proxy, fired on a buy-form submit ---- */ function onSubmit(e) { var form = e.target; if (!form || form.tagName !== "FORM") return; var action = form.getAttribute("action") || ""; if (action.indexOf("/shop/post_basket") === -1) return; // Not configured, no consent, or no tag: do nothing, let the native POST go through. if (!cfg || !isGranted() || typeof window.gtag !== "function") return; e.preventDefault(); var submitted = false; function submitNow() { if (submitted) return; submitted = true; form.submit(); // bypasses the submit event: no loop, no re-fire } try { window.gtag("event", "conversion", { send_to: cfg.sendTo, event_callback: submitNow, }); } catch (err) { submitNow(); } // Hard fallback: a blocked / slow / failed tag must never stop the purchase. window.setTimeout(submitNow, SUBMIT_FALLBACK_MS); } /* ---- the banner ---- */ function bannerEl() { return document.getElementById("adsConsent"); } function showBanner() { var el = bannerEl(); if (el) el.hidden = false; } function hideBanner() { var el = bannerEl(); if (el) el.hidden = true; } function accept() { saveChoice("granted"); loadGtag(); hideBanner(); } function decline() { saveChoice("denied"); clearAdsCookies(); hideBanner(); } function init() { document.addEventListener("submit", onSubmit, false); var banner = bannerEl(); if (!banner) return; // Config lives in the page. Missing config -> stay inert (no tag, no conversion). var id = banner.getAttribute("data-ads-id"); var sendTo = banner.getAttribute("data-ads-send-to"); if (!id || !sendTo) return; cfg = { id: id, sendTo: sendTo }; var choice = readChoice(); if (choice === "granted") { loadGtag(); // returning visitor who already accepted: load straight away } else if (choice !== "denied") { showBanner(); // first visit (or no stored choice): default deny, ask } var acceptBtn = document.getElementById("adsConsentAccept"); if (acceptBtn) acceptBtn.addEventListener("click", accept); var declineBtn = document.getElementById("adsConsentDecline"); if (declineBtn) declineBtn.addEventListener("click", decline); // A real way to change the choice later. var opener = document.getElementById("adsConsentOpen"); if (opener) { opener.addEventListener("click", function (e) { e.preventDefault(); showBanner(); }); } } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", init); } else { init(); } })();