@whotechs/react-gdpr-cookie-banner
Version:
A modern, customizable GDPR & Google Consent Mode v2 compliant cookie consent banner
205 lines (197 loc) • 16.6 kB
JavaScript
import React, { useState, useEffect } from 'react';
import styled from 'styled-components';
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
var __assign = function() {
__assign = Object.assign || function __assign(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
function __makeTemplateObject(cooked, raw) {
if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; }
return cooked;
}
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};
var defaultProps = {
primaryColor: '#FFD700',
textColor: '#FFFFFF',
backgroundColor: 'rgba(0, 0, 0, 0.95)',
className: '',
onPreferencesUpdate: function () { },
titleText: 'Privacy Preferences Center',
descriptionText: 'We use cookies and similar technologies to help personalize content, tailor and measure ads, and provide a better experience. By clicking "Accept All", you agree to this use. You can manage your preferences below. These settings will apply to all websites in this domain.',
showDelay: 1000,
gtmContainerId: ''
};
var BannerContainer = styled.div(templateObject_1 || (templateObject_1 = __makeTemplateObject(["\n position: fixed;\n bottom: 0;\n left: 0;\n right: 0;\n background: ", ";\n color: ", ";\n padding: 1.5rem;\n display: flex;\n flex-direction: column;\n gap: 1.5rem;\n z-index: 9999;\n transform: ", ";\n transition: transform 0.3s ease-in-out;\n opacity: ", ";\n visibility: ", ";\n max-width: 1200px;\n margin: 0 auto;\n border-top-left-radius: 12px;\n border-top-right-radius: 12px;\n box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.15);\n \n @media (max-width: 768px) {\n padding: 1rem;\n }\n"], ["\n position: fixed;\n bottom: 0;\n left: 0;\n right: 0;\n background: ", ";\n color: ", ";\n padding: 1.5rem;\n display: flex;\n flex-direction: column;\n gap: 1.5rem;\n z-index: 9999;\n transform: ", ";\n transition: transform 0.3s ease-in-out;\n opacity: ", ";\n visibility: ", ";\n max-width: 1200px;\n margin: 0 auto;\n border-top-left-radius: 12px;\n border-top-right-radius: 12px;\n box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.15);\n \n @media (max-width: 768px) {\n padding: 1rem;\n }\n"])), function (props) { return props.backgroundColor; }, function (props) { return props.textColor; }, function (_a) {
var show = _a.show;
return (show ? 'translateY(0)' : 'translateY(100%)');
}, function (_a) {
var show = _a.show;
return (show ? '1' : '0');
}, function (_a) {
var show = _a.show;
return (show ? 'visible' : 'hidden');
});
var Message = styled.div(templateObject_2 || (templateObject_2 = __makeTemplateObject(["\n display: flex;\n flex-direction: column;\n gap: 1rem;\n"], ["\n display: flex;\n flex-direction: column;\n gap: 1rem;\n"])));
var Title = styled.h3(templateObject_3 || (templateObject_3 = __makeTemplateObject(["\n margin: 0;\n font-size: 1.2rem;\n font-weight: 600;\n"], ["\n margin: 0;\n font-size: 1.2rem;\n font-weight: 600;\n"])));
var Description = styled.p(templateObject_4 || (templateObject_4 = __makeTemplateObject(["\n margin: 0;\n font-size: 0.9rem;\n line-height: 1.5;\n color: rgba(255, 255, 255, 0.9);\n"], ["\n margin: 0;\n font-size: 0.9rem;\n line-height: 1.5;\n color: rgba(255, 255, 255, 0.9);\n"])));
var PreferenceSection = styled.div(templateObject_5 || (templateObject_5 = __makeTemplateObject(["\n display: flex;\n flex-wrap: wrap;\n gap: 1rem;\n"], ["\n display: flex;\n flex-wrap: wrap;\n gap: 1rem;\n"])));
var PreferenceItem = styled.div(templateObject_6 || (templateObject_6 = __makeTemplateObject(["\n display: flex;\n align-items: center;\n gap: 0.5rem;\n"], ["\n display: flex;\n align-items: center;\n gap: 0.5rem;\n"])));
var Switch = styled.label(templateObject_7 || (templateObject_7 = __makeTemplateObject(["\n position: relative;\n display: inline-block;\n width: 48px;\n height: 24px;\n \n input {\n opacity: 0;\n width: 0;\n height: 0;\n \n &:checked + span {\n background-color: ", ";\n }\n \n &:checked + span:before {\n transform: translateX(24px);\n }\n \n &:disabled + span {\n opacity: 0.5;\n cursor: not-allowed;\n }\n }\n \n span {\n position: absolute;\n cursor: pointer;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background-color: #ccc;\n transition: 0.3s;\n border-radius: 24px;\n \n &:before {\n position: absolute;\n content: \"\";\n height: 18px;\n width: 18px;\n left: 3px;\n bottom: 3px;\n background-color: white;\n transition: 0.3s;\n border-radius: 50%;\n }\n }\n"], ["\n position: relative;\n display: inline-block;\n width: 48px;\n height: 24px;\n \n input {\n opacity: 0;\n width: 0;\n height: 0;\n \n &:checked + span {\n background-color: ", ";\n }\n \n &:checked + span:before {\n transform: translateX(24px);\n }\n \n &:disabled + span {\n opacity: 0.5;\n cursor: not-allowed;\n }\n }\n \n span {\n position: absolute;\n cursor: pointer;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background-color: #ccc;\n transition: 0.3s;\n border-radius: 24px;\n \n &:before {\n position: absolute;\n content: \"\";\n height: 18px;\n width: 18px;\n left: 3px;\n bottom: 3px;\n background-color: white;\n transition: 0.3s;\n border-radius: 50%;\n }\n }\n"])), function (props) { return props.$primaryColor; });
var ButtonGroup = styled.div(templateObject_8 || (templateObject_8 = __makeTemplateObject(["\n display: flex;\n gap: 1rem;\n margin-top: 1rem;\n \n @media (max-width: 768px) {\n flex-direction: column;\n }\n"], ["\n display: flex;\n gap: 1rem;\n margin-top: 1rem;\n \n @media (max-width: 768px) {\n flex-direction: column;\n }\n"])));
var Button = styled.button(templateObject_9 || (templateObject_9 = __makeTemplateObject(["\n padding: 0.75rem 1.5rem;\n border-radius: 6px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.2s ease;\n font-size: 0.9rem;\n \n &.accept-all {\n background: ", ";\n color: black;\n border: none;\n \n &:hover {\n filter: brightness(0.9);\n }\n }\n \n &.accept-selected {\n background: transparent;\n color: ", ";\n border: 1px solid ", ";\n \n &:hover {\n background: ", "1A;\n }\n }\n \n &.decline-all {\n background: transparent;\n color: ", ";\n border: 1px solid ", ";\n \n &:hover {\n background: ", "1A;\n }\n }\n \n @media (max-width: 768px) {\n width: 100%;\n }\n"], ["\n padding: 0.75rem 1.5rem;\n border-radius: 6px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.2s ease;\n font-size: 0.9rem;\n \n &.accept-all {\n background: ", ";\n color: black;\n border: none;\n \n &:hover {\n filter: brightness(0.9);\n }\n }\n \n &.accept-selected {\n background: transparent;\n color: ", ";\n border: 1px solid ", ";\n \n &:hover {\n background: ", "1A;\n }\n }\n \n &.decline-all {\n background: transparent;\n color: ", ";\n border: 1px solid ", ";\n \n &:hover {\n background: ", "1A;\n }\n }\n \n @media (max-width: 768px) {\n width: 100%;\n }\n"])), function (props) { return props.$primaryColor; }, function (props) { return props.$textColor; }, function (props) { return props.$primaryColor; }, function (props) { return props.$primaryColor; }, function (props) { return props.$textColor; }, function (props) { return props.$textColor; }, function (props) { return props.$textColor; });
var CookieBanner = function (props) {
var _a = __assign(__assign({}, defaultProps), props), primaryColor = _a.primaryColor, textColor = _a.textColor, backgroundColor = _a.backgroundColor, className = _a.className, onPreferencesUpdate = _a.onPreferencesUpdate, titleText = _a.titleText, descriptionText = _a.descriptionText, showDelay = _a.showDelay, gtmContainerId = _a.gtmContainerId;
var _b = useState(false), show = _b[0], setShow = _b[1];
var _c = useState({
essential: true,
analytics_storage: false,
ad_storage: false,
ad_user_data: false,
ad_personalization: false
}), preferences = _c[0], setPreferences = _c[1];
var updateGoogleConsent = function (prefs) {
if (!gtmContainerId)
return;
window.dataLayer = window.dataLayer || [];
window.gtag = window.gtag || function () {
window.dataLayer.push(arguments);
};
window.gtag('consent', 'update', {
'ad_user_data': prefs.ad_user_data ? 'granted' : 'denied',
'ad_personalization': prefs.ad_personalization ? 'granted' : 'denied',
'ad_storage': prefs.ad_storage ? 'granted' : 'denied',
'analytics_storage': prefs.analytics_storage ? 'granted' : 'denied'
});
};
useEffect(function () {
var checkConsent = function () {
try {
var savedConsent = localStorage.getItem('cookieConsent');
if (!savedConsent) {
setShow(true);
if (gtmContainerId) {
window.dataLayer = window.dataLayer || [];
window.gtag = window.gtag || function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
window.dataLayer.push(arguments);
};
window.gtag('consent', 'default', {
'ad_user_data': 'denied',
'ad_personalization': 'denied',
'ad_storage': 'denied',
'analytics_storage': 'denied',
'wait_for_update': 500
});
}
}
else {
var savedPreferences = JSON.parse(savedConsent);
setPreferences(savedPreferences);
updateGoogleConsent(savedPreferences);
}
}
catch (error) {
console.error('localStorage error:', error);
setShow(true);
}
};
if (typeof window !== 'undefined') {
setTimeout(checkConsent, showDelay);
}
}, [showDelay, gtmContainerId]);
var handlePreferenceChange = function (key) {
setPreferences(function (prev) {
var _a;
return (__assign(__assign({}, prev), (_a = {}, _a[key] = !prev[key], _a)));
});
};
var handleAcceptAll = function () {
var allAccepted = {
essential: true,
analytics_storage: true,
ad_storage: true,
ad_user_data: true,
ad_personalization: true
};
setPreferences(allAccepted);
localStorage.setItem('cookieConsent', JSON.stringify(allAccepted));
updateGoogleConsent(allAccepted);
onPreferencesUpdate(allAccepted);
setShow(false);
};
var handleAcceptSelected = function () {
localStorage.setItem('cookieConsent', JSON.stringify(preferences));
updateGoogleConsent(preferences);
onPreferencesUpdate(preferences);
setShow(false);
};
var handleDeclineAll = function () {
var allDeclined = {
essential: true,
analytics_storage: false,
ad_storage: false,
ad_user_data: false,
ad_personalization: false
};
setPreferences(allDeclined);
localStorage.setItem('cookieConsent', JSON.stringify(allDeclined));
updateGoogleConsent(allDeclined);
onPreferencesUpdate(allDeclined);
setShow(false);
};
return (React.createElement(BannerContainer, { show: show, backgroundColor: backgroundColor, textColor: textColor, className: className, role: "dialog", "aria-modal": "true", "aria-label": "Cookie consent preferences" },
React.createElement(Message, null,
React.createElement(Title, null, titleText),
React.createElement(Description, null, descriptionText)),
React.createElement(PreferenceSection, null,
React.createElement(PreferenceItem, null,
React.createElement(Switch, { "$primaryColor": primaryColor },
React.createElement("input", { type: "checkbox", checked: preferences.essential, disabled: true, "aria-label": "Essential cookies" }),
React.createElement("span", null)),
React.createElement("span", null, "Essential Cookies (Required)")),
React.createElement(PreferenceItem, null,
React.createElement(Switch, { "$primaryColor": primaryColor },
React.createElement("input", { type: "checkbox", checked: preferences.analytics_storage, onChange: function () { return handlePreferenceChange('analytics_storage'); }, "aria-label": "Analytics cookies" }),
React.createElement("span", null)),
React.createElement("span", null, "Analytics Cookies")),
React.createElement(PreferenceItem, null,
React.createElement(Switch, { "$primaryColor": primaryColor },
React.createElement("input", { type: "checkbox", checked: preferences.ad_storage, onChange: function () { return handlePreferenceChange('ad_storage'); }, "aria-label": "Advertising cookies" }),
React.createElement("span", null)),
React.createElement("span", null, "Advertising Cookies")),
React.createElement(PreferenceItem, null,
React.createElement(Switch, { "$primaryColor": primaryColor },
React.createElement("input", { type: "checkbox", checked: preferences.ad_personalization, onChange: function () { return handlePreferenceChange('ad_personalization'); }, "aria-label": "Personalization cookies" }),
React.createElement("span", null)),
React.createElement("span", null, "Personalization"))),
React.createElement(ButtonGroup, null,
React.createElement(Button, { "$primaryColor": primaryColor, "$textColor": textColor, className: "accept-all", onClick: handleAcceptAll, "aria-label": "Accept all cookies" }, "Accept All"),
React.createElement(Button, { "$primaryColor": primaryColor, "$textColor": textColor, className: "accept-selected", onClick: handleAcceptSelected, "aria-label": "Accept selected cookies" }, "Accept Selected"),
React.createElement(Button, { "$primaryColor": primaryColor, "$textColor": textColor, className: "decline-all", onClick: handleDeclineAll, "aria-label": "Decline all cookies" }, "Decline All"))));
};
var templateObject_1, templateObject_2, templateObject_3, templateObject_4, templateObject_5, templateObject_6, templateObject_7, templateObject_8, templateObject_9;
export { CookieBanner };
//# sourceMappingURL=index.esm.js.map