gatsby-matrix-theme 53.25.1 → 53.26.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,25 @@
1
+ ## [53.26.1](https://gitlab.com/g2m-gentoo/team-floyd/themes/matrix-theme/compare/v53.26.0...v53.26.1) (2026-07-08)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * align serach in matrix ([bc65d7b](https://gitlab.com/g2m-gentoo/team-floyd/themes/matrix-theme/commit/bc65d7b8f9f21740339613a0ca9215eaefcb2798))
7
+
8
+ # [53.26.0](https://gitlab.com/g2m-gentoo/team-floyd/themes/matrix-theme/compare/v53.25.1...v53.26.0) (2026-07-07)
9
+
10
+
11
+ ### Config
12
+
13
+ * update theme ([ea2f1a2](https://gitlab.com/g2m-gentoo/team-floyd/themes/matrix-theme/commit/ea2f1a2d125636833c6cba9061949cf6bea0d4b0))
14
+
15
+
16
+ * Merge branch 'EN-576/coupon-acts-as-cta' into 'master' ([fe0c2ad](https://gitlab.com/g2m-gentoo/team-floyd/themes/matrix-theme/commit/fe0c2adb0e51d6744e064cad5a01e689ec6865ef))
17
+
18
+
19
+ ### Features
20
+
21
+ * coupon acts as CTA with tracking and delayed operator redirect ([b27d65c](https://gitlab.com/g2m-gentoo/team-floyd/themes/matrix-theme/commit/b27d65ce42b9f5650fecdbab6be312dadefa3f2d))
22
+
1
23
  ## [53.25.1](https://gitlab.com/g2m-gentoo/team-floyd/themes/matrix-theme/compare/v53.25.0...v53.25.1) (2026-07-07)
2
24
 
3
25
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gatsby-matrix-theme",
3
- "version": "53.25.1",
3
+ "version": "53.26.1",
4
4
  "main": "index.js",
5
5
  "description": "Matrix Theme NPM Package",
6
6
  "author": "",
@@ -26,7 +26,7 @@
26
26
  "@react-icons/all-files": "^4.1.0",
27
27
  "@gigmedia/enigma-utils": "^1.20.0",
28
28
  "gatsby": "^5.11.0",
29
- "gatsby-core-theme": "44.30.7",
29
+ "gatsby-core-theme": "44.31.0",
30
30
  "gatsby-plugin-sharp": "^5.11.0",
31
31
  "gatsby-plugin-sitemap": "^6.13.1",
32
32
  "gatsby-transformer-sharp": "^5.11.0",
@@ -1,5 +1,9 @@
1
- import React, { useState } from 'react';
1
+ /* eslint-disable react/jsx-no-target-blank */
2
+ import React, { useState, useRef, useEffect } from 'react';
2
3
  import PropTypes from 'prop-types';
4
+ import useCtaClickHandler from 'gatsby-core-theme/src/hooks/useCtaClickHandler/index';
5
+ import { prettyTracker } from 'gatsby-core-theme/src/helpers/getters';
6
+ import { getCtaDataAttributes } from 'gatsby-core-theme/src/helpers/tracker';
3
7
  import useTranslate from '~hooks/useTranslate/useTranslate';
4
8
  import CopyIcon from '../../../images/icons/copy-icon';
5
9
  import CheckCircleIcon from '../../../images/icons/check-circle';
@@ -15,26 +19,142 @@ const Coupon = ({
15
19
  icon = <CopyIcon />,
16
20
  checkIcon = <CheckCircleIcon />,
17
21
  showIcon = true,
18
- shortCode
22
+ shortCode,
23
+ // CTA / redirect behaviour (opt-in)
24
+ actLikeCta = false,
25
+ operator = null,
26
+ tracker = 'main',
27
+ moduleName = null,
28
+ pageTemplate = null,
29
+ clickedElement = 'coupon',
30
+ modulePosition,
31
+ itemPosition,
32
+ rel = 'nofollow sponsored',
33
+ redirectDelay = 2000
19
34
  }) => {
20
- if (!code) return;
21
-
22
35
  const [copySuccess, setCopySuccess] = useState('');
36
+ const redirectTimer = useRef(null);
37
+ const isPending = useRef(false); // a redirect is scheduled
38
+ const allowNavigation = useRef(false); // the next click is our own re-dispatch
39
+
40
+ // Reuses the core CTA tracking: `fireTracking` drops the affiliate cookie /
41
+ // fires the pixel; `ctaRef` points at the coupon anchor we redirect through.
42
+ const { ctaRef, fireTracking } = useCtaClickHandler({
43
+ pageTemplate,
44
+ moduleName,
45
+ tracker,
46
+ clickedElement,
47
+ modulePosition,
48
+ itemPosition
49
+ });
50
+
51
+ // Clear a scheduled redirect if the component unmounts mid-flow
52
+ useEffect(
53
+ () => () => {
54
+ if (redirectTimer.current) clearTimeout(redirectTimer.current);
55
+ },
56
+ []
57
+ );
58
+
59
+ // Resolve translations unconditionally so the hook order stays stable across
60
+ // the copy/redirect state change (Rules of Hooks — useTranslate uses useContext).
61
+ const prefixLabel = useTranslate(translationKey, defaultTranslation);
62
+ const noCouponLabel = useTranslate('no_coupon_available', 'Not Needed');
63
+ const codeCopiedLabel = useTranslate('code_copied', 'CODE COPIED');
64
+
65
+ if (!code) return null;
66
+
67
+ const inactive = status === 'inactive';
68
+ const isBet365 = shortCode === 'bet365';
69
+
70
+ const trackerType = tracker?.toLowerCase()?.replace(' ', '_');
71
+ const prettyLink = actLikeCta ? prettyTracker(operator, trackerType, false) : null;
72
+ // Only act as a CTA when we have a valid outbound link for an active operator
73
+ const ctaEnabled = actLikeCta && !!prettyLink && !inactive && !isBet365;
74
+
75
+ const handleClick = async (e) => {
76
+ // Our own re-dispatched click: let the anchor navigate + GTM record it
77
+ if (allowNavigation.current) {
78
+ allowNavigation.current = false;
79
+ return;
80
+ }
23
81
 
24
- const copyToClipBoard = async (e, copyMe) => {
25
- e.preventDefault();
26
- e.stopPropagation();
82
+ e.preventDefault();
83
+ e.stopPropagation();
27
84
 
85
+ if (inactive || isBet365) return;
86
+ // Ignore extra clicks while a redirect is already queued
87
+ if (isPending.current) return;
88
+
89
+ let copied = false;
28
90
  try {
29
- await navigator.clipboard.writeText(copyMe);
30
- setCopySuccess(true);
91
+ await navigator.clipboard.writeText(code);
92
+ copied = true;
31
93
  } catch (err) {
32
- setCopySuccess(false);
94
+ copied = false;
95
+ }
96
+
97
+ if (!ctaEnabled) {
98
+ // Original behaviour: flip the label immediately, no redirect
99
+ setCopySuccess(copied);
100
+ return;
33
101
  }
102
+
103
+ // t=0: code is on the clipboard — flip to "copied" and drop the tracking cookie
104
+ setCopySuccess(true);
105
+ fireTracking();
106
+ isPending.current = true;
107
+
108
+ // t=redirectDelay: open the operator in a new tab by re-dispatching a real
109
+ // click on the anchor (so GTM's outbound-click trigger fires), then revert
110
+ // the "copied" state. The delay stays within the browser's transient-activation
111
+ // window so the programmatic open is not blocked as a popup.
112
+ redirectTimer.current = setTimeout(() => {
113
+ isPending.current = false;
114
+
115
+ const anchor = ctaRef.current;
116
+ if (!anchor) return;
117
+
118
+ // Re-dispatch a real click so the anchor navigates (new tab) and GTM's
119
+ // outbound-click trigger still fires. `allowNavigation` lets our own
120
+ // handler fall through instead of intercepting the click again.
121
+ allowNavigation.current = true;
122
+ const clickEvent = new MouseEvent('click', {
123
+ bubbles: true,
124
+ cancelable: true,
125
+ view: window
126
+ });
127
+ const opened = anchor.dispatchEvent(clickEvent);
128
+
129
+ // If a wrapping anchor (e.g. a parent PrettyLink) cancelled the default
130
+ // navigation, open the operator manually so the tab still opens.
131
+ if (!opened && prettyLink) {
132
+ window.open(prettyLink, '_blank');
133
+ }
134
+
135
+ // Revert to the default label once the redirect has fired.
136
+ setCopySuccess(false);
137
+ }, redirectDelay);
34
138
  };
35
139
 
36
- const inactive = status === 'inactive';
37
- const isBet365 = shortCode === 'bet365';
140
+ const couponClass = `${styles.coupon} ${copySuccess ? styles.copied : ''}
141
+ ${code === null ? styles.noCode : ''}
142
+ ${inactive ? styles.disabled : ''}`;
143
+
144
+ const couponContent = !copySuccess ? (
145
+ <>
146
+ {templateOne && showprefix && (
147
+ <span className={styles.prefix}>{prefixLabel}</span>
148
+ )}
149
+ {code || noCouponLabel}
150
+ {showIcon && code !== null && !inactive && !isBet365 && icon}
151
+ </>
152
+ ) : (
153
+ <>
154
+ <span>{codeCopiedLabel}</span>
155
+ {showIcon && checkIcon}
156
+ </>
157
+ );
38
158
 
39
159
  return (
40
160
  <div
@@ -43,33 +163,35 @@ const Coupon = ({
43
163
  } `}
44
164
  >
45
165
  {!templateOne && showprefix && (
46
- <span className={styles.prefix}>{useTranslate(translationKey, defaultTranslation)}</span>
166
+ <span className={styles.prefix}>{prefixLabel}</span>
167
+ )}
168
+ {ctaEnabled ? (
169
+ <a
170
+ ref={ctaRef}
171
+ href={prettyLink}
172
+ target="_blank"
173
+ rel={rel}
174
+ onClick={handleClick}
175
+ className={couponClass}
176
+ {...getCtaDataAttributes(operator, {
177
+ tracker,
178
+ moduleName,
179
+ modulePosition,
180
+ itemPosition
181
+ })}
182
+ >
183
+ {couponContent}
184
+ </a>
185
+ ) : (
186
+ <button
187
+ onClick={handleClick}
188
+ type="button"
189
+ className={couponClass}
190
+ disabled={inactive || isBet365}
191
+ >
192
+ {couponContent}
193
+ </button>
47
194
  )}
48
- <button
49
- onClick={(e) => (code !== null && !inactive && !isBet365 ? copyToClipBoard(e, code) : '')}
50
- type="button"
51
- className={`${styles.coupon} ${copySuccess ? styles.copied : ''}
52
- ${code === null ? styles.noCode : ''}
53
- ${inactive ? styles.disabled : ''}`}
54
- disabled={inactive || isBet365}
55
- >
56
- {!copySuccess ? (
57
- <>
58
- {templateOne && showprefix && (
59
- <span className={styles.prefix}>
60
- {useTranslate(translationKey, defaultTranslation)}
61
- </span>
62
- )}
63
- {code || useTranslate('no_coupon_available', 'Not Needed')}
64
- {showIcon && code !== null && !inactive && !isBet365 && icon}
65
- </>
66
- ) : (
67
- <>
68
- <span>{useTranslate('code_copied', 'CODE COPIED')}</span>
69
- {showIcon && checkIcon}
70
- </>
71
- )}
72
- </button>
73
195
  </div>
74
196
  );
75
197
  };
@@ -84,7 +206,22 @@ Coupon.propTypes = {
84
206
  showIcon: PropTypes.bool,
85
207
  translationKey: PropTypes.string,
86
208
  defaultTranslation: PropTypes.string,
87
- shortCode:PropTypes.string
209
+ shortCode: PropTypes.string,
210
+ actLikeCta: PropTypes.bool,
211
+ operator: PropTypes.shape({
212
+ name: PropTypes.string,
213
+ short_name: PropTypes.string,
214
+ status: PropTypes.string,
215
+ type: PropTypes.string
216
+ }),
217
+ tracker: PropTypes.string,
218
+ moduleName: PropTypes.string,
219
+ pageTemplate: PropTypes.string,
220
+ clickedElement: PropTypes.string,
221
+ modulePosition: PropTypes.number,
222
+ itemPosition: PropTypes.number,
223
+ rel: PropTypes.string,
224
+ redirectDelay: PropTypes.number
88
225
  };
89
226
 
90
227
  export default Coupon;
@@ -13,6 +13,7 @@ import styles from './recommended-operators.module.scss';
13
13
 
14
14
  const RecommendedOperators = ({
15
15
  showCouponCode = false,
16
+ couponActsAsCta = false,
16
17
  showRating = false,
17
18
  showAuthor = false,
18
19
  title = 'Recommended Casinos:',
@@ -62,7 +63,18 @@ const RecommendedOperators = ({
62
63
  </div>
63
64
  {(showCouponCode && couponCode) && (
64
65
  <div className={styles.couponCtaContainer}>
65
- <Coupon code={couponCode} shortCode={operator?.short_name} />
66
+ <Coupon
67
+ code={couponCode}
68
+ shortCode={operator?.short_name}
69
+ actLikeCta={couponActsAsCta}
70
+ operator={operator}
71
+ tracker="main"
72
+ clickedElement="coupon"
73
+ pageTemplate={pageTemplate}
74
+ moduleName={TrackingKeys?.WAGERINGCALCULATOR}
75
+ modulePosition={modulePosition}
76
+ itemPosition={index + 1}
77
+ />
66
78
  <OperatorCta
67
79
  operator={operator}
68
80
  tracker="main"
@@ -95,6 +107,7 @@ export default RecommendedOperators;
95
107
 
96
108
  RecommendedOperators.propTypes = {
97
109
  showCouponCode: PropTypes.bool,
110
+ couponActsAsCta: PropTypes.bool,
98
111
  showRating: PropTypes.bool,
99
112
  tncFixed: PropTypes.bool,
100
113
  showAuthor: PropTypes.bool,
@@ -21,7 +21,8 @@ const WageringCalculator = ({
21
21
  pageContext = {},
22
22
  buttonType = 'primary',
23
23
  modulePosition,
24
- currency = '€'
24
+ currency = '€',
25
+ couponActsAsCta = false
25
26
  }) => {
26
27
  const [depositAmount, setDepositAmount] = useState('');
27
28
  const [bonusAmount, setBonusAmount] = useState('');
@@ -180,6 +181,7 @@ const WageringCalculator = ({
180
181
  operators={operators}
181
182
  pageTemplate={pageContext?.page?.template}
182
183
  modulePosition={modulePosition}
184
+ couponActsAsCta={couponActsAsCta}
183
185
  />
184
186
  )}
185
187
  </div>
@@ -198,6 +200,7 @@ WageringCalculator.propTypes = {
198
200
  }),
199
201
  modulePosition: PropTypes.number,
200
202
  currency: PropTypes.string,
203
+ couponActsAsCta: PropTypes.bool,
201
204
  };
202
205
 
203
206
  export default WageringCalculator;
@@ -18,6 +18,7 @@ import {
18
18
  import loadSource from '~helpers/search-source';
19
19
  import { deparam } from '~helpers/strings';
20
20
  import { groupBy } from '~helpers/getters';
21
+ import { filterSearchResults } from "~helpers/search";
21
22
 
22
23
  export async function getAPIData(page, url, header, preview) {
23
24
  if (preview) {
@@ -87,13 +88,6 @@ export async function getAPIData(page, url, header, preview) {
87
88
  return { props: {} };
88
89
  }
89
90
 
90
- function normalizeString(str) {
91
- return str
92
- .normalize('NFD')
93
- .replace(/[\u0300-\u036f]/g, '')
94
- .toLowerCase();
95
- }
96
-
97
91
  if (page.path.endsWith('/s') || page.path === 's' || page.template === 'search') {
98
92
  const results = await loadSource(page.market);
99
93
  if (page.path.endsWith('/s') || page.path === 's') {
@@ -102,22 +96,17 @@ export async function getAPIData(page, url, header, preview) {
102
96
  urlParams = deparam(`${url.split('?')[1]}`);
103
97
  }
104
98
 
105
- const searchData = urlParams
106
- ? urlParams.hasOwnProperty('s')
107
- ? groupBy(
108
- results.filter((item) =>
109
- normalizeString(item.title).includes(normalizeString(urlParams.s))
110
- ),
111
- 'pageType'
112
- )
113
- : null
114
- : null;
99
+ const hasSearchParam = Boolean(urlParams && urlParams.hasOwnProperty("s"));
100
+
101
+ const searchData = hasSearchParam
102
+ ? groupBy(filterSearchResults(results, urlParams.s), "pageType")
103
+ : null;
104
+
115
105
 
116
106
  return {
117
107
  props: {
118
108
  data: searchData,
119
- searchParam: urlParams ? urlParams.s : null,
120
- },
109
+ searchParam: hasSearchParam ? urlParams.s : null, },
121
110
  };
122
111
  }
123
112
  }