gatsby-core-theme 44.27.0 → 44.29.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.
Files changed (23) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/package.json +1 -1
  3. package/src/components/atoms/notifications/index.js +110 -38
  4. package/src/components/atoms/notifications/notification-items/bonuses/bonuses.test.js +63 -0
  5. package/src/components/atoms/notifications/notification-items/bonuses/index.js +168 -0
  6. package/src/components/atoms/notifications/notification-items/cards-v2/index.js +102 -90
  7. package/src/components/atoms/notifications/notification-items/cards-v2/notification-items.module.scss +165 -215
  8. package/src/components/atoms/notifications/notification-items/spotlight/index.js +111 -83
  9. package/src/components/atoms/notifications/notifications.module.scss +267 -19
  10. package/src/components/atoms/notifications/notifications.test.js +10 -4
  11. package/src/components/atoms/notifications/panel-header/index.js +45 -0
  12. package/src/components/atoms/notifications/panel-header/panel-header.module.scss +51 -0
  13. package/src/components/atoms/notifications/panel-tabs/index.js +79 -0
  14. package/src/components/atoms/notifications/panel-tabs/panel-tabs.module.scss +115 -0
  15. package/src/components/molecules/newsletter/toggle-button/index.js +4 -2
  16. package/src/components/organisms/navigation/index.js +6 -1
  17. package/src/constants/pick-keys.mjs +2 -0
  18. package/src/images/icons/copyIcon.js +26 -0
  19. package/src/images/icons/countryFlag.js +19 -0
  20. package/src/images/icons/verifiedBadgeIcon.js +35 -0
  21. package/src/resolver/index.mjs +10 -7
  22. package/src/resolver/modules.mjs +11 -2
  23. package/src/components/atoms/notifications/notification-items/spotlight/notification-items.module.scss +0 -271
package/CHANGELOG.md CHANGED
@@ -1,3 +1,31 @@
1
+ # [44.29.0](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/compare/v44.28.0...v44.29.0) (2026-06-18)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * refactor notification panel code ([073b7ca](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/073b7ca9ff3f4c978b161b32a470fc01e6a9ab03))
7
+ * show the inactive casino in the search page but no in the autocomplete ([fc03761](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/fc037613b1ed9941d58f4a4f8787780ad381bab8))
8
+
9
+
10
+ * Merge branch 'master' of gitlab.com:g2m-gentoo/team-floyd/themes/gatsby-themes ([efb9e68](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/efb9e68764108c499c2e264009b5c0cbd7814171))
11
+ * Merge branch 'EN-536/notification-panel' into 'master' ([f94a556](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/f94a556a1f11e582f15b57307d749b926b2b7869))
12
+
13
+
14
+ ### Features
15
+
16
+ * add more variables and country flag icon ([8f2051a](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/8f2051a3817d3406fb96c0346c16e4608a403f2f))
17
+ * update notification panel ([3315645](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/3315645a744dd61c651260224db1197717a86ffa))
18
+
19
+ # [44.28.0](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/compare/v44.27.0...v44.28.0) (2026-06-17)
20
+
21
+
22
+ * Merge branch 'update-newsletter' into 'master' ([e1b2c7e](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/e1b2c7e0490c9ef1c929049e20d7ff28d1d71b9d))
23
+
24
+
25
+ ### Features
26
+
27
+ * update newsletter button ([063907c](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/commit/063907c743eea01d39155344c750831a42d0de5d))
28
+
1
29
  # [44.27.0](https://gitlab.com/g2m-gentoo/team-floyd/themes/gatsby-themes/compare/v44.26.2...v44.27.0) (2026-06-16)
2
30
 
3
31
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gatsby-core-theme",
3
- "version": "44.27.0",
3
+ "version": "44.29.0",
4
4
  "description": "Gatsby Theme NPM Package",
5
5
  "author": "",
6
6
  "license": "ISC",
@@ -3,27 +3,63 @@ import PropTypes from "prop-types";
3
3
  import { toggleScroll } from "~helpers/scroll";
4
4
  import { NavigationContext } from "~organisms/navigation/navigationContext";
