diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ab1f25..0b1c3aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ## [Unreleased] +### Added +- Cookie consent banner: fixed bottom bar with "Accept all" and "Manage preferences" (toggle for functional/A/B cookies); consent stored in `cookie_consent` cookie for 1 year; "Manage Cookies" link added to footer Legal section + +### Changed +- Defer Paddle.js loading to only the 3 pages that use checkout (export, supplier signup, supplier dashboard) — removed from global `base.html` head; all other pages no longer receive Paddle's third-party cookies +- Gate A/B test cookie (`ab_*`) on functional cookie consent: variant is still picked per-request for rendering, but the cookie is only persisted when the visitor has accepted functional cookies +- Privacy policy section 6 (Cookies): full disclosure of all cookie categories (essential, functional, payment); fix "Plausible" → "Umami" in service providers list + ### Changed - Auto-create Resend audiences per blueprint: `capture_waitlist_email()` now derives the audience name from `request.blueprints[0]` (e.g., `waitlist-auth`, `waitlist-suppliers`) and lazily creates audiences via the Resend API on first use, caching IDs in a new `resend_audiences` table; removes `RESEND_AUDIENCE_WAITLIST` env var — only `RESEND_API_KEY` needed diff --git a/padelnomics/src/padelnomics/core.py b/padelnomics/src/padelnomics/core.py index 30e46a6..7dcb14d 100644 --- a/padelnomics/src/padelnomics/core.py +++ b/padelnomics/src/padelnomics/core.py @@ -464,13 +464,25 @@ def slugify(text: str, max_length_chars: int = 80) -> str: # A/B Testing # ============================================================================= +def _has_functional_consent() -> bool: + """Return True if the visitor has accepted functional cookies.""" + return "functional" in request.cookies.get("cookie_consent", "") + + def ab_test(experiment: str, variants: tuple = ("control", "treatment")): - """Assign visitor to an A/B test variant via cookie, tag Umami pageviews.""" + """Assign visitor to an A/B test variant, tag Umami pageviews. + + Only persists the variant cookie when the visitor has given functional + cookie consent. Without consent a random variant is picked per-request + (so the page renders fine and Umami is tagged), but no cookie is set. + """ def decorator(f): @wraps(f) async def wrapper(*args, **kwargs): cookie_key = f"ab_{experiment}" - assigned = request.cookies.get(cookie_key) + has_consent = _has_functional_consent() + + assigned = request.cookies.get(cookie_key) if has_consent else None if assigned not in variants: assigned = random.choice(variants) @@ -478,7 +490,8 @@ def ab_test(experiment: str, variants: tuple = ("control", "treatment")): g.ab_tag = f"{experiment}-{assigned}" response = await make_response(await f(*args, **kwargs)) - response.set_cookie(cookie_key, assigned, max_age=30 * 24 * 60 * 60) + if has_consent: + response.set_cookie(cookie_key, assigned, max_age=30 * 24 * 60 * 60) return response return wrapper return decorator diff --git a/padelnomics/src/padelnomics/planner/templates/export.html b/padelnomics/src/padelnomics/planner/templates/export.html index 7212f0c..37b3119 100644 --- a/padelnomics/src/padelnomics/planner/templates/export.html +++ b/padelnomics/src/padelnomics/planner/templates/export.html @@ -1,5 +1,6 @@ {% extends "base.html" %} {% block title %}Export Business Plan - {{ config.APP_NAME }}{% endblock %} +{% block paddle %}{% include "_paddle.html" %}{% endblock %} {% block head %}