gatsby-core-theme 1.6.22 → 2.0.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 (29) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/gatsby-node.esm.js +14 -3
  3. package/package.json +1 -1
  4. package/src/components/app.js +4 -0
  5. package/src/components/atoms/iframe/index.js +15 -9
  6. package/src/components/atoms/selling-points/index.js +1 -2
  7. package/src/components/atoms/selling-points/selling-points.module.scss +12 -5
  8. package/src/components/atoms/selling-points/selling-points.test.js +1 -1
  9. package/src/components/molecules/content/index.js +5 -1
  10. package/src/components/molecules/link-list/index.js +9 -12
  11. package/src/components/molecules/operator-banner/operator-banner.test.js +0 -1
  12. package/src/components/molecules/star-rating/index.js +8 -24
  13. package/src/components/molecules/star-rating/star-rating.module.scss +13 -1
  14. package/src/components/molecules/star-rating/star-rating.test.js +4 -2
  15. package/src/components/molecules/tnc/index.js +2 -3
  16. package/src/components/molecules/tnc/tnc.test.js +0 -1
  17. package/src/components/molecules/toplist/default-row/index.js +22 -20
  18. package/src/helpers/device-detect.js +5 -6
  19. package/src/helpers/events.js +91 -0
  20. package/src/helpers/generators.js +11 -3
  21. package/src/helpers/generators.test.js +34 -12
  22. package/src/helpers/processor/sports.js +0 -2
  23. package/src/helpers/rating.js +2 -0
  24. package/src/helpers/schedule.js +0 -36
  25. package/src/hooks/lazy-image/index.js +30 -44
  26. package/src/hooks/tabs/index.js +4 -2
  27. package/src/hooks/tabs/tabs.test.js +32 -15
  28. package/src/styles/utils/_mixins.scss +42 -0
  29. package/src/styles/utils/variables/_main.scss +8 -0
package/CHANGELOG.md CHANGED
@@ -1,3 +1,54 @@
1
+ # [2.0.0](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/compare/v1.6.22...v2.0.0) (2021-12-16)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * add function to transform event datetime ([7167a6b](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/commit/7167a6b52e0a19064f2a906fe30a4cccfeca41e4))
7
+ * added full date format in currentdate placeholder ([275d9d3](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/commit/275d9d3b9b2394acfe8074e50ada241c86aee133))
8
+ * added lazyload if native lazy is not supported ([d27115c](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/commit/d27115cca38920a11fdca342d3eb631490ee0108))
9
+ * added translation in toplist default row ([c4c100e](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/commit/c4c100eb9e1b5ce67e72bb42551cbb37311969f7))
10
+ * content fix ([73a6eac](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/commit/73a6eac2cbc4c1bb428312b95867e844c2c2abcd))
11
+ * content module removed parser and used replace function ([cc2973f](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/commit/cc2973f42e6db2fcd999879e97af502092d65324))
12
+ * lazyimage component ([5230b91](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/commit/5230b91bbde26015ebaf49621d8c47207b20d3c6))
13
+ * removed direct mutating, and wip on replacing all events with updated timezone ([1571eb8](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/commit/1571eb86149eedc74c9a69c77102ceb10d16c9aa))
14
+ * removed function that changes root scss variables ([0b60602](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/commit/0b60602002774f3c9d3b2895f87574e3c6c75d44))
15
+ * removed unused lazyloads ([9cc1c5c](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/commit/9cc1c5c4a89d3e67fe65d85c2e57dd6854dd76a4))
16
+ * replace star svg with css ([868e3a2](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/commit/868e3a20ce2804c3deab67d62f64348e657bdbf1))
17
+ * return main component as it was ([db3aca5](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/commit/db3aca55c82839d0d281b77b7225a6f75d35c1d7))
18
+ * selling points icon color moved to css var ([7283fc1](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/commit/7283fc1ef56a7fae24acca91f65a72c0752cd635))
19
+ * selling points tick ([71b3aaf](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/commit/71b3aaf4105c6584ede11791cac14a04f8fe9338))
20
+ * small fix ([f6444c1](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/commit/f6444c1f7421dd9063e9029d02eb8441ec10ffc3))
21
+ * small issue ([43be83b](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/commit/43be83bdd5fa5b3d5fe13160a78432a9963c33ec))
22
+ * small issue ([1d49e35](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/commit/1d49e357d2098c557d0f7b16e6ab9d7033f16674))
23
+ * test fixed issue ([320e030](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/commit/320e030e49a8c6d7e461578d01e72c2948bdec5a))
24
+ * tests and lazy iframe ([c0b63d5](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/commit/c0b63d5445f85f7e6b5539b628f516ecbc86547d))
25
+ * tests and lazy iframe ([364f449](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/commit/364f449965ee3998b2dbbdb5132c6a91951ff000))
26
+ * toplist row images ([3ffeb17](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/commit/3ffeb17696e2d82fe8b9841defe2b6e78be1e68b))
27
+
28
+
29
+ ### Code Refactoring
30
+
31
+ * add error handling for invalid date ([caa308b](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/commit/caa308b9b50352228d86d031c449918c7d81840a))
32
+ * remove unneeded function ([d2fb77a](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/commit/d2fb77a3810e5b767b716d87db4fb63853995d46))
33
+ * tabs set to loadable ([9079a60](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/commit/9079a60aaead65be9d70629fdd1025ce46ed8db8))
34
+ * translations change on operator cta ([0a476cf](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/commit/0a476cf2f1ab02eeba06db8af9eaf692b9715b75))
35
+ * translations change on operator cta ([ee9fded](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/commit/ee9fded3227ab34287d539676bc581bcb1a5abee))
36
+ * update placeholders generation for meta tags ([0a4e14b](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/commit/0a4e14bf2f594a7c28b5698df06436df43bd378c))
37
+
38
+
39
+ ### Config
40
+
41
+ * merged master ([08e4355](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/commit/08e43557f6f3827f612fed9448d0db5a5d65577a))
42
+
43
+
44
+ * Merge branch 'add_timezone_prefix' into 'master' ([b5d22b5](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/commit/b5d22b57eb52d8ed97e2928706c98d437a08738b))
45
+ * Merge branch 'tm-2617-update-placeholders-for-meta-tags' into 'master' ([9c5bf49](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/commit/9c5bf492688ddb28d01182a5f46ce18d8f7342d6))
46
+ * Merge branch 'cta-play-now' into 'master' ([29107c4](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/commit/29107c4da0339cb798b4c6b5c9dfe98c69bab015))
47
+ * Merge branch 'master' into cta-play-now ([f6aa117](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/commit/f6aa11733e67c700aa44fd8dec7b918c8d3db661))
48
+ * Merge branch 'selling-points' into 'master' ([4dc0e96](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/commit/4dc0e96e48f5219bbeede529fd346e16ede5b514))
49
+ * Merge branch 'star-rating' into 'master' ([ecc5e24](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/commit/ecc5e24f9e9f45e72c1ee50dd552fb1a68d04f44))
50
+ * Update .env.development ([a91cd2f](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/commit/a91cd2fc6355e31644d22749cb98ec1e2c81160a))
51
+
1
52
  ## [1.6.22](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/compare/v1.6.21...v1.6.22) (2021-12-10)
