gatsby-core-theme 44.30.6 → 44.31.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,24 @@
1
+ # [44.31.0](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/compare/v44.30.7...v44.31.0) (2026-07-07)
2
+
3
+
4
+ * Merge branch 'feat/coupon-acts-like-cta' into 'master' ([f5e1823](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/f5e182396a398e9fcf8dd598474483b4c05e0ff1))
5
+
6
+
7
+ ### Features
8
+
9
+ * coupon act as cta opt in option for bonus ([6e2bcb6](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/6e2bcb67404c234e7935567762668cc694607811))
10
+ * share CTA tracking data-attributes and expose fireTracking for coupon CTA ([17025e2](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/17025e229d2b097662aa81ea9b0de26b6758c1e8))
11
+
12
+ ## [44.30.7](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/compare/v44.30.6...v44.30.7) (2026-07-06)
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * filter nonactive opertors ([9b9e7ee](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/9b9e7eed246697404be814022f09b9cc430de7d1))
18
+
19
+
20
+ * Merge branch 'en=580-nonactive-operators' into 'master' ([4896cdd](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/4896cdd5304f25c51c0b85cc342cb37419604292))
21
+
1
22
  ## [44.30.6](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/compare/v44.30.5...v44.30.6) (2026-06-29)
2
23
 
3
24
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gatsby-core-theme",
3
- "version": "44.30.6",
3
+ "version": "44.31.0",
4
4
  "description": "Gatsby Theme NPM Package",
5
5
  "author": "",
6
6
  "license": "ISC",
@@ -4,10 +4,9 @@ import React from 'react';
4
4
  import PropTypes from 'prop-types';
5
5
  import useTranslate from '~hooks/useTranslate/useTranslate';
6
6
  import { prettyTracker } from '~helpers/getters';
7
- import { trackerLinkActive } from '~helpers/tracker';
7
+ import { trackerLinkActive, getCtaDataAttributes } from '~helpers/tracker';
8
8
  import styles from './button.module.scss';
9
9
  import useCtaClickHandler from '../../../hooks/useCtaClickHandler/index';
10
- import operatorTypes from "../../../constants/operatorTypes";
11
10
 
