gatsby-core-theme 44.22.0 → 44.22.2

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,30 @@
1
+ ## [44.22.2](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/compare/v44.22.1...v44.22.2) (2026-04-24)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * mapped spotlight mode sports_odds ([e8d5757](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/e8d575769e213645f7b0096589b7297c93f63d24))
7
+
8
+
9
+ * Merge branch 'EN-497-Enable-new-spotlight-mode' into 'master' ([0e1f54d](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/0e1f54d2b36cf678fbbad1199c041d7996624d73))
10
+
11
+ ## [44.22.1](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/compare/v44.22.0...v44.22.1) (2026-04-21)
12
+
13
+
14
+ ### Bug Fixes
15
+
16
+ * fix eslint error ([7698d62](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/7698d62217cc3133caf4b18f1a291bcaea08a3f5))
17
+ * fix tests for cookie consent ([60c738f](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/60c738f4d5cb7b79c5fb573adecb1076c26dfa96))
18
+
19
+
20
+ ### Config
21
+
22
+ * change logic to config driven ([e546f1a](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/e546f1a06609649fe229d31c2cc5c5e3e13ae127))
23
+ * refactor cookie consents to use custom content ([0b611c4](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/0b611c423aa7406450b63e554df1db1b621d5a75))
24
+
25
+
26
+ * Merge branch 'EN-475/refactor-cookie-consents' into 'master' ([11aba91](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/11aba914e3f81e33c36c3b28c2c671f124ec532f))
27
+
1
28
  # [44.22.0](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/compare/v44.21.1...v44.22.0) (2026-04-14)
2
29
 