2
53
 
3
54
 
@@ -5,7 +5,7 @@
5
5
  import { cloneDeep, chunk, pick } from 'lodash';
6
6
  import chalk from 'chalk';
7
7
  import { getData, getLocalData } from './src/helpers/api';
8
- import { generateMetaTitle, generateTrackerLink } from './src/helpers/generators';
8
+ import { generateMetaString, generateTrackerLink } from './src/helpers/generators';
9
9
  import processor, { processSitemapPages } from './src/helpers/processor';
10
10
  import { pickAuthorsPageKeys } from './src/constants/pick-keys';
11
11
 
@@ -211,8 +211,19 @@ exports.createPages = async ({ actions: { createPage } }, themeOptions) => {
211
211
  page.authors = Object.keys(authors || {}).map((key) =>
212
212
  pick(authors[key], pickAuthorsPageKeys)
213
213
  );
214
- // Done for matrix-theme sites for [MONTH][YEAR] auto update
215
- page.meta_title = page.meta_title && generateMetaTitle(page.meta_title, translations);
214
+ // Done for matrix-theme sites for placeholders auto update
215
+ page.meta_title =
216
+ page.meta_title &&
217
+ generateMetaString(page.meta_title, translations, {
218
+ siteName: response.general.site_name,
219
+ siteTitle: page.title,
220
+ });
221
+ page.meta_description =
222
+ page.meta_description &&
223
+ generateMetaString(page.meta_description, translations, {
224
+ siteName: response.general.site_name,
225
+ siteTitle: page.title,
226
+ });
216
227
 
217
228
  const contextData = {
218
229
  page,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gatsby-core-theme",
3
- "version": "1.6.22",
3
+ "version": "2.0.0",
4
4
  "description": "Gatsby Theme NPM Package",
5
5
  "main": "index.js",
6
6
  "GATSBY_RECAPTCHA_SITEKEY": "6LfoyvMUAAAAAO4nl_MQnqHb4XdHxEiu5cXgIqeB",
@@ -66,6 +66,10 @@ const App = ({ pageContext }) => {
66
66
 
67
67
  const TrackerContent = isTracker ? loadable(() => import(`~pages/tracker`)) : null;
68
68
 
69
+ if (pageContext.siteInfo?.site_name === 'playcasino.co.za') {
70
+ translations.play_now = 'Play now';
71
+ }
72
+
69
73
  return isTracker ? (
70
74
  <>
71
75
  <Head page={pageContext.page} siteInfo={pageContext.siteInfo} />
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
- import LazyLoad from 'react-lazyload';
4
-
3
+ import loadable from '@loadable/component';
4
+ import { isNativeImageLazyLoadingSupported } from '~helpers/device-detect';
5
5
  import styles from './iframe.module.scss';
6
6
 
7
7
  const Iframe = ({
@@ -10,9 +10,10 @@ const Iframe = ({
10
10
  minHeight = '45rem',
11
11
  lazyLoad = true,
12
12
  frameBorder = 1,
13
- }) => (
14
- <div className={styles.iframeContainer}>
15
- {lazyLoad ? (
13
+ }) => {
14
+ if (!isNativeImageLazyLoadingSupported() && lazyLoad) {
15
+ const LazyLoad = loadable(() => import(`react-lazyload`));
16
+ return (
16
17
  <LazyLoad>
17
18
  <iframe
18
19
  title={title}
@@ -21,16 +22,21 @@ const Iframe = ({
21
22
  frameBorder={frameBorder}
22
23
  />
23
24
  </LazyLoad>
24
- ) : (
25
+ );
26
+ }
27
+
28
+ return (
29
+ <div className={styles.iframeContainer}>
25
30
  <iframe
26
31
  title={title}
27
32
  style={{ width: '100%', minHeight, display: 'block' }}
28
33
  src={src}
34
+ loading={lazyLoad ? 'lazy' : 'eager'}
29
35
  frameBorder={frameBorder}
30
36
  />
31
- )}
32
- </div>
33
- );
37
+ </div>
38
+ );
39
+ };
34
40
 
35
41
  Iframe.propTypes = {
36
42
  src: PropTypes.string.isRequired,
@@ -1,6 +1,5 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
- import LazyLoad from 'react-lazyload';
4
3
 
5
4
  import keygen from '~helpers/keygen';
6
5
  import styles from './selling-points.module.scss';
@@ -13,7 +12,7 @@ const SellingPoints = ({ sellingPoints, icon, limit = 3 }) => {
13
12
  <ul className={styles.sellingPoint}>
14
13
  {sellingPoints.slice(0, limit).map((item) => (
15
14
  <li key={keygen()}>
16
- {icon && <LazyLoad once>{icon}</LazyLoad>}
15
+ <span className={`${icon && styles.tick}`}>{` `}</span>
17
16
  <span>{item}</span>
18
17
  </li>
19
18
  ))}
@@ -7,11 +7,18 @@
7
7
  font-size: 1.4rem;
8
8
  width: 100%;
9
9
 
10
- svg {
11
- margin-right: 1rem;
12
- font-weight: 700;
13
- color: var(--color-22);
14
- flex: 0 0 1.4rem;
10
+ .tick {
11
+ position: relative;
12
+ margin-left: 2rem;
13
+ &::before {
14
+ @include arrow(var(--selling-point-icon-color), 1.5rem, down, false);
15
+ position: absolute;
16
+ padding: 0px 0px .8rem .32rem;
17
+ border-right-width: .18rem;
18
+ border-bottom-width: .18rem;
19
+ bottom: -.3rem;
20
+ left: -1.8rem;
21
+ }
15
22
  }
16
23
  }
17
24
 
@@ -24,7 +24,7 @@ describe('Selling Points Component', () => {
24
24
  const { container } = render(
25
25
  <SellingPoints sellingPoints={sellingPointsList} icon={<p>test icon</p>} />
26
26
  );
27
- expect(container.querySelectorAll('.lazyload-placeholder')).toHaveLength(3);
27
+ expect(container.querySelectorAll('.tick')).toHaveLength(3);
28
28
  });
29
29
 
30
30
  test('test empty selling point list', () => {
@@ -1,9 +1,12 @@
1
+ /* eslint-disable react/forbid-prop-types */
2
+ /* eslint-disable react/no-danger */
1
3
  import React from 'react';
2
- import parse from 'html-react-parser';
3
4
  import PropTypes from 'prop-types';
5
+ import parse from 'html-react-parser';
4
6
 
5
7
  import LazyImage from '~hooks/lazy-image';
6
8
  import { parseCss } from '~helpers/css-parser';
9
+
7
10
  import styles from './content.module.scss';
8
11
 
9
12
  const Content = ({ module, pageContext = null }) => {
@@ -49,6 +52,7 @@ const Content = ({ module, pageContext = null }) => {
49
52
  };
50
53
 
51
54
  Content.propTypes = {
55
+ pageContext: PropTypes.object,
52
56
  module: PropTypes.shape({
53
57
  anchor_label: PropTypes.string,
54
58
  name: PropTypes.string,
@@ -26,18 +26,15 @@ const LinkList = ({
26
26
  function renderLinkContent(item, index) {
27
27
  const icon = listIcon[index];
28
28
 
29
- const LinkImage = () => {
30
- return disableLazyLoad ? (
31
- <img src={imagePrettyUrl(item.image)} width={width} height={height} alt={item.title} />
32
- ) : (
33
- <LazyImage
34
- src={imagePrettyUrl(item.image)}
35
- alt={item.title}
36
- width={width}
37
- height={height}
38
- />
39
- );
40
- };
29
+ const LinkImage = () => (
30
+ <LazyImage
31
+ src={imagePrettyUrl(item.image)}
32
+ alt={item.title}
33
+ width={width}
34
+ height={height}
35
+ loading={disableLazyLoad ? 'eager' : 'loading'}
36
+ />
37
+ );
41
38
 
42
39
  return (
43
40
  <>
@@ -12,7 +12,6 @@ describe('OperatorBanner Component', () => {
12
12
  );
13
13
  expect(container).toBeTruthy();
14
14
  expect(container.querySelectorAll('.sticky')).toHaveLength(1);
15
- expect(container.querySelectorAll('.lazyload-placeholder')).toHaveLength(1);
16
15
  expect(getByText('Rizk Casino')).toBeTruthy();
17
16
  expect(getByText('Visit')).toBeTruthy();
18
17
  });
@@ -1,20 +1,11 @@
1
1
  import React from 'react';
2
- import { FaStar, FaStarHalfAlt, FaRegStar } from 'react-icons/fa';
3
2
  import PropTypes from 'prop-types';
4
- import LazyLoad from 'react-lazyload';
5
3
 
6
4
  import styles from './star-rating.module.scss';
7
5
  import keygen from '~helpers/keygen';
8
6
  import { getRating } from '~helpers/rating';
9
7
 
10
- const StarRating = ({
11
- numOfStars,
12
- iconEmpty = <FaRegStar />,
13
- iconHalf = <FaStarHalfAlt />,
14
- iconFull = <FaStar />,
15
- rating,
16
- halfStars = true,
17
- }) => {
8
+ const StarRating = ({ numOfStars, rating, halfStars = true }) => {
18
9
  const renderFarm = () => {
19
10
  const output = [];
20
11
  const rate = getRating(rating);
@@ -22,38 +13,31 @@ const StarRating = ({
22
13
  for (let i = 1; i < numOfStars + 1; i += 1) {
23
14
  if (i <= rate) {
24
15
  output.push(
25
- <span className={`${styles.starItem} ${styles.starFull}`} key={keygen()}>
26
- {iconFull}
16
+ <span className={styles.fullStar} key={keygen()}>
17
+ {' '}
27
18
  </span>
28
19
  );
29
20
  } else if (halfStars && i - 0.5 === rate) {
30
21
  output.push(
31
- <span className={`${styles.starItem} ${styles.starHalf}`} key={keygen()}>
32
- {iconHalf}
22
+ <span className={styles.halfStar} key={keygen()}>
23
+ {' '}
33
24
  </span>
34
25
  );
35
26
  } else {
36
27
  output.push(
37
- <span className={`${styles.starItem} ${styles.starEmpty}`} key={keygen()}>
38
- {iconEmpty}
28
+ <span className={styles.emptyStar} key={keygen()}>
29
+ {' '}
39
30
  </span>
40
31
  );
41
32
  }
42
33
  }
43
34
  return output;
44
35
  };
45
- return (
46
- <LazyLoad once>
47
- <div className={styles.starRatingContainer}>{renderFarm()}</div>
48
- </LazyLoad>
49
- );
36
+ return <div className={styles.starRatingContainer}>{renderFarm()}</div>;
50
37
  };
51
38
 
52
39
  StarRating.propTypes = {
53
40
  numOfStars: PropTypes.oneOf([5, 10]).isRequired,
54
- iconEmpty: PropTypes.element,
55
- iconHalf: PropTypes.element,
56
- iconFull: PropTypes.element,
57
41
  rating: PropTypes.oneOfType([PropTypes.array, PropTypes.number, PropTypes.string]).isRequired,
58
42
  halfStars: PropTypes.bool,
59
43
  };
@@ -7,4 +7,16 @@
7
7
  fill: var(--star-rating-color);
8
8
  }
9
9
  }
10
- }
10
+
11
+ .fullStar{
12
+ @include star(var(--full-star-fill-color), var(--full-star-border-color))
13
+ }
14
+
15
+ .halfStar{
16
+ @include half-star(var(--half-star-border-color), var(--halfFull-star-fill-color), var(--halfEmpty-star-fill-color))
17
+ }
18
+
19
+ .emptyStar{
20
+ @include star(var(--empty-star-border-color), var(--empty-star-fill-color))
21
+ }
22
+ }
@@ -7,8 +7,10 @@ import StarRating from '.';
7
7
  describe('StarRating Component', () => {
8
8
  test('render container with lazyload', () => {
9
9
  const { container } = render(<StarRating numOfStars={10} rating={6.5} />);
10
- expect(container).toBeTruthy();
11
- expect(container.querySelectorAll('.lazyload-placeholder')).toHaveLength(1);
10
+
11
+ expect(container.querySelectorAll('.fullStar')).toHaveLength(6);
12
+ expect(container.querySelectorAll('.halfStar')).toHaveLength(1);
13
+ expect(container.querySelectorAll('.emptyStar')).toHaveLength(3);
12
14
  });
13
15
  });
14
16
  afterEach(() => {
@@ -1,6 +1,5 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
- import LazyLoad from 'react-lazyload';
4
3
 
5
4
  import Collapse from '~atoms/collapse';
6
5
 
@@ -13,7 +12,7 @@ const Tnc = ({
13
12
  initOpen = false,
14
13
  operator,
15
14
  }) => (
16
- <LazyLoad once>
15
+ <>
17
16
  {hasCollapse ? (
18
17
  <Collapse
19
18
  onlyMobile={onlyMobile}
@@ -26,7 +25,7 @@ const Tnc = ({
26
25
  ) : (
27
26
  contentText
28
27
  )}
29
- </LazyLoad>
28
+ </>
30
29
  );
31
30
 
32
31
  Tnc.propTypes = {
@@ -8,7 +8,6 @@ describe('Tnc Component', () => {
8
8
  test('render component', () => {
9
9
  const { container } = render(<Tnc />);
10
10
  expect(container).toBeTruthy();
11
- expect(container.querySelectorAll('.lazyload-placeholder')).toHaveLength(1);
12
11
  });
13
12
  });
14
13
  afterEach(() => {
@@ -1,18 +1,21 @@
1
- import React from 'react';
1
+ import React, { useContext } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { MdCheck } from 'react-icons/md';
4
-
5
- import Button from '~atoms/button';
6
4
  import Bonus from '~atoms/bonus';
5
+ import { Context } from '~context/TranslationsProvider';
7
6
  import SellingPoints from '~atoms/selling-points';
8
-
9
- import { prettyTracker, imagePrettyUrl } from '~helpers/getters';
7
+ import StarRating from '~molecules/star-rating';
8
+ import Link from '~hooks/link';
9
+ import OperatorCta from '~atoms/operator-cta';
10
+ import { prettyTracker, imagePrettyUrl, translate } from '~helpers/getters';
10
11
  import LazyImage from '~hooks/lazy-image';
11
12
 
12
13
  import styles from './default-row.module.scss';
13
14
 
14
- const Row = ({ item, oneliner = 'main', layout = 'list' }) => {
15
+ const Row = ({ item, oneliner = 'main', layout = 'list', tracker = 'main' }) => {
15
16
  const prettyLink = prettyTracker(item);
17
+ const itemRating = item.rating;
18
+ const { translations } = useContext(Context) || {};
16
19
 
17
20
  return (
18
21
  <div className={`${styles.row} ${styles[layout]}`}>
@@ -33,26 +36,23 @@ const Row = ({ item, oneliner = 'main', layout = 'list' }) => {
33
36
  rel="noreferrer"
34
37
  aria-label={`${item.name} Link`}
35
38
  >
36
- <LazyImage alt={item.name} src={imagePrettyUrl(item.logo_url, 100)} />
39
+ <LazyImage
40
+ alt={item.name}
41
+ src={imagePrettyUrl(item.logo_url, 100)}
42
+ width={150}
43
+ height={40}
44
+ />
37
45
  </a>
38
46
  <Bonus item={item} tracker={oneliner} />
47
+ <StarRating numOfStars={5} rating={itemRating} />
39
48
  <SellingPoints icon={<MdCheck />} sellingPoints={item.selling_points} />
40
49
  <div>
41
- <Button
42
- to={`/${item.review_link}`}
43
- primaryColor={false}
44
- gtmClass="casinos-top-list-gtm btn-cta"
45
- btnText="Omtale"
46
- />
50
+ <Link className={`${styles.reviewLink} toplist-variant-one-gtm`} to={item.review_link}>
51
+ {`${translate(translations, 'read_review', 'Review')}`}
52
+ </Link>
47
53
  </div>
48
54
  <div>
49
- <Button
50
- to={prettyLink}
51
- btnText="Spill nå!"
52
- gtmClass="casinos-top-list-gtm btn-cta"
53
- primaryColor
54
- isInternalLink={false}
55
- />
55
+ <OperatorCta operator={item} tracker={tracker} gtmClass="toplist-operator-cta-gtm" />
56
56
  </div>
57
57
  </div>
58
58
  );
@@ -61,11 +61,13 @@ const Row = ({ item, oneliner = 'main', layout = 'list' }) => {
61
61
  Row.propTypes = {
62
62
  oneliner: PropTypes.string,
63
63
  layout: PropTypes.string,
64
+ tracker: PropTypes.string,
64
65
  item: PropTypes.shape({
65
66
  name: PropTypes.string,
66
67
  logo_url: PropTypes.string,
67
68
  selling_points: PropTypes.arrayOf(PropTypes.string),
68
69
  review_link: PropTypes.string,
70
+ rating: PropTypes.string,
69
71
  one_liners: PropTypes.shape({
70
72
  main: PropTypes.shape({
71
73
  one_liner: PropTypes.string,
@@ -14,12 +14,11 @@ export function isMobileDevice() {
14
14
  const isIos = () => Boolean(userAgent.match(/iPhone|iPad|iPod/i));
15
15
  const isOpera = () => Boolean(userAgent.match(/Opera Mini/i));
16
16
  const isWindows = () => Boolean(userAgent.match(/IEMobile/i));
17
- const isIos13 = () =>
18
- Boolean(
19
- platform === 'MacIntel' &&
20
- maxTouchPoints &&
21
- maxTouchPoints === 5
22
- );
17
+ const isIos13 = () => Boolean(platform === 'MacIntel' && maxTouchPoints && maxTouchPoints === 5);
23
18
 
24
19
  return Boolean(isAndroid() || isIos() || isOpera() || isWindows() || isIos13());
25
20
  }
21
+
22
+ export function isNativeImageLazyLoadingSupported() {
23
+ return typeof HTMLImageElement !== 'undefined' && 'loading' in HTMLImageElement.prototype;
24
+ }
@@ -0,0 +1,91 @@
1
+ /* eslint-disable camelcase */
2
+ /* eslint-disable import/prefer-default-export */
3
+ import { zeroPadding } from './schedule';
4
+
5
+ function isValidDate(d) {
6
+ return d instanceof Date && !Number.isNaN(d);
7
+ }
8
+
9
+ export function convertTimeZone(event) {
10
+ if (!event || !event.date || !event.date.time) {
11
+ return event;
12
+ }
13
+ const timeArr = event.date.time.split(':');
14
+
15
+ // ecmascript has month ranges between 0 - 11, that's why we do -1 to month_number
16
+ const currentTimeZoneDate = new Date(
17
+ Date.UTC(
18
+ event.date.year_number,
19
+ zeroPadding(event.date.month_number - 1, 2),
20
+ zeroPadding(event.date.day_number, 2),
21
+ timeArr[0],
22
+ timeArr[1]
23
+ )
24
+ );
25
+
26
+ if (!isValidDate(currentTimeZoneDate)) return event;
27
+
28
+ const time = `${zeroPadding(currentTimeZoneDate.getHours(), 2)}:${zeroPadding(
29
+ currentTimeZoneDate.getMinutes(),
30
+ 2
31
+ )}`;
32
+
33
+ // in case the utc is 23:00, but user's timezone can get 00:00 so update the date object.
34
+ const day_number = zeroPadding(currentTimeZoneDate.getDate(), 2);
35
+ const month_number = zeroPadding(currentTimeZoneDate.getMonth() + 1, 2);
36
+ const date = `${day_number}-${month_number}-${zeroPadding(currentTimeZoneDate.getFullYear(), 2)}`;
37
+
38
+ const dateAttrs = {
39
+ date: {
40
+ ...event.date,
41
+ time,
42
+ month_number,
43
+ day_number,
44
+ date,
45
+ },
46
+ };
47
+
48
+ return { ...event, ...dateAttrs };
49
+ }
50
+
51
+ export function updateAllTimezones(relation) {
52
+ const relationSchedule = relation?.schedule;
53
+ const relationFeaturedEvents = relation?.featured_events;
54
+ const relationShowcasedEvents = relation?.showcased_events;
55
+
56
+ // formated timezone schedule
57
+ const schedule = relationSchedule
58
+ ? Object.keys(relationSchedule).reduce((acc, dateKey) => {
59
+ const { tournaments } = relationSchedule[dateKey] || {};
60
+
61
+ const formattedTournaments = Object.keys(tournaments).reduce(
62
+ (tournamentAcc, currentTournamentKey) => {
63
+ const league = tournaments[currentTournamentKey];
64
+ const formattedEvents = league.events.map((event) => convertTimeZone(event));
65
+ tournamentAcc[currentTournamentKey] = { ...league, events: formattedEvents };
66
+ return tournamentAcc;
67
+ },
68
+ {}
69
+ );
70
+
71
+ acc[dateKey] = { ...relation.schedule[dateKey], tournaments: formattedTournaments };
72
+ return acc;
73
+ }, {})
74
+ : {};
75
+
76
+ // formated timezone featured events
77
+ const featured_events = relationFeaturedEvents?.map((event) => convertTimeZone(event)) || [];
78
+
79
+ // formated timezone showcased events
80
+ const showcased_events = relationShowcasedEvents?.map((event) => convertTimeZone(event)) || [];
81
+
82
+ // format timezone for relation.event
83
+ const event = convertTimeZone(relation?.event);
84
+
85
+ const updatedObject = { ...relation, schedule, featured_events, showcased_events };
86
+ if (event) {
87
+ updatedObject.event = event;
88
+ }
89
+
90
+ return updatedObject;
91
+ }
@@ -21,17 +21,25 @@ export function generateTrackerLink(operator, trackerType) {
21
21
  }
22
22
  }
23
23
 
24
- export function generateMetaTitle(metaTitle, translations) {
24
+ export function generateMetaString(metaString, translations, props) {
25
25
  const date = new Date();
26
+ const day = date.getDate();
26
27
  const month = months[date.getMonth()];
27
28
  const year = date.getFullYear();
29
+ const regex =
30
+ /\[MONTH\]|\[YEAR\]|\[currentyear\]|\[sitename\]|\[currentmonth\]|\[title\]|\[currentdate\]/gi;
28
31
 
29
- return metaTitle.replace(
30
- /\[MONTH\]|\[YEAR\]/gi,
32
+ return metaString.replace(
33
+ regex,
31
34
  (match) =>
32
35
  ({
33
36
  '[MONTH]': (translations && translations[month]) || month,
34
37
  '[YEAR]': year,
38
+ '[currentdate]': `${(translations && translations[month]) || month} ${day}, ${year}`,
39
+ '[currentmonth]': (translations && translations[month]) || month,
40
+ '[currentyear]': year,
41
+ '[sitename]': (props && props.siteName) || '',
42
+ '[title]': (props && props.siteTitle) || '',
35
43
  }[match])
36
44
  );
37
45
  }
@@ -1,4 +1,4 @@
1
- import { generateTrackerLink, generateMetaTitle } from './generators';
1
+ import { generateTrackerLink, generateMetaString } from './generators';
2
2
  import { months } from '~constants/common';
3
3
 
4
4
  describe('Generate Tracker Link Helper', () => {
@@ -17,24 +17,46 @@ describe('Generate Tracker Link Helper', () => {
17
17
  });
18
18
 
19
19
  describe('Generate Meta Title Helper', () => {
20
- test('generateMetaTitle main', () => {
21
- const metaTitle = generateMetaTitle('meta title [MONTH] [YEAR]');
20
+ test('generateMetaString uppercase', () => {
21
+ const metaString = generateMetaString('meta title [MONTH] [YEAR]');
22
22
  const date = new Date();
23
- expect(metaTitle).not.toContain('[MONTH]');
24
- expect(metaTitle).not.toContain('[YEAR]');
25
- expect(metaTitle).toContain(date.getFullYear());
26
- expect(metaTitle).toContain(months[date.getMonth()]);
23
+
24
+ expect(metaString).not.toContain('[MONTH]');
25
+ expect(metaString).not.toContain('[YEAR]');
26
+ expect(metaString).toContain(date.getFullYear());
27
+ expect(metaString).toContain(months[date.getMonth()]);
28
+ });
29
+ test('generateMetaString lowercase', () => {
30
+ const metaString = generateMetaString(
31
+ 'meta title [currentdate] [currentmonth] [currentyear] [sitename] [title]',
32
+ null,
33
+ {
34
+ siteName: 'Site Name',
35
+ siteTitle: 'Site Title',
36
+ }
37
+ );
38
+ const date = new Date();
39
+
40
+ expect(metaString).not.toContain('[currentdate]');
41
+ expect(metaString).not.toContain('[currentmonth]');
42
+ expect(metaString).not.toContain('[currentyear]');
43
+ expect(metaString).not.toContain('[sitename]');
44
+ expect(metaString).not.toContain('[title]');
45
+ expect(metaString).toContain(date.getDate());
46
+ expect(metaString).toContain(months[date.getMonth()]);
47
+ expect(metaString).toContain(date.getFullYear());
48
+ expect(metaString).toContain('Site Name');
49
+ expect(metaString).toContain('Site Title');
27
50
  });
28
- test('generateMetaTitle translated', () => {
51
+ test('generateMetaString translated', () => {
29
52
  const translateMonths = months.reduce(
30
53
  (translated, month) => ({ ...translated, [month]: `translate${month.substring(0, 1)}` }),
31
54
  {}
32
55
  );
33
- const metaTitle = generateMetaTitle('meta title [MONTH] [YEAR]', translateMonths);
56
+ const metaString = generateMetaString('meta title [MONTH]', translateMonths);
34
57
  const date = new Date();
35
58
 
36
- expect(metaTitle).not.toContain('[MONTH]');
37
- expect(metaTitle).not.toContain('[YEAR]');
38
- expect(metaTitle).toContain(translateMonths[months[date.getMonth()]]);
59
+ expect(metaString).not.toContain('[MONTH]');
60
+ expect(metaString).toContain(translateMonths[months[date.getMonth()]]);
39
61
  });
40
62
  });
@@ -6,11 +6,9 @@ import {
6
6
  filterEvents,
7
7
  findTournaments,
8
8
  getSortedEvents,
9
- formatEvents,
10
9
  } from '../schedule';
11
10
 
12
11
  export function prepareSportsData(sportsData) {
13
- sportsData.events = formatEvents(sportsData.events);
14
12
  Object.keys(sportsData.tournaments).forEach((key) => {
15
13
  if (sportsData.tournaments[key].events) {
16
14
  sportsData.tournaments[key].events = filterEvents(
@@ -1,3 +1,5 @@
1
+ /* eslint-disable arrow-body-style */
2
+ /* eslint-disable no-unused-expressions */
1
3
  export const roundHalf = (num) => {
2
4
  return Math.round(num * 2) / 2;
3
5
  };
@@ -120,39 +120,3 @@ export function filterEvents(tournamentEvents, events) {
120
120
 
121
121
  return futureEvents;
122
122
  }
123
-
124
- export function changeEventHour(event, hour = 1) {
125
- if (!event || !event.date || !event.date.time) {
126
- return event;
127
- }
128
-
129
- const timeArr = event.date.time.split(':');
130
- timeArr[0] = Number(timeArr[0]) - hour;
131
- event.date.time = timeArr.join(':');
132
-
133
- return event;
134
- }
135
-
136
- export function formatEvents(events) {
137
- const eventsArr = Object.values(events);
138
- const siteName = process.env.GATSBY_SITE_NAME;
139
- let prefix = '0';
140
-
141
- switch (siteName) {
142
- case 'sefodbold.dk':
143
- case 'sesport.dk':
144
- case 'watchfooty.co.uk':
145
- case 'bekijksport.nl':
146
- case 'livestreamsvoetbal.nl':
147
- prefix = 1;
148
- break;
149
- default:
150
- prefix = 0;
151
- }
152
-
153
- return eventsArr.reduce((acc, current) => {
154
- const event = changeEventHour(current, prefix);
155
- acc[event.id] = event;
156
- return acc;
157
- }, {});
158
- }
@@ -1,39 +1,29 @@
1
- import React from 'react';
1
+ import React, { useState } from 'react';
2
2
  // eslint-disable-next-line import/no-extraneous-dependencies
3
- // import LazyLoad from 'react-lazyload';
4
3
  import PropTypes from 'prop-types';
4
+ import loadable from '@loadable/component';
5
+ import { isNativeImageLazyLoadingSupported } from '~helpers/device-detect';
5
6
 
6
- // When to use this component:
7
- // 1. If you have 1 image to lazyload
8
- // 2. If you have images for different breakpoints and want the browser to decide when to serve the images based on the device, bandwidth, etc.
9
7
  export default function LazyImage({
10
8
  height,
11
9
  width,
12
- // offset = 200,
13
10
  style = {},
14
11
  className,
15
12
  src = '#',
16
- // srcSet = '',
17
13
  alt = '',
18
- // sizes,
19
- // defaultImg,
20
- // once = false,
14
+ defaultImg,
15
+ loading = 'lazy',
21
16
  }) {
22
- // const [errorImage, setErrorImage] = useState(false);
17
+ const [errorImage, setErrorImage] = useState(false);
23
18
 
24
- // if ((defaultImg && !src) || errorImage === true) {
25
- // return defaultImg;
26
- // }
19
+ if ((defaultImg && !src) || errorImage === true) {
20
+ return defaultImg;
21
+ }
27
22
 
28
- return (
29
- <>
30
- {/* <LazyLoad
31
- height={`${height}px`}
32
- width={`${width}px`}
33
- offset={offset}
34
- debounce={0}
35
- once={once}
36
- >
23
+ if (!isNativeImageLazyLoadingSupported()) {
24
+ const LazyLoad = loadable(() => import(`react-lazyload`));
25
+ return (
26
+ <LazyLoad height={`${height}px`} width={`${width}px`} debounce={0}>
37
27
  <img
38
28
  src={src}
39
29
  className={className}
@@ -41,36 +31,32 @@ export default function LazyImage({
41
31
  width={width}
42
32
  alt={alt}
43
33
  style={style}
44
- srcSet={srcSet}
45
- sizes={sizes}
46
- onError={() => setErrorImage(true)}
47
34
  />
48
- </LazyLoad> */}
49
- {/* <noscript> */}
50
- <img
51
- src={src}
52
- loading="lazy"
53
- className={className}
54
- height={height}
55
- width={width}
56
- alt={alt}
57
- style={style}
58
- />
59
- {/* </noscript> */}
60
- </>
35
+ </LazyLoad>
36
+ );
37
+ }
38
+
39
+ return (
40
+ <img
41
+ src={src}
42
+ loading={loading}
43
+ className={className}
44
+ height={height}
45
+ width={width}
46
+ alt={alt}
47
+ style={style}
48
+ onError={() => setErrorImage(true)}
49
+ />
61
50
  );
62
51
  }
63
52
 
64
53
  LazyImage.propTypes = {
65
54
  width: PropTypes.number,
66
55
  height: PropTypes.number,
67
- // offset: PropTypes.number,
68
56
  style: PropTypes.shape({}),
69
57
  className: PropTypes.string,
70
58
  src: PropTypes.string,
71
59
  alt: PropTypes.string,
72
- // sizes: PropTypes.string,
73
- // srcSet: PropTypes.string,
74
- // defaultImg: PropTypes.element,
75
- // once: PropTypes.bool,
60
+ defaultImg: PropTypes.element,
61
+ loading: PropTypes.string,
76
62
  };
@@ -1,7 +1,7 @@
1
1
  import React, { useState } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { forceCheck } from 'react-lazyload';
4
- import TabList from './tab/tab-list';
4
+ import loadable from '@loadable/component';
5
5
  import styles from './tabs.module.scss';
6
6
 
7
7
  const Tabs = ({
@@ -19,6 +19,8 @@ const Tabs = ({
19
19
  showAll ? showAllTabId : `${children[0].props.label}_0`
20
20
  );
21
21
 
22
+ const TabList = showTabs ? loadable(() => import('./tab/tab-list')) : null;
23
+
22
24
  const tabHeaderClass = `${styles.tabsHeader} ${!HeaderComp && styles.tabsOnly} ${
23
25
  styles[headerClass]
24
26
  } ${tabsAlign === 'right' && styles.invertOrder}`;
@@ -31,7 +33,7 @@ const Tabs = ({
31
33
  return (
32
34
  <>
33
35
  <div className={tabHeaderClass}>
34
- {showTabs && (
36
+ {TabList && (
35
37
  <TabList
36
38
  onClick={onClickTabItem}
37
39
  items={children}
@@ -1,11 +1,11 @@
1
1
  import React from 'react';
2
- import { render, cleanup, fireEvent } from '@testing-library/react';
2
+ import { render, cleanup, fireEvent, waitFor } from '@testing-library/react';
3
3
  import '@testing-library/jest-dom/extend-expect';
4
4
 
5
5
  import Tabs from '.';
6
6
 
7
7
  describe('Tabs Component', () => {
8
- test('render tabs', () => {
8
+ test('render tabs', async () => {
9
9
  const { container, getByText } = render(
10
10
  <Tabs tabsAlign="right" HeaderComp={<div className="header">my header</div>}>
11
11
  <div label="one" key={1}>
@@ -19,15 +19,18 @@ describe('Tabs Component', () => {
19
19
  </div>
20
20
  </Tabs>
21
21
  );
22
- expect(container.querySelectorAll('.tabListItem')).toHaveLength(2);
23
- // tabsAlign="right"
24
- expect(container.querySelectorAll('div.tabsHeader.invertOrder')).toHaveLength(1);
25
- // // HeaderComp
26
- expect(getByText('my header')).toBeTruthy();
27
- expect(getByText('1111111111111')).toBeTruthy();
22
+
23
+ await waitFor(() => {
24
+ expect(container.querySelectorAll('.tabListItem')).toHaveLength(2);
25
+ // tabsAlign="right"
26
+ expect(container.querySelectorAll('div.tabsHeader.invertOrder')).toHaveLength(1);
27
+ // // HeaderComp
28
+ expect(getByText('my header')).toBeTruthy();
29
+ expect(getByText('1111111111111')).toBeTruthy();
30
+ });
28
31
  });
29
32
 
30
- test('show all', () => {
33
+ test('show all', async () => {
31
34
  const { container, getByText } = render(
32
35
  <Tabs
33
36
  tabsAlign="left"
@@ -45,12 +48,14 @@ describe('Tabs Component', () => {
45
48
  </div>
46
49
  </Tabs>
47
50
  );
48
- expect(container.querySelectorAll('.tabListItem')).toHaveLength(3);
49
- expect(container.querySelectorAll('div.tabsHeader')).toHaveLength(1);
50
- expect(getByText('1111111111111222222233333333')).toBeInTheDocument();
51
+ await waitFor(() => {
52
+ expect(container.querySelectorAll('.tabListItem')).toHaveLength(3);
53
+ expect(container.querySelectorAll('div.tabsHeader')).toHaveLength(1);
54
+ expect(getByText('1111111111111222222233333333')).toBeInTheDocument();
55
+ });
51
56
  });
52
57
 
53
- test('switch tabs', () => {
58
+ test('switch tabs', async () => {
54
59
  const { getByText } = render(
55
60
  <Tabs tabsAlign="right" HeaderComp={<div className="header">my header</div>}>
56
61
  <div label="one" key={1}>
@@ -64,11 +69,23 @@ describe('Tabs Component', () => {
64
69
  </div>
65
70
  </Tabs>
66
71
  );
72
+
73
+ await waitFor(() => {
74
+ expect(getByText('two')).toBeTruthy();
75
+ });
76
+
67
77
  fireEvent.click(getByText('two'));
68
78
  expect(getByText('2222222')).toBeTruthy();
69
- fireEvent.click(getByText('three'));
79
+
80
+ await waitFor(() => {
81
+ fireEvent.click(getByText('three'));
82
+ });
83
+
70
84
  expect(getByText('33333333')).toBeTruthy();
71
- fireEvent.click(getByText('one'));
85
+
86
+ await waitFor(() => {
87
+ fireEvent.click(getByText('one'));
88
+ });
72
89
  expect(getByText('1111111111111')).toBeTruthy();
73
90
  });
74
91
  });
@@ -84,3 +84,45 @@
84
84
  background: linear-gradient(to bottom, $color2 0, $color1 100%);
85
85
  }
86
86
  }
87
+
88
+ @mixin star($border-color: #fba62f, $fill-color: #fba62f) {
89
+ line-height: 2rem;
90
+ width: 16px;
91
+ font-weight: normal;
92
+ display: inline-block;
93
+ color: $fill-color;
94
+ font-size: 15px;
95
+ position: relative;
96
+ text-shadow: -1px 0 $border-color, 0 1px $border-color, 1px 0 $border-color, 0 -1px $border-color;
97
+
98
+ &:last-child {
99
+ margin-right: 0;
100
+ }
101
+ &:before {
102
+ content: '\2605';
103
+ }
104
+ }
105
+
106
+
107
+ @mixin half-star($border-color: #fba62f, $half-empty-color: #fba62f , $half-full-color: white) {
108
+ line-height: 2rem;
109
+ width: 16px;
110
+ font-weight: normal;
111
+ display: inline-block;
112
+ color: $half-full-color;
113
+ font-size: 15px;
114
+ position: relative;
115
+ &:before {
116
+ content: '\2605';
117
+ }
118
+ text-shadow: -1px 0 $border-color, 0 1px $border-color, 1px 0 $border-color, 0 -1px $border-color;
119
+ &:after {
120
+ content: '\2605';
121
+ color: $half-empty-color;
122
+ position: absolute;
123
+ width: 7px;
124
+ overflow: hidden;
125
+ bottom: 0;
126
+ left: 0;
127
+ }
128
+ }
@@ -9,6 +9,13 @@
9
9
  --secondary-text-color: var(--color-4);
10
10
  --heading-base-color: var(--color-12);
11
11
  --text-link-color: var(--color-13);
12
+ --full-star-fill-color: #fba62f;
13
+ --full-star-border-color: #fba62f;
14
+ --halfFull-star-fill-color: #fba62f;
15
+ --halfEmpty-star-fill-color: white;
16
+ --half-star-border-color: #fba62f;
17
+ --empty-star-fill-color: white;
18
+ --empty-star-border-color: grey;
12
19
 
13
20
  // Font
14
21
  --main-font: Nunito;
@@ -51,6 +58,7 @@
51
58
  --modal-background-color: white;
52
59
 
53
60
  --star-rating-color: orange;
61
+ --selling-point-icon-color: #00889e;
54
62
 
55
63
  --scroll-to-top-background-color: var(--color-1);
56
64