Packages
phoenix_kit
1.7.75
1.7.210
1.7.209
1.7.208
1.7.207
1.7.206
1.7.205
1.7.204
1.7.203
1.7.202
1.7.201
1.7.200
1.7.199
1.7.198
1.7.197
1.7.196
1.7.194
1.7.193
1.7.192
1.7.191
1.7.190
1.7.189
1.7.187
1.7.186
1.7.185
1.7.184
1.7.183
1.7.182
1.7.181
1.7.180
1.7.179
1.7.178
1.7.177
1.7.176
1.7.175
1.7.174
1.7.173
1.7.172
1.7.171
1.7.170
1.7.169
1.7.168
1.7.167
1.7.166
1.7.165
1.7.164
1.7.162
1.7.161
1.7.160
1.7.159
1.7.157
1.7.156
1.7.155
1.7.154
1.7.153
1.7.152
1.7.151
1.7.150
1.7.149
1.7.146
1.7.145
1.7.144
1.7.143
1.7.138
1.7.133
1.7.132
1.7.131
1.7.130
1.7.128
1.7.126
1.7.125
1.7.121
1.7.120
1.7.119
1.7.118
1.7.117
1.7.116
1.7.115
1.7.114
1.7.113
1.7.112
1.7.111
1.7.110
1.7.109
1.7.108
1.7.107
1.7.106
1.7.105
1.7.104
1.7.103
1.7.102
1.7.101
1.7.100
1.7.99
1.7.98
1.7.97
1.7.96
1.7.95
1.7.94
1.7.93
1.7.92
1.7.91
1.7.90
1.7.89
1.7.88
1.7.87
1.7.86
1.7.85
1.7.84
1.7.83
1.7.82
1.7.81
1.7.80
1.7.79
1.7.78
1.7.77
1.7.76
1.7.75
1.7.74
1.7.71
1.7.70
1.7.69
1.7.66
1.7.65
1.7.64
1.7.63
1.7.62
1.7.61
1.7.59
1.7.58
1.7.57
1.7.56
1.7.55
1.7.54
1.7.53
1.7.52
1.7.51
1.7.49
1.7.44
1.7.43
1.7.42
1.7.41
1.7.39
1.7.38
1.7.37
1.7.36
1.7.34
1.7.33
1.7.31
1.7.30
1.7.29
1.7.28
1.7.27
1.7.26
1.7.25
1.7.24
1.7.23
1.7.22
1.7.21
1.7.20
1.7.19
1.7.18
1.7.17
1.7.16
1.7.15
1.7.14
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.6.20
1.6.19
1.6.18
1.6.17
1.6.16
1.6.15
1.6.14
1.6.13
1.6.12
1.6.11
1.6.10
1.6.9
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.5.2
1.5.1
1.5.0
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.2
1.3.1
1.3.0
1.2.10
1.2.9
1.2.8
1.2.7
1.2.5
1.2.4
1.2.2
1.2.1
1.2.0
1.1.0
1.0.0
A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more
Current section
Files
Jump to
Current section
Files
priv/static/assets/phoenix_kit_consent.js
/**
* PhoenixKit Cookie Consent Manager
*
* A lightweight, GDPR/CCPA compliant cookie consent solution with:
* - Automatic DOM injection (no layout changes required)
* - localStorage persistence with cross-tab sync
* - Google Consent Mode v2 integration
* - Script blocking by category
* - Phoenix LiveView hook integration
*
* @version 2.0.0
* @license MIT
*/
(function() {
"use strict";
// Prevent double initialization
if (window.PhoenixKitConsent) return;
// =====================================================
// CONSTANTS
// =====================================================
const STORAGE_KEY = "pk_consent";
const VERSION_KEY = "pk_consent_version";
const CATEGORIES = ["necessary", "analytics", "marketing", "preferences"];
// Get PhoenixKit URL prefix from global variable set by PhoenixKit
function getConfigEndpoint() {
const prefix = window.PHOENIX_KIT_PREFIX || "/phoenix_kit";
// Handle case when prefix is "/" to avoid double slash (//api/...)
if (prefix === "/") {
return "/api/consent-config";
}
return prefix + "/api/consent-config";
}
const OPT_IN_FRAMEWORKS = ["gdpr", "uk_gdpr", "lgpd", "pipeda"];
// =====================================================
// MODULE STATE
// =====================================================
var PhoenixKitConsent = {
initialized: false,
config: {
frameworks: [],
policyVersion: "1.0",
googleConsentMode: false,
iconPosition: "bottom-right",
showIcon: false,
cookiePolicyUrl: "/legal/cookie-policy",
privacyPolicyUrl: "/legal/privacy-policy"
},
consent: null,
elements: {
root: null,
icon: null,
banner: null,
modal: null
}
};
// =====================================================
// UTILITIES
// =====================================================
function log(message, data) {
if (typeof console !== "undefined" && console.debug) {
console.debug("[PhoenixKitConsent] " + message, data || "");
}
}
function isOptInMode() {
var frameworks = PhoenixKitConsent.config.frameworks;
for (var i = 0; i < OPT_IN_FRAMEWORKS.length; i++) {
if (frameworks.indexOf(OPT_IN_FRAMEWORKS[i]) !== -1) {
return true;
}
}
return false;
}
// =====================================================
// STORAGE
// =====================================================
function loadConsent() {
try {
var stored = localStorage.getItem(STORAGE_KEY);
if (stored) {
return JSON.parse(stored);
}
} catch (e) {
log("Could not load consent", e);
}
return null;
}
function saveConsent(consent) {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(consent));
localStorage.setItem(VERSION_KEY, PhoenixKitConsent.config.policyVersion);
log("Consent saved", consent);
} catch (e) {
log("Could not save consent", e);
}
}
function getStoredVersion() {
try {
return localStorage.getItem(VERSION_KEY);
} catch (e) {
return null;
}
}
function shouldShowBanner() {
var stored = loadConsent();
var storedVersion = getStoredVersion();
var currentVersion = PhoenixKitConsent.config.policyVersion;
var consentMode = PhoenixKitConsent.config.consentMode;
// In notice mode: only show once (don't re-show on version change)
if (consentMode === "notice") {
return !stored;
}
// In strict mode: re-show if no consent or version changed
if (!stored || storedVersion !== currentVersion) {
return true;
}
return false;
}
// =====================================================
// CROSS-TAB SYNC
// =====================================================
function setupCrossTabSync() {
window.addEventListener("storage", function(e) {
if (e.key === STORAGE_KEY && e.newValue) {
try {
var newConsent = JSON.parse(e.newValue);
PhoenixKitConsent.consent = newConsent;
applyConsent(newConsent);
updateUI();
log("Cross-tab sync: consent updated");
} catch (err) {
log("Cross-tab sync error", err);
}
}
});
}
// =====================================================
// GOOGLE CONSENT MODE V2
// =====================================================
function initGoogleConsentMode() {
if (!PhoenixKitConsent.config.googleConsentMode) return;
window.dataLayer = window.dataLayer || [];
function gtag() { window.dataLayer.push(arguments); }
gtag("consent", "default", {
"ad_storage": "denied",
"analytics_storage": "denied",
"ad_user_data": "denied",
"ad_personalization": "denied",
"personalization_storage": "denied",
"functionality_storage": "granted",
"security_storage": "granted",
"wait_for_update": 500
});
gtag("set", "ads_data_redaction", true);
gtag("set", "url_passthrough", true);
log("Google Consent Mode v2 initialized");
}
function updateGoogleConsent(consent) {
if (!PhoenixKitConsent.config.googleConsentMode) return;
window.dataLayer = window.dataLayer || [];
function gtag() { window.dataLayer.push(arguments); }
gtag("consent", "update", {
"analytics_storage": consent.analytics ? "granted" : "denied",
"ad_storage": consent.marketing ? "granted" : "denied",
"ad_user_data": consent.marketing ? "granted" : "denied",
"ad_personalization": consent.marketing ? "granted" : "denied",
"personalization_storage": consent.preferences ? "granted" : "denied"
});
log("Google Consent Mode updated", consent);
}
function resetGoogleConsentMode() {
// Reset Google Consent Mode to granted state when widget is disabled
// This ensures no blocking occurs when the consent widget is turned off
if (typeof window.dataLayer === "undefined") return;
function gtag() { window.dataLayer.push(arguments); }
gtag("consent", "update", {
"ad_storage": "granted",
"analytics_storage": "granted",
"ad_user_data": "granted",
"ad_personalization": "granted",
"personalization_storage": "granted",
"functionality_storage": "granted",
"security_storage": "granted"
});
log("Google Consent Mode reset to granted (widget disabled)");
}
// =====================================================
// SCRIPT BLOCKING
// =====================================================
function blockScripts() {
var scripts = document.querySelectorAll("script[data-consent-category]");
scripts.forEach(function(script) {
var category = script.getAttribute("data-consent-category");
if (category !== "necessary") {
script.setAttribute("type", "text/plain");
script.setAttribute("data-blocked", "true");
}
});
log("Non-essential scripts blocked");
}
function unblockScripts(category) {
var scripts = document.querySelectorAll(
'script[data-consent-category="' + category + '"][data-blocked="true"]'
);
scripts.forEach(function(script) {
var newScript = document.createElement("script");
Array.from(script.attributes).forEach(function(attr) {
if (attr.name !== "type" && attr.name !== "data-blocked") {
newScript.setAttribute(attr.name, attr.value);
}
});
if (script.src) {
newScript.src = script.src;
} else {
newScript.textContent = script.textContent;
}
script.parentNode.replaceChild(newScript, script);
});
if (scripts.length > 0) {
log("Scripts unblocked for category: " + category);
}
}
function applyConsent(consent) {
CATEGORIES.forEach(function(category) {
if (consent[category]) {
unblockScripts(category);
}
});
updateGoogleConsent(consent);
window.dispatchEvent(new CustomEvent("phx:consent-updated", {
detail: { consent: consent }
}));
}
// =====================================================
// DOM CREATION
// =====================================================
function getIconPositionClass(position) {
switch(position) {
case "bottom-left": return "bottom: 1rem; left: 1rem;";
case "top-left": return "top: 1rem; left: 1rem;";
case "top-right": return "top: 1rem; right: 1rem;";
default: return "bottom: 1rem; right: 1rem;";
}
}
function createWidgetHTML(config) {
var showIcon = isOptInMode();
var iconStyle = getIconPositionClass(config.icon_position || config.iconPosition);
// Use daisyUI CSS variables for theme support
return '<div id="pk-consent-root" class="pk-consent-widget">' +
'<style>' +
// CSS variables using daisyUI theme colors
'.pk-consent-widget{' +
'--pk-bg:oklch(var(--b1));' +
'--pk-bg-alt:oklch(var(--b2));' +
'--pk-border:oklch(var(--b3));' +
'--pk-text:oklch(var(--bc));' +
'--pk-text-muted:oklch(var(--bc)/0.6);' +
'--pk-primary:oklch(var(--p));' +
'--pk-primary-content:oklch(var(--pc));' +
'--pk-primary-soft:oklch(var(--p)/0.1);' +
'--pk-primary-glow:oklch(var(--p)/0.4);' +
'--pk-shadow:0 8px 32px oklch(var(--bc)/0.12);' +
'}' +
'@keyframes pk-breathe{0%,100%{box-shadow:0 0 0 0 var(--pk-primary-glow),0 4px 12px oklch(var(--bc)/0.15)}50%{box-shadow:0 0 0 8px transparent,0 4px 16px oklch(var(--bc)/0.2)}}' +
'@keyframes pk-slide-up{from{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}' +
'@keyframes pk-fade-in{from{opacity:0}to{opacity:1}}' +
'.pk-floating-icon{animation:pk-breathe 3s ease-in-out infinite;transition:transform 0.2s cubic-bezier(0.34,1.56,0.64,1),box-shadow 0.2s ease}' +
'.pk-floating-icon:hover{transform:scale(1.1);animation:none;box-shadow:0 0 0 4px var(--pk-primary-glow),0 8px 24px oklch(var(--bc)/0.25)}' +
'.pk-floating-icon:active{transform:scale(0.95)}' +
'.pk-banner{animation:pk-slide-up 0.4s cubic-bezier(0.16,1,0.3,1) forwards}' +
'.pk-modal-backdrop{animation:pk-fade-in 0.2s ease forwards}' +
'.pk-modal-content{animation:pk-slide-up 0.3s cubic-bezier(0.16,1,0.3,1) forwards}' +
'.pk-glass{background:oklch(var(--b1)/0.95);backdrop-filter:blur(20px) saturate(180%);-webkit-backdrop-filter:blur(20px) saturate(180%);border:1px solid var(--pk-border);box-shadow:var(--pk-shadow)}' +
'.pk-category-card{transition:all 0.2s ease;background:var(--pk-bg-alt);border:1px solid var(--pk-border)}' +
'.pk-category-card:hover{transform:translateY(-2px);box-shadow:0 4px 12px oklch(var(--bc)/0.1)}' +
'.pk-toggle-track{background:var(--pk-border);transition:background 0.2s}' +
'.pk-toggle-track.active{background:var(--pk-primary)}' +
'.pk-toggle-thumb{background:var(--pk-bg);box-shadow:0 1px 3px oklch(var(--bc)/0.2)}' +
'</style>' +
// Floating Icon - using theme colors
(showIcon ?
'<button id="pk-consent-icon" type="button" onclick="window.PhoenixKitConsent.openPreferences()" ' +
'class="pk-floating-icon pk-glass" ' +
'style="position:fixed;z-index:50;width:3rem;height:3rem;border-radius:9999px;display:flex;align-items:center;justify-content:center;cursor:pointer;background:var(--pk-primary);' + iconStyle + '" ' +
'aria-label="Cookie preferences" title="Cookie preferences">' +
'<svg style="width:1.5rem;height:1.5rem;color:var(--pk-primary-content)" viewBox="0 0 24 24" fill="currentColor">' +
'<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"/>' +
'</svg>' +
'</button>' : '') +
// Banner - using theme colors
'<div id="pk-consent-banner" class="pk-banner pk-glass" style="position:fixed;bottom:0;left:0;right:0;z-index:50;display:none;border-radius:0" role="dialog" aria-label="Cookie consent" aria-hidden="true">' +
'<div style="max-width:64rem;margin:0 auto;padding:1rem 1.5rem">' +
'<div style="display:flex;flex-wrap:wrap;align-items:center;gap:1rem">' +
'<div style="flex:1;display:flex;align-items:flex-start;gap:0.75rem;min-width:200px">' +
'<div style="flex-shrink:0;width:2.5rem;height:2.5rem;border-radius:9999px;background:var(--pk-primary-soft);display:flex;align-items:center;justify-content:center">' +
'<svg style="width:1.25rem;height:1.25rem;color:var(--pk-primary)" viewBox="0 0 24 24" fill="currentColor">' +
'<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93z"/>' +
'</svg>' +
'</div>' +
'<div>' +
'<h3 style="font-weight:600;font-size:0.875rem;margin:0;color:var(--pk-text)">We value your privacy</h3>' +
'<p style="font-size:0.75rem;color:var(--pk-text-muted);margin:0.25rem 0 0 0">' +
'We use cookies to enhance your experience. ' +
'<a href="' + (config.cookie_policy_url || '/legal/cookie-policy') + '" style="color:var(--pk-primary);text-decoration:underline" target="_blank">Cookie Policy</a>' +
'</p>' +
'</div>' +
'</div>' +
'<div style="display:flex;gap:0.5rem;flex-wrap:wrap">' +
'<button type="button" onclick="window.PhoenixKitConsent.openPreferences()" class="btn btn-ghost btn-sm" style="font-size:0.75rem">Customize</button>' +
'<button type="button" onclick="window.PhoenixKitConsent.rejectAll()" class="btn btn-outline btn-sm" style="font-size:0.75rem">Reject</button>' +
'<button type="button" onclick="window.PhoenixKitConsent.acceptAll()" class="btn btn-primary btn-sm" style="font-size:0.75rem">Accept All</button>' +
'</div>' +
'</div>' +
'</div>' +
'</div>' +
// Modal - using theme colors
'<div id="pk-consent-modal" style="position:fixed;inset:0;z-index:100;display:none" role="dialog" aria-modal="true" aria-label="Cookie preferences">' +
'<div class="pk-modal-backdrop" onclick="window.PhoenixKitConsent.closePreferences()" style="position:absolute;inset:0;background:oklch(var(--bc)/0.4);backdrop-filter:blur(4px)"></div>' +
'<div style="position:absolute;inset:0;display:flex;align-items:center;justify-content:center;padding:1rem;pointer-events:none">' +
'<div class="pk-modal-content pk-glass" style="width:100%;max-width:28rem;max-height:85vh;overflow:hidden;border-radius:1rem;pointer-events:auto">' +
// Modal Header
'<div style="display:flex;align-items:center;justify-content:space-between;padding:1rem 1.5rem;border-bottom:1px solid var(--pk-border)">' +
'<div style="display:flex;align-items:center;gap:0.75rem;flex:1">' +
'<div style="width:2.5rem;height:2.5rem;border-radius:9999px;background:var(--pk-primary-soft);display:flex;align-items:center;justify-content:center">' +
'<svg style="width:1.25rem;height:1.25rem;color:var(--pk-primary)" viewBox="0 0 24 24" fill="currentColor">' +
'<path d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm0 10.99h7c-.53 4.12-3.28 7.79-7 8.94V12H5V6.3l7-3.11v8.8z"/>' +
'</svg>' +
'</div>' +
'<div>' +
'<h2 style="font-weight:600;font-size:1.125rem;margin:0;color:var(--pk-text)">Privacy Preferences</h2>' +
'<p style="font-size:0.75rem;color:var(--pk-text-muted);margin:0">Manage your cookie settings</p>' +
'</div>' +
'</div>' +
'<button type="button" onclick="window.PhoenixKitConsent.closePreferences()" class="btn btn-ghost btn-sm btn-circle" aria-label="Close">' +
'<svg style="width:1.25rem;height:1.25rem" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">' +
'<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/>' +
'</svg>' +
'</button>' +
'</div>' +
// Categories
'<div style="padding:1rem 1.5rem;overflow-y:auto;max-height:50vh">' +
createCategoryHTML("necessary", "🔒", "Essential", "Required for core functionality. Cannot be disabled.", true) +
createCategoryHTML("analytics", "📊", "Analytics", "Help us understand how you use our site.") +
createCategoryHTML("marketing", "📢", "Marketing", "Used for personalized advertising.") +
createCategoryHTML("preferences", "⚙️", "Preferences", "Remember your settings and preferences.") +
'</div>' +
// Modal Footer
'<div style="padding:1rem 1.5rem;border-top:1px solid var(--pk-border);background:var(--pk-bg-alt)">' +
'<div style="display:flex;flex-wrap:wrap;align-items:center;gap:0.75rem">' +
'<div style="font-size:0.75rem;color:var(--pk-text-muted)">' +
'<a href="' + (config.privacy_policy_url || '/legal/privacy-policy') + '" style="color:inherit;text-decoration:underline" target="_blank">Privacy Policy</a>' +
' • ' +
'<a href="' + (config.cookie_policy_url || '/legal/cookie-policy') + '" style="color:inherit;text-decoration:underline" target="_blank">Cookie Policy</a>' +
'</div>' +
'<div style="margin-left:auto;display:flex;gap:0.5rem">' +
'<button type="button" onclick="window.PhoenixKitConsent.rejectAll()" class="btn btn-ghost btn-sm" style="font-size:0.75rem">Reject All</button>' +
'<button type="button" onclick="window.PhoenixKitConsent.savePreferences()" class="btn btn-primary btn-sm" style="font-size:0.75rem">Save Preferences</button>' +
'</div>' +
'</div>' +
'</div>' +
'</div>' +
'</div>' +
'</div>' +
'</div>';
}
function createCategoryHTML(id, icon, name, description, required) {
var checkedAttr = required ? ' checked disabled' : '';
var requiredBadge = required ? '<span class="badge badge-ghost badge-xs" style="margin-left:0.5rem">Required</span>' : '';
return '<div class="pk-category-card" style="border-radius:0.75rem;padding:1rem;margin-bottom:0.75rem">' +
'<div style="display:flex;align-items:flex-start;justify-content:space-between;gap:0.75rem">' +
'<div style="display:flex;align-items:flex-start;gap:0.75rem;flex:1">' +
'<span style="font-size:1.25rem">' + icon + '</span>' +
'<div>' +
'<div style="display:flex;align-items:center">' +
'<span style="font-weight:500;font-size:0.875rem;color:var(--pk-text)">' + name + '</span>' +
requiredBadge +
'</div>' +
'<p style="font-size:0.75rem;color:var(--pk-text-muted);margin:0.25rem 0 0 0">' + description + '</p>' +
'</div>' +
'</div>' +
'<label style="position:relative;display:inline-flex;cursor:pointer;flex-shrink:0">' +
'<input type="checkbox" id="pk-consent-' + id + '" class="toggle toggle-primary toggle-sm" data-category="' + id + '"' + checkedAttr + '>' +
'</label>' +
'</div>' +
'</div>';
}
function injectWidget(config) {
// Remove existing widget if any
var existing = document.getElementById("pk-consent-root");
if (existing) {
existing.remove();
}
// Create widget container
var container = document.createElement("div");
container.innerHTML = createWidgetHTML(config);
document.body.appendChild(container.firstChild);
log("Widget injected into DOM");
}
// =====================================================
// UI MANAGEMENT
// =====================================================
function showBanner() {
var banner = document.getElementById("pk-consent-banner");
if (banner) {
banner.style.display = "block";
banner.setAttribute("aria-hidden", "false");
}
}
function hideBanner() {
var banner = document.getElementById("pk-consent-banner");
if (banner) {
banner.style.display = "none";
banner.setAttribute("aria-hidden", "true");
}
}
function showIcon() {
var icon = document.getElementById("pk-consent-icon");
if (icon && isOptInMode()) {
icon.style.display = "flex";
icon.style.opacity = "1";
}
}
function hideIcon() {
var icon = document.getElementById("pk-consent-icon");
if (icon) {
icon.style.opacity = "0";
}
}
function showModal() {
var modal = document.getElementById("pk-consent-modal");
if (modal) {
modal.style.display = "block";
hideBanner();
setTimeout(function() {
var firstButton = modal.querySelector("button");
if (firstButton) firstButton.focus();
}, 100);
}
}
function hideModal() {
var modal = document.getElementById("pk-consent-modal");
if (modal) {
modal.style.display = "none";
}
}
function updateCheckboxes() {
var consent = PhoenixKitConsent.consent || {};
CATEGORIES.forEach(function(category) {
var checkbox = document.getElementById("pk-consent-" + category);
if (checkbox && !checkbox.disabled) {
checkbox.checked = !!consent[category];
}
});
}
function readCheckboxes() {
var preferences = { necessary: true };
CATEGORIES.forEach(function(category) {
if (category === "necessary") return;
var checkbox = document.getElementById("pk-consent-" + category);
preferences[category] = checkbox ? checkbox.checked : false;
});
return preferences;
}
function updateUI() {
if (shouldShowBanner()) {
showBanner();
hideIcon();
} else {
hideBanner();
showIcon();
}
updateCheckboxes();
}
// =====================================================
// PUBLIC API
// =====================================================
PhoenixKitConsent.acceptAll = function() {
var consent = {
necessary: true,
analytics: true,
marketing: true,
preferences: true,
timestamp: new Date().toISOString()
};
PhoenixKitConsent.consent = consent;
saveConsent(consent);
applyConsent(consent);
hideBanner();
hideModal();
showIcon();
log("All cookies accepted");
};
PhoenixKitConsent.rejectAll = function() {
var consent = {
necessary: true,
analytics: false,
marketing: false,
preferences: false,
timestamp: new Date().toISOString()
};
PhoenixKitConsent.consent = consent;
saveConsent(consent);
applyConsent(consent);
hideBanner();
hideModal();
showIcon();
log("Non-essential cookies rejected");
};
PhoenixKitConsent.savePreferences = function() {
var preferences = readCheckboxes();
preferences.timestamp = new Date().toISOString();
PhoenixKitConsent.consent = preferences;
saveConsent(preferences);
applyConsent(preferences);
hideModal();
showIcon();
log("Preferences saved", preferences);
};
PhoenixKitConsent.openPreferences = function() {
updateCheckboxes();
showModal();
log("Preferences modal opened");
};
PhoenixKitConsent.closePreferences = function() {
hideModal();
log("Preferences modal closed");
};
PhoenixKitConsent.getConsent = function() {
return PhoenixKitConsent.consent;
};
PhoenixKitConsent.hasConsent = function(category) {
return PhoenixKitConsent.consent && !!PhoenixKitConsent.consent[category];
};
PhoenixKitConsent.revokeConsent = function() {
try {
localStorage.removeItem(STORAGE_KEY);
localStorage.removeItem(VERSION_KEY);
PhoenixKitConsent.consent = null;
updateUI();
log("Consent revoked");
} catch (e) {
log("Could not revoke consent", e);
}
};
// =====================================================
// INITIALIZATION
// =====================================================
function initFromConfig(config) {
// Check if widget should be shown (respects hide_for_authenticated)
if (config.should_show === false) {
log("Widget hidden (user authenticated or disabled)");
return;
}
PhoenixKitConsent.config = {
frameworks: config.frameworks || [],
consentMode: config.consent_mode || "strict",
policyVersion: config.policy_version || "1.0",
googleConsentMode: config.google_consent_mode || false,
iconPosition: config.icon_position || "bottom-right",
showIcon: config.show_icon || false,
cookiePolicyUrl: config.cookie_policy_url || "/legal/cookie-policy",
privacyPolicyUrl: config.privacy_policy_url || "/legal/privacy-policy"
};
// Inject widget into DOM
injectWidget(config);
// Cache DOM elements
PhoenixKitConsent.elements = {
root: document.getElementById("pk-consent-root"),
icon: document.getElementById("pk-consent-icon"),
banner: document.getElementById("pk-consent-banner"),
modal: document.getElementById("pk-consent-modal")
};
// Initialize Google Consent Mode (only in strict mode)
if (PhoenixKitConsent.config.consentMode === "strict") {
initGoogleConsentMode();
}
// Setup cross-tab sync
setupCrossTabSync();
// Load existing consent
var stored = loadConsent();
if (stored) {
PhoenixKitConsent.consent = stored;
applyConsent(stored);
} else if (isOptInMode() && PhoenixKitConsent.config.consentMode === "strict") {
// Only block scripts in strict mode
blockScripts();
}
// Update UI
updateUI();
// Handle Escape key
document.addEventListener("keydown", function(e) {
if (e.key === "Escape") {
hideModal();
}
});
PhoenixKitConsent.initialized = true;
log("Initialized with config", PhoenixKitConsent.config);
}
function initFromElement(rootElement) {
var config = {
frameworks: JSON.parse(rootElement.dataset.frameworks || "[]"),
consent_mode: rootElement.dataset.consentMode || "strict",
policy_version: rootElement.dataset.policyVersion || "1.0",
google_consent_mode: rootElement.dataset.googleConsentMode === "true",
icon_position: rootElement.dataset.iconPosition || "bottom-right",
show_icon: rootElement.dataset.showIcon === "true",
cookie_policy_url: rootElement.dataset.cookiePolicyUrl || "/legal/cookie-policy",
privacy_policy_url: rootElement.dataset.privacyPolicyUrl || "/legal/privacy-policy"
};
PhoenixKitConsent.config = {
frameworks: config.frameworks,
consentMode: config.consent_mode,
policyVersion: config.policy_version,
googleConsentMode: config.google_consent_mode,
iconPosition: config.icon_position,
showIcon: config.show_icon,
cookiePolicyUrl: config.cookie_policy_url,
privacyPolicyUrl: config.privacy_policy_url
};
PhoenixKitConsent.elements = {
root: rootElement,
icon: document.getElementById("pk-consent-icon"),
banner: document.getElementById("pk-consent-banner"),
modal: document.getElementById("pk-consent-modal")
};
// Initialize Google Consent Mode (only in strict mode)
if (PhoenixKitConsent.config.consentMode === "strict") {
initGoogleConsentMode();
}
setupCrossTabSync();
var stored = loadConsent();
if (stored) {
PhoenixKitConsent.consent = stored;
applyConsent(stored);
} else if (isOptInMode() && PhoenixKitConsent.config.consentMode === "strict") {
// Only block scripts in strict mode
blockScripts();
}
updateUI();
document.addEventListener("keydown", function(e) {
if (e.key === "Escape") {
hideModal();
}
});
PhoenixKitConsent.initialized = true;
log("Initialized from element", PhoenixKitConsent.config);
}
function fetchConfigAndInit() {
fetch(getConfigEndpoint(), { credentials: "same-origin" })
.then(function(response) {
if (!response.ok) {
throw new Error("Config endpoint returned " + response.status);
}
return response.json();
})
.then(function(config) {
if (config.enabled && config.should_show !== false) {
initFromConfig(config);
} else {
log("Consent widget is disabled");
// Reset Google Consent Mode to granted state when widget is disabled
resetGoogleConsentMode();
}
})
.catch(function(err) {
log("Could not fetch config (widget disabled or endpoint unavailable)", err);
});
}
// =====================================================
// PHOENIX LIVEVIEW HOOK
// =====================================================
var CookieConsentHook = {
mounted: function() {
if (!PhoenixKitConsent.initialized) {
initFromElement(this.el);
}
},
destroyed: function() {
PhoenixKitConsent.initialized = false;
}
};
// Export hook for Phoenix LiveView
window.PhoenixKitHooks = window.PhoenixKitHooks || {};
window.PhoenixKitHooks.CookieConsent = CookieConsentHook;
// Export module
window.PhoenixKitConsent = PhoenixKitConsent;
// Auto-init: fetch config from API and inject widget
document.addEventListener("DOMContentLoaded", function() {
// Check if widget already exists (injected via component)
var existingRoot = document.getElementById("pk-consent-root");
if (existingRoot) {
// Widget already in DOM, will be initialized by LiveView hook
log("Widget already in DOM, waiting for LiveView hook");
return;
}
// Otherwise, fetch config and auto-inject
fetchConfigAndInit();
});
})();