gatsby-core-theme 44.30.0 → 44.30.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,20 @@
1
+ ## [44.30.2](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/compare/v44.30.1...v44.30.2) (2026-06-24)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * fixes for notification panel ([80c7649](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/80c7649bb5650317b017c336b3b9653fa52a1042))
7
+
8
+ ## [44.30.1](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/compare/v44.30.0...v44.30.1) (2026-06-22)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * fix style of notification panel ([b21a180](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/b21a1808842776e36ef14f3f8f1fbe70a20f1854))
14
+
15
+
16
+ * Merge branch 'master' of gitlab.com:g2m-gentoo/team-floyd/themes/gatsby-themes ([850c758](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/850c7581eb1a7a19fa20b63eace13032cd54919a))
17
+
1
18
  # [44.30.0](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/compare/v44.29.0...v44.30.0) (2026-06-22)
2
19
 
3
20
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gatsby-core-theme",
3
- "version": "44.30.0",
3
+ "version": "44.30.2",
4
4
  "description": "Gatsby Theme NPM Package",
5
5
  "author": "",
6
6
  "license": "ISC",
@@ -25,7 +25,7 @@ const getNotificationToplist = (modules) => {
25
25
  );
26
26
  };
27
27
 
28
- const Notifications = ({ section, market }) => {
28
+ const Notifications = ({ section, market, hideBadges = false }) => {
29
29
  const { setShowSearch, setShowMenu, showMenu, showSearch } =
30
30
  useContext(NavigationContext);
31
31
  const notificationRef = useRef(null);
@@ -139,6 +139,7 @@ const Notifications = ({ section, market }) => {
139
139
  activeTab={activeTab}
140
140
  onTabChange={setActiveTab}
141
141
  market={market}
142
+ hideBadges={hideBadges}
142
143
  />
143
144
 
144
145
  <div className={styles.content}>
@@ -146,6 +147,7 @@ const Notifications = ({ section, market }) => {
146
147
  <BonusesItems
147
148
  toplist={notificationToplist}
148
149
  showUnread={newNotification}
150
+ hideBadges={hideBadges}
149
151
  />
150
152
  ) : (
151
153
  renderTrendingContent()
@@ -163,6 +165,7 @@ Notifications.propTypes = {
163
165
  section: PropTypes.shape({
164
166
  modules: PropTypes.arrayOf(PropTypes.shape({})),
165
167
  }),
168
+ hideBadges: PropTypes.bool
166
169
  };
167
170
 
168
171
  export default Notifications;
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { render, screen, fireEvent } from '@testing-library/react';
2
+ import { render, screen, fireEvent, waitFor } from '@testing-library/react';
3
3
  import '@testing-library/jest-dom/extend-expect';
4
4
  import { getListToplistItem } from '~tests/factories/modules/toplist.factory';
5
5
  import BonusesItems from '.';
@@ -25,7 +25,7 @@ const buildToplist = (overrides = {}) => ({
25
25
  describe('BonusesItems', () => {
26
26
  beforeEach(() => {
27
27
  Object.assign(navigator, {
28
- clipboard: { writeText: jest.fn() },
28
+ clipboard: { writeText: jest.fn().mockResolvedValue(undefined) },
29
29
  });
30
30
  });
31
31
 
@@ -34,7 +34,7 @@ describe('BonusesItems', () => {
34
34
  expect(screen.getByText('No new items')).toBeVisible();
35
35
  });
36
36
 
37
- test('renders bonus item with promo code and copies on click', () => {
37
+ test('renders bonus item with promo code and copies on click', async () => {
38
38
  const toplist = buildToplist();
39
39
  const { container } = render(<BonusesItems toplist={toplist} showUnread />);
40
40
 
@@ -44,7 +44,10 @@ describe('BonusesItems', () => {
44
44
  expect(screen.getByText('BONUS123')).toBeVisible();
45
45
 
46
46
  fireEvent.click(screen.getByText('BONUS123'));
47
- expect(navigator.clipboard.writeText).toHaveBeenCalledWith('BONUS123');
47
+ await waitFor(() => {
48
+ expect(navigator.clipboard.writeText).toHaveBeenCalledWith('BONUS123');
49
+ expect(screen.getByText('CODE COPIED')).toBeVisible();
50
+ });
48
51
  });
49
52
 
50
53
  test('renders bonus item without promo code row', () => {
@@ -60,4 +63,4 @@ describe('BonusesItems', () => {
60
63
  expect(container.querySelector('.bonusCodeRow')).toBeFalsy();
61
64
  expect(screen.getByText('name1 Review')).toBeVisible();
62
65
  });
63
- });
66
+ });
@@ -1,7 +1,8 @@
1
- import React from "react";
1
+ import React, { useState } from "react";
2
2
  import PropTypes from "prop-types";
3
3
  import { FaArrowRight } from "@react-icons/all-files/fa/FaArrowRight";
4
4
  import CopyIcon from "~images/icons/copyIcon";
5
+ import CheckCircleIcon from "~images/icons/check-circle";
5
6
  import LazyImage from "~hooks/lazy-image";
6
7
  import PrettyLink from "~atoms/pretty-link";
7
8
  import ReviewLink from "~atoms/review-link";
@@ -10,11 +11,13 @@ import keygen from "~helpers/keygen";
10
11
  import {
11
12
  getBonusData,
12
13
  getPromoCode,
14
+ getOperatorTnc,
13
15
  imagePrettyUrl,
14
16
  } from "~helpers/getters";
15
17
  import { getAltText } from "~helpers/image";
16
- import { getTimeAgo } from "~helpers/date-time";
18
+ import { getTimeAgo, parseSortableDate } from "~helpers/date-time";
17
19
  import { TrackingKeys } from "~constants/tracking-api";
20
+ import VerifiedBadgeIcon from "~images/icons/verifiedBadgeIcon";
18
21
  import useTranslate from "~hooks/useTranslate/useTranslate";
19
22
  import styles from "../../notifications.module.scss";
20
23
 
@@ -25,7 +28,9 @@ const BonusItem = ({
25
28
  showUnread,
26
29
  bonusCodeLabel,
27
30
  updatedLabel,
31
+ hideBadges
28
32
  }) => {
33
+ const [copySuccess, setCopySuccess] = useState(false);
29
34
  const bonus = getBonusData(item, tracker);
30
35
  const oneliner = [bonus?.mainLine, bonus?.secondLine]
31
36
  .filter(Boolean)
@@ -34,14 +39,45 @@ const BonusItem = ({
34
39
  const reviewPath = item.review_link
35
40
  ? `/${item.review_link}`
36
41
  : item.path || null;
37
- const timeData = getTimeAgo(item.updated_at) || {};
42
+ const timeData = item.updated_at ? getTimeAgo(item.updated_at) : null;
38
43
  const timeKeyLabel = useTranslate(
39
- timeData.key,
40
- timeData.key?.replace(/_/g, " ")
44
+ timeData?.key,
45
+ timeData?.key?.replace(/_/g, " ")
41
46
  );
42
- const timeAgo = timeData.value
43
- ? `${updatedLabel} ${timeData.value} ${timeKeyLabel}`
44
- : "";
47
+ const foundedYear = item?.founded?.substring(0, 4);
48
+ let timeAgo = "";
49
+
50
+ if (timeData?.value) {
51
+ timeAgo = `${updatedLabel} ${timeData.value} ${timeKeyLabel}`;
52
+ } else if (foundedYear) {
53
+ // eslint-disable-next-line react-hooks/rules-of-hooks
54
+ timeAgo = `${useTranslate("founded", "Founded")} ${foundedYear}`;
55
+ }
56
+
57
+ const hasTnc = Boolean(getOperatorTnc(item, tracker));
58
+
59
+ const verifiedMonth = new Intl.DateTimeFormat("en-US", {
60
+ month: "long",
61
+ }).format(new Date());
62
+
63
+ const verifiedLabel = useTranslate(
64
+ "notification_verified_in",
65
+ "Verified in [month]"
66
+ ).replace("[month]", verifiedMonth);
67
+ const codeCopiedLabel = useTranslate("code_copied", "CODE COPIED");
68
+ const isBet365 = item.short_name === "bet365";
69
+
70
+ const copyToClipBoard = async (e, copyMe) => {
71
+ e.preventDefault();
72
+ e.stopPropagation();
73
+
74
+ try {
75
+ await navigator.clipboard.writeText(copyMe);
76
+ setCopySuccess(true);
77
+ } catch (err) {
78
+ setCopySuccess(false);
79
+ }
80
+ };
45
81
 
46
82
  return (
47
83
  <li className={styles.bonusItem}>
@@ -59,7 +95,7 @@ const BonusItem = ({
59
95
  width={52}
60
96
  height={52}
61
97
  alt={getAltText(item.logo, item.name)}
62
- src={imagePrettyUrl(item.logo?.filename, 52, 52)}
98
+ src={imagePrettyUrl(item.logo?.filename)}
63
99
  />
64
100
  </PrettyLink>
65
101
 
@@ -70,11 +106,30 @@ const BonusItem = ({
70
106
  <span className={styles.bonusCodeLabel}>{bonusCodeLabel}</span>
71
107
  <button
72
108
  type="button"
73
- className={styles.bonusCode}
74
- onClick={() => navigator.clipboard.writeText(promoCode)}
109
+ className={`${styles.bonusCode} ${copySuccess ? styles.bonusCodeCopied : ""}`}
110
+ onClick={(e) =>
111
+ promoCode && !isBet365 ? copyToClipBoard(e, promoCode) : undefined
112
+ }
113
+ disabled={isBet365}
75
114
  >
76
- <span>{promoCode}</span>
77
- <CopyIcon width={20} height={20} />
115
+ {!copySuccess ? (
116
+ <>
117
+ <span>{promoCode}</span>
118
+ {!isBet365 && <CopyIcon width={20} height={20} />}
119
+ </>
120
+ ) : (
121
+ <>
122
+ <span>{codeCopiedLabel}</span>
123
+ <span className={styles.bonusCodeCheckIcon}>
124
+ <CheckCircleIcon
125
+ width={20}
126
+ height={20}
127
+ bgColor="var(--notification-bonus-code-border, #20cd77)"
128
+ color="#fff"
129
+ />
130
+ </span>
131
+ </>
132
+ )}
78
133
  </button>
79
134
  </div>
80
135
  )}
@@ -102,10 +157,18 @@ const BonusItem = ({
102
157
  )}
103
158
  {timeAgo && <span className={styles.updated}>{timeAgo}</span>}
104
159
  </div>
160
+ {hideBadges && (
161
+ <span className={styles.customVerifiedBadge}>
162
+ <VerifiedBadgeIcon />
163
+ {verifiedLabel}
164
+ </span>
165
+ )}
105
166
 
106
- <div className={styles.legal}>
107
- <Tnc operator={item} tracker={tracker} isFixed />
108
- </div>
167
+ {hasTnc && (
168
+ <div className={styles.legal}>
169
+ <Tnc operator={item} tracker={tracker} isFixed />
170
+ </div>
171
+ )}
109
172
  </li>
110
173
  );
111
174
  };
@@ -117,6 +180,7 @@ BonusItem.propTypes = {
117
180
  updated_at: PropTypes.string,
118
181
  short_name: PropTypes.string,
119
182
  name: PropTypes.string,
183
+ founded: PropTypes.string,
120
184
  logo: PropTypes.shape({
121
185
  filename: PropTypes.string,
122
186
  }),
@@ -126,10 +190,15 @@ BonusItem.propTypes = {
126
190
  showUnread: PropTypes.bool,
127
191
  bonusCodeLabel: PropTypes.string.isRequired,
128
192
  updatedLabel: PropTypes.string.isRequired,
193
+ hideBadges: PropTypes.bool
129
194
  };
130
195
 
131
- const BonusesItems = ({ toplist, showUnread }) => {
132
- const items = toplist?.items || [];
196
+ const BonusesItems = ({ toplist, showUnread, hideBadges }) => {
197
+ const items = [...(toplist?.items || [])].sort(
198
+ (a, b) =>
199
+ parseSortableDate(b.updated_at || b.founded) -
200
+ parseSortableDate(a.updated_at || a.founded)
201
+ );
133
202
  const tracker =
134
203
  toplist?.toplist_bonus || toplist?.one_liner || toplist?.tracker || "main";
135
204
  const noBonuses = useTranslate("noNewUpdates", "No new items");
@@ -149,6 +218,7 @@ const BonusesItems = ({ toplist, showUnread }) => {
149
218
  showUnread={showUnread}
150
219
  bonusCodeLabel={bonusCodeLabel}
151
220
  updatedLabel={updatedLabel}
221
+ hideBadges={hideBadges}
152
222
  />
153
223
  ))}
154
224
  </ul>
@@ -163,6 +233,7 @@ BonusesItems.propTypes = {
163
233
  tracker: PropTypes.string,
164
234
  }),
165
235
  showUnread: PropTypes.bool,
236
+ hideBadges: PropTypes.bool
166
237
  };
167
238
 
168
- export default BonusesItems;
239
+ export default BonusesItems;
@@ -2,20 +2,28 @@
2
2
  /* eslint-disable react-hooks/rules-of-hooks */
3
3
  import React from "react";
4
4
  import PropTypes from "prop-types";
5
+ import { FaArrowRight } from "@react-icons/all-files/fa/FaArrowRight";
5
6
  import LazyImage from "~hooks/lazy-image";
6
7
  import keygen from "~helpers/keygen";
7
8
  import Link from "~hooks/link";
8
9
  import {
9
10
  imagePrettyUrl,
10
11
  getExtraField,
11
- textWordsLimit,
12
12
  } from "~helpers/getters";
13
13
  import { getAltText, getImageFilename } from "~helpers/image";
14
14
  import ArticleIcon from "../../../../../images/icons/articleIcon";
15
15
  import useTranslate from "~hooks/useTranslate/useTranslate";
16
- import { getTimeAgo } from "~helpers/date-time";
16
+ import { getTimeAgo, parseSortableDate } from "~helpers/date-time";
17
17
  import styles from "./notification-items.module.scss";
18
18
 
19
+ const getTrendingSortDate = (item) =>
20
+ parseSortableDate(
21
+ item?.manual_updated_date ||
22
+ item?.updated_at ||
23
+ item?.manual_published_date ||
24
+ item?.created_at
25
+ );
26
+
19
27
  const CategoryTag = ({ category }) => {
20
28
  const label = useTranslate(
21
29
  `category_${category.short_name}`,
@@ -27,10 +35,11 @@ const CategoryTag = ({ category }) => {
27
35
  return <span className={styles.tag}>{label}</span>;
28
36
  };
29
37
 
30
- const CardsItems = ({ module, wordLimit = 12, showUnread = false }) => {
31
- const { items } = module || {};
38
+ const CardsItems = ({ module, showUnread = false }) => {
39
+ const items = [...(module?.items || [])].sort(
40
+ (a, b) => getTrendingSortDate(b) - getTrendingSortDate(a)
41
+ );
32
42
  const noNewUpdates = useTranslate("noNewUpdates", "No new items");
33
- const updatedLabel = useTranslate("updated", "Updated");
34
43
 
35
44
  const renderItem = (item) => {
36
45
  const isArticleOrPage = ["article", "page"].includes(item?.relation_type);
@@ -46,20 +55,20 @@ const CardsItems = ({ module, wordLimit = 12, showUnread = false }) => {
46
55
  const categories = item?.categories || [];
47
56
  const description =
48
57
  subtitle || item?.short_description || item?.description || "";
49
- const timeData =
50
- getTimeAgo(
51
- item?.manual_updated_date ||
52
- item?.updated_at ||
53
- item?.manual_published_date ||
54
- item?.created_at
55
- ) || {};
56
- const timeAgo = timeData.value
57
- ? `${updatedLabel} ${timeData.value} ${useTranslate(
58
+ const dateModified = item?.manual_updated_date || item?.updated_at;
59
+ const datePublished = item?.manual_published_date || item?.created_at;
60
+ const dateString = dateModified || datePublished;
61
+ const usePublishedLabel = !dateModified && Boolean(datePublished);
62
+ const timeData = dateString ? getTimeAgo(dateString) : null;
63
+ const timeAgo = timeData?.value
64
+ ? `${useTranslate(
65
+ usePublishedLabel ? "published" : "updated",
66
+ usePublishedLabel ? "Published" : "Updated"
67
+ )} ${timeData.value} ${useTranslate(
58
68
  timeData.key,
59
69
  timeData.key?.replace(/_/g, " ")
60
70
  )}`
61
71
  : "";
62
-
63
72
  const thumbnail = img ? (
64
73
  <LazyImage
65
74
  width={40}
@@ -87,14 +96,13 @@ const CardsItems = ({ module, wordLimit = 12, showUnread = false }) => {
87
96
  )}
88
97
  {description && (
89
98
  <p className={styles.subtitle}>
90
- {textWordsLimit(
91
- description.replace(/<\/?[^>]+(>|$)/g, ""),
92
- wordLimit
93
- )}
99
+ {description.replace(/<\/?[^>]+(>|$)/g, "")}
94
100
  </p>
95
101
  )}
96
102
  </div>
97
- <span className={styles.cta} aria-hidden="true" />
103
+ <span className={styles.cta} aria-hidden="true">
104
+ <FaArrowRight size={12} />
105
+ </span>
98
106
  </div>
99
107
  {(categories.length > 0 || ribbon || timeAgo) && (
100
108
  <div className={styles.meta}>
@@ -134,7 +134,7 @@
134
134
  }
135
135
 
136
136
  .unreadDot {
137
- left: -1.2rem;
137
+ left: -0.8rem;
138
138
  top: -0.8rem;
139
139
  }
140
140
 
@@ -157,10 +157,6 @@
157
157
  }
158
158
  }
159
159
 
160
- .text {
161
- padding-right: 2.4rem;
162
- }
163
-
164
160
  .title {
165
161
  font-weight: 700;
166
162
  color: var(--notification-oneliner-color, #1b1b1c);
@@ -169,6 +165,10 @@
169
165
  .subtitle {
170
166
  display: -webkit-box;
171
167
  overflow: hidden;
168
+ -webkit-box-orient: vertical;
169
+ -webkit-line-clamp: 2;
170
+ line-clamp: 2;
171
+ text-overflow: ellipsis;
172
172
  color: var(--notification-trending-description-color, #6e6e84);
173
173
  }
174
174
 
@@ -206,15 +206,16 @@
206
206
  flex-shrink: 0;
207
207
  width: 3.6rem;
208
208
  height: 3.6rem;
209
+ padding: 0.2rem 0.8rem 0.4rem;
210
+ border: var(--notification-panel-cta-border, none);
209
211
  border-radius: var(--notification-cta-border-radius, 4.8rem);
210
- background: var(--notification-trending-cta-bg, #f4f4f4)
211
- url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12' fill='none'%3E%3Cpath d='M4.5 2.5L8 6L4.5 9.5' stroke='%236B6A72' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E")
212
- no-repeat center;
212
+ background: var(--notification-trending-cta-bg, #f4f4f4);
213
+ color: var(--notification-trending-cta-color, #6b6a72);
213
214
  pointer-events: none;
214
215
  }
215
216
  }
216
217
 
217
218
  .embeddedList {
218
219
  gap: 1.2rem;
219
- padding: 0.8rem 0 0 1.2rem;
220
+ padding: 0.8rem 0 0;
220
221
  }
@@ -2,19 +2,29 @@
2
2
  /* eslint-disable react-hooks/rules-of-hooks */
3
3
  import React from "react";
4
4
  import PropTypes from "prop-types";
5
- import { imagePrettyUrl, textWordsLimit } from "~helpers/getters";
5
+ import { FaArrowRight } from "@react-icons/all-files/fa/FaArrowRight";
6
+ import { imagePrettyUrl } from "~helpers/getters";
6
7
  import { getAltText, getImageFilename } from "~helpers/image";
7
8
  import LazyImage from "~hooks/lazy-image";
8
9
  import keygen from "~helpers/keygen";
9
10
  import Link from "~hooks/link";
10
11
  import useTranslate from "~hooks/useTranslate/useTranslate";
11
- import { getTimeAgo } from "~helpers/date-time";
12
+ import { getTimeAgo, parseSortableDate } from "~helpers/date-time";
12
13
  import styles from "../cards-v2/notification-items.module.scss";
13
14
 
14
- const SpotlightItems = ({ module, showUnread = false, wordLimit = 12 }) => {
15
- const { items } = module || {};
15
+ const getTrendingSortDate = (item) =>
16
+ parseSortableDate(
17
+ item?.manual_updated_date ||
18
+ item?.updated_at ||
19
+ item?.manual_published_date ||
20
+ item?.created_at
21
+ );
22
+
23
+ const SpotlightItems = ({ module, showUnread = false }) => {
24
+ const items = [...(module?.items || [])].sort(
25
+ (a, b) => getTrendingSortDate(b) - getTrendingSortDate(a)
26
+ );
16
27
  const noNewUpdates = useTranslate("noNewUpdates", "No new items");
17
- const updatedLabel = useTranslate("updated", "Updated");
18
28
 
19
29
  const renderItem = (item) => {
20
30
  const img = item?.image;
@@ -24,21 +34,24 @@ const SpotlightItems = ({ module, showUnread = false, wordLimit = 12 }) => {
24
34
  const ribbon = item?.subtitle?.split(",")[0]?.replace("[null]", "")?.trim();
25
35
  const metaText = item?.subtitle?.split(",").slice(1).join(",").trim();
26
36
  const description = item?.short_description || item?.text || "";
27
- const timeData =
28
- getTimeAgo(
29
- item?.manual_updated_date ||
30
- item?.updated_at
31
- ) || {};
37
+ const dateModified = item?.manual_updated_date || item?.updated_at;
38
+ const datePublished = item?.manual_published_date || item?.created_at;
39
+ const dateString = dateModified || datePublished;
40
+ const usePublishedLabel = !dateModified && Boolean(datePublished);
41
+ const timeData = dateString ? getTimeAgo(dateString) : null;
32
42
  const timeKeyLabel = useTranslate(
33
- timeData.key,
34
- timeData.key?.replace(/_/g, " ")
43
+ timeData?.key,
44
+ timeData?.key?.replace(/_/g, " ")
35
45
  );
36
46
  let timeAgo = "";
37
47
 
38
- if (timeData.value) {
39
- timeAgo = `${updatedLabel} ${timeData.value} ${timeKeyLabel}`;
48
+ if (timeData?.value) {
49
+ timeAgo = `${useTranslate(
50
+ usePublishedLabel ? "published" : "updated",
51
+ usePublishedLabel ? "Published" : "Updated"
52
+ )} ${timeData.value} ${timeKeyLabel}`;
40
53
  } else if (metaText) {
41
- timeAgo = `${updatedLabel} ${metaText}`;
54
+ timeAgo = `${useTranslate("updated", "Updated")} ${metaText}`;
42
55
  }
43
56
 
44
57
  return (
@@ -64,14 +77,13 @@ const SpotlightItems = ({ module, showUnread = false, wordLimit = 12 }) => {
64
77
  )}
65
78
  {description && (
66
79
  <p className={styles.subtitle}>
67
- {textWordsLimit(
68
- description.replace(/<\/?[^>]+(>|$)/g, ""),
69
- wordLimit
70
- )}
80
+ {description.replace(/<\/?[^>]+(>|$)/g, "")}
71
81
  </p>
72
82
  )}
73
83
  </div>
74
- <span className={styles.cta} aria-hidden="true" />
84
+ <span className={styles.cta} aria-hidden="true">
85
+ <FaArrowRight size={12} />
86
+ </span>
75
87
  </div>
76
88
  {(ribbon || timeAgo) && (
77
89
  <div className={styles.meta}>
@@ -138,7 +150,6 @@ SpotlightItems.propTypes = {
138
150
  items: PropTypes.arrayOf(PropTypes.shape({})),
139
151
  }),
140
152
  showUnread: PropTypes.bool,
141
- wordLimit: PropTypes.number,
142
153
  };
143
154
 
144
155
  export default SpotlightItems;
@@ -62,6 +62,7 @@
62
62
  height: calc(100% - var(--nav-height-mobile, 4.8rem));
63
63
  background: var(--notification-panel-bg, #fff);
64
64
  border-left: var(--notification-panel-border-left, 0 solid transparent);
65
+ border-top: var(--notification-panel-border-top, 0 solid transparent);
65
66
 
66
67
  @include min(tablet) {
67
68
  top: 0;
@@ -115,6 +116,7 @@
115
116
  gap: 0.8rem;
116
117
  padding: 0 0 0.8rem;
117
118
  border-bottom: 1px solid var(--notification-item-border, #e3e6ef);
119
+ cursor: default;
118
120
  }
119
121
 
120
122
  .unreadDot {
@@ -134,6 +136,7 @@
134
136
  gap: 0.8rem;
135
137
  width: 100%;
136
138
  min-height: 5.2rem;
139
+ cursor: default;
137
140
  }
138
141
 
139
142
  .logo {
@@ -144,6 +147,7 @@
144
147
  height: 5.2rem;
145
148
  border: 1px solid var(--notification-logo-border, #dedede);
146
149
  border-radius: var(--notification-logo-border-radius, 0.8rem);
150
+ cursor: default;
147
151
 
148
152
  img {
149
153
  display: block;
@@ -153,6 +157,7 @@
153
157
  }
154
158
  }
155
159
 
160
+
156
161
  .bonusText {
157
162
  @include flex-direction(column);
158
163
  @include flex-align(flex-start, center);
@@ -161,6 +166,7 @@
161
166
  min-width: 0;
162
167
  gap: 0.4rem;
163
168
  padding-right: 2.4rem;
169
+ cursor: default;
164
170
  }
165
171
 
166
172
  .oneliner {
@@ -169,6 +175,7 @@
169
175
  font-weight: 700;
170
176
  line-height: 2.2rem;
171
177
  color: var(--notification-oneliner-color, #1b1b1c);
178
+ cursor: default;
172
179
  }
173
180
 
174
181
  .bonusCodeRow {
@@ -210,15 +217,36 @@
210
217
 
211
218
  svg {
212
219
  flex-shrink: 0;
220
+ pointer-events: none;
213
221
  color: var(--notification-bonus-code-icon, #262629);
214
222
  }
215
223
  }
216
224
 
225
+ .bonusCodeCopied {
226
+ border-color: var(--notification-bonus-code-border, #20cd77);
227
+ }
228
+
229
+ .bonusCodeCheckIcon {
230
+ @include flex-align(center, center);
231
+
232
+ flex-shrink: 0;
233
+ width: 2rem;
234
+ height: 2rem;
235
+ pointer-events: none;
236
+
237
+ svg {
238
+ display: block;
239
+ width: 2rem;
240
+ height: 2rem;
241
+ }
242
+ }
243
+
217
244
  .meta {
218
245
  @include flex-align(center, flex-start);
219
246
 
220
247
  width: 100%;
221
248
  min-height: 1.8rem;
249
+ cursor: default;
222
250
  }
223
251
 
224
252
  .reviewLink {
@@ -229,6 +257,7 @@
229
257
  line-height: 1.8rem;
230
258
  color: var(--notification-link-color, #165af8);
231
259
  text-decoration: underline;
260
+ cursor: pointer;
232
261
  }
233
262
 
234
263
  .updated {
@@ -240,6 +269,7 @@
240
269
  color: var(--notification-meta-color, #515156);
241
270
  text-align: right;
242
271
  white-space: nowrap;
272
+ cursor: default;
243
273
  }
244
274
 
245
275
  .cta {
@@ -253,6 +283,7 @@
253
283
  background: var(--notification-cta-bg, #6e33e5);
254
284
  color: var(--notification-cta-color, #fff);
255
285
  text-decoration: none;
286
+ cursor: default;
256
287
 
257
288
  &:hover {
258
289
  background: var(--notification-cta-hover-bg, #5a28c7);
@@ -270,3 +301,21 @@
270
301
  text-decoration: none;
271
302
  }
272
303
  }
304
+
305
+
306
+ .customVerifiedBadge {
307
+ @include flex-align(center, flex-start);
308
+
309
+ box-sizing: border-box;
310
+ width: fit-content;
311
+ gap: 0.4rem;
312
+ height: 2rem;
313
+ padding: 0.1rem 0.6rem 0.1rem 0.2rem;
314
+ border-radius: var(--notification-badge-border-radius, 10rem);
315
+ font-size: 1.2rem;
316
+ font-weight: 400;
317
+ line-height: 1.8rem;
318
+ background: var(--notification-verified-badge-bg, #DEE9FF);
319
+ color: var(--notification-verified-badge-color, #000);
320
+ border: 1px solid var(--notification-verified-badge-border, transparent);
321
+ }
@@ -10,9 +10,9 @@
10
10
  h2 {
11
11
  flex: 1;
12
12
  margin: 0;
13
- font-size: 2rem;
13
+ font-size: var(--notification-panel-title-size, 2rem);
14
14
  font-weight: 700;
15
- line-height: 2.8rem;
15
+ line-height: var(--notification-panel-title-line-height, 2.8rem);
16
16
  color: var(--notification-title-color, #0f0c28);
17
17
  text-transform: capitalize;
18
18
  }
@@ -5,7 +5,7 @@ import CountryFlagIcon from "~images/icons/countryFlag";
5
5
  import useTranslate from "~hooks/useTranslate/useTranslate";
6
6
  import styles from "./panel-tabs.module.scss";
7
7
 
8
- const PanelTabs = ({ activeTab, onTabChange, market }) => {
8
+ const PanelTabs = ({ activeTab, onTabChange, market, hideBadges }) => {
9
9
  const countryCode = market?.split("_")[0]?.toUpperCase();
10
10
  const verifiedMonth = new Intl.DateTimeFormat("en-US", {
11
11
  month: "long",
@@ -45,8 +45,7 @@ const PanelTabs = ({ activeTab, onTabChange, market }) => {
45
45
  {trendingContentLabel}
46
46
  </button>
47
47
  </div>
48
-
49
- {activeTab === "bonuses" && (
48
+ {(activeTab === "bonuses" && !hideBadges) && (
50
49
  <div className={styles.badges}>
51
50
  <span className={styles.verifiedBadge}>
52
51
  <VerifiedBadgeIcon />
@@ -74,6 +73,7 @@ PanelTabs.propTypes = {
74
73
  activeTab: PropTypes.oneOf(["bonuses", "trending"]).isRequired,
75
74
  onTabChange: PropTypes.func.isRequired,
76
75
  market: PropTypes.string,
76
+ hideBadges: PropTypes.bool
77
77
  };
78
78
 
79
79
  export default PanelTabs;
@@ -11,7 +11,6 @@
11
11
  @include flex-align(flex-start, stretch);
12
12
 
13
13
  box-sizing: border-box;
14
- height: 4rem;
15
14
  padding: 0.4rem;
16
15
  border: 1px solid var(--notification-tabs-border, #dedede);
17
16
  border-radius: var(--notification-tabs-border-radius, 1.2rem);
@@ -21,10 +20,9 @@
21
20
  .tab,
22
21
  .tabActive {
23
22
  flex: 1;
24
- height: 3.2rem;
25
23
  border: 0;
26
24
  border-radius: var(--notification-tab-border-radius, 0.8rem);
27
- padding: 0.4rem 1rem;
25
+ padding: 0.7rem 1rem;
28
26
  font-size: 1.2rem;
29
27
  line-height: 1.8rem;
30
28
  cursor: pointer;
@@ -52,3 +52,11 @@ export const getTimeAgo = (dateString) => {
52
52
  }
53
53
  return { value: diffMinutes, key: diffMinutes === 1 ? "minute_ago" : "minutes_ago" };
54
54
  };
55
+
56
+ export const parseSortableDate = (dateString) => {
57
+ if (!dateString) return 0;
58
+
59
+ const time = Date.parse(String(dateString).replace(" ", "T"));
60
+
61
+ return Number.isNaN(time) ? 0 : time;
62
+ };
@@ -1,4 +1,4 @@
1
- import { formatDate, getTimeAgo } from './date-time';
1
+ import { formatDate, getTimeAgo, parseSortableDate } from './date-time';
2
2
 
3
3
  describe('Date-Time Helper', () => {
4
4
  test('formatDate()', () => {
@@ -22,4 +22,11 @@ describe('Date-Time Helper', () => {
22
22
  expect(getTimeAgo(daysAgo)).toMatchObject({ key: 'days_ago', value: 10 });
23
23
  expect(getTimeAgo(monthsAgo)).toMatchObject({ key: 'months_ago', value: 2 });
24
24
  });
25
+
26
+ test('parseSortableDate()', () => {
27
+ expect(parseSortableDate(null)).toBe(0);
28
+ expect(parseSortableDate('2025-06-01 10:00:00')).toBeGreaterThan(
29
+ parseSortableDate('2024-01-01 10:00:00')
30
+ );
31
+ });
25
32
  });
@@ -6,7 +6,7 @@ export default function CheckCircleIcon({ width = 24, height = 24, color = '#272
6
6
  <svg
7
7
  width={width}
8
8
  height={height}
9
- viewBox={`0 0 ${width} ${height}`}
9
+ viewBox="0 0 24 24"
10
10
  fill="none"
11
11
  aria-labelledby="checkCircleIcon"
12
12
  xmlns="http://www.w3.org/2000/svg"
@@ -520,6 +520,35 @@ export function shouldSavePrefilled(module = {}, siteName) {
520
520
  );
521
521
  }
522
522
 
523
+ function normalizePathForMatch(path) {
524
+ if (!path || typeof path !== "string") return "";
525
+ return path.replace(/^\/+|\/+$/g, "");
526
+ }
527
+
528
+ function getSpotlightLinkedPage(link, pagesMappedById = {}) {
529
+ if (!link || link.type !== "page") return null;
530
+
531
+ if (link.page_id && pagesMappedById[link.page_id]) {
532
+ return pagesMappedById[link.page_id];
533
+ }
534
+
535
+ const linkValue = link.value;
536
+ if (!linkValue) return null;
537
+
538
+ if (pagesMappedById[linkValue]) {
539
+ return pagesMappedById[linkValue];
540
+ }
541
+
542
+ const linkPath = normalizePathForMatch(linkValue);
543
+ if (!linkPath) return null;
544
+
545
+ return (
546
+ Object.values(pagesMappedById).find(
547
+ (page) => normalizePathForMatch(page?.path) === linkPath
548
+ ) || null
549
+ );
550
+ }
551
+
523
552
  export function processSpotlightModule(module = {}, content, relations, ribbons, previewPageID, translations, relationData, operators, games,
524
553
  pagesMappedById = {}
525
554
  ) {
@@ -543,11 +572,22 @@ export function processSpotlightModule(module = {}, content, relations, ribbons,
543
572
  games
544
573
  );
545
574
 
546
- const pageId = item?.link?.page_id;
547
- const linkedPage = pageId ? pagesMappedById[pageId] : null;
575
+ const linkedPage = getSpotlightLinkedPage(item?.link, pagesMappedById);
548
576
  if (linkedPage?.short_description) {
549
577
  item.short_description = linkedPage.short_description;
550
578
  }
579
+ if (linkedPage?.updated_at) {
580
+ item.updated_at = linkedPage.updated_at;
581
+ }
582
+ if (linkedPage?.manual_updated_date) {
583
+ item.manual_updated_date = linkedPage.manual_updated_date;
584
+ }
585
+ if (linkedPage?.created_at) {
586
+ item.created_at = linkedPage.created_at;
587
+ }
588
+ if (linkedPage?.manual_published_date) {
589
+ item.manual_published_date = linkedPage.manual_published_date;
590
+ }
551
591
  });
552
592
  }
553
593
 
@@ -11,6 +11,7 @@ import {
11
11
  shouldSavePrefilled,
12
12
  processContentModule,
13
13
  processAnchor,
14
+ processSpotlightModule,
14
15
  } from "./modules.mjs";
15
16
  import { objectsHolder } from "~tests/factories/modules/modules.factory";
16
17
  import getPageDataList from "~tests/factories/pages/list.factory";
@@ -391,4 +392,43 @@ expect(module.value.ribbons[0]).toBe("testRibbon");
391
392
 
392
393
  expect(prefilledModule.items).toHaveLength(3);
393
394
  });
395
+
396
+ test("Spotlights resolve linked page by path when page_id is missing", () => {
397
+ const linkedPage = {
398
+ ...getPageData(),
399
+ id: 123,
400
+ path: "casino-bonus/irishlucks-exclusive-bonuses-vault",
401
+ updated_at: "2024-06-10 12:00:00",
402
+ short_description: "Linked page description",
403
+ };
404
+ const module = {
405
+ mode: "image_text",
406
+ items: [
407
+ {
408
+ label: "Bonus Club",
409
+ text: "<p>Join now!</p>",
410
+ link: {
411
+ type: "page",
412
+ value: "/casino-bonus/irishlucks-exclusive-bonuses-vault",
413
+ },
414
+ },
415
+ ],
416
+ };
417
+
418
+ processSpotlightModule(
419
+ module,
420
+ {},
421
+ {},
422
+ {},
423
+ null,
424
+ {},
425
+ {},
426
+ {},
427
+ {},
428
+ { 123: linkedPage }
429
+ );
430
+
431
+ expect(module.items[0].short_description).toBe("Linked page description");
432
+ expect(module.items[0].updated_at).toBe("2024-06-10 12:00:00");
433
+ });
394
434
  });