gatsby-core-theme 44.22.0 → 44.22.1

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 CHANGED
@@ -1,3 +1,20 @@
1
+ ## [44.22.1](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/compare/v44.22.0...v44.22.1) (2026-04-21)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * fix eslint error ([7698d62](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/7698d62217cc3133caf4b18f1a291bcaea08a3f5))
7
+ * fix tests for cookie consent ([60c738f](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/60c738f4d5cb7b79c5fb573adecb1076c26dfa96))
8
+
9
+
10
+ ### Config
11
+
12
+ * change logic to config driven ([e546f1a](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/e546f1a06609649fe229d31c2cc5c5e3e13ae127))
13
+ * refactor cookie consents to use custom content ([0b611c4](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/0b611c423aa7406450b63e554df1db1b621d5a75))
14
+
15
+
16
+ * Merge branch 'EN-475/refactor-cookie-consents' into 'master' ([11aba91](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/11aba914e3f81e33c36c3b28c2c671f124ec532f))
17
+
1
18
  # [44.22.0](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/compare/v44.21.1...v44.22.0) (2026-04-14)
2
19
 
3
20
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gatsby-core-theme",
3
- "version": "44.22.0",
3
+ "version": "44.22.1",
4
4
  "description": "Gatsby Theme NPM Package",
5
5
  "author": "",
6
6
  "license": "ISC",
@@ -1,5 +1,6 @@
1
1
  /* eslint-disable import/no-extraneous-dependencies */
2
2
  /* eslint-disable jsx-a11y/no-redundant-roles */
3
+ /* eslint-disable react-hooks/rules-of-hooks */
3
4
  import React, { useState, useRef, useEffect } from "react";
4
5
  import PropTypes from "prop-types";
5
6
 
@@ -11,6 +12,8 @@ import LazyImage from "~hooks/lazy-image";
11
12
  import CategoryContainer from "./category/index";
12
13
  import Button from "~atoms/button/button";
13
14
  import styles from "./cookie-modal.module.scss";
15
+ import cookiesContent from '../../../constants/cookies';
16
+ import { parseCookieTextWithLinks } from '../../../helpers/generators.mjs';
14
17
 
