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 +21 -0
- package/package.json +1 -1
- package/src/components/atoms/button/operator-cta.js +7 -17
- package/src/components/molecules/bonus/template-one/index.js +11 -5
- package/src/components/molecules/bonus/template-two/index.js +15 -7
- package/src/helpers/search.js +17 -0
- package/src/helpers/search.test.js +72 -1
- package/src/helpers/server-data.js +6 -11
- package/src/helpers/tracker.mjs +31 -0
- package/src/helpers/tracker.test.js +32 -0
- package/src/hooks/useCtaClickHandler/index.js +1 -1
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
|
@@ -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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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 || "" : ""} ${
|
|
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
|
|
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
|
};
|
package/src/helpers/search.js
CHANGED
|
@@ -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 {
|
|
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
|
|
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:
|
|
104
|
+
searchParam: hasSearchParam ? urlParams.s : null,
|
|
110
105
|
},
|
|
111
106
|
};
|
|
112
107
|
}
|
package/src/helpers/tracker.mjs
CHANGED
|
@@ -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
|
});
|