12
11
  const OperatorCtaButton = ({
13
12
  operator,
@@ -71,14 +70,6 @@ const OperatorCtaButton = ({
71
70
  const classes = `${styles[buttonType] || ''} ${status && styles[status] ? styles[status] : ''} ${buttonSize ? styles[`${buttonSize}_size`] : ''
72
71
  } `;
73
72
 
74
- const operatorName = operator?.short_name || operator?.name || undefined;
75
- const dataModule = moduleName && modulePosition ? `${moduleName}-${modulePosition}` : undefined;
76
- const dataLocation =
77
- moduleName && modulePosition
78
- ? `${moduleName}-${modulePosition}${
79
- itemPosition ? `-position-${itemPosition}` : ""
80
- }`
81
- : undefined;
82
73
  return (
83
74
  trackerLinkActive(operator, tracker) && status === "active" ? (
84
75
  <a
@@ -92,13 +83,12 @@ const OperatorCtaButton = ({
92
83
  target="_blank"
93
84
  rel={rel}
94
85
  onClick={handleCtaClick}
95
- data-interaction="outbound-click"
96
- {...(operator?.type && { 'data-vertical': operatorTypes[operator?.type] || operator?.type })}
97
- {...(operatorName && { 'data-operator': operatorName })}
98
- {...(trackerType && { 'data-tracker': trackerType })}
99
- {...(modulePosition && { 'data-location': dataLocation })}
100
- {...(itemPosition && { 'data-position': itemPosition })}
101
- {...(dataModule && { 'data-module': dataModule })}
86
+ {...getCtaDataAttributes(operator, {
87
+ tracker,
88
+ moduleName,
89
+ modulePosition,
90
+ itemPosition,
91
+ })}
102
92
  >
103
93
  {translateBtn}
104
94
  {icon && icon}
@@ -8,7 +8,7 @@ import OperatorCta from "~atoms/button/operator-cta";
8
8
  import Rating from "~molecules/star-rating/one-star";
9
9
  import Tnc from "~molecules/tnc";
10
10
  import PrettyLink from "~atoms/pretty-link";
11
- import { TrackingKeys } from '~constants/tracking-api'
11
+ import { TrackingKeys } from "~constants/tracking-api";
12
12
  import styles from "./bonus.module.scss";
13
13
  import Ribbons from "../../../atoms/ribbons";
14
14
 
@@ -26,12 +26,16 @@ export default function Bonus({
26
26
  ctaIcon = <FaArrowRight fontSize={20} title="Right-pointing Arrow Icon" />,
27
27
  modulePosition,
28
28
  showRibbon = false,
29
- customClass='',
29
+ customClass = "",
30
+ couponActsAsCta = false,
30
31
  }) {
31
-
32
32
  const { logo, bonus, name } = operator || {};
33
33
  return (
34
- <div className={`${styles.operatorBanner || ""} ${customClass && styles[customClass]}`}>
34
+ <div
35
+ className={`${styles.operatorBanner || ""} ${
36
+ customClass && styles[customClass]
37
+ }`}
38
+ >
35
39
  <div className={styles.container}>
36
40
  {showRibbon && <Ribbons item={operator.ribbons} />}
37
41
  <div>
@@ -72,6 +76,7 @@ export default function Bonus({
72
76
  pageTemplate={pageTemplate}
73
77
  tracker={module?.type || "main"}
74
78
  modulePosition={modulePosition}
79
+ couponActsAsCta={couponActsAsCta}
75
80
  />
76
81
  {!tncFixed && (
77
82
  <Tnc
@@ -135,5 +140,6 @@ Bonus.propTypes = {
135
140
  ctaIcon: PropTypes.elementType,
136
141
  modulePosition: PropTypes.number,
137
142
  showRibbon: PropTypes.bool,
138
- customClass: PropTypes.string
143
+ customClass: PropTypes.string,
144
+ couponActsAsCta: PropTypes.bool,
139
145
  };
@@ -11,7 +11,7 @@ import Rating from "~molecules/star-rating/one-star";
11
11
  import Tnc from "~molecules/tnc";
12
12
  import PrettyLink from "~atoms/pretty-link";
13
13
  import useTranslate from "~hooks/useTranslate/useTranslate";
14
- import { TrackingKeys } from '~constants/tracking-api'
14
+ import { TrackingKeys } from "~constants/tracking-api";
15
15
  import styles from "./bonus.module.scss";
16
16
 
17
17
  export default function TemplateOne({
@@ -29,12 +29,12 @@ export default function TemplateOne({
29
29
  height = 120,
30
30
  showRatingLabel = false,
31
31
  showLabelMiddle = false,
32
- ctaIcon = <FaArrowRight fontSize={20} title="Right-pointing Arrow Icon" />
33
-
32
+ couponActsAsCta = false,
33
+ ctaIcon = <FaArrowRight fontSize={20} title="Right-pointing Arrow Icon" />,
34
34
  }) {
35
35
  const { logo, bonus, name } = operator || {};
36
36
  const hasTncEnable = getOperatorTnc(operator);
37
-
37
+
38
38
  const welcomeBonusText = useTranslate("welcome_bonus", "Welcome Bonus");
39
39
  const translationsObj = {
40
40
  active: {
@@ -54,8 +54,9 @@ export default function TemplateOne({
54
54
  ${hasTncEnable ? styles.operatorBannerTNC : ""}
55
55
  ${!closedBanner ? styles.show : styles.hide}
56
56
  ${styles.tncEnable}
57
- ${tncFixed ? styles.showTncFixed || "" : ""} ${floating ? styles.floating : ""
58
- }`}
57
+ ${tncFixed ? styles.showTncFixed || "" : ""} ${
58
+ floating ? styles.floating : ""
59
+ }`}
59
60
  >
60
61
  <div className={styles.container}>
61
62
  <div>
@@ -94,7 +95,13 @@ export default function TemplateOne({
94
95
  <p className={styles.welcomeBonus}>{welcomeBonusText}</p>
95
96
  )}
96
97
 
97
- <BonusBox moduleName={moduleName} item={operator} tracker={module?.type || "main"} pageTemplate={pageTemplate} />
98
+ <BonusBox
99
+ moduleName={moduleName}
100
+ item={operator}
101
+ tracker={module?.type || "main"}
102
+ pageTemplate={pageTemplate}
103
+ couponActsAsCta={couponActsAsCta}
104
+ />
98
105
  {!tncFixed && (
99
106
  <Tnc
100
107
  isFixed={tncFixed}
@@ -158,4 +165,5 @@ TemplateOne.propTypes = {
158
165
  showLabelMiddle: PropTypes.bool,
159
166
  ctaIcon: PropTypes.elementType,
160
167
  showRoundedRating: PropTypes.bool,
168
+ couponActsAsCta: PropTypes.bool,
161
169
  };
@@ -50,3 +50,20 @@ export function sortIntOn(key) {
50
50
  export function normalizeForSearch(str) {
51
51
  return str.toLowerCase().replace(/\s+/g, "");
52
52
  }
53
+
54
+ const isInactiveOperator = (item) =>
55
+ item.type === "operator" && item?.relation?.status !== "active";
56
+ export function filterSearchResults(results = [], searchTerm) {
57
+ const term = (searchTerm || "").toLowerCase();
58
+ const matched = results.filter((item) =>
59
+ item.title.toLowerCase().includes(term)
60
+ );
61
+
62
+ if (!searchTerm) {
63
+ return matched.filter((item) => !isInactiveOperator(item));
64
+ }
65
+
66
+ return [...matched].sort(
67
+ (a, b) => Number(isInactiveOperator(a)) - Number(isInactiveOperator(b))
68
+ );
69
+ }
@@ -1,5 +1,12 @@
1
- import { filterByKey, sortStringOn, sortIntOn, sortDateOn } from './search';
1
+ import {
2
+ filterByKey,
3
+ sortStringOn,
4
+ sortIntOn,
5
+ sortDateOn,
6
+ filterSearchResults,
7
+ } from './search';
2
8
  import loadSource from './search-source';
9
+ import searchIndex from '../../tests/factories/search/data.json';
3
10
 
4
11
  describe('Scroll test', () => {
5
12
  test('loadSource()', () => {
@@ -62,3 +69,67 @@ describe('Scroll test', () => {
62
69
  expect(arr.sort(sortDateOn('created_at'))).toEqual(sorted);
63
70
  });
64
71
  });
72
+
73
+ describe('filterSearchResults', () => {
74
+ const results = [
75
+ { title: 'Ladbrokes Casino', type: 'operator', relation: { status: 'inactive' } },
76
+ { title: 'Betfair Casino', type: 'operator', relation: { status: 'blacklisted' } },
77
+ { title: 'Upcoming Casino', type: 'operator', relation: { status: 'coming_soon' } },
78
+ { title: 'NotRec Casino', type: 'operator', relation: { status: 'not_recommended' } },
79
+ { title: 'Active Casino', type: 'operator', relation: { status: 'active' } },
80
+ { title: 'Another Active Casino', type: 'operator', relation: { status: 'active' } },
81
+ { title: 'Casino News Article', type: 'article' },
82
+ { title: 'Blackjack Game', type: 'game' },
83
+ ];
84
+
85
+ test('empty search shows active casinos only (hides inactive/upcoming/blacklisted/not_recommended)', () => {
86
+ const titles = filterSearchResults(results, '').map((i) => i.title);
87
+ expect(titles).toContain('Active Casino');
88
+ expect(titles).toContain('Another Active Casino');
89
+ expect(titles).not.toContain('Ladbrokes Casino');
90
+ expect(titles).not.toContain('Betfair Casino');
91
+ expect(titles).not.toContain('Upcoming Casino');
92
+ expect(titles).not.toContain('NotRec Casino');
93
+ });
94
+
95
+ test('empty search keeps non-operator content (articles, games)', () => {
96
+ const titles = filterSearchResults(results, '').map((i) => i.title);
97
+ expect(titles).toContain('Casino News Article');
98
+ expect(titles).toContain('Blackjack Game');
99
+ });
100
+
101
+ test('active search keeps inactive casinos so they remain findable', () => {
102
+ const titles = filterSearchResults(results, 'ladbrokes').map((i) => i.title);
103
+ expect(titles).toContain('Ladbrokes Casino');
104
+ });
105
+
106
+ test('active search orders active casinos before inactive ones', () => {
107
+ const operators = filterSearchResults(results, 'casino').filter(
108
+ (i) => i.type === 'operator'
109
+ );
110
+ let seenInactive = false;
111
+ const activeAfterInactive = operators.some((op) => {
112
+ const active = op.relation?.status === 'active';
113
+ if (!active) {
114
+ seenInactive = true;
115
+ return false;
116
+ }
117
+ return seenInactive;
118
+ });
119
+ expect(activeAfterInactive).toBe(false);
120
+ });
121
+
122
+ test('does not mutate the input array', () => {
123
+ const snapshot = [...results];
124
+ filterSearchResults(results, 'casino');
125
+ expect(results).toEqual(snapshot);
126
+ });
127
+
128
+ test('empty search on the generated index keeps only active operators', () => {
129
+ const operatorStatuses = filterSearchResults(searchIndex.arab_en, '')
130
+ .filter((i) => i.type === 'operator')
131
+ .map((i) => i.relation?.status);
132
+ expect(operatorStatuses.length).toBeGreaterThan(0);
133
+ expect(operatorStatuses.every((status) => status === 'active')).toBe(true);
134
+ });
135
+ });
@@ -8,6 +8,7 @@ import { deparam } from "~helpers/strings";
8
8
  import { groupBy } from "~helpers/getters";
9
9
  import processor from "../resolver/index.mjs";
10
10
  import { fetchSiteSettings } from "../services/fetch.mjs";
11
+ import { filterSearchResults } from "~helpers/search";
11
12
 
12
13
  export async function getAPIData(page, url, headers, preview) {
13
14
  if (preview) {
@@ -91,22 +92,16 @@ export async function getAPIData(page, url, headers, preview) {
91
92
  urlParams = deparam(`${url.split("?")[1]}`);
92
93
  }
93
94
 
94
- const searchData = urlParams
95
- ? urlParams.hasOwnProperty("s")
96
- ? groupBy(
97
- results.filter((item) =>
98
- item.title.toLowerCase().includes(urlParams.s.toLowerCase())
99
- ),
100
- "pageType"
101
- )
102
- : null
103
- : null;
95
+ const hasSearchParam = Boolean(urlParams && urlParams.hasOwnProperty("s"));
104
96
 
97
+ const searchData = hasSearchParam
98
+ ? groupBy(filterSearchResults(results, urlParams.s), "pageType")
99
+ : null;
105
100
 
106
101
  return {
107
102
  props: {
108
103
  data: searchData,
109
- searchParam: urlParams ? urlParams.s : null,
104
+ searchParam: hasSearchParam ? urlParams.s : null,
110
105
  },
111
106
  };
112
107
  }
@@ -3,6 +3,7 @@
3
3
  import { generateTrackerLink } from "./generators.mjs";
4
4
  import { stripSuffixSlash } from "./strings.mjs";
5
5
  import { getCookie } from "./cookies.mjs";
6
+ import operatorTypes from "../constants/operatorTypes.js";
6
7
 
7
8
  export function getTrackerName(operator, page, path) {
8
9
  const trackerLinks = operator ? Object.keys(operator.links) : [];
@@ -352,3 +353,33 @@ export function buildExternalRefererDeleteSetCookies(headers) {
352
353
 
353
354
  return buildDeleteCookie("");
354
355
  }
356
+
357
+ // Builds the GTM `data-*` attributes for an outbound CTA anchor so that every
358
+ // CTA (operator button, coupon redirect, …) tags clicks identically.
359
+ export function getCtaDataAttributes(
360
+ operator,
361
+ { tracker = "main", moduleName, modulePosition, itemPosition } = {}
362
+ ) {
363
+ const trackerType = tracker?.toLowerCase()?.replace(" ", "_");
364
+ const operatorName = operator?.short_name || operator?.name || undefined;
365
+ const dataModule =
366
+ moduleName && modulePosition ? `${moduleName}-${modulePosition}` : undefined;
367
+ const dataLocation =
368
+ moduleName && modulePosition
369
+ ? `${moduleName}-${modulePosition}${
370
+ itemPosition ? `-position-${itemPosition}` : ""
371
+ }`
372
+ : undefined;
373
+
374
+ return {
375
+ "data-interaction": "outbound-click",
376
+ ...(operator?.type && {
377
+ "data-vertical": operatorTypes[operator?.type] || operator?.type,
378
+ }),
379
+ ...(operatorName && { "data-operator": operatorName }),
380
+ ...(trackerType && { "data-tracker": trackerType }),
381
+ ...(modulePosition && { "data-location": dataLocation }),
382
+ ...(itemPosition && { "data-position": itemPosition }),
383
+ ...(dataModule && { "data-module": dataModule }),
384
+ };
385
+ }
@@ -470,4 +470,36 @@ describe("Tracker Helper", () => {
470
470
  expect(result[1]).toBe(`${cookieBase}; SameSite=Lax`);
471
471
  });
472
472
  });
473
+
474
+ describe("getCtaDataAttributes", () => {
475
+ test("builds outbound data-* attributes from operator + tracking context", () => {
476
+ const attrs = Tracker.getCtaDataAttributes(
477
+ { short_name: "rizk", type: "sportsbook" },
478
+ {
479
+ tracker: "PPC Fallback",
480
+ moduleName: "wageringCalculator",
481
+ modulePosition: 2,
482
+ itemPosition: 3,
483
+ }
484
+ );
485
+ expect(attrs).toEqual({
486
+ "data-interaction": "outbound-click",
487
+ "data-vertical": "sportsbook",
488
+ "data-operator": "rizk",
489
+ "data-tracker": "ppc_fallback",
490
+ "data-location": "wageringCalculator-2-position-3",
491
+ "data-position": 3,
492
+ "data-module": "wageringCalculator-2",
493
+ });
494
+ });
495
+
496
+ test("omits optional attributes when tracking context is missing", () => {
497
+ const attrs = Tracker.getCtaDataAttributes({ name: "Rizk Casino" });
498
+ expect(attrs).toEqual({
499
+ "data-interaction": "outbound-click",
500
+ "data-operator": "Rizk Casino",
501
+ "data-tracker": "main",
502
+ });
503
+ });
504
+ });
473
505
  });
@@ -76,7 +76,7 @@ const useCtaClickHandler = ({
76
76
  [fireTracking]
77
77
  );
78
78
 
79
- return { ctaRef, handleCtaClick };
79
+ return { ctaRef, handleCtaClick, fireTracking };
80
80
  };
81
81
 
82
82
  export default useCtaClickHandler;