react-cookie-kit
Version:
GDPR / CCPA Easy Cookie, Script, Do-Not-Sell, and Fingerprint Consent Management for Websites.
883 lines (827 loc) • 33.5 kB
JavaScript
import React from "react";
import PropTypes from "prop-types";
import ReactCountryFlag from "react-country-flag";
import {
cookieDefns as allAvailCookieDefns,
cookieTypes,
locales,
links,
themes,
} from "xcoobee-cookie-kit-core/src/configs";
import { countryCodes } from "xcoobee-cookie-kit-core/src/countryData";
import renderText from "xcoobee-cookie-kit-core/src/renderText";
import { getLocale, saveLocale, getCountryCode, saveCountryCode } from "xcoobee-cookie-kit-core/src/LocaleManager";
import closeIcon from "./assets/close-icon.svg";
import xbLogo from "./assets/xcoobee-logo.svg";
import caretUp from "./assets/caret-arrow-up.png";
import caretDown from "./assets/caret-down.png";
import CookieConsentShape from "./lib/CookieConsentShape";
import { xbOrigin } from "./configs";
const OPTION = "loginstatus";
export default class CookieKitPopup extends React.PureComponent {
static propTypes = {
companyLogo: PropTypes.string,
cookieConsents: PropTypes.arrayOf(CookieConsentShape.isRequired).isRequired,
displayDoNotSell: PropTypes.bool,
displayFingerprint: PropTypes.bool,
doNotSellConsent: PropTypes.bool,
fingerprintConsent: PropTypes.bool,
hideBrandTag: PropTypes.bool.isRequired,
isConnected: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
onLogin: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
loginStatus: PropTypes.bool,
privacyUrl: PropTypes.string.isRequired,
requestDataTypes: PropTypes.arrayOf(
PropTypes.oneOf(cookieTypes).isRequired,
).isRequired,
termsUrl: PropTypes.string.isRequired,
textMessage: PropTypes.oneOfType([
PropTypes.string.isRequired,
PropTypes.shape({
"de-de": PropTypes.string,
"en-us": PropTypes.string,
"es-419": PropTypes.string,
"fr-fr": PropTypes.string,
}).isRequired,
]).isRequired,
theme: PropTypes.oneOf(themes),
};
static defaultProps = {
companyLogo: null,
displayDoNotSell: false,
displayFingerprint: false,
doNotSellConsent: false,
fingerprintConsent: false,
loginStatus: false,
theme: "popup",
};
constructor(props) {
// console.log('CookieKitPopup#constructor');
super(props);
const { cookieConsents, fingerprintConsent, doNotSellConsent, requestDataTypes } = this.props;
const consentSettings = {};
cookieConsents.filter(cookieConsent => requestDataTypes.includes(cookieConsent.type)).forEach((cookieConsent) => {
consentSettings[cookieConsent.type] = cookieConsent.checked;
});
// Ensure all requested data types are in consent settings. Otherwise logic using
// state.consentSettings doesn't work correctly.
requestDataTypes.forEach((dataType) => {
if (!(dataType in consentSettings)) {
consentSettings[dataType] = false;
}
});
this.state = {
consentSettings,
countryCode: getCountryCode() || "EU",
doNotSellConsent,
fingerprintConsent,
isCountrySelectShown: false,
isLocaleSelectShown: false,
selectedLocale: getLocale() || "en-us",
};
window.addEventListener("message", this.onMessage);
}
componentWillUnmount() {
// console.log('CookieKitPopup#componentWillUnmount');
window.removeEventListener("message", this.onMessage);
}
onMessage = (event) => {
if (!event.data || typeof event.data !== "string") {
return;
}
const { onLogin } = this.props;
const loginStatus = JSON.parse(event.data)[OPTION];
if (loginStatus) {
onLogin();
}
};
handleLocaleChange = (locale) => {
// console.log('CookieKitPopup#handleLocaleChange');
this.setState({ selectedLocale: locale.code, isLocaleSelectShown: false });
saveLocale(locale.code);
};
handleCountryChange = (countryCode) => {
// console.log('CookieKitPopup#handleCountryChange');
this.setState({ countryCode, isCountrySelectShown: false });
saveCountryCode(countryCode);
};
handleCountrySelectToggle = (e) => {
// console.log('CookieKitPopup#handleCountrySelectToggle');
e.stopPropagation();
this.setState(state => ({ isCountrySelectShown: !state.isCountrySelectShown, isLocaleSelectShown: false }));
};
handleLocaleSelectToggle = (e) => {
// console.log('CookieKitPopup#handleLocaleSelectToggle');
e.stopPropagation();
this.setState(state => ({ isLocaleSelectShown: !state.isLocaleSelectShown, isCountrySelectShown: false }));
};
handleCookieCheck = (e, type) => {
// console.log('CookieKitPopup#handleCookieCheck');
const { consentSettings } = this.state;
const checked = {
...consentSettings,
};
checked[type] = e.target.checked;
this.setState({ consentSettings: checked });
};
handleCheckAll = () => {
// console.log('CookieKitPopup#handleCheckAll');
const { displayDoNotSell, displayFingerprint } = this.props;
const { consentSettings } = this.state;
const isAllConsentsChecked = this.areAllConsentsChecked();
if (isAllConsentsChecked) {
const noneChecked = {};
Object.keys(consentSettings).forEach((cookieName) => {
noneChecked[cookieName] = false;
});
this.setState({
consentSettings: noneChecked,
doNotSellConsent: false,
fingerprintConsent: false,
});
} else {
const allChecked = {};
Object.keys(consentSettings).forEach((cookieName) => {
allChecked[cookieName] = true;
});
this.setState({
consentSettings: allChecked,
doNotSellConsent: displayDoNotSell,
fingerprintConsent: displayFingerprint,
});
}
};
handleDoNotSellCheck = (e) => {
// console.log('CookieKitPopup#handleDoNotSellCheck');
this.setState({ doNotSellConsent: e.target.checked });
};
handleFingerprintCheck = (e) => {
// console.log('CookieKitPopup#handleFingerprintCheck');
this.setState({ fingerprintConsent: e.target.checked });
};
handleSubmit = () => {
// console.log('CookieKitPopup#handleSubmit');
const { onSubmit } = this.props;
const { consentSettings, doNotSellConsent, fingerprintConsent } = this.state;
onSubmit(consentSettings, doNotSellConsent, fingerprintConsent);
};
handleScroll = (e, direction) => {
e.stopPropagation();
const currentScrollPosition = this.countryPicker.scrollTop;
if (direction === "up") {
this.countryPicker.scrollTop = currentScrollPosition - 22;
} else {
this.countryPicker.scrollTop = currentScrollPosition + 22;
}
};
areAllConsentsChecked() {
const { displayDoNotSell, displayFingerprint } = this.props;
const { consentSettings, doNotSellConsent, fingerprintConsent } = this.state;
const isAllCookiesChecked = Object.values(consentSettings).every(checked => checked);
let isAllConsentsChecked = isAllCookiesChecked;
if (isAllConsentsChecked && displayDoNotSell) {
isAllConsentsChecked = doNotSellConsent;
}
if (isAllConsentsChecked && displayFingerprint) {
isAllConsentsChecked = fingerprintConsent;
}
return isAllConsentsChecked;
}
renderTextMessage(textMessage) {
// console.log('CookieKitPopup#renderTextMessage');
if (typeof textMessage === "string") {
return textMessage;
}
const { selectedLocale } = this.state;
return textMessage[selectedLocale] || textMessage["en-us"];
}
render() {
// console.log('CookieKitPopup#render');
const {
companyLogo,
displayFingerprint,
displayDoNotSell,
hideBrandTag,
loginStatus,
isConnected,
onClose,
privacyUrl,
requestDataTypes,
termsUrl,
textMessage,
theme,
} = this.props;
const {
consentSettings,
countryCode,
doNotSellConsent,
fingerprintConsent,
isCountrySelectShown,
isLocaleSelectShown,
selectedLocale,
} = this.state;
// console.log("countryCode:", countryCode);
const appearsToBeLoggedIn = loginStatus;
const targetUrl = encodeURIComponent(window.location.origin);
const isAllConsentsChecked = this.areAllConsentsChecked();
const cookieDefns = allAvailCookieDefns.filter(
defn => requestDataTypes.includes(defn.type),
);
const BLOCK = theme === "popup" ? "xb-cookie-kit-popup" : "xb-cookie-kit-overlay";
const loginModalFeatures = "left=400, top=100, width=500, height=600";
let consentCount = cookieDefns.length;
if (displayFingerprint) {
consentCount += 1;
}
if (displayDoNotSell) {
consentCount += 1;
}
let layout;
if (theme === "popup") {
layout = (
// eslint-disable-next-line max-len
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions
<div
className={BLOCK}
onClick={() => this.setState({ isCountrySelectShown: false, isLocaleSelectShown: false })}
role="dialog"
tabIndex="-1"
>
<div className={`${BLOCK}__header`}>
<div className={`${BLOCK}__logo`}>
{
isConnected && companyLogo && (
<img
className={`${BLOCK}__company-logo`}
src={companyLogo}
alt="Company logo"
/>
)
}
</div>
<div className={`${BLOCK}__title`}>{renderText("CookieKit.Title", selectedLocale)}</div>
<button
type="button"
className={`xb-cookie-kit__button ${BLOCK}__close-button`}
onClick={onClose}
>
<img
className={`${BLOCK}__close-button-icon`}
src={closeIcon}
alt="close-icon"
/>
</button>
</div>
<div className={`${BLOCK}__text-container`}>
<div className={`${BLOCK}__text`}>
{ this.renderTextMessage(textMessage) }
</div>
<div className={`${BLOCK}__locale-container`}>
<div className={`${BLOCK}__locale`}>
<button
type="button"
className={`xb-cookie-kit__button ${BLOCK}__language-picker`}
onClick={this.handleLocaleSelectToggle}
>
{ locales.find(locale => locale.code === selectedLocale)?.text || "?" }
</button>
<div
className={`${BLOCK}__block ${BLOCK}__block--sm ${BLOCK}__country-picker`}
title={countryCode}
>
<button
type="button"
className={`xb-cookie-kit__button ${BLOCK}__country-picker-button`}
onClick={this.handleCountrySelectToggle}
>
<div className={`${BLOCK}__flag`}>
<ReactCountryFlag code={countryCode} svg />
</div>
</button>
</div>
</div>
{ isLocaleSelectShown && (
<div className={`${BLOCK}__custom-select`}>
{ locales.map(locale => (
<button
key={locale.code}
className={`xb-cookie-kit__button ${BLOCK}__language-picker-button`}
type="button"
onClick={() => this.handleLocaleChange(locale)}
>
{locale.text}
</button>
))}
</div>
)}
{ isCountrySelectShown && (
<div className={`${BLOCK}__country-picker-select`}>
{ countryCodes.map(cCode => (
<button
type="button"
key={`country-flag-${cCode}`}
className={`xb-cookie-kit__button ${BLOCK}__country-picker-button`}
onClick={() => this.handleCountryChange(cCode)}
title={cCode}
>
<div className={`${BLOCK}__flag`}>
<ReactCountryFlag code={cCode} svg />
</div>
</button>
))}
</div>
)}
</div>
</div>
<div className={`${BLOCK}__cookie-list`}>
{ cookieDefns.map(cookieDefn => (
<div key={cookieDefn.type} className={`${BLOCK}__cookie`}>
<div className={`${BLOCK}__block ${BLOCK}__block--lg`}>
<div>
<input
id={`xbCheckbox_${cookieDefn.id}`}
type="checkbox"
// Application cookies are always checked.
checked={cookieDefn.type === "application" || consentSettings[cookieDefn.type]}
// Application cookies are always disabled.
disabled={cookieDefn.type === "application"}
onChange={e => this.handleCookieCheck(e, cookieDefn.type)}
/>
<label
htmlFor={`xbCheckbox_${cookieDefn.id}`}
className={`${BLOCK}__checkbox`}
// Application cookies are always disabled.
disabled={cookieDefn.type === "application"}
/>
</div>
<div
className={`${BLOCK}__consent-label`}
title={renderText("CookieKit.MoreInfo", selectedLocale)}
>
<a
className={`${BLOCK}__consent-label-link`}
href={cookieDefn.url}
target="_blank"
rel="noopener noreferrer"
>
{renderText(cookieDefn.localeKey, selectedLocale)}
</a>
</div>
</div>
</div>
))}
</div>
{ displayFingerprint || displayDoNotSell
? (
<div className={`${BLOCK}__consents-container`}>
{ displayFingerprint && (
<div className={`${BLOCK}__consent ${BLOCK}__consent--fingerprint`}>
<div className={`${BLOCK}__consent-checkbox`}>
<input
id="xbCheckbox_fingerprint"
type="checkbox"
checked={fingerprintConsent}
onChange={this.handleFingerprintCheck}
/>
<label
htmlFor="xbCheckbox_fingerprint"
className={`${BLOCK}__checkbox`}
/>
</div>
<div
className={`${BLOCK}__consent-label`}
title={renderText("CookieKit.MoreInfo", selectedLocale)}
>
<a
className={`${BLOCK}__consent-label-link`}
href={links.fingerprinting}
target="_blank"
rel="noopener noreferrer"
>
{renderText("CookieKit.FingerprintLabel", selectedLocale)}
</a>
</div>
</div>
)}
{ displayDoNotSell && (
<div className={`${BLOCK}__consent ${BLOCK}__consent--donotsell`}>
<div className={`${BLOCK}__consent-checkbox`}>
<input
id="xbCheckbox_donotsell"
type="checkbox"
checked={doNotSellConsent}
onChange={this.handleDoNotSellCheck}
/>
<label
htmlFor="xbCheckbox_donotsell"
className={`${BLOCK}__checkbox`}
/>
</div>
<div
className={`${BLOCK}__consent-label`}
title={renderText("CookieKit.MoreInfo", selectedLocale)}
>
<a
className={`${BLOCK}__consent-label-link`}
href={links.donotsell}
target="_blank"
rel="noopener noreferrer"
>
{renderText("CookieKit.DoNotSellLabel", selectedLocale)}
</a>
</div>
</div>
)}
</div>
)
: null
}
{ consentCount > 1 && (
<button
type="button"
className={`xb-cookie-kit__button ${BLOCK}__check-all`}
onClick={this.handleCheckAll}
>
{isAllConsentsChecked
? renderText("CookieKit.UncheckButton", selectedLocale)
: renderText("CookieKit.CheckAllButton", selectedLocale)}
</button>
)}
<div className={`${BLOCK}__actions`}>
{ !hideBrandTag && (
<div className={`${BLOCK}__privacy-partner-container`}>
<a
className={`${BLOCK}__privacy-partner-link`}
href={links.poweredBy}
target="_blank"
rel="noopener noreferrer"
>
<div className={`${BLOCK}__privacy-partner`}>
{renderText("CookieKit.PoweredByText", selectedLocale)}
<img
className={`${BLOCK}__privacy-partner-logo`}
src={xbLogo}
alt="XcooBee logo"
/>
</div>
</a>
</div>
)}
<div className={`${BLOCK}__button-container`}>
<button
type="button"
className={`xb-cookie-kit__button ${BLOCK}__button`}
onClick={this.handleSubmit}
>
{renderText("CookieKit.OkButton", selectedLocale)}
</button>
</div>
</div>
<div className={`${BLOCK}__links`}>
{ isConnected && (appearsToBeLoggedIn
? (
<a
className={`${BLOCK}__link`}
href={`${xbOrigin}${links.manage}`}
target="_blank"
rel="noopener noreferrer"
>
{renderText("CookieKit.ManageLink", selectedLocale)}
</a>
)
: (
<button
className={`xb-cookie-kit__button ${BLOCK}__link`}
type="button"
onClick={() => window.open(
`${xbOrigin}${links.login}?targetUrl=${targetUrl}`, "", loginModalFeatures,
)}
>
{renderText("CookieKit.LoginLink", selectedLocale)}
</button>
)
)}
<a
className={`${BLOCK}__link`}
href={privacyUrl}
target="_blank"
rel="noopener noreferrer"
>
{renderText("CookieKit.PolicyLink", selectedLocale)}
</a>
<a
className={`${BLOCK}__link`}
href={termsUrl}
target="_blank"
rel="noopener noreferrer"
>
{renderText("CookieKit.TermsLink", selectedLocale)}
</a>
</div>
{ !hideBrandTag && (
<div className={`${BLOCK}__powered-by`}>
Powered by
<a
className={`${BLOCK}__powered-by-link`}
href={links.poweredBy}
target="_blank"
rel="noopener noreferrer"
>
XcooBee
</a>
</div>
)}
</div>
);
} else {
layout = (
// eslint-disable-next-line max-len
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions
<div
className={BLOCK}
onClick={() => this.setState({ isCountrySelectShown: false, isLocaleSelectShown: false })}
role="dialog"
tabIndex="-1"
>
<div className={`${BLOCK}__content`}>
<div className={`${BLOCK}__text`}>
{ this.renderTextMessage(textMessage) }
</div>
<div className={`${BLOCK}__main`}>
<div className={`${BLOCK}__links-container`}>
<div className={`${BLOCK}__links`}>
<a
className={`${BLOCK}__link`}
href={privacyUrl}
target="_blank"
rel="noopener noreferrer"
>
{renderText("CookieKit.PolicyLink", selectedLocale)}
</a>
<a
className={`${BLOCK}__link`}
href={termsUrl}
target="_blank"
rel="noopener noreferrer"
>
{renderText("CookieKit.TermsLink", selectedLocale)}
</a>
</div>
<div className={`${BLOCK}__login-container`}>
{ !hideBrandTag ? (
<div className={`${BLOCK}__powered-by-container`}>
<div className={`${BLOCK}__powered-by`}>
Powered by
<a
className={`${BLOCK}__powered-by-link`}
href={links.poweredBy}
target="_blank"
rel="noopener noreferrer"
>
XcooBee
</a>
</div>
</div>
) : <div />}
{ isConnected && (appearsToBeLoggedIn
? (
<a
className={`${BLOCK}__link`}
href={`${xbOrigin}${links.manage}`}
target="_blank"
rel="noopener noreferrer"
>
{renderText("CookieKit.ManageLink", selectedLocale)}
</a>
) : (
<div className={`${BLOCK}__login-button`}>
<button
className={`xb-cookie-kit__button ${BLOCK}__link`}
type="button"
onClick={() => window.open(
`${xbOrigin}${links.login}?targetUrl=${targetUrl}`, "", loginModalFeatures,
)}
>
{renderText("CookieKit.LoginLink", selectedLocale)}
</button>
</div>
)
)}
</div>
</div>
<div className={`${BLOCK}__consents-container`}>
<div className={`${BLOCK}__consents-container-body`}>
{ cookieDefns.map(cookieDefn => (
<div key={cookieDefn.type} className={`${BLOCK}__consent ${BLOCK}__cookie`}>
<div className={`${BLOCK}__checkbox-container`}>
<input
id={`xbCheckbox_${cookieDefn.id}`}
type="checkbox"
// Application cookies are always checked.
checked={cookieDefn.type === "application" || consentSettings[cookieDefn.type]}
// Application cookies are always disabled.
disabled={cookieDefn.type === "application"}
onChange={e => this.handleCookieCheck(e, cookieDefn.type)}
/>
<label
htmlFor={`xbCheckbox_${cookieDefn.id}`}
className={`${BLOCK}__checkbox`}
// Application cookies are always disabled.
disabled={cookieDefn.type === "application"}
/>
</div>
<div
className={`${BLOCK}__consent-label`}
title={renderText("CookieKit.MoreInfo", selectedLocale)}
>
<a
className={`${BLOCK}__consent-label-link`}
href={cookieDefn.url}
target="_blank"
rel="noopener noreferrer"
>
{renderText(cookieDefn.localeKey, selectedLocale)}
</a>
</div>
</div>
))}
{ displayFingerprint && (
<div className={`${BLOCK}__consent ${BLOCK}__fingerprint`}>
<div className={`${BLOCK}__checkbox-container ${BLOCK}__fingerprint-checkbox`}>
<input
id="xbCheckbox_fingerprint"
type="checkbox"
checked={fingerprintConsent}
onChange={this.handleFingerprintCheck}
/>
<label
htmlFor="xbCheckbox_fingerprint"
className={`${BLOCK}__checkbox`}
/>
</div>
<div className={`${BLOCK}__consent-label`}>
<a
className={`${BLOCK}__consent-label-link`}
href={links.donotsell}
target="_blank"
rel="noopener noreferrer"
>
{renderText("CookieKit.FingerprintLabel", selectedLocale)}
</a>
</div>
</div>
)}
{ displayDoNotSell && (
<div className={`${BLOCK}__consent ${BLOCK}__donotsell`}>
<div className={`${BLOCK}__checkbox-container ${BLOCK}__donotsell-checkbox`}>
<input
id="xbCheckbox_donotsell"
type="checkbox"
checked={doNotSellConsent}
onChange={this.handleDoNotSellCheck}
/>
<label
htmlFor="xbCheckbox_donotsell"
className={`${BLOCK}__checkbox`}
/>
</div>
<div className={`${BLOCK}__consent-label`}>
<a
className={`${BLOCK}__consent-label-link`}
href={links.donotsell}
target="_blank"
rel="noopener noreferrer"
>
{renderText("CookieKit.DoNotSellLabel", selectedLocale)}
</a>
</div>
</div>
)}
</div>
{ consentCount > 1 && (
<div className={`${BLOCK}__consents-container-foot`}>
<button
type="button"
className={`xb-cookie-kit__button ${BLOCK}__check-all`}
onClick={this.handleCheckAll}
>
{isAllConsentsChecked
? renderText("CookieKit.UncheckButton", selectedLocale)
: renderText("CookieKit.CheckAllButton", selectedLocale)}
</button>
</div>
)}
</div>
<div className={`${BLOCK}__button-container`}>
<button
type="button"
className={`xb-cookie-kit__button ${BLOCK}__button`}
onClick={this.handleSubmit}
>
{renderText("CookieKit.OkButton", selectedLocale)}
</button>
</div>
</div>
</div>
<div className={`${BLOCK}__actions`}>
<button
type="button"
className={`xb-cookie-kit__button ${BLOCK}__close-button`}
onClick={onClose}
>
<img
className={`${BLOCK}__close-button-icon`}
src={closeIcon}
alt="close-icon"
/>
</button>
<div className={`${BLOCK}__language-picker-container`}>
<button
type="button"
className={`xb-cookie-kit__button ${BLOCK}__language-picker`}
onClick={this.handleLocaleSelectToggle}
>
{ locales.find(locale => locale.code === selectedLocale)?.text || "?" }
</button>
{ isLocaleSelectShown && (
<div className={`${BLOCK}__custom-select`}>
{ locales.map(locale => (
<button
key={locale.code}
className={`xb-cookie-kit__button ${BLOCK}__language-picker-button`}
type="button"
onClick={() => this.handleLocaleChange(locale)}
>
{locale.text}
</button>
))}
</div>
)}
</div>
<div
className={`${BLOCK}__block ${BLOCK}__block--sm ${BLOCK}__country-picker`}
title={countryCode}
>
<button
type="button"
className={`xb-cookie-kit__button ${BLOCK}__country-picker-button`}
onClick={this.handleCountrySelectToggle}
>
<div className={`${BLOCK}__flag`}>
<ReactCountryFlag code={countryCode} svg />
</div>
</button>
{ isCountrySelectShown && (
<div className={`${BLOCK}__country-picker-container`}>
<button
type="button"
className="xb-cookie-kit__button"
onClick={e => this.handleScroll(e, "up")}
>
<img
src={caretUp}
alt="caret-up"
/>
</button>
<div
ref={(countryPicker) => {
this.countryPicker = countryPicker;
}}
className={`${BLOCK}__country-picker-select`}
>
{ countryCodes.map(cCode => (
<button
type="button"
key={`country-flag-${cCode}`}
className={`xb-cookie-kit__button ${BLOCK}__country-picker-button`}
onClick={() => this.handleCountryChange(cCode)}
title={cCode}
>
<div className={`${BLOCK}__flag`}>
<ReactCountryFlag code={cCode} svg />
</div>
</button>
))}
</div>
<button
type="button"
className="xb-cookie-kit__button"
onClick={e => this.handleScroll(e, "bottom")}
>
<img
src={caretDown}
alt="caret-down"
/>
</button>
</div>
)}
</div>
</div>
</div>
);
}
return layout;
}
}