gatsby-theme-q3 3.1.4 → 3.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. package/CHANGELOG.md +74 -35
  2. package/gatsby-browser.js +29 -0
  3. package/gatsby-config.js +7 -47
  4. package/gatsby-node.js +0 -1
  5. package/lib/components/PageWrapper.js +2 -11
  6. package/lib/components/PublicTemplate.js +125 -6
  7. package/lib/components/PublicTemplateBackground.js +120 -0
  8. package/lib/components/SearchEngine.js +67 -20
  9. package/lib/components/Wrapper.js +3 -25
  10. package/lib/components/__tests__/SearchEngine.test.js +41 -0
  11. package/lib/components/useSiteMetaData.js +13 -12
  12. package/lib/components/withPublicTemplate.js +17 -0
  13. package/lib/components/withSuccessOp.js +1 -1
  14. package/lib/pages/login.js +5 -3
  15. package/lib/pages/password-change.js +4 -3
  16. package/lib/pages/password-reset.js +4 -3
  17. package/lib/pages/reverify.js +5 -2
  18. package/lib/pages/verify.js +5 -3
  19. package/package.json +6 -6
  20. package/src/components/PageWrapper.jsx +1 -13
  21. package/src/components/PublicTemplate.jsx +129 -5
  22. package/src/components/PublicTemplateBackground.jsx +140 -0
  23. package/src/components/SearchEngine.jsx +82 -23
  24. package/src/components/Wrapper.jsx +3 -27
  25. package/src/components/__tests__/SearchEngine.test.jsx +58 -0
  26. package/src/components/useSiteMetaData.js +17 -16
  27. package/src/components/withPublicTemplate.jsx +11 -0
  28. package/src/components/withSuccessOp.jsx +1 -1
  29. package/src/pages/login.jsx +51 -43
  30. package/src/pages/password-change.jsx +5 -3
  31. package/src/pages/password-reset.jsx +5 -3
  32. package/src/pages/reverify.jsx +4 -2
  33. package/src/pages/verify.jsx +9 -12
  34. package/__tests__/config.int.test.js +0 -73
  35. package/helpers/__tests__/loadContent.unit.test.js +0 -13
  36. package/helpers/__tests__/pagination.unit.test.js +0 -139
  37. package/helpers/__tests__/slug.unit.test.js +0 -21
  38. package/helpers/archive.js +0 -42
  39. package/helpers/index.js +0 -19
  40. package/helpers/loadContent.js +0 -45
  41. package/helpers/loadTheme.js +0 -10
  42. package/helpers/pagination.js +0 -109
  43. package/helpers/setup.js +0 -60
  44. package/helpers/slug.js +0 -31
  45. package/helpers/slugType.js +0 -24
  46. package/lib/components/LocaleBundles.js +0 -42
  47. package/lib/components/useLocale.js +0 -31
  48. package/src/components/LocaleBundles.jsx +0 -37
  49. package/src/components/useLocale.js +0 -20
@@ -1,32 +1,89 @@
1
1
  import React from 'react';
2
+ import { get, isFunction, isObject } from 'lodash';
2
3
  import PropTypes from 'prop-types';
3
4
  import { Helmet } from 'react-helmet';
5
+ import { browser } from 'q3-ui-helpers';
4
6
  import useSiteMetaData from './useSiteMetaData';
5
7
 
