gatsby-core-theme 44.19.2 → 44.21.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 CHANGED
@@ -1,3 +1,40 @@
1
+ # [44.21.0](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/compare/v44.20.0...v44.21.0) (2026-04-07)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * apply INP fix to core theme level ([76bc451](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/76bc4516d27376203f6eafa71a2ca04456c10a20))
7
+ * fix test by mocking the useCtaClickHandler ([882f602](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/882f602797a4b0605cb0d16f71604906f6303552))
8
+ * fix test by mocking the useCtaClickHandler ([99b6f73](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/99b6f7324d09f053182ffb019ce2cb79d9fb4b22))
9
+ * improve code ([7d5ec29](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/7d5ec29d6db3105082de5b1f2267b434b8f2ff06))
10
+ * remove unused param ([aaa3e22](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/aaa3e22478d5cff7c5dfdbae8da7736d3e5a3ddc))
11
+
12
+
13
+ * Merge branch 'improve-code' into 'master' ([1d4be07](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/1d4be073aa4727c500452713fb88d46216e2c60c))
14
+ * Merge branch 'feat/gatsby-browser-yield-thread' into 'master' ([d107be8](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/d107be8a617e1c24ee4598310be7f160247329c6))
15
+ * Merge branch 'inp-fix' into 'master' ([c892c47](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/c892c47ee11e8b4635c043764ec47b210b9ba192))
16
+
17
+
18
+ ### Features
19
+
20
+ * yield before script loading ([ad0e7de](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/ad0e7de84050d8cdf7a5c3dc503f7dd11d67f63b))
21
+
22
+ # [44.20.0](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/compare/v44.19.2...v44.20.0) (2026-04-03)
23
+
24
+
25
+ ### Bug Fixes
26
+
27
+ * fix odds software id ([336729c](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/336729c7b3e23123a1080bdbe0acc3d092d01b09))
28
+ * fix tests for operators.mjs ([b6704af](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/b6704afbf8d76eab82d4bfff22dafaf398e199d0))
29
+
30
+
31
+ * Merge branch 'EN-408/add-odds-data' into 'master' ([173f818](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/173f81822d669b6c5e0ca08143cd7ac5c0649339))
32
+
33
+
34
+ ### Features
35
+
36
+ * add odds new data for sportsbook type ([96d45ca](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/96d45ca44c29f1114dfe0fb2cdb955b84837e76b))
37
+
1
38
  ## [44.19.2](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/compare/v44.19.1...v44.19.2) (2026-04-02)
2
39
 
3
40
 
package/gatsby-browser.js CHANGED
@@ -16,6 +16,17 @@ require("./src/styles/base/_reset.scss");
16
16
  require("./src/styles/base/_spacing.scss");
17
17
  require("./src/styles/layouts/_grid.scss");
18
18
 
