gatsby-core-theme 44.22.4 → 44.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/gatsby-browser.js +66 -24
- package/package.json +1 -1
- package/src/components/molecules/cookie-modal/index.js +22 -2
- package/src/components/molecules/link-list/index.js +54 -30
- package/src/components/organisms/cookie-consent/cookie-consent.test.js +6 -1
- package/src/components/organisms/cookie-consent/index.js +92 -22
- package/src/constants/cookies.js +16 -0
- package/src/context/MainProvider.js +29 -0
- package/src/helpers/cookies.mjs +21 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,30 @@
|
|
|
1
|
+
# [44.23.0](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/compare/v44.22.4...v44.23.0) (2026-04-29)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* per-category cookie is authoritative; legacy fallback only when unset ([4dd15ad](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/4dd15adbe30a688bf6b7a255e54cf190864ce958))
|
|
7
|
+
* revert ([76d3a86](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/76d3a86e475a75b8949ee75591c34d53165b86e3))
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Code Refactoring
|
|
11
|
+
|
|
12
|
+
* extract consent gates into cookies helper for site-level reuse ([1be4643](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/1be4643e6917c44eeaaf650d1559172ea26b05ae))
|
|
13
|
+
* self-remove consent listener after all gated categories init ([e502ecd](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/e502ecdf7e5dcd004e7591c5f601c2a5d2ab4a72))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
* Merge branch 'en-492-cookie-consent' into 'master' ([b7e3d5b](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/b7e3d5b20f264814d77b3409445be72aa903071e))
|
|
17
|
+
* Merge branch 'en-492-cookie-consent' into 'master' ([c7e21bd](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/c7e21bd7a3e98a38d3da6351ffccb8b9a68d8c55))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
* add functionality category, gate optinmonster on it ([41c2d47](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/41c2d4725424d621824412f50af555118703a7e8))
|
|
23
|
+
* enable cookie consent on demo ([7dc252f](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/7dc252fb1c8ca024fff6e32b4868400f39f7866e))
|
|
24
|
+
* per-category script gating with site-level toggle ([7f7230c](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/7f7230cfef830356342c8afca2c7724a515564b3))
|
|
25
|
+
* reopen cookie settings modal via context + link-list sentinel ([6e68856](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/6e68856451984653f3578f1552f45936d821f693))
|
|
26
|
+
* script loading gated by cookie consent ([09ce499](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/09ce499b5567acf10ccca4611f7027f88781639b))
|
|
27
|
+
|
|
1
28
|
## [44.22.4](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/compare/v44.22.3...v44.22.4) (2026-04-28)
|
|
2
29
|
|
|
3
30
|
|
package/gatsby-browser.js
CHANGED
|
@@ -26,7 +26,16 @@ async function yieldToMain() {
|
|
|
26
26
|
});
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
const {
|
|
30
|
+
isAnalyticalGranted,
|
|
31
|
+
isMarketingGranted,
|
|
32
|
+
isFunctionalityGranted,
|
|
33
|
+
} = require("./src/helpers/cookies.mjs");
|
|
34
|
+
|
|
35
|
+
let piguardDidInit = false;
|
|
36
|
+
let analyticalDidInit = false;
|
|
37
|
+
let marketingDidInit = false;
|
|
38
|
+
let functionalityDidInit = false;
|
|
30
39
|
|
|
31
40
|
function initGTM() {
|
|
32
41
|
if (
|
|
@@ -141,30 +150,23 @@ const piguard = () => {
|
|
|
141
150
|
document.head.appendChild(script);
|
|
142
151
|
};
|
|
143
152
|
|
|
144
|
-
|
|
145
|
-
if (
|
|
146
|
-
|
|
147
|
-
await yieldToMain();
|
|
148
|
-
initGTM();
|
|
149
|
-
|
|
153
|
+
function initPiguardCategory() {
|
|
154
|
+
if (piguardDidInit) return;
|
|
155
|
+
piguardDidInit = true;
|
|
150
156
|
if (
|
|
151
157
|
!document.getElementById("piguard") &&
|
|
152
158
|
process.env.ENABLE_PIGUARD === "true"
|
|
153
159
|
) {
|
|
154
160
|
piguard();
|
|
155
161
|
}
|
|
162
|
+
}
|
|
156
163
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
164
|
+
function initAnalyticalCategory() {
|
|
165
|
+
if (analyticalDidInit) return;
|
|
166
|
+
if (!isAnalyticalGranted()) return;
|
|
167
|
+
analyticalDidInit = true;
|
|
168
|
+
initGTM();
|
|
162
169
|
|
|
163
|
-
if (
|
|
164
|
-
process.env.ENABLE_OPTINMONSTR === "true" &&
|
|
165
|
-
!document.getElementById("optin-monstr")
|
|
166
|
-
)
|
|
167
|
-
optinMonster();
|
|
168
170
|
if (
|
|
169
171
|
process.env.ENABLE_PIXEL === "true" &&
|
|
170
172
|
!document.getElementById("pixel-code")
|
|
@@ -174,33 +176,73 @@ async function scrollEvent() {
|
|
|
174
176
|
window.location.pathname !== process.env.PAGE_EXCLUDE_PIXEL
|
|
175
177
|
) {
|
|
176
178
|
loadFacebookPixel();
|
|
177
|
-
|
|
178
179
|
fbq("init", process.env.PIXEL_ID);
|
|
179
|
-
|
|
180
|
-
// Initialize and track the PageView event
|
|
181
180
|
fbq("track", "PageView");
|
|
182
181
|
}
|
|
183
182
|
}
|
|
184
183
|
}
|
|
185
184
|
|
|
185
|
+
function initMarketingCategory() {
|
|
186
|
+
if (marketingDidInit) return;
|
|
187
|
+
if (!isMarketingGranted()) return;
|
|
188
|
+
marketingDidInit = true;
|
|
189
|
+
|
|
190
|
+
if (
|
|
191
|
+
process.env.ENABLE_MICROSOFT === "true" &&
|
|
192
|
+
!document.getElementById("microsoft-code")
|
|
193
|
+
)
|
|
194
|
+
microsoftAdvertising();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function initFunctionalityCategory() {
|
|
198
|
+
if (functionalityDidInit) return;
|
|
199
|
+
if (!isFunctionalityGranted()) return;
|
|
200
|
+
functionalityDidInit = true;
|
|
201
|
+
|
|
202
|
+
if (
|
|
203
|
+
process.env.ENABLE_OPTINMONSTR === "true" &&
|
|
204
|
+
!document.getElementById("optin-monstr")
|
|
205
|
+
)
|
|
206
|
+
optinMonster();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async function tryInitTrackingScripts() {
|
|
210
|
+
await yieldToMain();
|
|
211
|
+
initPiguardCategory();
|
|
212
|
+
initAnalyticalCategory();
|
|
213
|
+
initMarketingCategory();
|
|
214
|
+
initFunctionalityCategory();
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const allGatedCategoriesInitialized = () =>
|
|
218
|
+
analyticalDidInit && marketingDidInit && functionalityDidInit;
|
|
219
|
+
|
|
186
220
|
exports.onClientEntry = () => {
|
|
221
|
+
const consentHandler = async () => {
|
|
222
|
+
await tryInitTrackingScripts();
|
|
223
|
+
if (allGatedCategoriesInitialized()) {
|
|
224
|
+
window.removeEventListener("consent:accepted", consentHandler);
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
window.addEventListener("consent:accepted", consentHandler);
|
|
228
|
+
|
|
187
229
|
if (
|
|
188
230
|
process.env.PPC === "true" ||
|
|
189
231
|
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
|
|
190
232
|
navigator.userAgent
|
|
191
233
|
)
|
|
192
234
|
) {
|
|
193
|
-
|
|
235
|
+
tryInitTrackingScripts();
|
|
194
236
|
} else {
|
|
195
|
-
document.addEventListener("scroll",
|
|
237
|
+
document.addEventListener("scroll", tryInitTrackingScripts, {
|
|
196
238
|
passive: true,
|
|
197
239
|
once: true,
|
|
198
240
|
});
|
|
199
|
-
document.addEventListener("mousemove",
|
|
241
|
+
document.addEventListener("mousemove", tryInitTrackingScripts, {
|
|
200
242
|
passive: true,
|
|
201
243
|
once: true,
|
|
202
244
|
});
|
|
203
|
-
document.addEventListener("touchstart",
|
|
245
|
+
document.addEventListener("touchstart", tryInitTrackingScripts, {
|
|
204
246
|
passive: true,
|
|
205
247
|
once: true,
|
|
206
248
|
});
|
package/package.json
CHANGED
|
@@ -14,10 +14,24 @@ import Button from "~atoms/button/button";
|
|
|
14
14
|
import styles from "./cookie-modal.module.scss";
|
|
15
15
|
import cookiesContent from '../../../constants/cookies';
|
|
16
16
|
import { parseCookieTextWithLinks } from '../../../helpers/generators.mjs';
|
|
17
|
+
import { getCookie } from '~helpers/cookies';
|
|
18
|
+
|
|
19
|
+
const initialCategoryStates = () => {
|
|
20
|
+
const states = {};
|
|
21
|
+
(cookiesContent.modal?.categories || []).forEach((cat, idx) => {
|
|
22
|
+
if (cat.hasToggle === false) {
|
|
23
|
+
states[idx] = true;
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
states[idx] = cat.cookieName ? getCookie(cat.cookieName) === 'true' : false;
|
|
27
|
+
});
|
|
28
|
+
return states;
|
|
29
|
+
};
|
|
17
30
|
|
|
18
31
|
const CookieModal = ({
|
|
19
32
|
handleAcceptCookies,
|
|
20
33
|
handleDeclineCookies,
|
|
34
|
+
handleConfirmChoices,
|
|
21
35
|
declineButtonType = "secondary",
|
|
22
36
|
acceptButtonType = "primary",
|
|
23
37
|
buttonSize = "m",
|
|
@@ -27,7 +41,7 @@ const CookieModal = ({
|
|
|
27
41
|
hide,
|
|
28
42
|
}) => {
|
|
29
43
|
const [categorySection, setCategorySection] = useState(0);
|
|
30
|
-
const [categoryStates, setCategoryStates] = useState(
|
|
44
|
+
const [categoryStates, setCategoryStates] = useState(initialCategoryStates);
|
|
31
45
|
const [menage, setMenage] = useState(true);
|
|
32
46
|
|
|
33
47
|
const modal = useRef(null);
|
|
@@ -68,6 +82,7 @@ const CookieModal = ({
|
|
|
68
82
|
textKey: item?.description?.translationKey,
|
|
69
83
|
defaultText: item?.description?.label,
|
|
70
84
|
alwaysEnabledLabel: item?.alwaysEnabledLabel,
|
|
85
|
+
cookieName: item?.cookieName,
|
|
71
86
|
}));
|
|
72
87
|
|
|
73
88
|
|
|
@@ -204,7 +219,11 @@ const CookieModal = ({
|
|
|
204
219
|
{!templateTwo && (
|
|
205
220
|
<div className={styles?.lastButton || ""}>
|
|
206
221
|
<Button
|
|
207
|
-
onClick={() =>
|
|
222
|
+
onClick={() =>
|
|
223
|
+
handleConfirmChoices
|
|
224
|
+
? handleConfirmChoices(categoryStates)
|
|
225
|
+
: handleAcceptCookies()
|
|
226
|
+
}
|
|
208
227
|
btnText={useTranslate(
|
|
209
228
|
modalSettings?.confirmButton?.translationKey,
|
|
210
229
|
modalSettings?.confirmButton?.label
|
|
@@ -224,6 +243,7 @@ const CookieModal = ({
|
|
|
224
243
|
CookieModal.propTypes = {
|
|
225
244
|
handleAcceptCookies: PropTypes.func,
|
|
226
245
|
handleDeclineCookies: PropTypes.func,
|
|
246
|
+
handleConfirmChoices: PropTypes.func,
|
|
227
247
|
closeModal: PropTypes.func,
|
|
228
248
|
logo: PropTypes.string,
|
|
229
249
|
buttonSize: PropTypes.string,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* eslint-disable arrow-body-style */
|
|
2
2
|
/* eslint-disable no-nested-ternary */
|
|
3
|
-
import React from 'react';
|
|
3
|
+
import React, { useContext } from 'react';
|
|
4
4
|
import PropTypes from 'prop-types';
|
|
5
5
|
|
|
6
6
|
import keygen from '~helpers/keygen';
|
|
@@ -8,6 +8,10 @@ import { imagePrettyUrl } from '~helpers/getters';
|
|
|
8
8
|
import { getAltText } from '~helpers/image';
|
|
9
9
|
import Link from '~hooks/link';
|
|
10
10
|
import LazyImage from '~hooks/lazy-image';
|
|
11
|
+
import { Context } from '~context/MainProvider';
|
|
12
|
+
|
|
13
|
+
const COOKIE_SETTINGS_SENTINEL =
|
|
14
|
+
process.env.GATSBY_COOKIE_SETTINGS_SENTINEL || '#cookie-settings';
|
|
11
15
|
|
|
12
16
|
/* eslint-disable react/jsx-no-target-blank */
|
|
13
17
|
|
|
@@ -25,6 +29,8 @@ const LinkList = ({
|
|
|
25
29
|
gtmClass = '',
|
|
26
30
|
showLinks = true,
|
|
27
31
|
}) => {
|
|
32
|
+
const { openCookieSettings } = useContext(Context)?.cookieConsentContext || {};
|
|
33
|
+
|
|
28
34
|
function renderLinkContent(item, index) {
|
|
29
35
|
const icon = listIcon[index];
|
|
30
36
|
|
|
@@ -56,41 +62,59 @@ const LinkList = ({
|
|
|
56
62
|
);
|
|
57
63
|
}
|
|
58
64
|
|
|
59
|
-
function
|
|
65
|
+
function renderLinkWrapper(item, index) {
|
|
60
66
|
const link = item?.value || item?.url;
|
|
67
|
+
const content = renderLinkContent(item, index);
|
|
68
|
+
const ariaLabel = `${item.title || item.name || item.url} Link`;
|
|
69
|
+
|
|
70
|
+
if (link === COOKIE_SETTINGS_SENTINEL) {
|
|
71
|
+
return (
|
|
72
|
+
<button
|
|
73
|
+
type="button"
|
|
74
|
+
onClick={() => openCookieSettings && openCookieSettings()}
|
|
75
|
+
title={item.title || item.name}
|
|
76
|
+
className={gtmClass || ''}
|
|
77
|
+
aria-label={ariaLabel}
|
|
78
|
+
>
|
|
79
|
+
{content}
|
|
80
|
+
</button>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!link) return content;
|
|
85
|
+
|
|
86
|
+
if (link.includes('http') || link.includes('www')) {
|
|
87
|
+
return (
|
|
88
|
+
<a
|
|
89
|
+
href={link}
|
|
90
|
+
title={item.title || item.name}
|
|
91
|
+
rel={`noreferrer ${item.nofollow ? 'nofollow' : ''}`}
|
|
92
|
+
target="_blank"
|
|
93
|
+
className={gtmClass || ''}
|
|
94
|
+
aria-label={ariaLabel}
|
|
95
|
+
>
|
|
96
|
+
{content}
|
|
97
|
+
</a>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
61
100
|
|
|
62
101
|
return (
|
|
63
|
-
<
|
|
64
|
-
{
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
aria-label={`${item.title || item.name} Link`}
|
|
73
|
-
>
|
|
74
|
-
{renderLinkContent(item, index)}
|
|
75
|
-
</a>
|
|
76
|
-
) : (
|
|
77
|
-
<Link
|
|
78
|
-
to={showLinks && link}
|
|
79
|
-
title={item.title || item.name}
|
|
80
|
-
className={gtmClass || ''}
|
|
81
|
-
rel={item.nofollow ? 'nofollow' : ''}
|
|
82
|
-
aria-label={`${item.title || item.url} Link`}
|
|
83
|
-
>
|
|
84
|
-
{renderLinkContent(item, index)}
|
|
85
|
-
</Link>
|
|
86
|
-
)
|
|
87
|
-
) : (
|
|
88
|
-
renderLinkContent(item, index)
|
|
89
|
-
)}
|
|
90
|
-
</li>
|
|
102
|
+
<Link
|
|
103
|
+
to={showLinks && link}
|
|
104
|
+
title={item.title || item.name}
|
|
105
|
+
className={gtmClass || ''}
|
|
106
|
+
rel={item.nofollow ? 'nofollow' : ''}
|
|
107
|
+
aria-label={ariaLabel}
|
|
108
|
+
>
|
|
109
|
+
{content}
|
|
110
|
+
</Link>
|
|
91
111
|
);
|
|
92
112
|
}
|
|
93
113
|
|
|
114
|
+
function renderItems(item, index) {
|
|
115
|
+
return <li key={keygen()}>{renderLinkWrapper(item, index)}</li>;
|
|
116
|
+
}
|
|
117
|
+
|
|
94
118
|
const renderSingleList = () => {
|
|
95
119
|
return (
|
|
96
120
|
<ul className={classes}>
|
|
@@ -53,6 +53,11 @@ describe('cookie consent component', () => {
|
|
|
53
53
|
});
|
|
54
54
|
afterEach(() => {
|
|
55
55
|
cleanup();
|
|
56
|
-
|
|
56
|
+
const expired = 'expires=Thu, 01 Jan 1970 00:00:00; path=/';
|
|
57
|
+
document.cookie = `CookieConsent=; ${expired}`;
|
|
58
|
+
document.cookie = `cookie_necessary=; ${expired}`;
|
|
59
|
+
document.cookie = `cookie_analytical=; ${expired}`;
|
|
60
|
+
document.cookie = `cookie_marketing=; ${expired}`;
|
|
61
|
+
document.cookie = `showCookie=; ${expired}`;
|
|
57
62
|
sessionStorage.removeItem('CookieConsentDecline');
|
|
58
63
|
});
|
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
/* eslint-disable jsx-a11y/click-events-have-key-events */
|
|
3
3
|
/* eslint-disable import/no-extraneous-dependencies */
|
|
4
4
|
/* eslint-disable react-hooks/rules-of-hooks */
|
|
5
|
-
import React, {
|
|
5
|
+
import React, { useContext, useEffect, lazy, Suspense } from "react";
|
|
6
6
|
import PropTypes from "prop-types";
|
|
7
7
|
|
|
8
8
|
import useTranslate from "~hooks/useTranslate/useTranslate";
|
|
9
9
|
import { setCookie, getCookie } from "~helpers/cookies";
|
|
10
|
+
import { Context } from "~context/MainProvider";
|
|
10
11
|
import styles from "./cookie-consent.module.scss";
|
|
11
12
|
|
|
12
13
|
import cookiesContent from '../../../constants/cookies';
|
|
@@ -20,9 +21,14 @@ const CookieConsent = ({
|
|
|
20
21
|
icon = null,
|
|
21
22
|
showRejectButton = false,
|
|
22
23
|
}) => {
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
const {
|
|
25
|
+
showCookieBanner = false,
|
|
26
|
+
showCookieModal = false,
|
|
27
|
+
openCookieBanner = () => {},
|
|
28
|
+
openCookieSettings = () => {},
|
|
29
|
+
closeCookieModal = () => {},
|
|
30
|
+
closeCookieConsent = () => {},
|
|
31
|
+
} = useContext(Context)?.cookieConsentContext || {};
|
|
26
32
|
|
|
27
33
|
const CookieModal = lazy(() => import("../../molecules/cookie-modal"));
|
|
28
34
|
|
|
@@ -44,37 +50,100 @@ const CookieConsent = ({
|
|
|
44
50
|
cookiesContent.text?.translationKey,
|
|
45
51
|
cookiesContent.text?.label
|
|
46
52
|
);
|
|
47
|
-
|
|
53
|
+
|
|
54
|
+
const categories = cookiesContent.modal?.categories || [];
|
|
55
|
+
|
|
56
|
+
const dispatchConsentEvent = () => {
|
|
57
|
+
if (typeof window !== "undefined") {
|
|
58
|
+
window.dispatchEvent(new CustomEvent("consent:accepted"));
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// Necessary categories (hasToggle === false) are always written as true.
|
|
63
|
+
// Toggleable categories are written from the provided state map.
|
|
64
|
+
const writeCategoryCookies = (stateByIndex) => {
|
|
65
|
+
categories.forEach((cat, idx) => {
|
|
66
|
+
if (!cat.cookieName) return;
|
|
67
|
+
const value = cat.hasToggle === false ? true : Boolean(stateByIndex[idx]);
|
|
68
|
+
setCookie(cat.cookieName, value, 365, "/");
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Detect a toggle going from on -> off, which requires a reload to unload
|
|
73
|
+
// tracking scripts that have already executed in this session.
|
|
74
|
+
const wasAnyCategoryRevoked = (newStateByIndex) =>
|
|
75
|
+
categories.some((cat, idx) => {
|
|
76
|
+
if (!cat.cookieName || cat.hasToggle === false) return false;
|
|
77
|
+
const wasEnabled = getCookie(cat.cookieName) === "true";
|
|
78
|
+
const willBeEnabled = Boolean(newStateByIndex[idx]);
|
|
79
|
+
return wasEnabled && !willBeEnabled;
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const closeBanner = () => {
|
|
83
|
+
setCookie("showCookie", false, 365, "/");
|
|
84
|
+
closeCookieConsent();
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// when user declines (reject non-necessary): all toggleable categories -> false.
|
|
88
|
+
// Still dispatches the consent event because `necessary` stays true and every
|
|
89
|
+
// tracking script is currently gated on `necessary`. Revisit when scripts get
|
|
90
|
+
// re-tagged to analytical/marketing — decline should then NOT fire the event.
|
|
48
91
|
const handleDecline = () => {
|
|
92
|
+
const allFalse = {};
|
|
93
|
+
categories.forEach((_, idx) => {
|
|
94
|
+
allFalse[idx] = false;
|
|
95
|
+
});
|
|
96
|
+
const revoked = wasAnyCategoryRevoked(allFalse);
|
|
97
|
+
writeCategoryCookies(allFalse);
|
|
49
98
|
setCookie(cookieName, false, 365, "/");
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
99
|
+
if (revoked) {
|
|
100
|
+
window.location.reload();
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
closeBanner();
|
|
104
|
+
dispatchConsentEvent();
|
|
54
105
|
};
|
|
55
106
|
|
|
56
|
-
// when user accepts
|
|
107
|
+
// when user accepts all: every toggleable category -> true
|
|
57
108
|
const handleAccept = () => {
|
|
109
|
+
const allTrue = {};
|
|
110
|
+
categories.forEach((_, idx) => {
|
|
111
|
+
allTrue[idx] = true;
|
|
112
|
+
});
|
|
113
|
+
writeCategoryCookies(allTrue);
|
|
58
114
|
setCookie(cookieName, true, 365, "/");
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
115
|
+
closeBanner();
|
|
116
|
+
dispatchConsentEvent();
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// when user confirms a custom mix from the modal toggles
|
|
120
|
+
const handleConfirmChoices = (categoryStates) => {
|
|
121
|
+
const revoked = wasAnyCategoryRevoked(categoryStates);
|
|
122
|
+
writeCategoryCookies(categoryStates);
|
|
123
|
+
setCookie(cookieName, true, 365, "/");
|
|
124
|
+
if (revoked) {
|
|
125
|
+
window.location.reload();
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
closeBanner();
|
|
129
|
+
dispatchConsentEvent();
|
|
63
130
|
};
|
|
64
131
|
|
|
65
132
|
const handleShowModalClick = () => {
|
|
66
|
-
|
|
67
|
-
|
|
133
|
+
if (showCookieModal) {
|
|
134
|
+
closeCookieModal();
|
|
135
|
+
} else {
|
|
136
|
+
openCookieSettings();
|
|
137
|
+
}
|
|
68
138
|
};
|
|
69
139
|
|
|
70
140
|
const closeModal = () => {
|
|
71
|
-
|
|
72
|
-
document.body.style.overflow = "auto";
|
|
141
|
+
closeCookieModal();
|
|
73
142
|
};
|
|
74
143
|
|
|
75
144
|
useEffect(() => {
|
|
76
145
|
if (typeof window !== `undefined` && getCookie("showCookie") !== "false") {
|
|
77
|
-
|
|
146
|
+
openCookieBanner();
|
|
78
147
|
}
|
|
79
148
|
}, []);
|
|
80
149
|
|
|
@@ -82,7 +151,7 @@ const CookieConsent = ({
|
|
|
82
151
|
<>
|
|
83
152
|
<div
|
|
84
153
|
className={`${styles.cookieConsent} ${
|
|
85
|
-
(
|
|
154
|
+
(showCookieBanner && styles.show) || ""
|
|
86
155
|
}`}
|
|
87
156
|
>
|
|
88
157
|
<div className={`${styles?.consent || ""}`}>
|
|
@@ -130,13 +199,14 @@ const CookieConsent = ({
|
|
|
130
199
|
</div>
|
|
131
200
|
</div>
|
|
132
201
|
</div>
|
|
133
|
-
{settingsCookie &&
|
|
202
|
+
{settingsCookie && showCookieModal && (
|
|
134
203
|
<Suspense fallback={null}>
|
|
135
204
|
<CookieModal
|
|
136
205
|
logo={logo}
|
|
137
|
-
hide={!
|
|
206
|
+
hide={!showCookieModal}
|
|
138
207
|
handleAcceptCookies={handleAccept}
|
|
139
208
|
handleDeclineCookies={handleDecline}
|
|
209
|
+
handleConfirmChoices={handleConfirmChoices}
|
|
140
210
|
closeModal={closeModal}
|
|
141
211
|
/>
|
|
142
212
|
</Suspense>
|
package/src/constants/cookies.js
CHANGED
|
@@ -57,6 +57,7 @@ const cookiesContent = {
|
|
|
57
57
|
},
|
|
58
58
|
categories: [
|
|
59
59
|
{
|
|
60
|
+
cookieName: 'cookie_necessary',
|
|
60
61
|
title: {
|
|
61
62
|
label: 'Neccesary',
|
|
62
63
|
translationKey: 'neccesary_cookie_title',
|
|
@@ -69,6 +70,7 @@ const cookiesContent = {
|
|
|
69
70
|
hasToggle: false,
|
|
70
71
|
},
|
|
71
72
|
{
|
|
73
|
+
cookieName: 'cookie_analytical',
|
|
72
74
|
title: {
|
|
73
75
|
label: 'Analytical And Stadistical',
|
|
74
76
|
translationKey: 'analytical_cookie_title',
|
|
@@ -81,6 +83,20 @@ const cookiesContent = {
|
|
|
81
83
|
hasToggle: true,
|
|
82
84
|
},
|
|
83
85
|
{
|
|
86
|
+
cookieName: 'cookie_functionality',
|
|
87
|
+
title: {
|
|
88
|
+
label: 'Functionality',
|
|
89
|
+
translationKey: 'functionality_cookie_title',
|
|
90
|
+
},
|
|
91
|
+
description: {
|
|
92
|
+
label:
|
|
93
|
+
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nibh aliquam non sit morbi neque in sodales tellus. Cursus neque sed quis tincidunt sed vestibulum rhoncus dolor elementum. Imperdiet tortor dolor sit nisi, magnis cras id ut. Id dolor, vel neque lobortis. Diam commodo vitae vulputate at ultrices id odio praesent. Nisi, sit massa orci accumsan.',
|
|
94
|
+
translationKey: 'functionality_cookie_text',
|
|
95
|
+
},
|
|
96
|
+
hasToggle: true,
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
cookieName: 'cookie_marketing',
|
|
84
100
|
title: {
|
|
85
101
|
label: 'Marketing',
|
|
86
102
|
translationKey: 'marketing_cookie_title',
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
/* eslint-disable react/prop-types */
|
|
2
2
|
import React, { createContext, useState } from "react";
|
|
3
3
|
|
|
4
|
+
const setBodyScrollLock = (locked) => {
|
|
5
|
+
if (typeof document === "undefined") return;
|
|
6
|
+
document.body.style.overflow = locked ? "hidden" : "auto";
|
|
7
|
+
};
|
|
8
|
+
|
|
4
9
|
export const Context = createContext();
|
|
5
10
|
|
|
6
11
|
export default (props) => {
|
|
@@ -15,6 +20,29 @@ export default (props) => {
|
|
|
15
20
|
topListFilters,
|
|
16
21
|
setTopListFilters,
|
|
17
22
|
};
|
|
23
|
+
|
|
24
|
+
const [showCookieBanner, setShowCookieBanner] = useState(false);
|
|
25
|
+
const [showCookieModal, setShowCookieModal] = useState(false);
|
|
26
|
+
|
|
27
|
+
const cookieConsentContext = {
|
|
28
|
+
showCookieBanner,
|
|
29
|
+
showCookieModal,
|
|
30
|
+
openCookieBanner: () => setShowCookieBanner(true),
|
|
31
|
+
openCookieSettings: () => {
|
|
32
|
+
setShowCookieModal(true);
|
|
33
|
+
setBodyScrollLock(true);
|
|
34
|
+
},
|
|
35
|
+
closeCookieModal: () => {
|
|
36
|
+
setShowCookieModal(false);
|
|
37
|
+
setBodyScrollLock(false);
|
|
38
|
+
},
|
|
39
|
+
closeCookieConsent: () => {
|
|
40
|
+
setShowCookieBanner(false);
|
|
41
|
+
setShowCookieModal(false);
|
|
42
|
+
setBodyScrollLock(false);
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
18
46
|
return (
|
|
19
47
|
<Context.Provider
|
|
20
48
|
value={{
|
|
@@ -25,6 +53,7 @@ export default (props) => {
|
|
|
25
53
|
setShowTranslationKeys,
|
|
26
54
|
showTranslationKeys,
|
|
27
55
|
topListContext,
|
|
56
|
+
cookieConsentContext,
|
|
28
57
|
}}
|
|
29
58
|
>
|
|
30
59
|
{children}
|
package/src/helpers/cookies.mjs
CHANGED
|
@@ -19,3 +19,24 @@ export function getCookie(cname) {
|
|
|
19
19
|
}
|
|
20
20
|
return '';
|
|
21
21
|
}
|
|
22
|
+
|
|
23
|
+
export const isCookieConsentEnabled = () =>
|
|
24
|
+
process.env.GATSBY_COOKIE_CONSENT_ENABLED === 'true';
|
|
25
|
+
|
|
26
|
+
export const hasLegacyAcceptAll = () => getCookie('CookieConsent') === 'true';
|
|
27
|
+
|
|
28
|
+
// Per-category cookie is authoritative once set. Only fall back to the legacy
|
|
29
|
+
// accept-all cookie when the per-category value is absent (pre-categories
|
|
30
|
+
// users), so an explicit "false" doesn't get re-granted by the legacy flag.
|
|
31
|
+
const isCategoryGranted = (cookieName) => {
|
|
32
|
+
if (!isCookieConsentEnabled()) return true;
|
|
33
|
+
const value = getCookie(cookieName);
|
|
34
|
+
if (value === 'true') return true;
|
|
35
|
+
if (value === 'false') return false;
|
|
36
|
+
return hasLegacyAcceptAll();
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const isAnalyticalGranted = () => isCategoryGranted('cookie_analytical');
|
|
40
|
+
export const isMarketingGranted = () => isCategoryGranted('cookie_marketing');
|
|
41
|
+
export const isFunctionalityGranted = () =>
|
|
42
|
+
isCategoryGranted('cookie_functionality');
|