5
5
  import SpotlightItems from "./notification-items/spotlight";
6
+ import BonusesItems from "./notification-items/bonuses";
6
7
  import CardsItems from "./notification-items/cards-v2";
8
+ import PanelHeader from "./panel-header";
9
+ import PanelTabs from "./panel-tabs";
7
10
  import BellIcon from "~images/icons/bell";
8
11
  import CloseIcon from "~images/icons/closeIcon";
9
- import styles from "./notifications.module.scss";
10
12
  import useTranslate from "../../../hooks/useTranslate/useTranslate";
13
+ import styles from "./notifications.module.scss";
14
+
15
+ const getNotificationToplist = (modules) => {
16
+ const toplistModule = modules.find((m) => m.name === "top_list");
17
+ if (!toplistModule) return null;
18
+
19
+ return (
20
+ toplistModule.items?.find(
21
+ (item) =>
22
+ item.value === "notification_panel_toplist" ||
23
+ item.short_name === "notification_panel_toplist"
24
+ ) ?? toplistModule.items?.[0]
25
+ );
26
+ };
11
27
 
12
- const Notifications = ({ section }) => {
28
+ const Notifications = ({ section, market }) => {
13
29
  const { setShowSearch, setShowMenu, showMenu, showSearch } =
14
30
  useContext(NavigationContext);
15
31
  const notificationRef = useRef(null);
16
32
  const [newNotification, setNewNotification] = useState(true);
17
33
  const [openNotification, setOpenNotification] = useState(false);
18
34
 
19
- const { modules } = section || {};
20
- const getNotifications = () =>
21
- modules?.find(
22
- (module) => module?.name === "spotlights" || module?.name === "cards_v2"
23
- ) || {};
35
+ const modules = section?.modules || [];
36
+ const notificationToplist = getNotificationToplist(modules);
37
+ const cardsModule = modules.find((m) => m.name === "cards_v2");
38
+ const spotlightModule = modules.find((m) => m.name === "spotlights");
39
+
40
+ const [activeTab, setActiveTab] = useState(
41
+ notificationToplist?.items?.length ? "bonuses" : "trending"
42
+ );
43
+
44
+ const renderTrendingContent = () => {
45
+ if (cardsModule) {
46
+ return (
47
+ <CardsItems
48
+ module={cardsModule}
49
+ showUnread={newNotification}
50
+ />
51
+ );
52
+ }
53
+
54
+ return (
55
+ <SpotlightItems
56
+ module={spotlightModule}
57
+ showUnread={newNotification}
58
+ />
59
+ );
60
+ };
24
61
 
25
62
  const notificationActions = () => {
26
- setNewNotification(false);
27
63
  setOpenNotification(!openNotification);
28
64
 
29
65
  if (showSearch) {
@@ -36,6 +72,14 @@ const Notifications = ({ section }) => {
36
72
  }
37
73
  };
38
74
 
75
+ useEffect(() => {
76
+ if (!openNotification) return undefined;
77
+
78
+ toggleScroll("notifications");
79
+
80
+ return () => toggleScroll("notifications");
81
+ }, [openNotification]);
82
+
39
83
  useEffect(() => {
40
84
  function handleClickOutside(event) {
41
85
  if (
@@ -46,51 +90,79 @@ const Notifications = ({ section }) => {
46
90
  }
47
91
  }
48
92
 
49
- // Bind the event listener
50
93
  document.addEventListener("mousedown", handleClickOutside);
94
+ return () => document.removeEventListener("mousedown", handleClickOutside);
95
+ }, []);
51
96
 
52
- return () => {
53
- // Unbind the event listener on clean up
54
- document.removeEventListener("mousedown", handleClickOutside);
55
- };
56
- }, [notificationRef]);
97
+ const bellAriaLabel = useTranslate(
98
+ "ariaLabel-notificationBellIcon",
99
+ "Notification Bell Icon"
100
+ );
101
+ const closeLabel = useTranslate("close", "Close");
57
102
 
58
103
  return (
59
- <div ref={notificationRef} className={styles?.notificationOuter || ""}>
104
+ <div ref={notificationRef} className={styles.notificationOuter}>
60
105
  <div
61
- className={`${styles.notificationIcon || ""} ${
62
- newNotification ? styles.newNotification || "" : ""
106
+ className={`${styles.notificationIcon} ${
107
+ newNotification ? styles.newNotification : ""
63
108
  } notification-bell-gtm`}
64
- onKeyDown={() => notificationActions()}
65
- onClick={() => notificationActions()}
109
+ onKeyDown={notificationActions}
110
+ onClick={notificationActions}
66
111
  role="button"
67
- aria-label={useTranslate(
68
- "ariaLabel-notificationBellIcon",
69
- "Notification Bell Icon"
70
- )}
112
+ aria-label={bellAriaLabel}
71
113
  tabIndex={0}
72
114
  >
73
- {openNotification ? <CloseIcon /> : <BellIcon />}
74
- </div>
75
- {openNotification &&
76
- (getNotifications()?.name === "cards_v2" ? (
77
- <CardsItems
78
- module={getNotifications()}
79
- onClose={() => notificationActions()}
80
- />
115
+ {openNotification ? (
116
+ <CloseIcon color="var(--notification-bell-color, #1c1a28)" />
81
117
  ) : (
82
- <SpotlightItems
83
- module={getNotifications()}
84
- onClose={() => notificationActions()}
85
- />
86
- ))}
118
+ <BellIcon color="var(--notification-bell-color, #1c1a28)" />
119
+ )}
120
+ </div>
121
+
122
+ {openNotification && (
123
+ <div className={styles.container}>
124
+ <div
125
+ className={styles.overlay}
126
+ onKeyDown={notificationActions}
127
+ onClick={notificationActions}
128
+ role="button"
129
+ aria-label={closeLabel}
130
+ tabIndex={0}
131
+ />
132
+ <div className={styles.panel}>
133
+ <PanelHeader
134
+ onClose={notificationActions}
135
+ onMarkAllRead={() => setNewNotification(false)}
136
+ />
137
+
138
+ <PanelTabs
139
+ activeTab={activeTab}
140
+ onTabChange={setActiveTab}
141
+ market={market}
142
+ />
143
+
144
+ <div className={styles.content}>
145
+ {activeTab === "bonuses" ? (
146
+ <BonusesItems
147
+ toplist={notificationToplist}
148
+ showUnread={newNotification}
149
+ />
150
+ ) : (
151
+ renderTrendingContent()
152
+ )}
153
+ </div>
154
+ </div>
155
+ </div>
156
+ )}
87
157
  </div>
88
158
  );
89
159
  };
90
160
 
91
161
  Notifications.propTypes = {
92
- pageContext: PropTypes.shape({}),
93
- section: PropTypes.shape({}),
162
+ market: PropTypes.string,
163
+ section: PropTypes.shape({
164
+ modules: PropTypes.arrayOf(PropTypes.shape({})),
165
+ }),
94
166
  };
95
167
 
96
168
  export default Notifications;
@@ -0,0 +1,63 @@
1
+ import React from 'react';
2
+ import { render, screen, fireEvent } from '@testing-library/react';
3
+ import '@testing-library/jest-dom/extend-expect';
4
+ import { getListToplistItem } from '~tests/factories/modules/toplist.factory';
5
+ import BonusesItems from '.';
6
+
7
+ const buildToplist = (overrides = {}) => ({
8
+ tracker: 'main',
9
+ items: getListToplistItem(1).map((item) => ({
10
+ ...item,
11
+ updated_at: '2025-01-14 11:04:49',
12
+ logo: { filename: 'logo1.jpg' },
13
+ bonuses: {
14
+ main: {
15
+ ...item.bonuses.main,
16
+ promo_code: 'BONUS123',
17
+ terms_and_conditions_enabled: true,
18
+ terms_and_conditions: 'Terms apply',
19
+ },
20
+ },
21
+ })),
22
+ ...overrides,
23
+ });
24
+
25
+ describe('BonusesItems', () => {
26
+ beforeEach(() => {
27
+ Object.assign(navigator, {
28
+ clipboard: { writeText: jest.fn() },
29
+ });
30
+ });
31
+
32
+ test('renders empty state when toplist has no items', () => {
33
+ render(<BonusesItems toplist={{ items: [] }} />);
34
+ expect(screen.getByText('No new items')).toBeVisible();
35
+ });
36
+
37
+ test('renders bonus item with promo code and copies on click', () => {
38
+ const toplist = buildToplist();
39
+ const { container } = render(<BonusesItems toplist={toplist} showUnread />);
40
+
41
+ expect(container.querySelector('.bonusList')).toBeVisible();
42
+ expect(container.querySelector('.unreadDot')).toBeVisible();
43
+ expect(screen.getByText('Bonus Code:')).toBeVisible();
44
+ expect(screen.getByText('BONUS123')).toBeVisible();
45
+
46
+ fireEvent.click(screen.getByText('BONUS123'));
47
+ expect(navigator.clipboard.writeText).toHaveBeenCalledWith('BONUS123');
48
+ });
49
+
50
+ test('renders bonus item without promo code row', () => {
51
+ const toplist = buildToplist({
52
+ items: getListToplistItem(1).map((item) => ({
53
+ ...item,
54
+ updated_at: '2025-01-14 11:04:49',
55
+ logo: { filename: 'logo1.jpg' },
56
+ })),
57
+ });
58
+
59
+ const { container } = render(<BonusesItems toplist={toplist} />);
60
+ expect(container.querySelector('.bonusCodeRow')).toBeFalsy();
61
+ expect(screen.getByText('name1 Review')).toBeVisible();
62
+ });
63
+ });
@@ -0,0 +1,168 @@
1
+ import React from "react";
2
+ import PropTypes from "prop-types";
3
+ import { FaArrowRight } from "@react-icons/all-files/fa/FaArrowRight";
4
+ import CopyIcon from "~images/icons/copyIcon";
5
+ import LazyImage from "~hooks/lazy-image";
6
+ import PrettyLink from "~atoms/pretty-link";
7
+ import ReviewLink from "~atoms/review-link";
8
+ import Tnc from "~molecules/tnc";
9
+ import keygen from "~helpers/keygen";
10
+ import {
11
+ getBonusData,
12
+ getPromoCode,
13
+ imagePrettyUrl,
14
+ } from "~helpers/getters";
15
+ import { getAltText } from "~helpers/image";
16
+ import { getTimeAgo } from "~helpers/date-time";
17
+ import { TrackingKeys } from "~constants/tracking-api";
18
+ import useTranslate from "~hooks/useTranslate/useTranslate";
19
+ import styles from "../../notifications.module.scss";
20
+
21
+ const BonusItem = ({
22
+ item,
23
+ index,
24
+ tracker,
25
+ showUnread,
26
+ bonusCodeLabel,
27
+ updatedLabel,
28
+ }) => {
29
+ const bonus = getBonusData(item, tracker);
30
+ const oneliner = [bonus?.mainLine, bonus?.secondLine]
31
+ .filter(Boolean)
32
+ .join(" + ");
33
+ const promoCode = getPromoCode(item, tracker);
34
+ const reviewPath = item.review_link
35
+ ? `/${item.review_link}`
36
+ : item.path || null;
37
+ const timeData = getTimeAgo(item.updated_at) || {};
38
+ const timeKeyLabel = useTranslate(
39
+ timeData.key,
40
+ timeData.key?.replace(/_/g, " ")
41
+ );
42
+ const timeAgo = timeData.value
43
+ ? `${updatedLabel} ${timeData.value} ${timeKeyLabel}`
44
+ : "";
45
+
46
+ return (
47
+ <li className={styles.bonusItem}>
48
+ {showUnread && <span className={styles.unreadDot} />}
49
+ <div className={styles.bonusTopRow}>
50
+ <PrettyLink
51
+ operator={item}
52
+ tracker={tracker}
53
+ moduleName={TrackingKeys.NAVIGATIONTOPLIST}
54
+ clickedElement="operator_logo"
55
+ className={styles.logo}
56
+ itemPosition={index + 1}
57
+ >
58
+ <LazyImage
59
+ width={52}
60
+ height={52}
61
+ alt={getAltText(item.logo, item.name)}
62
+ src={imagePrettyUrl(item.logo?.filename, 52, 52)}
63
+ />
64
+ </PrettyLink>
65
+
66
+ <div className={styles.bonusText}>
67
+ {oneliner && <p className={styles.oneliner}>{oneliner}</p>}
68
+ {promoCode && (
69
+ <div className={styles.bonusCodeRow}>
70
+ <span className={styles.bonusCodeLabel}>{bonusCodeLabel}</span>
71
+ <button
72
+ type="button"
73
+ className={styles.bonusCode}
74
+ onClick={() => navigator.clipboard.writeText(promoCode)}
75
+ >
76
+ <span>{promoCode}</span>
77
+ <CopyIcon width={20} height={20} />
78
+ </button>
79
+ </div>
80
+ )}
81
+ </div>
82
+
83
+ <PrettyLink
84
+ operator={item}
85
+ tracker={tracker}
86
+ moduleName={TrackingKeys.NAVIGATIONTOPLIST}
87
+ clickedElement="notification_cta"
88
+ className={styles.cta}
89
+ itemPosition={index + 1}
90
+ >
91
+ <FaArrowRight size={12} />
92
+ </PrettyLink>
93
+ </div>
94
+
95
+ <div className={styles.meta}>
96
+ {reviewPath && (
97
+ <ReviewLink
98
+ className={styles.reviewLink}
99
+ operatorName={item.name}
100
+ reviewPath={reviewPath}
101
+ />
102
+ )}
103
+ {timeAgo && <span className={styles.updated}>{timeAgo}</span>}
104
+ </div>
105
+
106
+ <div className={styles.legal}>
107
+ <Tnc operator={item} tracker={tracker} isFixed />
108
+ </div>
109
+ </li>
110
+ );
111
+ };
112
+
113
+ BonusItem.propTypes = {
114
+ item: PropTypes.shape({
115
+ review_link: PropTypes.string,
116
+ path: PropTypes.string,
117
+ updated_at: PropTypes.string,
118
+ short_name: PropTypes.string,
119
+ name: PropTypes.string,
120
+ logo: PropTypes.shape({
121
+ filename: PropTypes.string,
122
+ }),
123
+ }).isRequired,
124
+ index: PropTypes.number.isRequired,
125
+ tracker: PropTypes.string.isRequired,
126
+ showUnread: PropTypes.bool,
127
+ bonusCodeLabel: PropTypes.string.isRequired,
128
+ updatedLabel: PropTypes.string.isRequired,
129
+ };
130
+
131
+ const BonusesItems = ({ toplist, showUnread }) => {
132
+ const items = toplist?.items || [];
133
+ const tracker =
134
+ toplist?.toplist_bonus || toplist?.one_liner || toplist?.tracker || "main";
135
+ const noBonuses = useTranslate("noNewUpdates", "No new items");
136
+ const bonusCodeLabel = useTranslate("bonus_code", "Bonus Code:");
137
+ const updatedLabel = useTranslate("updated", "Updated");
138
+
139
+ if (!items.length) return <p className={styles.empty}>{noBonuses}</p>;
140
+
141
+ return (
142
+ <ul className={styles.bonusList}>
143
+ {items.map((item, index) => (
144
+ <BonusItem
145
+ key={item.short_name || keygen()}
146
+ item={item}
147
+ index={index}
148
+ tracker={tracker}
149
+ showUnread={showUnread}
150
+ bonusCodeLabel={bonusCodeLabel}
151
+ updatedLabel={updatedLabel}
152
+ />
153
+ ))}
154
+ </ul>
155
+ );
156
+ };
157
+
158
+ BonusesItems.propTypes = {
159
+ toplist: PropTypes.shape({
160
+ items: PropTypes.arrayOf(PropTypes.shape({})),
161
+ toplist_bonus: PropTypes.string,
162
+ one_liner: PropTypes.string,
163
+ tracker: PropTypes.string,
164
+ }),
165
+ showUnread: PropTypes.bool,
166
+ };
167
+
168
+ export default BonusesItems;