15
18
  const CookieModal = ({
16
19
  handleAcceptCookies,
@@ -19,21 +22,23 @@ const CookieModal = ({
19
22
  acceptButtonType = "primary",
20
23
  buttonSize = "m",
21
24
  closeModal,
22
- cookieLink = {
23
- title: "",
24
- path: "",
25
- },
26
25
  logo = "/images/logo.svg",
27
26
  templateTwo = false,
28
27
  hide,
29
28
  }) => {
30
29
  const [categorySection, setCategorySection] = useState(0);
31
- const [anlyticsButton, setAnlyticsButton] = useState(false);
32
- const [marketing, setMarketingButton] = useState(false);
30
+ const [categoryStates, setCategoryStates] = useState({});
33
31
  const [menage, setMenage] = useState(true);
34
32
 
35
33
  const modal = useRef(null);
36
34
 
35
+ const modalSettings = cookiesContent.modal;
36
+
37
+ const translatedDescriptionLinks = (modalSettings?.links || []).map((link) => ({
38
+ ...link,
39
+ translatedText: useTranslate(link.translationKey || "", link.text || ""),
40
+ }));
41
+
37
42
  const handlePreference = () => {
38
43
  if (templateTwo) {
39
44
  setMenage(!menage);
@@ -55,40 +60,20 @@ const CookieModal = ({
55
60
  // eslint-disable-next-line react-hooks/exhaustive-deps
56
61
  }, []);
57
62
 
58
- const CategoryObj = [
59
- {
60
- translateKey: "neccesary_cookie_title",
61
- button: false,
62
- altTranslation: "Neccesary",
63
- textKey: "neccesary_cookie_text",
64
- defaultText:
65
- "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.",
66
- },
67
- {
68
- translateKey: "analytical_cookie_title",
69
- button: true,
70
- altTranslation: "Analytical And Stadistical",
71
- textKey: "analytical_cookie_text",
72
- defaultText:
73
- "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.",
74
- },
75
- {
76
- translateKey: "marketing_cookie_title",
77
- button: true,
78
- altTranslation: "Marketing",
79
- textKey: "marketing_cookie_text",
80
- defaultText:
81
- "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.",
82
- },
83
- ];
84
-
85
- const privacyText = useTranslate(
86
- "privacy_preference_title",
87
- "Privacy Preference Center"
88
- );
89
- const buttonText = useTranslate(
90
- "cookie_confirm_button",
91
- "Confirm my choices"
63
+
64
+ const CategoryObj = (modalSettings?.categories || []).map((item) => ({
65
+ translateKey: item?.title?.translationKey,
66
+ button: item?.hasToggle,
67
+ altTranslation: item?.title?.label,
68
+ textKey: item?.description?.translationKey,
69
+ defaultText: item?.description?.label,
70
+ alwaysEnabledLabel: item?.alwaysEnabledLabel,
71
+ }));
72
+
73
+
74
+ const privacyDescription = useTranslate(
75
+ modalSettings?.description?.translationKey,
76
+ modalSettings?.description?.label
92
77
  );
93
78
 
94
79
  return (
@@ -107,12 +92,15 @@ const CookieModal = ({
107
92
  src={logo}
108
93
  />
109
94
  ) : (
110
- <span>{privacyText}</span>
95
+ <span>{useTranslate(modalSettings?.title?.translationKey, modalSettings?.title?.label)}</span>
111
96
  )}
112
97
 
113
98
  <button
114
99
  role="button"
115
- aria-label={useTranslate("ariaLabel-closeCookie", "close cookie")}
100
+ aria-label={useTranslate(
101
+ modalSettings?.closeAriaLabel?.translationKey,
102
+ modalSettings?.closeAriaLabel?.label
103
+ )}
116
104
  type="button"
117
105
  onClick={() => closeModal()}
118
106
  >
@@ -120,16 +108,15 @@ const CookieModal = ({
120
108
  </button>
121
109
  </div>
122
110
  <div className={styles?.modalTextHeader || ""}>
123
- {!templateTwo && <span>{privacyText}</span>}
124
- <p>
125
- {useTranslate(
126
- "privacy_preference_text",
127
- "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."
128
- )}
129
- {cookieLink?.title && cookieLink?.path && (
130
- <Link to={cookieLink?.path} className={styles.cookieLink}>
131
- {cookieLink?.title}
111
+ {!templateTwo && <span>{useTranslate(modalSettings?.title?.translationKey, modalSettings?.title?.label)}</span>}
112
+ <p>{parseCookieTextWithLinks(
113
+ privacyDescription,
114
+ translatedDescriptionLinks,
115
+ (link, key) => (
116
+ <Link key={key} to={link.url} className={styles.cookieLink}>
117
+ {link.translatedText}
132
118
  </Link>
119
+ )
133
120
  )}
134
121
  </p>
135
122
  </div>
@@ -137,8 +124,8 @@ const CookieModal = ({
137
124
  <Button
138
125
  onClick={() => handleAcceptCookies()}
139
126
  btnText={useTranslate(
140
- "cookie_accept_all_button",
141
- "Accept All Cookies"
127
+ modalSettings?.acceptAllButton?.translationKey,
128
+ modalSettings?.acceptAllButton?.label
142
129
  )}
143
130
  isInternalLink={false}
144
131
  buttonType={acceptButtonType}
@@ -148,8 +135,8 @@ const CookieModal = ({
148
135
  <Button
149
136
  onClick={() => handleDeclineCookies()}
150
137
  btnText={useTranslate(
151
- "cookie_reject_nonnecessary_button",
152
- "I reject Non-Necessary"
138
+ modalSettings?.rejectNonNecessaryButton?.translationKey,
139
+ modalSettings?.rejectNonNecessaryButton?.label
153
140
  )}
154
141
  isInternalLink={false}
155
142
  buttonType={declineButtonType}
@@ -168,8 +155,8 @@ const CookieModal = ({
168
155
  className={styles?.mainTitle || ""}
169
156
  >
170
157
  {useTranslate(
171
- "menage_consent_preference",
172
- "Manage Consent Preferences"
158
+ modalSettings?.manageConsentTitle?.translationKey,
159
+ modalSettings?.manageConsentTitle?.label
173
160
  )}
174
161
  {templateTwo && (
175
162
  <svg
@@ -192,15 +179,21 @@ const CookieModal = ({
192
179
  </span>
193
180
 
194
181
  {CategoryObj.map((elm, index) => {
195
- const state = index === 1 ? anlyticsButton : marketing;
196
- const updateState =
197
- index === 1 ? setAnlyticsButton : setMarketingButton;
182
+ const currentState = categoryStates[index] || false;
183
+
184
+ const updateState = (value) => {
185
+ setCategoryStates((prev) => ({
186
+ ...prev,
187
+ [index]: value,
188
+ }));
189
+ };
198
190
 
199
191
  return (
200
192
  <CategoryContainer
193
+ key={elm.translateKey || index}
201
194
  obj={elm}
202
195
  index={index}
203
- currentState={state}
196
+ currentState={currentState}
204
197
  updateState={updateState}
205
198
  setCurrentSection={setCategorySection}
206
199
  currentSection={categorySection}
@@ -212,7 +205,10 @@ const CookieModal = ({
212
205
  <div className={styles?.lastButton || ""}>
213
206
  <Button
214
207
  onClick={() => handleAcceptCookies()}
215
- btnText={buttonText}
208
+ btnText={useTranslate(
209
+ modalSettings?.confirmButton?.translationKey,
210
+ modalSettings?.confirmButton?.label
211
+ )}
216
212
  isInternalLink={false}
217
213
  buttonType={acceptButtonType}
218
214
  buttonSize={buttonSize}
@@ -1,5 +1,6 @@
1
1
  /* eslint-disable no-case-declarations */
2
2
  /* eslint-disable react/no-danger */
3
+ /* eslint-disable react-hooks/rules-of-hooks */
3
4
  import React, { useState, lazy, Suspense } from "react";
4
5
  import PropTypes from "prop-types";
5
6
  // eslint-disable-next-line import/no-extraneous-dependencies
@@ -11,6 +12,7 @@ import { mainSettings } from "../../../constants/site-settings/main";
11
12
  import styles from "./floating-area.module.scss";
12
13
  import { pageTypes } from "../../../constants/site-settings/navigation";
13
14
  import { trackerLinkActive } from "~helpers/tracker.mjs";
15
+ import useTranslate from "~hooks/useTranslate/useTranslate";
14
16
 
15
17
  const showOperatorBanner = (operator, pageTemplate) =>
16
18
  mainSettings[pageTemplate]?.operator_banner &&
@@ -24,7 +26,6 @@ export default function FloatingArea({
24
26
  template,
25
27
  offsetTop = 400,
26
28
  showCookies = true,
27
- customCookieContent,
28
29
  }) {
29
30
  const showScroll = isSticky(offsetTop);
30
31
  const [closedBanner, setClosedBanner] = useState(false);
@@ -82,24 +83,25 @@ export default function FloatingArea({
82
83
  </Suspense>
83
84
  )}
84
85
 
85
- {CookieConsent && showCookies && (
86
+ {showCookies && (
86
87
  <CookieConsent
87
88
  market={pageContext?.page?.market}
88
89
  type={pageType}
89
90
  template={template}
90
91
  settingsCookie
91
92
  isPageHomepage={isPageHomepage}
92
- >
93
- {customCookieContent || (
94
- <p>
95
- We use cookies in order to optimise our site and improve your
96
- experience with us. By using the site you consent to our
97
- <a href="/cookies" className="cookie-consent-gtm">
98
- Cookie Policy
99
- </a>
100
- .
101
- </p>
102
- )}
93
+ >
94
+ <p>
95
+ {useTranslate(
96
+ "cookie_consent_default_text",
97
+ "We use cookies in order to optimise our site and improve your experience with us. By using the site you consent to our"
98
+ )}
99
+ <a href="/cookies" className="cookie-consent-gtm">
100
+ {" "}
101
+ {useTranslate("cookie_policy_link", "Cookie Policy")}
102
+ </a>
103
+ .
104
+ </p>
103
105
  </CookieConsent>
104
106
  )}
105
107
  {FooterNavigation && (
@@ -142,5 +144,4 @@ FloatingArea.propTypes = {
142
144
  offsetTop: PropTypes.number,
143
145
  template: PropTypes.string,
144
146
  showCookies: PropTypes.bool,
145
- customCookieContent: PropTypes.node,
146
147
  };
@@ -1,3 +1,4 @@
1
+ /* eslint-disable no-restricted-syntax */
1
2
  import React from 'react';
2
3
  import { render, cleanup, fireEvent } from '@testing-library/react';
3
4
  import '@testing-library/jest-dom/extend-expect';
@@ -40,11 +41,11 @@ describe('cookie consent component', () => {
40
41
  });
41
42
  test('Buttons', () => {
42
43
  const { getByText } = renderComponent();
43
- expect(getByText('Accept Me')).toBeTruthy();
44
+ expect(getByText('Accept')).toBeTruthy();
44
45
  });
45
46
  test('Hide on accept', () => {
46
47
  const { container, getByText } = renderComponent();
47
- const btn = getByText('Accept Me');
48
+ const btn = getByText('Accept');
48
49
  fireEvent.click(btn);
49
50
  expect(container.querySelector('div.cookieConsent')).not.toHaveClass('show');
50
51
  expect(Boolean(getCookie('CookieConsent'))).toBe(true);
@@ -1,6 +1,7 @@
1
1
  /* eslint-disable react-hooks/exhaustive-deps */
2
2
  /* eslint-disable jsx-a11y/click-events-have-key-events */
3
3
  /* eslint-disable import/no-extraneous-dependencies */
4
+ /* eslint-disable react-hooks/rules-of-hooks */
4
5
  import React, { useState, useEffect, lazy, Suspense } from "react";
5
6
  import PropTypes from "prop-types";
6
7
 
@@ -8,9 +9,10 @@ import useTranslate from "~hooks/useTranslate/useTranslate";
8
9
  import { setCookie, getCookie } from "~helpers/cookies";
9
10
  import styles from "./cookie-consent.module.scss";
10
11
 
12
+ import cookiesContent from '../../../constants/cookies';
13
+ import { parseCookieTextWithLinks } from '../../../helpers/generators.mjs'
14
+
11
15
  const CookieConsent = ({
12
- acceptText = "Accept",
13
- rejectText = "Reject",
14
16
  settingsCookie,
15
17
  children,
16
18
  cookieName = "CookieConsent",
@@ -24,6 +26,24 @@ const CookieConsent = ({
24
26
 
25
27
  const CookieModal = lazy(() => import("../../molecules/cookie-modal"));
26
28
 
29
+
30
+ const shouldShowRejectButton =
31
+ typeof cookiesContent.showRejectButton === "boolean"
32
+ ? cookiesContent.showRejectButton
33
+ : showRejectButton;
34
+
35
+ const shouldIgnoreComponentChildren =
36
+ cookiesContent?.ignoreComponentChildren === true;
37
+
38
+ const translatedLinks = (cookiesContent.links || []).map((link) => ({
39
+ ...link,
40
+ translatedText: useTranslate(link.translationKey || "", link.text || ""),
41
+ }));
42
+
43
+ const bannerText = useTranslate(
44
+ cookiesContent.text?.translationKey,
45
+ cookiesContent.text?.label
46
+ );
27
47
  // when user declines
28
48
  const handleDecline = () => {
29
49
  setCookie(cookieName, false, 365, "/");
@@ -58,11 +78,6 @@ const CookieConsent = ({
58
78
  }
59
79
  }, []);
60
80
 
61
- const settingCookieText = useTranslate(
62
- "cookie_setting_button",
63
- "Cookie Setting"
64
- );
65
-
66
81
  return (
67
82
  <>
68
83
  <div
@@ -71,7 +86,15 @@ const CookieConsent = ({
71
86
  }`}
72
87
  >
73
88
  <div className={`${styles?.consent || ""}`}>
74
- <div className={styles?.content || ""}>{children}</div>
89
+ <div className={styles?.content || ""}>{shouldIgnoreComponentChildren ? (
90
+ parseCookieTextWithLinks(bannerText, translatedLinks, (link, key) => (
91
+ <a key={key} href={link.url} className="cookie-consent-gtm">
92
+ {link.translatedText}
93
+ </a>
94
+ ))
95
+ ) : (
96
+ children
97
+ )}</div>
75
98
  <div className={styles?.buttonsContainer || ""}>
76
99
  {settingsCookie && (
77
100
  <button
@@ -79,18 +102,21 @@ const CookieConsent = ({
79
102
  className="cookie-consent-gtm btn-cta"
80
103
  type="button"
81
104
  >
82
- {settingCookieText}
105
+ {useTranslate(
106
+ cookiesContent.managePreferences?.translationKey,
107
+ cookiesContent.managePreferences?.label
108
+ )}
83
109
  {icon && icon}
84
110
  </button>
85
111
  )}
86
- {showRejectButton && (
112
+ {shouldShowRejectButton && (
87
113
  <button
88
114
  onClick={() => handleDecline()}
89
115
  className={`${styles.rejectButton} cookie-consent-gtm btn-cta`}
90
116
  type="button"
91
117
  >
92
118
  {/* eslint-disable-next-line react/jsx-no-comment-textnodes, react-hooks/rules-of-hooks */}
93
- {useTranslate("cookie_reject_button", rejectText)}
119
+ {useTranslate(cookiesContent.rejectButton?.translationKey, cookiesContent.rejectButton?.label)}
94
120
  </button>
95
121
  )}
96
122
  <button
@@ -98,7 +124,7 @@ const CookieConsent = ({
98
124
  className="cookie-consent-gtm btn-cta"
99
125
  type="button"
100
126
  >
101
- {useTranslate("cookie_accept_button", acceptText)}
127
+ {useTranslate(cookiesContent.acceptButton?.translationKey, cookiesContent.acceptButton?.label)}
102
128
  {icon && icon}
103
129
  </button>
104
130
  </div>
@@ -120,8 +146,6 @@ const CookieConsent = ({
120
146
  };
121
147
 
122
148
  CookieConsent.propTypes = {
123
- acceptText: PropTypes.string,
124
- rejectText: PropTypes.string,
125
149
  showRejectButton: PropTypes.bool,
126
150
  settingsCookie: PropTypes.bool,
127
151
  cookieName: PropTypes.string,
@@ -1,3 +1,4 @@
1
+ /* eslint-disable no-restricted-syntax */
1
2
  import React from 'react';
2
3
  import { render, cleanup } from '@testing-library/react';
3
4
  import '@testing-library/jest-dom/extend-expect';
@@ -29,7 +30,6 @@ describe('Body', () => {
29
30
  const { container } = renderComponent();
30
31
  // all sections have their own test, we only need to test body renders without crash
31
32
  expect(container).toBeTruthy();
32
- expect(container.querySelector('.scrollToTop')).toBeTruthy();
33
33
  });
34
34
  });
35
35
  afterEach(() => {
@@ -0,0 +1,99 @@
1
+ const cookiesContent = {
2
+ ignoreComponentChildren: false,
3
+ showRejectButton: false,
4
+ text: {
5
+ label:
6
+ 'We use cookies in order to optimise our site and improve your experience with us. By using the site you consent to our [link-0].',
7
+ translationKey: 'cookie_consent_default_text',
8
+ },
9
+ links: [
10
+ {
11
+ url: '/cookies',
12
+ text: 'Cookie Policy',
13
+ translationKey: 'cookie_policy_link',
14
+ }
15
+ ],
16
+ managePreferences: {
17
+ label: 'Cookie Setting',
18
+ translationKey: 'cookie_setting_button',
19
+ },
20
+ acceptButton: {
21
+ label: 'Accept',
22
+ translationKey: 'cookie_accept_button',
23
+ },
24
+ rejectButton: {
25
+ label: 'Reject',
26
+ translationKey: 'cookie_reject_button',
27
+ },
28
+ modal: {
29
+ title: {
30
+ label: 'Privacy Preference Center',
31
+ translationKey: 'privacy_preference_title',
32
+ },
33
+ description: {
34
+ label:
35
+ '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.',
36
+ translationKey: 'privacy_preference_text',
37
+ },
38
+ confirmButton: {
39
+ label: 'Confirm my choices',
40
+ translationKey: 'cookie_confirm_button',
41
+ },
42
+ acceptAllButton: {
43
+ label: 'Accept All Cookies',
44
+ translationKey: 'cookie_accept_all_button',
45
+ },
46
+ rejectNonNecessaryButton: {
47
+ label: 'I reject Non-Necessary',
48
+ translationKey: 'cookie_reject_nonnecessary_button',
49
+ },
50
+ manageConsentTitle: {
51
+ label: 'Manage Consent Preferences',
52
+ translationKey: 'menage_consent_preference',
53
+ },
54
+ closeAriaLabel: {
55
+ label: 'close cookie',
56
+ translationKey: 'ariaLabel-closeCookie',
57
+ },
58
+ categories: [
59
+ {
60
+ title: {
61
+ label: 'Neccesary',
62
+ translationKey: 'neccesary_cookie_title',
63
+ },
64
+ description: {
65
+ label:
66
+ '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.',
67
+ translationKey: 'neccesary_cookie_text',
68
+ },
69
+ hasToggle: false,
70
+ },
71
+ {
72
+ title: {
73
+ label: 'Analytical And Stadistical',
74
+ translationKey: 'analytical_cookie_title',
75
+ },
76
+ description: {
77
+ label:
78
+ '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.',
79
+ translationKey: 'analytical_cookie_text',
80
+ },
81
+ hasToggle: true,
82
+ },
83
+ {
84
+ title: {
85
+ label: 'Marketing',
86
+ translationKey: 'marketing_cookie_title',
87
+ },
88
+ description: {
89
+ label:
90
+ '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.',
91
+ translationKey: 'marketing_cookie_text',
92
+ },
93
+ hasToggle: true,
94
+ },
95
+ ],
96
+ },
97
+ };
98
+
99
+ export default cookiesContent;
@@ -68,3 +68,23 @@ export function generateArray(obj) {
68
68
  ? Object.keys(obj).map((key) => obj[key])
69
69
  : [];
70
70
  }
71
+
72
+ export const parseCookieTextWithLinks = (
73
+ text,
74
+ translatedLinks = [],
75
+ renderLink
76
+ ) =>
77
+ text.split(/(\[link-\d+\])/g).map((part, i) => {
78
+ const match = part.match(/^\[link-(\d+)\]$/);
79
+
80
+ if (!match) return part;
81
+
82
+ const linkIndex = Number(match[1]);
83
+ const link = translatedLinks[linkIndex];
84
+
85
+ if (!link) return part;
86
+
87
+ return renderLink(link, i);
88
+ });
89
+
90
+ export default parseCookieTextWithLinks;
@@ -2,6 +2,7 @@ import {
2
2
  generateTrackerLink,
3
3
  generatePlaceholderString,
4
4
  generateArray,
5
+ parseCookieTextWithLinks
5
6
  } from "./generators.mjs";
6
7
  import { months } from "~constants/common";
7
8
 
@@ -149,3 +150,54 @@ describe("Generate Array From Object", () => {
149
150
  expect(output[1]).toEqual("b");
150
151
  });
151
152
  });
153
+
154
+ describe("Parse Cookie Text With Links", () => {
155
+ test("replaces indexed link placeholders with rendered links", () => {
156
+ const output = parseCookieTextWithLinks(
157
+ "Read our [link-0] and [link-1] for more details.",
158
+ [
159
+ {
160
+ url: "/cookies",
161
+ translatedText: "Cookie Policy",
162
+ },
163
+ {
164
+ url: "/privacy-policy",
165
+ translatedText: "Privacy Policy",
166
+ },
167
+ ],
168
+ (link) => `[${link.translatedText}:${link.url}]`
169
+ );
170
+
171
+ expect(output).toEqual([
172
+ "Read our ",
173
+ "[Cookie Policy:/cookies]",
174
+ " and ",
175
+ "[Privacy Policy:/privacy-policy]",
176
+ " for more details.",
177
+ ]);
178
+ });
179
+
180
+ test("leaves text unchanged when placeholder link is missing", () => {
181
+ const output = parseCookieTextWithLinks(
182
+ "Read our [link-0] for more details.",
183
+ [],
184
+ (link) => `[${link.translatedText}:${link.url}]`
185
+ );
186
+
187
+ expect(output).toEqual([
188
+ "Read our ",
189
+ "[link-0]",
190
+ " for more details.",
191
+ ]);
192
+ });
193
+
194
+ test("returns plain text when there are no link placeholders", () => {
195
+ const output = parseCookieTextWithLinks(
196
+ "No links here.",
197
+ [{ url: "/cookies", translatedText: "Cookie Policy" }],
198
+ (link) => `[${link.translatedText}:${link.url}]`
199
+ );
200
+
201
+ expect(output).toEqual(["No links here."]);
202
+ });
203
+ });