3
30
 
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.2",
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
+ });
@@ -387,7 +387,7 @@ export function processTopListModule(
387
387
  const itemsCount = +listItem.num_items_initial_load;
388
388
  const toplistItem = toplists && listItem.id
389
389
  ? toplists[listItem.id.toString()] ||
390
- toplists[`g_${listItem.id.toString()}`]
390
+ toplists[`g_${listItem.id.toString()}`]
391
391
  : undefined;
392
392
  if (toplistItem) {
393
393
  listItem.market = toplistItem.market?.short_code;
@@ -413,24 +413,24 @@ export function processTopListModule(
413
413
  // build top list
414
414
  listItem.items = listItem.items
415
415
  ? listItem.items
416
- .filter(
417
- (item) =>
418
- Object.values(relations.operator).find(
419
- (operator) =>
420
- operator.operator_id === item.operator_id &&
421
- operator.market === listItem.market &&
422
- type === operator.type
423
- ) ||
424
- Object.values(relations.game).find(
425
- (game) => game.game_id === item.game_id
426
- )
427
- )
428
- .map((item) => {
429
- if (item.operator_id) return processOperatorToplist(item, ctx);
430
- if (item.game_id) return processGameToplist(item, ctx);
431
- return null;
432
- })
433
- .filter(Boolean)
416
+ .filter(
417
+ (item) =>
418
+ Object.values(relations.operator).find(
419
+ (operator) =>
420
+ operator.operator_id === item.operator_id &&
421
+ operator.market === listItem.market &&
422
+ type === operator.type
423
+ ) ||
424
+ Object.values(relations.game).find(
425
+ (game) => game.game_id === item.game_id
426
+ )
427
+ )
428
+ .map((item) => {
429
+ if (item.operator_id) return processOperatorToplist(item, ctx);
430
+ if (item.game_id) return processGameToplist(item, ctx);
431
+ return null;
432
+ })
433
+ .filter(Boolean)
434
434
  : [];
435
435
  const latestItems = listItem.items
436
436
  .map((toplist) => ({
@@ -456,10 +456,10 @@ export function processTopListModule(
456
456
  // eslint-disable-next-line no-restricted-globals
457
457
  latestUpdatedDate instanceof Date && !isNaN(latestUpdatedDate)
458
458
  ? new Intl.DateTimeFormat("en-US", {
459
- year: "numeric",
460
- month: "long",
461
- day: "numeric",
462
- }).format(latestUpdatedDate)
459
+ year: "numeric",
460
+ month: "long",
461
+ day: "numeric",
462
+ }).format(latestUpdatedDate)
463
463
  : null;
464
464
 
465
465
  listItem.latest_items = formattedLatestItems;
@@ -513,15 +513,22 @@ export function shouldSavePrefilled(module = {}, siteName) {
513
513
  );
514
514
  }
515
515
 
516
- export function processSpotlightModule(module = {}, content, previewPageID) {
517
- module.items.forEach((item) => {
518
- item.content = trailingSlash(
519
- previewPageID ? item.content : (content && content[item.content]) || ""
520
- );
521
- item.text = trailingSlash(
522
- previewPageID ? item.text : (content && content[item.text]) || "",
523
- );
524
- });
516
+ export function processSpotlightModule(module = {}, content, relations, ribbons, previewPageID) {
517
+ if (module.mode === "sport_odds") {
518
+ module.items.forEach((item) => {
519
+ item.relation = relations?.[item.page.relation_id];
520
+ item.page.ribbons = item.page.ribbon_ids.map((id) => ribbons?.[id]);
521
+ });
522
+ } else {
523
+ module.items.forEach((item) => {
524
+ item.content = trailingSlash(
525
+ previewPageID ? item.content : (content && content[item.content]) || ""
526
+ );
527
+ item.text = trailingSlash(
528
+ previewPageID ? item.text : (content && content[item.text]) || "",
529
+ );
530
+ });
531
+ }
525
532
 
526
533
  return module;
527
534
  }
@@ -542,23 +549,23 @@ export function processFaq(module = {}, content, relationData, previewPageID) {
542
549
  previewPageID
543
550
  ? generatePlaceholderString(item.question, null, relationData)
544
551
  : (content
545
- ? generatePlaceholderString(
546
- content[item.question],
547
- null,
548
- relationData
549
- )
550
- : "") || ""
552
+ ? generatePlaceholderString(
553
+ content[item.question],
554
+ null,
555
+ relationData
556
+ )
557
+ : "") || ""
551
558
  );
552
559
  item.answer = trailingSlash(
553
560
  previewPageID
554
561
  ? generatePlaceholderString(item.answer, null, relationData)
555
562
  : (content &&
556
- generatePlaceholderString(
557
- content[item.answer],
558
- null,
559
- relationData
560
- )) ||
561
- ""
563
+ generatePlaceholderString(
564
+ content[item.answer],
565
+ null,
566
+ relationData
567
+ )) ||
568
+ ""
562
569
  );
563
570
  });
564
571
  }
@@ -599,13 +606,14 @@ export function processModule(
599
606
  prefilledMarketModules,
600
607
  previewPageID
601
608
  ) {
609
+
602
610
  module.module_title =
603
611
  module.module_title &&
604
612
  generatePlaceholderString(module.module_title, translations, relationData);
605
613
  module.title =
606
614
  module.title &&
607
615
  generatePlaceholderString(module.title, translations, relationData);
608
-
616
+
609
617
  module.anchor_slug =
610
618
  module.anchor_slug &&
611
619
  generatePlaceholderString(module.anchor_slug, translations, relationData);
@@ -616,7 +624,7 @@ export function processModule(
616
624
  module.module_introduction,
617
625
  translations,
618
626
  relationData,
619
- );
627
+ );
620
628
 
621
629
  // See more link
622
630
  if (
@@ -663,7 +671,7 @@ export function processModule(
663
671
  } else if (module.name === "anchor") {
664
672
  processAnchor(module, relationData, translations);
665
673
  } else if (module.name === "spotlights") {
666
- processSpotlightModule(module, content, previewPageID);
674
+ processSpotlightModule(module, content, relations?.operator, data?.ribbons, previewPageID);
667
675
  } else if (module.name === "menu" && menus && menus[module.menu_id]) {
668
676
  module = Object.assign(module, menus[module.menu_id]);
669
677
  } else if (module.name === "statistics_counter") {