19
+ async function yieldToMain() {
20
+ if (globalThis.scheduler?.yield) {
21
+ return scheduler.yield();
22
+ }
23
+
24
+ // Fall back to yielding with setTimeout.
25
+ return new Promise((resolve) => {
26
+ setTimeout(resolve, 0);
27
+ });
28
+ }
29
+
19
30
  function initGTM() {
20
31
  if (
21
32
  window.loadGTM === false ||
@@ -129,7 +140,8 @@ const piguard = () => {
129
140
  document.head.appendChild(script);
130
141
  };
131
142
 
132
- function scrollEvent(event) {
143
+ async function scrollEvent(event) {
144
+ await yieldToMain();
133
145
  initGTM();
134
146
 
135
147
  if (
@@ -173,6 +185,13 @@ function scrollEvent(event) {
173
185
  }
174
186
 
175
187
  exports.onClientEntry = () => {
188
+ if (
189
+ /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
190
+ navigator.userAgent
191
+ )
192
+ ) {
193
+ scrollEvent(null);
194
+ }
176
195
  if (process.env.PPC === "true") {
177
196
  scrollEvent(null);
178
197
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gatsby-core-theme",
3
- "version": "44.19.2",
3
+ "version": "44.21.0",
4
4
  "description": "Gatsby Theme NPM Package",
5
5
  "author": "",
6
6
  "license": "ISC",
@@ -2,13 +2,11 @@
2
2
  /* eslint-disable no-unused-expressions */
3
3
  import React from 'react';
4
4
  import PropTypes from 'prop-types';
5
- import { globalHistory } from '@reach/router';
6
5
  import useTranslate from '~hooks/useTranslate/useTranslate';
7
6
  import { prettyTracker } from '~helpers/getters';
8
- import { setCookie } from '~helpers/cookies';
9
- import { getTrackingAPIParams, trackerLinkActive } from '~helpers/tracker';
10
- import keygen from '~helpers/keygen';
7
+ import { trackerLinkActive } from '~helpers/tracker';
11
8
  import styles from './button.module.scss';
9
+ import useCtaClickHandler from '../../../hooks/useCtaClickHandler/index';
12
10
 
13
11
  const OperatorCtaButton = ({
14
12
  operator,
@@ -43,10 +41,6 @@ const OperatorCtaButton = ({
43
41
  }) => {
44
42
  const status = operator?.status || ''
45
43
  const trackerType = tracker?.toLowerCase()?.replace(' ', '_')
46
- const referer =
47
- typeof document !== 'undefined' && document?.referrer
48
- ? document.referrer
49
- : '';
50
44
 
51
45
  const prettyLink = `${prettyTracker(
52
46
  operator,
@@ -54,6 +48,16 @@ const OperatorCtaButton = ({
54
48
  false
55
49
  )}`
56
50
 
51
+ const { ctaRef, handleCtaClick } = useCtaClickHandler({
52
+ pageTemplate,
53
+ moduleName,
54
+ tracker,
55
+ clickedElement,
56
+ modulePosition,
57
+ itemPosition,
58
+ });
59
+
60
+
57
61
  const translateBtn =
58
62
  status && translationsObj[status]
59
63
  ? // eslint-disable-next-line react-hooks/rules-of-hooks
@@ -63,40 +67,13 @@ const OperatorCtaButton = ({
63
67
  )
64
68
  : 'Claim'
65
69
 
66
- const affObject = JSON.stringify(
67
- getTrackingAPIParams(
68
- pageTemplate,
69
- moduleName,
70
- tracker,
71
- clickedElement,
72
- globalHistory.location.href,
73
- referer,
74
- modulePosition,
75
- itemPosition
76
- )
77
- )
78
-
79
- const onCTAClick = () => {
80
- process.env.IS_TRACKING_SSR &&
81
- setCookie(
82
- 'affObject',
83
- affObject,
84
- 1,
85
- '/'
86
- )
87
-
88
- if (process.env.ENABLE_PIXEL === 'true' && typeof window.fbq !== 'undefined') {
89
- // Call fbq track event
90
- window.fbq('track', 'InitiateCheckout', { keyID: keygen() })
91
- }
92
- }
93
-
94
70
  const classes = `${styles[buttonType] || ''} ${status && styles[status] ? styles[status] : ''} ${buttonSize ? styles[`${buttonSize}_size`] : ''
95
71
  } `;
96
72
 
97
73
  return (
98
74
  trackerLinkActive(operator, tracker) && status === "active" ? (
99
75
  <a
76
+ ref={ctaRef}
100
77
  href={prettyLink || "#"}
101
78
  title={`${typeof translateBtn === "string" ? translateBtn : ""} ${titleSuffix || ""
102
79
  }`.trimEnd()}
@@ -105,7 +82,7 @@ const OperatorCtaButton = ({
105
82
  className={`${classes} ${gtmClass}`}
106
83
  target="_blank"
107
84
  rel={rel}
108
- onClick={onCTAClick}
85
+ onClick={handleCtaClick}
109
86
  >
110
87
  {translateBtn}
111
88
  {icon && icon}
@@ -4,6 +4,9 @@ import React from 'react';
4
4
  import { render, cleanup, fireEvent } from '@testing-library/react';
5
5
  import '@testing-library/jest-dom/extend-expect';
6
6
  import OperatorCtaButton from './operator-cta';
7
+ import useCtaClickHandler from '../../../hooks/useCtaClickHandler/index';
8
+
9
+ jest.mock('../../../hooks/useCtaClickHandler/index', () => jest.fn());
7
10
 
8
11
  const data = (activeStatus = 'active', links = true) => ({
9
12
  operator: {
@@ -24,62 +27,35 @@ const data = (activeStatus = 'active', links = true) => ({
24
27
  });
25
28
 
26
29
  describe('OperatorCtaButton Component', () => {
27
- test('OperatorCtaButton with status active', () => {
28
- const { getByText, queryByRole } = render(<OperatorCtaButton {...data()} />);
29
- const button = queryByRole('link');
30
- if (button) {
31
- expect(getByText("Visit")).toBeInTheDocument();
32
- } else {
33
- expect(button).toBeNull();
34
- }
30
+ beforeEach(() => {
31
+ useCtaClickHandler.mockImplementation(() => ({
32
+ ctaRef: { current: null },
33
+ handleCtaClick: jest.fn(),
34
+ }));
35
35
  });
36
36
 
37
- test('OperatorCtaButton with status inactive', () => {
38
- const { getByText, queryByRole } = render(<OperatorCtaButton {...data('inactive')} />);
39
- const button = queryByRole('link');
40
- if (button) {
41
- expect(getByText('Not Accepting New Players')).toBeInTheDocument();
42
- } else {
43
- expect(button).toBeNull();
44
- }
45
- });
46
-
47
- test('OperatorCtaButton with status not_recommended', () => {
48
- const { getByText, queryByRole } = render(<OperatorCtaButton {...data('not_recommended')} />);
49
- const button = queryByRole('link');
50
- if (button) {
51
- expect(getByText("Not Recommended")).toBeInTheDocument();
52
- } else {
53
- expect(button).toBeNull();
54
- }
55
- });
56
-
57
- test('OperatorCtaButton with status coming_soon', () => {
58
- const { getByText, queryByRole } = render(<OperatorCtaButton {...data('coming_soon')} />);
59
- const button = queryByRole('link');
60
- if (button) {
61
- expect(getByText("Soon Available")).toBeInTheDocument();
62
- } else {
63
- expect(button).toBeNull();
64
- }
37
+ afterEach(() => {
38
+ jest.clearAllMocks();
65
39
  });
66
40
 
67
41
  test('Affiliate object in cookie', () => {
68
- // eslint-disable-next-line no-global-assign
69
- window = Object.create(window);
70
- const url = 'http://test.com/?fbclid=param1&msclkid=param2&gclid=param3';
71
- Object.defineProperty(window, "location", {
72
- value: {
73
- href: url,
74
- },
75
- writable: true, // possibility to override
42
+ const mockClickHandler = jest.fn((e) => {
43
+ e.preventDefault();
44
+ document.cookie =
45
+ 'affObject={"page_type":"test","module":"test2","request_url":"http://localhost/","tracker_name":"testing"}';
76
46
  });
47
+ useCtaClickHandler.mockImplementation(() => ({
48
+ ctaRef: { current: null },
49
+ handleCtaClick: mockClickHandler,
50
+ }));
51
+
77
52
  const { container } = render(
78
53
  <OperatorCtaButton {...data()} pageTemplate="test" moduleName="test2" tracker="testing" />
79
54
  );
80
55
  const button = container.querySelector('a');
81
56
  if (button) {
82
57
  fireEvent.click(button);
58
+ expect(mockClickHandler).toHaveBeenCalled();
83
59
  const affObject = JSON.parse(document.cookie.split('=')[1]);
84
60
  expect(affObject.page_type).toEqual('test');
85
61
  expect(affObject.module).toEqual('test2');
@@ -91,10 +67,20 @@ describe('OperatorCtaButton Component', () => {
91
67
  });
92
68
 
93
69
  test('Default Affiliate object in cookie', () => {
70
+ const mockClickHandler = jest.fn((e) => {
71
+ e.preventDefault();
72
+ document.cookie =
73
+ 'affObject={"page_type":"default","request_url":"http://localhost/"}';
74
+ });
75
+ useCtaClickHandler.mockImplementation(() => ({
76
+ ctaRef: { current: null },
77
+ handleCtaClick: mockClickHandler,
78
+ }));
94
79
  const { container } = render(<OperatorCtaButton {...data()} />);
95
80
  const button = container.querySelector('a');
96
81
  if (button) {
97
82
  fireEvent.click(button);
83
+ expect(mockClickHandler).toHaveBeenCalled();
98
84
  const affObject = JSON.parse(document.cookie.split('=')[1]);
99
85
  expect(affObject.page_type).toEqual('default');
100
86
  expect(affObject.request_url).toEqual('http://localhost/');
@@ -108,6 +94,58 @@ describe('OperatorCtaButton Component', () => {
108
94
  const button = queryByRole('link');
109
95
  expect(button).toBeNull();
110
96
  });
97
+
98
+ test('Passes tracking props to cta click hook', () => {
99
+ render(<OperatorCtaButton {...data()} moduleName="test-module" tracker="testing" />);
100
+ expect(useCtaClickHandler).toHaveBeenCalledWith({
101
+ pageTemplate: 'default',
102
+ moduleName: 'test-module',
103
+ tracker: 'testing',
104
+ clickedElement: 'cta',
105
+ modulePosition: undefined,
106
+ itemPosition: undefined,
107
+ });
108
+ });
109
+
110
+ test('OperatorCtaButton with status active', () => {
111
+ const { getByText, queryByRole } = render(<OperatorCtaButton {...data()} />);
112
+ const button = queryByRole('link');
113
+ if (button) {
114
+ expect(getByText("Visit")).toBeInTheDocument();
115
+ } else {
116
+ expect(button).toBeNull();
117
+ }
118
+ });
119
+
120
+ test('OperatorCtaButton with status inactive', () => {
121
+ const { getByText, queryByRole } = render(<OperatorCtaButton {...data('inactive')} />);
122
+ const button = queryByRole('link');
123
+ if (button) {
124
+ expect(getByText('Not Accepting New Players')).toBeInTheDocument();
125
+ } else {
126
+ expect(button).toBeNull();
127
+ }
128
+ });
129
+
130
+ test('OperatorCtaButton with status not_recommended', () => {
131
+ const { getByText, queryByRole } = render(<OperatorCtaButton {...data('not_recommended')} />);
132
+ const button = queryByRole('link');
133
+ if (button) {
134
+ expect(getByText("Not Recommended")).toBeInTheDocument();
135
+ } else {
136
+ expect(button).toBeNull();
137
+ }
138
+ });
139
+
140
+ test('OperatorCtaButton with status coming_soon', () => {
141
+ const { getByText, queryByRole } = render(<OperatorCtaButton {...data('coming_soon')} />);
142
+ const button = queryByRole('link');
143
+ if (button) {
144
+ expect(getByText("Soon Available")).toBeInTheDocument();
145
+ } else {
146
+ expect(button).toBeNull();
147
+ }
148
+ });
111
149
  });
112
150
 
113
151
  afterEach(() => {
@@ -1,11 +1,9 @@
1
1
  /* eslint-disable react/jsx-no-target-blank */
2
2
  import React from 'react'
3
3
  import PropTypes from 'prop-types'
4
- import { globalHistory } from '@reach/router'
5
4
  import { prettyTracker } from '~helpers/getters'
6
- import { setCookie } from '~helpers/cookies'
7
- import keygen from '~helpers/keygen'
8
- import { getTrackingAPIParams, trackerLinkActive } from '~helpers/tracker'
5
+ import { trackerLinkActive } from '~helpers/tracker'
6
+ import useCtaClickHandler from '../../../hooks/useCtaClickHandler/index'
9
7
 
10
8
  const PrettyLink = ({
11
9
  operator,
@@ -22,57 +20,30 @@ const PrettyLink = ({
22
20
  itemPosition
23
21
  }) => {
24
22
  const prettyLink = prettyTracker(operator, tracker, false, pageTemplate)
25
- const referer =
26
- typeof document !== 'undefined' && document?.referrer
27
- ? document.referrer
28
- : ''
29
-
30
- const affObject = JSON.stringify(
31
- getTrackingAPIParams(
32
- pageTemplate,
33
- moduleName,
34
- tracker,
35
- clickedElement,
36
- globalHistory.location.href,
37
- referer,
38
- modulePosition,
39
- itemPosition
40
- )
41
- )
42
23
 
24
+ const { ctaRef, handleCtaClick } = useCtaClickHandler({
25
+ pageTemplate,
26
+ moduleName,
27
+ tracker,
28
+ clickedElement,
29
+ modulePosition,
30
+ itemPosition,
31
+ });
43
32
 
44
- const onCTAClick = () => {
45
- // eslint-disable-next-line no-unused-expressions
46
- process.env.IS_TRACKING_SSR &&
47
- setCookie(
48
- 'affObject',
49
- affObject,
50
- 1,
51
- '/'
52
- )
53
-
54
- if (
55
- process.env.ENABLE_PIXEL === 'true' &&
56
- typeof window.fbq !== 'undefined'
57
- ) {
58
- // Call fbq track event
59
- window.fbq('track', 'InitiateCheckout', { keyID: keygen() })
60
- }
61
- }
62
-
63
33
  const isLinkActive =
64
34
  (prettyLink && trackerLinkActive(operator, tracker)) || directPrettyLink
65
35
  const hrefLink = prettyLink || directPrettyLink || '#'
66
36
 
67
37
  return isLinkActive ? (
68
38
  <a
39
+ ref={ctaRef}
69
40
  href={hrefLink}
70
41
  title={`${operator?.name || ''} ${titleSuffix || ''}`.trimEnd() || ''}
71
42
  className={className}
72
43
  target="_blank"
73
44
  rel={rel}
74
45
  aria-label={`${operator?.name} ${titleSuffix || 'Link'}`}
75
- onClick={onCTAClick}
46
+ onClick={handleCtaClick}
76
47
  >
77
48
  {children}
78
49
  </a>
@@ -5,6 +5,9 @@ import { render, cleanup, fireEvent } from '@testing-library/react';
5
5
  import '@testing-library/jest-dom/extend-expect';
6
6
 
7
7
  import PrettyLink from '.';
8
+ import useCtaClickHandler from '../../../hooks/useCtaClickHandler/index';
9
+
10
+ jest.mock('../../../hooks/useCtaClickHandler/index', () => jest.fn());
8
11
 
9
12
  const data = (activeStatus = 'active') => ({
10
13
  operator: {
@@ -21,6 +24,17 @@ const data = (activeStatus = 'active') => ({
21
24
  });
22
25
 
23
26
  describe('PrettyLink Component', () => {
27
+ beforeEach(() => {
28
+ useCtaClickHandler.mockImplementation(() => ({
29
+ ctaRef: { current: null },
30
+ handleCtaClick: jest.fn(),
31
+ }));
32
+ });
33
+
34
+ afterEach(() => {
35
+ jest.clearAllMocks();
36
+ });
37
+
24
38
  test('PrettyLink children and link', () => {
25
39
  const { getByText, container } = render(<PrettyLink {...data()} />);
26
40
  expect(getByText('Test children').tagName.toLowerCase()).toBe('h1');
@@ -28,6 +42,15 @@ describe('PrettyLink Component', () => {
28
42
  });
29
43
 
30
44
  test('Affiliate object in cookie', () => {
45
+ const mockClickHandler = jest.fn((e) => {
46
+ e.preventDefault();
47
+ document.cookie =
48
+ 'affObject={"page_type":"test","module":"test2","request_url":"http://localhost/","tracker_name":"testing","clicked_element":"cta"}';
49
+ });
50
+ useCtaClickHandler.mockImplementation(() => ({
51
+ ctaRef: { current: null },
52
+ handleCtaClick: mockClickHandler,
53
+ }));
31
54
  const { container } = render(
32
55
  <PrettyLink
33
56
  {...data()}
@@ -39,6 +62,7 @@ describe('PrettyLink Component', () => {
39
62
  );
40
63
  const button = container.querySelector('a');
41
64
  fireEvent.click(button);
65
+ expect(mockClickHandler).toHaveBeenCalled();
42
66
  const affObject = JSON.parse(document.cookie.split('=')[1]);
43
67
  expect(affObject.page_type).toEqual('test');
44
68
  expect(affObject.module).toEqual('test2');
@@ -62,13 +86,43 @@ describe('PrettyLink Component', () => {
62
86
  });
63
87
 
64
88
  test('Default Affiliate object in cookie', () => {
89
+ const mockClickHandler = jest.fn((e) => {
90
+ e.preventDefault();
91
+ document.cookie =
92
+ 'affObject={"page_type":"default","request_url":"http://localhost/"}';
93
+ });
94
+ useCtaClickHandler.mockImplementation(() => ({
95
+ ctaRef: { current: null },
96
+ handleCtaClick: mockClickHandler,
97
+ }));
65
98
  const { container } = render(<PrettyLink {...data()} />);
66
99
  const button = container.querySelector('a');
67
100
  fireEvent.click(button);
101
+ expect(mockClickHandler).toHaveBeenCalled();
68
102
  const affObject = JSON.parse(document.cookie.split('=')[1]);
69
103
  expect(affObject.page_type).toEqual('default');
70
104
  expect(affObject.request_url).toEqual('http://localhost/');
71
105
  });
106
+
107
+ test('Passes tracking props to cta click hook', () => {
108
+ render(
109
+ <PrettyLink
110
+ {...data()}
111
+ pageTemplate="test"
112
+ moduleName="test2"
113
+ tracker="testing"
114
+ clickedElement="cta"
115
+ />
116
+ );
117
+ expect(useCtaClickHandler).toHaveBeenCalledWith({
118
+ pageTemplate: 'test',
119
+ moduleName: 'test2',
120
+ tracker: 'testing',
121
+ clickedElement: 'cta',
122
+ modulePosition: undefined,
123
+ itemPosition: undefined,
124
+ });
125
+ });
72
126
  });
73
127
 
74
128
  afterEach(() => {
@@ -137,12 +137,11 @@ export const pickRelationKeys = {
137
137
  'rating_comments',
138
138
  'toplist_bonus',
139
139
  'toplist_item_tracker_name',
140
- 'odds_types',
141
140
  'odds_provider',
142
141
  'live_streaming',
143
- 'sport_competitions_ids',
144
142
  'betting_types',
145
- 'betting_features'
143
+ 'betting_features',
144
+ 'sport_categories'
146
145
  ],
147
146
  operator_simplified: [
148
147
  "short_name",
@@ -1,17 +1,35 @@
1
- /* eslint-disable no-useless-return */
2
1
  // eslint-disable-next-line import/prefer-default-export
3
2
  import { excludeStatusInactiveToplist } from '../constants/excluded.mjs';
4
3
 
5
- export default function checkForInactiveOperatorToplist(item, status, res) {
6
- const shortCode = item && item.items && item.items.length && item.items[0].value;
4
+ const INACTIVE_SHORTCODES = new Set([
5
+ 'inactive_operator_toplist',
6
+ 'inactive_sportsbook_toplist',
7
+ ]);
8
+
9
+ export default function checkForInactiveOperatorToplist(item, status, res, pageType) {
10
+ const shortCode = item?.items?.[0]?.value;
7
11
  const siteName = process.env.GATSBY_SITE_NAME;
8
- const excludeStatus = excludeStatusInactiveToplist[siteName] || excludeStatusInactiveToplist.default;
9
- if (
10
- ((item.name === "top_list" && (shortCode === "inactive_operator_toplist" || shortCode === "inactive_sportsbook_toplist") && excludeStatus.includes(status)) ||
11
- (item.name === "cards_v2" && status === "active" && res === "pre_main_operators"))
12
- ) {
13
- return true;
12
+ const excludeStatus =
13
+ excludeStatusInactiveToplist[siteName] || excludeStatusInactiveToplist.default;
14
+
15
+ if (item.name === 'top_list') {
16
+
17
+ if (!INACTIVE_SHORTCODES.has(shortCode)) return false;
18
+
19
+
20
+ if (excludeStatus.includes(status)) return true;
21
+
22
+ const expectedShortcode =
23
+ pageType === 'sportsbook'
24
+ ? 'inactive_sportsbook_toplist'
25
+ : 'inactive_operator_toplist';
26
+
27
+ return shortCode !== expectedShortcode;
14
28
  }
15
29
 
30
+ if (item.name === 'cards_v2') {
31
+ return status === 'active' && res === 'pre_main_operators';
32
+ }
33
+
16
34
  return false;
17
35
  }
@@ -0,0 +1,82 @@
1
+ import { useCallback, useRef } from 'react';
2
+ import { globalHistory } from '@reach/router';
3
+ import { setCookie } from '~helpers/cookies';
4
+ import keygen from '~helpers/keygen';
5
+ import { getTrackingAPIParams } from '~helpers/tracker';
6
+
7
+ const useCtaClickHandler = ({
8
+ pageTemplate,
9
+ moduleName,
10
+ tracker,
11
+ clickedElement,
12
+ modulePosition,
13
+ itemPosition,
14
+ }) => {
15
+ const referer =
16
+ typeof document !== 'undefined' && document?.referrer
17
+ ? document.referrer
18
+ : '';
19
+
20
+ const affObject = JSON.stringify(
21
+ getTrackingAPIParams(
22
+ pageTemplate,
23
+ moduleName,
24
+ tracker,
25
+ clickedElement,
26
+ globalHistory.location.href,
27
+ referer,
28
+ modulePosition,
29
+ itemPosition
30
+ )
31
+ );
32
+
33
+ const ctaRef = useRef(null);
34
+
35
+ const fireTracking = useCallback(() => {
36
+ if (process.env.IS_TRACKING_SSR) {
37
+ setCookie('affObject', affObject, 1, '/');
38
+ }
39
+
40
+ if (
41
+ process.env.ENABLE_PIXEL === 'true' &&
42
+ typeof window.fbq !== 'undefined'
43
+ ) {
44
+ window.fbq('track', 'InitiateCheckout', { keyID: keygen() });
45
+ }
46
+ }, [affObject]);
47
+
48
+ const handleCtaClick = useCallback(
49
+ (e) => {
50
+ // Ignore synthetic events dispatched for GTM
51
+ if (!e.isTrusted) {
52
+ e.preventDefault();
53
+ return;
54
+ }
55
+
56
+ // Block GTM's click listener from running synchronously (it listens on document)
57
+ e.stopPropagation();
58
+
59
+ const anchor = ctaRef.current;
60
+ if (!anchor) return;
61
+
62
+ requestAnimationFrame(() => {
63
+ setTimeout(() => {
64
+ fireTracking();
65
+
66
+ // Re-notify GTM with a synthetic click so it can still track the event
67
+ const gtmEvent = new MouseEvent('click', {
68
+ bubbles: true,
69
+ cancelable: true,
70
+ view: window,
71
+ });
72
+ anchor.dispatchEvent(gtmEvent);
73
+ }, 0);
74
+ });
75
+ },
76
+ [fireTracking]
77
+ );
78
+
79
+ return { ctaRef, handleCtaClick };
80
+ };
81
+
82
+ export default useCtaClickHandler;
@@ -0,0 +1,145 @@
1
+ import { renderHook } from '@testing-library/react';
2
+ import useCtaClickHandler from './index';
3
+ import { setCookie } from '~helpers/cookies';
4
+ import keygen from '~helpers/keygen';
5
+ import { getTrackingAPIParams } from '~helpers/tracker';
6
+
7
+ jest.mock('@reach/router', () => ({
8
+ globalHistory: {
9
+ location: {
10
+ href: 'http://localhost/',
11
+ },
12
+ },
13
+ }));
14
+
15
+ jest.mock('~helpers/cookies', () => ({
16
+ setCookie: jest.fn(),
17
+ }));
18
+
19
+ jest.mock('~helpers/keygen', () => jest.fn());
20
+
21
+ jest.mock('~helpers/tracker', () => ({
22
+ getTrackingAPIParams: jest.fn(),
23
+ }));
24
+
25
+ describe('useCtaClickHandler', () => {
26
+ const originalTrackingSsr = process.env.IS_TRACKING_SSR;
27
+ const originalEnablePixel = process.env.ENABLE_PIXEL;
28
+ const originalFbq = window.fbq;
29
+ const originalRequestAnimationFrame = global.requestAnimationFrame;
30
+
31
+ beforeEach(() => {
32
+ jest.clearAllMocks();
33
+ process.env.IS_TRACKING_SSR = 'true';
34
+ process.env.ENABLE_PIXEL = 'false';
35
+ getTrackingAPIParams.mockReturnValue({
36
+ page_type: 'default',
37
+ request_url: 'http://localhost/',
38
+ });
39
+ keygen.mockReturnValue('generated-key');
40
+ global.requestAnimationFrame = (cb) => cb();
41
+ });
42
+
43
+ afterEach(() => {
44
+ process.env.IS_TRACKING_SSR = originalTrackingSsr;
45
+ process.env.ENABLE_PIXEL = originalEnablePixel;
46
+ window.fbq = originalFbq;
47
+ global.requestAnimationFrame = originalRequestAnimationFrame;
48
+ jest.useRealTimers();
49
+ });
50
+
51
+ test('builds tracking params from hook arguments', () => {
52
+ renderHook(() =>
53
+ useCtaClickHandler({
54
+ pageTemplate: 'review',
55
+ moduleName: 'top-list',
56
+ tracker: 'main',
57
+ clickedElement: 'cta',
58
+ modulePosition: 1,
59
+ itemPosition: 2,
60
+ })
61
+ );
62
+
63
+ expect(getTrackingAPIParams).toHaveBeenCalledWith(
64
+ 'review',
65
+ 'top-list',
66
+ 'main',
67
+ 'cta',
68
+ 'http://localhost/',
69
+ '',
70
+ 1,
71
+ 2
72
+ );
73
+ });
74
+
75
+ test('ignores non-trusted click events', () => {
76
+ const { result } = renderHook(() =>
77
+ useCtaClickHandler({
78
+ pageTemplate: 'default',
79
+ })
80
+ );
81
+ const event = {
82
+ isTrusted: false,
83
+ preventDefault: jest.fn(),
84
+ stopPropagation: jest.fn(),
85
+ };
86
+
87
+ result.current.handleCtaClick(event);
88
+
89
+ expect(event.preventDefault).toHaveBeenCalled();
90
+ expect(event.stopPropagation).not.toHaveBeenCalled();
91
+ expect(setCookie).not.toHaveBeenCalled();
92
+ });
93
+
94
+ test('does nothing when trusted click has no anchor ref', () => {
95
+ const { result } = renderHook(() =>
96
+ useCtaClickHandler({
97
+ pageTemplate: 'default',
98
+ })
99
+ );
100
+ const event = {
101
+ isTrusted: true,
102
+ preventDefault: jest.fn(),
103
+ stopPropagation: jest.fn(),
104
+ };
105
+
106
+ result.current.handleCtaClick(event);
107
+
108
+ expect(event.stopPropagation).toHaveBeenCalled();
109
+ expect(setCookie).not.toHaveBeenCalled();
110
+ });
111
+
112
+ test('fires tracking and redispatches click for trusted events', () => {
113
+ jest.useFakeTimers();
114
+ process.env.ENABLE_PIXEL = 'true';
115
+ window.fbq = jest.fn();
116
+
117
+ const { result } = renderHook(() =>
118
+ useCtaClickHandler({
119
+ pageTemplate: 'default',
120
+ tracker: 'main',
121
+ })
122
+ );
123
+ const dispatchEvent = jest.fn();
124
+ result.current.ctaRef.current = { dispatchEvent };
125
+ const event = {
126
+ isTrusted: true,
127
+ preventDefault: jest.fn(),
128
+ stopPropagation: jest.fn(),
129
+ };
130
+
131
+ result.current.handleCtaClick(event);
132
+ jest.runAllTimers();
133
+
134
+ expect(setCookie).toHaveBeenCalledWith(
135
+ 'affObject',
136
+ JSON.stringify({ page_type: 'default', request_url: 'http://localhost/' }),
137
+ 1,
138
+ '/'
139
+ );
140
+ expect(window.fbq).toHaveBeenCalledWith('track', 'InitiateCheckout', {
141
+ keyID: 'generated-key',
142
+ });
143
+ expect(dispatchEvent).toHaveBeenCalledTimes(1);
144
+ });
145
+ });
@@ -627,24 +627,11 @@ export default {
627
627
  checkForInactiveOperatorToplist(
628
628
  item,
629
629
  page.relation.status,
630
- res
630
+ res,
631
+ page.relation.type
631
632
  )
632
633
  )
633
634
  return;
634
-
635
- if (item.name === "top_list") {
636
- const shortCode = item?.items?.[0]?.value;
637
- const isSportsbook =
638
- page?.relation?.type === "sportsbook";
639
-
640
- const shouldSkip =
641
- (isSportsbook &&
642
- shortCode === "inactive_operator_toplist") ||
643
- (!isSportsbook &&
644
- shortCode === "inactive_sportsbook_toplist");
645
-
646
- if (shouldSkip) return;
647
- }
648
635
 
649
636
  //pre main games section will show only on inactive game page
650
637
  const isActiveGamePage = page?.relation?.type === "game" || page?.relation?.status === "active";
@@ -117,6 +117,15 @@ export function transformOperators(jsonData, relationsData, pages) {
117
117
  type: operatorType,
118
118
  };
119
119
 
120
+ newOperatorData.live_streaming = generalData.live_streaming;
121
+
122
+ // Odds providers
123
+ newOperatorData.odds_provider = generalData.odds_software_id
124
+ ? processProviders([generalData.odds_software_id], relationsData.providers)
125
+ .map((provider) => provider?.name)
126
+ .filter(Boolean)
127
+ : [];
128
+
120
129
  // OPERATOR LOGO (temp fix included and will be removed)
121
130
  newOperatorData.logo = processLogo(
122
131
  newOperatorData.logo,
@@ -1,11 +1,22 @@
1
1
  import { cleanup } from "@testing-library/react";
2
- import { sanitizeOperatorData } from "./operators.mjs";
2
+ import { sanitizeOperatorData, transformOperators } from "./operators.mjs";
3
3
  import {
4
4
  getRelationsData,
5
5
  getTransformedV2operator,
6
6
  } from "../../tests/factories/relations/operatorV2.factory";
7
7
  import getPageDataList from "../../tests/factories/pages/list.factory";
8
8
 
9
+ jest.mock("./relations.mjs", () => ({
10
+ processLogo: jest.fn((logo) => logo || null),
11
+ processPaymentMethods: jest.fn(() => []),
12
+ processCountries: jest.fn(() => []),
13
+ processCurrencies: jest.fn(() => []),
14
+ processProviders: jest.fn((ids) =>
15
+ ids?.includes(956) ? [{ id: 956, name: "18Peaches" }] : []
16
+ ),
17
+ processGamblingCompanies: jest.fn(() => null),
18
+ }));
19
+
9
20
  describe("Sanitize Operator Data", () => {
10
21
  test("Sanitize Data with Operator Page", () => {
11
22
  const operatorData = getTransformedV2operator();
@@ -46,10 +57,127 @@ expect(result.name).toBe("Test Casino");
46
57
  expect(result.url).toBe("/test-url");
47
58
  expect(result.ribbons).toEqual(["customLabel", "fromRelations"]);
48
59
  expect(result.author_name).toBeUndefined();
49
-
50
60
  });
51
61
  });
52
62
 
63
+ describe("transformOperators", () => {
64
+ test("uses live_streaming from general_data", () => {
65
+ const jsonData = {
66
+ bet365sb: {
67
+ general_data: {
68
+ id: 142,
69
+ name: "bet365",
70
+ short_name: "bet365sb",
71
+ live_streaming: true,
72
+ odds_software_id: null,
73
+ game_provider_ids: [],
74
+ currency_ids: [],
75
+ country_ids: [],
76
+ gambling_company_id: null,
77
+ },
78
+ markets_data: {
79
+ gb_en: {
80
+ sportsbook: {
81
+ live_streaming: false,
82
+ },
83
+ },
84
+ },
85
+ sites_data: {
86
+ gb_en: {
87
+ sportsbook: {
88
+ id: 45587,
89
+ market: "gb_en",
90
+ operator_id: 142,
91
+ status: "active",
92
+ },
93
+ },
94
+ },
95
+ },
96
+ };
97
+
98
+ const relationsData = {
99
+ providers: {},
100
+ payments: {},
101
+ currencies: {},
102
+ gamblingCompanies: {},
103
+ countries: {},
104
+ games: {},
105
+ ribbons: {},
106
+ };
107
+
108
+ const pages = {
109
+ 1: {
110
+ relation_type: "operator",
111
+ type: "operator",
112
+ relation_id: 45587,
113
+ path: "sportsbook-review-test",
114
+ },
115
+ };
116
+
117
+ const result = transformOperators(jsonData, relationsData, pages);
118
+
119
+ expect(result[45587].live_streaming).toBe(true);
120
+ });
121
+
122
+ test("maps odds provider from general data odds software id", () => {
123
+ const jsonData = {
124
+ bet365sb: {
125
+ general_data: {
126
+ id: 142,
127
+ name: "bet365",
128
+ short_name: "bet365sb",
129
+ live_streaming: true,
130
+ odds_software_id: 956,
131
+ game_provider_ids: [],
132
+ currency_ids: [],
133
+ country_ids: [],
134
+ gambling_company_id: null,
135
+ },
136
+ markets_data: {
137
+ gb_en: {
138
+ sportsbook: {},
139
+ },
140
+ },
141
+ sites_data: {
142
+ gb_en: {
143
+ sportsbook: {
144
+ id: 45587,
145
+ market: "gb_en",
146
+ operator_id: 142,
147
+ status: "active",
148
+ },
149
+ },
150
+ },
151
+ },
152
+ };
153
+
154
+ const relationsData = {
155
+ providers: {
156
+ peaches: { id: 956, name: "18Peaches" },
157
+ },
158
+ payments: {},
159
+ currencies: {},
160
+ gamblingCompanies: {},
161
+ countries: {},
162
+ games: {},
163
+ ribbons: {},
164
+ };
165
+
166
+ const pages = {
167
+ 1: {
168
+ relation_type: "operator",
169
+ type: "operator",
170
+ relation_id: 45587,
171
+ path: "sportsbook-review-test",
172
+ },
173
+ };
174
+
175
+ const result = transformOperators(jsonData, relationsData, pages);
176
+
177
+ expect(result[45587].odds_provider).toEqual(["18Peaches"]);
178
+ });
179
+ });
180
+
53
181
  afterEach(() => {
54
182
  cleanup();
55
183
  });