8
+ const withContent = (output) => (content) =>
9
+ content && isFunction(output) ? output(content) : [];
10
+
11
+ export const getStartUrl = () =>
12
+ browser.isBrowserReady()
13
+ ? get(window, 'location.host')
14
+ : '';
15
+
16
+ export const generateMetaDescriptionOptions = withContent(
17
+ (content) => [
18
+ {
19
+ name: 'description',
20
+ content,
21
+ },
22
+ {
23
+ property: 'og:description',
24
+ content,
25
+ },
26
+ {
27
+ name: 'twitter:description',
28
+ content,
29
+ },
30
+ ],
31
+ );
32
+
33
+ export const generateMetaTitleOptions = withContent(
34
+ (content) => [
35
+ {
36
+ property: 'og:title',
37
+ content,
38
+ },
39
+ {
40
+ name: 'twitter:title',
41
+ content,
42
+ },
43
+ ],
44
+ );
45
+
46
+ export const generateBrand = (xs) =>
47
+ xs ? `%s | ${xs}` : undefined;
48
+
49
+ export const generateIcons = (site = {}) =>
50
+ site?.favicon
51
+ ? [
52
+ {
53
+ src: site.favicon,
54
+ sizes: '512x512',
55
+ type: 'image/png',
56
+ },
57
+ ]
58
+ : [];
59
+
60
+ export const generateManifest = (site = {}) => ({
61
+ background_color: site.color,
62
+ description: site.description,
63
+ display: 'fullscreen',
64
+ icons: generateIcons(site),
65
+ name: site.title,
66
+ start_url: getStartUrl(),
67
+ short_name: site.brand,
68
+ theme_color: site.color,
69
+ });
70
+
6
71
  const SEO = ({ description, lang, meta, title }) => {
7
72
  const site = useSiteMetaData();
8
73
  const metaDescription = description || site.description;
74
+ const metaTitle = title || site.title;
75
+ const manifestData = generateManifest(site);
9
76
 
10
77
  return (
11
78
  <Helmet
12
79
  htmlAttributes={{
13
80
  lang,
14
81
  }}
15
- title={title || site.title}
16
- titleTemplate={`%s | ${site.brand}`}
82
+ title={metaTitle}
83
+ titleTemplate={generateBrand(site.brand)}
17
84
  meta={[
18
- {
19
- name: 'description',
20
- content: metaDescription,
21
- },
22
- {
23
- property: 'og:title',
24
- content: title,
25
- },
26
- {
27
- property: 'og:description',
28
- content: metaDescription,
29
- },
85
+ ...generateMetaTitleOptions(metaTitle),
86
+ ...generateMetaDescriptionOptions(metaDescription),
30
87
  {
31
88
  property: 'og:type',
32
89
  content: 'website',
@@ -35,16 +92,18 @@ const SEO = ({ description, lang, meta, title }) => {
35
92
  name: 'twitter:card',
36
93
  content: 'summary',
37
94
  },
38
- {
39
- name: 'twitter:title',
40
- content: title,
41
- },
42
- {
43
- name: 'twitter:description',
44
- content: metaDescription,
45
- },
46
95
  ].concat(meta)}
47
- />
96
+ >
97
+ {isObject(manifestData) ? (
98
+ <link
99
+ rel="manifest"
100
+ href={`data:application/manifest+json,${encodeURIComponent(
101
+ JSON.stringify(manifestData),
102
+ )}`}
103
+ />
104
+ ) : null}
105
+ <link rel="icon" href={site.favicon} />
106
+ </Helmet>
48
107
  );
49
108
  };
50
109
 
@@ -1,38 +1,14 @@
1
1
  /* eslint-disable import/no-extraneous-dependencies */
2
2
  import React from 'react';
3
- import axios from 'axios';
4
3
  import PropTypes from 'prop-types';
5
4
  import AuthProvider from 'q3-ui-permissions';
6
- import LocaleBundles from './LocaleBundles';
7
5
 
8
- const setBaseUrlForRest = (
9
- baseURL = process.env.GATSBY_APP_BASE_URL ||
10
- 'http://localhost:9000',
11
- ) => {
12
- axios.defaults.baseURL = baseURL;
13
- return axios.defaults;
14
- };
15
-
16
- const Wrapper = ({ baseURL, children, locale }) => {
17
- setBaseUrlForRest(baseURL);
18
-
19
- return (
20
- <LocaleBundles locale={locale}>
21
- <AuthProvider>{children}</AuthProvider>
22
- </LocaleBundles>
23
- );
24
- };
25
-
26
- Wrapper.defaultProps = {
27
- baseURL: undefined,
28
- };
6
+ const Wrapper = ({ children }) => (
7
+ <AuthProvider>{children}</AuthProvider>
8
+ );
29
9
 
30
10
  Wrapper.propTypes = {
31
- baseURL: PropTypes.string,
32
11
  children: PropTypes.node.isRequired,
33
-
34
- // eslint-disable-next-line
35
- locale: PropTypes.object.isRequired,
36
12
  };
37
13
 
38
14
  export default Wrapper;
@@ -0,0 +1,58 @@
1
+ import {
2
+ generateMetaDescriptionOptions,
3
+ getStartUrl,
4
+ generateIcons,
5
+ generateBrand,
6
+ } from '../SearchEngine';
7
+
8
+ jest.mock('q3-ui-locale', () => ({
9
+ browser: {
10
+ isBrowserReady: jest.fn(),
11
+ },
12
+ }));
13
+
14
+ const host = 'https://google.ca';
15
+
16
+ beforeEach(() => {
17
+ Object.defineProperty(window, 'location', {
18
+ value: {
19
+ host,
20
+ },
21
+ });
22
+ });
23
+
24
+ describe('SearchEngine', () => {
25
+ it('should not render descriptions without content', () => {
26
+ expect(generateMetaDescriptionOptions().length).toBe(0);
27
+ });
28
+
29
+ it('should render descriptions with content', () => {
30
+ expect(
31
+ generateMetaDescriptionOptions('foo').length,
32
+ ).toBeGreaterThanOrEqual(1);
33
+ });
34
+
35
+ it('should return host', () => {
36
+ expect(getStartUrl()).toMatch(host);
37
+ });
38
+
39
+ it('should render favicon', () => {
40
+ expect(
41
+ generateIcons({
42
+ favicon: host,
43
+ }),
44
+ ).toHaveLength(1);
45
+ });
46
+
47
+ it('should not render favicon', () => {
48
+ expect(
49
+ generateIcons({
50
+ favicon: undefined,
51
+ }),
52
+ ).toHaveLength(0);
53
+ });
54
+
55
+ it('should include template literals', () => {
56
+ expect(generateBrand('3merge')).toMatch('%s | 3merge');
57
+ });
58
+ });
@@ -1,22 +1,23 @@
1
- import { get } from 'lodash';
1
+ import { get, merge } from 'lodash';
2
2
  import { useStaticQuery, graphql } from 'gatsby';
3
+ import useRunTime from 'gatsby-theme-q3-mui/src/components/useRunTime';
3
4
 
4
5
  export default () =>
5
- get(
6
- useStaticQuery(graphql`
7
- query {
8
- site {
9
- siteMetadata {
10
- appDirectory
11
- brand
12
- description
13
- favicon
14
- logo
15
- title
6
+ merge(
7
+ get(
8
+ useStaticQuery(graphql`
9
+ query {
10
+ site {
11
+ siteMetadata {
12
+ appDirectory
13
+ description
14
+ title
15
+ }
16
16
  }
17
17
  }
18
- }
19
- `),
20
- 'site.siteMetadata',
21
- {},
18
+ `),
19
+ 'site.siteMetadata',
20
+ {},
21
+ ),
22
+ useRunTime(),
22
23
  );
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ import PublicTemplate from './PublicTemplate';
3
+
4
+ const withPublicTemplate = (Component) => (props) =>
5
+ (
6
+ <PublicTemplate {...props}>
7
+ <Component {...props} />
8
+ </PublicTemplate>
9
+ );
10
+
11
+ export default withPublicTemplate;
@@ -20,7 +20,7 @@ const withSuccessOp = (Component, msg) => {
20
20
  component={Link}
21
21
  to="/login"
22
22
  variant="contained"
23
- color="primary"
23
+ color="secondary"
24
24
  >
25
25
  {t('labels:login')}
26
26
  </Button>
@@ -9,48 +9,56 @@ import Box from '@material-ui/core/Box';
9
9
  import { Form, Field } from 'q3-ui-forms/lib/builders';
10
10
  import FormBox from '../components/FormBox';
11
11
  import withAuthenticate from '../components/withAuthenticate';
12
+ import withPublicTemplate from '../components/withPublicTemplate';
12
13
 
13
- export default withAuthenticate(({ authenticate }) => {
14
- const { t } = useTranslation();
14
+ const Login = withPublicTemplate(
15
+ withAuthenticate(({ authenticate }) => {
16
+ const { t } = useTranslation();
15
17
 
16
- return (
17
- <FormBox
18
- renderBottom={
19
- <>
20
- <Form onSubmit={authenticate}>
21
- <Field
22
- name="email"
23
- type="email"
24
- required
25
- xl={12}
26
- lg={12}
27
- />
28
- <Field
29
- required
30
- name="password"
31
- type="password"
32
- xl={12}
33
- lg={12}
34
- />
35
- </Form>
36
- <Box mt={2} mb={1}>
37
- <Divider />
38
- </Box>
39
- <Grid container spacing={1}>
40
- <MuiLink component={Link} to="/password-reset">
41
- {t('labels:requestNewPassword')}
42
- </MuiLink>
43
- <MuiLink component={Link} to="/reverify">
44
- {t('labels:reverifyLink')}
45
- </MuiLink>
46
- </Grid>
47
- </>
48
- }
49
- renderTop={
50
- <Typography variant="h1" gutterBottom>
51
- {t('titles:login')}
52
- </Typography>
53
- }
54
- />
55
- );
56
- });
18
+ return (
19
+ <FormBox
20
+ renderBottom={
21
+ <>
22
+ <Form onSubmit={authenticate}>
23
+ <Field
24
+ name="email"
25
+ type="email"
26
+ required
27
+ xl={12}
28
+ lg={12}
29
+ />
30
+ <Field
31
+ required
32
+ name="password"
33
+ type="password"
34
+ xl={12}
35
+ lg={12}
36
+ />
37
+ </Form>
38
+ <Box mt={2} mb={1}>
39
+ <Divider />
40
+ </Box>
41
+ <Grid container spacing={1}>
42
+ <MuiLink
43
+ component={Link}
44
+ to="/password-reset"
45
+ >
46
+ {t('labels:requestNewPassword')}
47
+ </MuiLink>
48
+ <MuiLink component={Link} to="/reverify">
49
+ {t('labels:reverifyLink')}
50
+ </MuiLink>
51
+ </Grid>
52
+ </>
53
+ }
54
+ renderTop={
55
+ <Typography variant="h1" gutterBottom>
56
+ {t('titles:login')}
57
+ </Typography>
58
+ }
59
+ />
60
+ );
61
+ }),
62
+ );
63
+
64
+ export default Login;
@@ -8,6 +8,7 @@ import { Form, Field } from 'q3-ui-forms/lib/builders';
8
8
  import FormBoxContent from '../components/FormBoxContent';
9
9
  import FormBox from '../components/FormBox';
10
10
  import withSuccessOp from '../components/withSuccessOp';
11
+ import withPublicTemplate from '../components/withPublicTemplate';
11
12
 
12
13
  const PasswordChange = ({ onSuccess, location }) => {
13
14
  const { passwordResetToken, email } = queryString.parse(
@@ -64,7 +65,8 @@ PasswordChange.propTypes = {
64
65
  }).isRequired,
65
66
  };
66
67
 
67
- export default withSuccessOp(
68
- PasswordChange,
69
- 'passwordChangeNotice',
68
+ const PasswordChangeWithTemplate = withPublicTemplate(
69
+ withSuccessOp(PasswordChange, 'passwordChangeNotice'),
70
70
  );
71
+
72
+ export default PasswordChangeWithTemplate;
@@ -5,6 +5,7 @@ import { Form, Field } from 'q3-ui-forms/lib/builders';
5
5
  import FormBox from '../components/FormBox';
6
6
  import FormBoxContent from '../components/FormBoxContent';
7
7
  import withSuccessOp from '../components/withSuccessOp';
8
+ import withPublicTemplate from '../components/withPublicTemplate';
8
9
 
9
10
  const PasswordReset = ({ onSuccess }) => (
10
11
  <FormBox
@@ -39,7 +40,8 @@ PasswordReset.propTypes = {
39
40
  onSuccess: PropTypes.func.isRequired,
40
41
  };
41
42
 
42
- export default withSuccessOp(
43
- PasswordReset,
44
- 'passwordResetNotice',
43
+ const PasswordResetWithTemplate = withPublicTemplate(
44
+ withSuccessOp(PasswordReset, 'passwordResetNotice'),
45
45
  );
46
+
47
+ export default PasswordResetWithTemplate;
@@ -9,6 +9,7 @@ import FormBox from '../components/FormBox';
9
9
  import FormBoxContent from '../components/FormBoxContent';
10
10
  import FormBoxNotice from '../components/FormBoxNotice';
11
11
  import { hasOp, toOp } from '../components/utils';
12
+ import withPublicTemplate from '../components/withPublicTemplate';
12
13
 
13
14
  const Reverify = ({ location: { search, pathname } }) => {
14
15
  const { t } = useTranslation();
@@ -23,7 +24,7 @@ const Reverify = ({ location: { search, pathname } }) => {
23
24
  component={Link}
24
25
  to="/reverify"
25
26
  variant="contained"
26
- color="primary"
27
+ color="secondary"
27
28
  >
28
29
  {t('labels:tryAgain')}
29
30
  </Button>
@@ -67,4 +68,5 @@ Reverify.propTypes = {
67
68
  }).isRequired,
68
69
  };
69
70
 
70
- export default Reverify;
71
+ const ReverifyWithTemplate = withPublicTemplate(Reverify);
72
+ export default ReverifyWithTemplate;
@@ -8,19 +8,14 @@ import { Form, Field } from 'q3-ui-forms/lib/builders';
8
8
  import FormBoxContent from '../components/FormBoxContent';
9
9
  import FormBox from '../components/FormBox';
10
10
  import withAuthenticate from '../components/withAuthenticate';
11
+ import withPublicTemplate from '../components/withPublicTemplate';
11
12
 
12
- export default withAuthenticate(
13
- ({ authenticate, ...props }) => {
14
- const {
15
- verificationCode,
16
- id,
17
- email,
18
- } = queryString.parse(
19
- get(props, 'location.search', ''),
20
- {
13
+ const Verify = withPublicTemplate(
14
+ withAuthenticate(({ authenticate, ...props }) => {
15
+ const { verificationCode, id, email } =
16
+ queryString.parse(get(props, 'location.search', ''), {
21
17
  decode: false,
22
- },
23
- );
18
+ });
24
19
 
25
20
  return (
26
21
  <FormBox
@@ -69,5 +64,7 @@ export default withAuthenticate(
69
64
  }
70
65
  />
71
66
  );
72
- },
67
+ }),
73
68
  );
69
+
70
+ export default Verify;
@@ -1,73 +0,0 @@
1
- import { get } from 'lodash';
2
- import config from '../gatsby-config';
3
-
4
- const CANONICAL = 'gatsby-plugin-canonical-urls';
5
- const MANIFEST = 'gatsby-plugin-manifest';
6
- const ROBOTS = 'gatsby-plugin-robots-txt';
7
-
8
- const ENV = {
9
- contentfulSpaceID: 1,
10
- contentfulAccessToken: 1,
11
- };
12
-
13
- const containsResolver = (plugins = [], name) =>
14
- plugins.find(
15
- (p) => typeof p === 'object' && p.resolve === name,
16
- );
17
-
18
- const checkPlugins = (args = {}, plugin) => {
19
- const { plugins } = config({ ...ENV, ...args });
20
- const statement = expect(
21
- containsResolver(plugins, plugin),
22
- );
23
-
24
- return {
25
- has: () => statement.not.toBeUndefined(),
26
- hasNot: () => statement.toBeUndefined(),
27
- };
28
- };
29
-
30
- describe('gatsby-config', () => {
31
- describe('plugins', () => {
32
- it('should error without contentful access token', () => {
33
- process.env.URL =
34
- 'https://development.netlify.3merge.com';
35
- expect(() =>
36
- config({
37
- contentfulSpaceID: '1',
38
- }),
39
- ).toThrowError();
40
- });
41
-
42
- it('should include conditional plugins', () =>
43
- [CANONICAL, MANIFEST].forEach((name) =>
44
- checkPlugins(
45
- {
46
- brandingColor: '#FFF',
47
- title: 'Foo',
48
- siteUrl: 'https://google.ca',
49
- },
50
- name,
51
- ).has(),
52
- ));
53
-
54
- it('should exclude conditional plugins', () =>
55
- [CANONICAL, MANIFEST].forEach((name) =>
56
- checkPlugins({}, name).hasNot(),
57
- ));
58
-
59
- it.each([
60
- ['https://dev.netlify.3merge.com', 'disallow'],
61
- ['https://3merge.com', 'allow'],
62
- ])('should disable indexing', (url, key) => {
63
- process.env.URL = url;
64
- const { plugins } = config({ ...ENV });
65
- const res = containsResolver(plugins, ROBOTS);
66
- const prod = get(
67
- res,
68
- 'options.env.production.policy',
69
- )[0];
70
- expect(prod).toHaveProperty(key, '/');
71
- });
72
- });
73
- });
@@ -1,13 +0,0 @@
1
- const path = require('path');
2
- const loadContent = require('../loadContent');
3
-
4
- describe('loadContent', () => {
5
- it('should fetch all content from directory', () => {
6
- const out = loadContent(
7
- path.resolve(__dirname, '../../__fixtures__'),
8
- );
9
-
10
- expect(out).toHaveProperty('en');
11
- expect(out).toHaveProperty('fr');
12
- });
13
- });