gatsby-theme-q3 3.2.0 → 3.2.4

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 (41) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/gatsby-browser.js +5 -0
  3. package/gatsby-config.js +7 -18
  4. package/gatsby-node.js +0 -1
  5. package/lib/components/FormBoxContent.js +2 -1
  6. package/lib/components/PageWrapper.js +1 -2
  7. package/lib/components/PublicTemplate.js +130 -6
  8. package/lib/components/SearchEngine.js +7 -10
  9. package/lib/components/Wrapper.js +2 -19
  10. package/lib/components/withPublicTemplate.js +17 -0
  11. package/lib/components/withSuccessOp.js +1 -1
  12. package/lib/pages/login.js +7 -4
  13. package/lib/pages/password-change.js +4 -3
  14. package/lib/pages/password-reset.js +4 -3
  15. package/lib/pages/reverify.js +5 -2
  16. package/lib/pages/verify.js +5 -3
  17. package/package.json +4 -4
  18. package/src/components/FormBoxContent.jsx +1 -1
  19. package/src/components/PageWrapper.jsx +1 -5
  20. package/src/components/PublicTemplate.jsx +134 -5
  21. package/src/components/SearchEngine.jsx +10 -14
  22. package/src/components/Wrapper.jsx +3 -21
  23. package/src/components/withPublicTemplate.jsx +11 -0
  24. package/src/components/withSuccessOp.jsx +1 -1
  25. package/src/pages/login.jsx +55 -43
  26. package/src/pages/password-change.jsx +5 -3
  27. package/src/pages/password-reset.jsx +5 -3
  28. package/src/pages/reverify.jsx +4 -2
  29. package/src/pages/verify.jsx +9 -12
  30. package/__tests__/config.int.test.js +0 -62
  31. package/helpers/__tests__/loadContent.unit.test.js +0 -13
  32. package/helpers/__tests__/pagination.unit.test.js +0 -139
  33. package/helpers/__tests__/slug.unit.test.js +0 -21
  34. package/helpers/archive.js +0 -42
  35. package/helpers/index.js +0 -19
  36. package/helpers/loadContent.js +0 -45
  37. package/helpers/loadTheme.js +0 -10
  38. package/helpers/pagination.js +0 -109
  39. package/helpers/setup.js +0 -60
  40. package/helpers/slug.js +0 -31
  41. package/helpers/slugType.js +0 -24
@@ -1,17 +1,146 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
- import { Public } from 'q3-admin/lib/components';
3
+ import {
4
+ Box,
5
+ Paper,
6
+ Link,
7
+ Grid,
8
+ Hidden,
9
+ makeStyles,
10
+ } from '@material-ui/core';
11
+ import { Link as ReachLink } from 'gatsby';
12
+ import { useTranslation } from 'q3-ui-locale';
13
+ import { isString } from 'lodash';
4
14
  import AdminPublicGateway from './AdminPublicGateway';
5
15
  import useSiteMetaData from './useSiteMetaData';
6
16
 
17
+ const useStyle = makeStyles((theme) => ({
18
+ logo: {
19
+ height: 95,
20
+ width: 180,
21
+ display: 'block',
22
+
23
+ '& img': {
24
+ objectFit: 'contain',
25
+ height: '100%',
26
+ width: '100%',
27
+ },
28
+ },
29
+ container: {
30
+ maxWidth: '85vw',
31
+ width: 850,
32
+
33
+ [theme.breakpoints.down('md')]: {
34
+ width: '100%',
35
+ },
36
+
37
+ [theme.breakpoints.down('sm')]: {
38
+ width: 'auto',
39
+ },
40
+ },
41
+ photo: ({ photo }) => ({
42
+ backgroundColor: theme.palette.secondary.light,
43
+ backgroundImage: isString(photo)
44
+ ? `url("${String(photo).replace(/\s/gi, '%20')}")`
45
+ : undefined,
46
+ backgroundSize: 'contain',
47
+ backgroundPosition: 'center',
48
+ backgroundRepeat: 'no-repeat',
49
+ width: '100%',
50
+ height: '100%',
51
+ backgroundBlendMode: 'multiply',
52
+ minHeight: '55vh',
53
+ }),
54
+ }));
55
+
56
+ const Copyright = () => {
57
+ const { brand } = useSiteMetaData();
58
+
59
+ return brand ? (
60
+ <Box display="inline-block" mx={1}>
61
+ ©{new Date().getFullYear()} {brand}
62
+ </Box>
63
+ ) : null;
64
+ };
65
+
66
+ // eslint-disable-next-line
67
+ const TextLink = ({ href, text }) => {
68
+ const { t } = useTranslation('labels');
69
+
70
+ return href ? (
71
+ <Box display="inline-block" mx={1}>
72
+ <Link href={href} target="_blank">
73
+ {t(text)}
74
+ </Link>
75
+ </Box>
76
+ ) : null;
77
+ };
78
+
7
79
  const PublicTemplate = ({ children, ...rest }) => {
8
- const { brand, logo } = useSiteMetaData();
80
+ const {
81
+ brand,
82
+ cancellation,
83
+ logo,
84
+ terms,
85
+ privacy,
86
+ photo,
87
+ } = useSiteMetaData();
88
+ const cls = useStyle({
89
+ photo,
90
+ });
9
91
 
10
92
  return (
11
93
  <AdminPublicGateway {...rest}>
12
- <Public companyName={brand} logo={logo}>
13
- {children}
14
- </Public>
94
+ <Box
95
+ alignItems="center"
96
+ component="article"
97
+ display="flex"
98
+ flexDirection="column"
99
+ justifyContent="center"
100
+ width="100%"
101
+ >
102
+ <Box
103
+ component="header"
104
+ mt="2vh"
105
+ mb={1}
106
+ textAlign="center"
107
+ >
108
+ <ReachLink to="/" className={cls.logo}>
109
+ <img alt={brand} src={logo} />
110
+ </ReachLink>
111
+ </Box>
112
+ <Paper className={cls.container}>
113
+ <Grid alignItems="center" container spacing={1}>
114
+ <Hidden smDown>
115
+ <Grid item xs={6}>
116
+ <Box className={cls.photo} />
117
+ </Grid>
118
+ </Hidden>
119
+ <Grid item md={6} xs={12}>
120
+ <Box p={2}>{children}</Box>
121
+ </Grid>
122
+ </Grid>
123
+ </Paper>
124
+ <Box
125
+ maxWidth="75vw"
126
+ component="footer"
127
+ mt={2}
128
+ textAlign="center"
129
+ >
130
+ <small>
131
+ <Copyright />
132
+ <TextLink
133
+ href={terms}
134
+ text="termsAndConditions"
135
+ />
136
+ <TextLink href={privacy} text="privacyPolicy" />
137
+ <TextLink
138
+ href={cancellation}
139
+ text="cancellationPolicy"
140
+ />
141
+ </small>
142
+ </Box>
143
+ </Box>
15
144
  </AdminPublicGateway>
16
145
  );
17
146
  };
@@ -68,20 +68,11 @@ export const generateManifest = (site = {}) => ({
68
68
  theme_color: site.color,
69
69
  });
70
70
 
71
- export const Manifest = (props) =>
72
- isObject(props) ? (
73
- <link
74
- rel="manifest"
75
- href={`data:application/manifest+json,${encodeURIComponent(
76
- JSON.stringify(props),
77
- )}`}
78
- />
79
- ) : null;
80
-
81
71
  const SEO = ({ description, lang, meta, title }) => {
82
72
  const site = useSiteMetaData();
83
73
  const metaDescription = description || site.description;
84
74
  const metaTitle = title || site.title;
75
+ const manifestData = generateManifest(site);
85
76
 
86
77
  return (
87
78
  <Helmet
@@ -89,9 +80,7 @@ const SEO = ({ description, lang, meta, title }) => {
89
80
  lang,
90
81
  }}
91
82
  title={metaTitle}
92
- titleTemplate={generateBrand(
93
- get(site, 'brand', 'Q3'),
94
- )}
83
+ titleTemplate={generateBrand(site.brand)}
95
84
  meta={[
96
85
  ...generateMetaTitleOptions(metaTitle),
97
86
  ...generateMetaDescriptionOptions(metaDescription),
@@ -105,7 +94,14 @@ const SEO = ({ description, lang, meta, title }) => {
105
94
  },
106
95
  ].concat(meta)}
107
96
  >
108
- <Manifest {...generateManifest(site)} />
97
+ {isObject(manifestData) ? (
98
+ <link
99
+ rel="manifest"
100
+ href={`data:application/manifest+json,${encodeURIComponent(
101
+ JSON.stringify(manifestData),
102
+ )}`}
103
+ />
104
+ ) : null}
109
105
  <link rel="icon" href={site.favicon} />
110
106
  </Helmet>
111
107
  );
@@ -1,32 +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
5
 
7
- const setBaseUrlForRest = (
8
- baseURL = process.env.GATSBY_APP_BASE_URL ||
9
- 'http://localhost:9000',
10
- ) => {
11
- axios.defaults.baseURL = baseURL;
12
- return axios.defaults;
13
- };
14
-
15
- const Wrapper = ({ baseURL, children }) => {
16
- setBaseUrlForRest(baseURL);
17
- return <AuthProvider>{children}</AuthProvider>;
18
- };
19
-
20
- Wrapper.defaultProps = {
21
- baseURL: undefined,
22
- };
6
+ const Wrapper = ({ children }) => (
7
+ <AuthProvider>{children}</AuthProvider>
8
+ );
23
9
 
24
10
  Wrapper.propTypes = {
25
- baseURL: PropTypes.string,
26
11
  children: PropTypes.node.isRequired,
27
-
28
- // eslint-disable-next-line
29
- locale: PropTypes.object.isRequired,
30
12
  };
31
13
 
32
14
  export default Wrapper;
@@ -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,60 @@ 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
56
+ component="h1"
57
+ variant="h2"
58
+ gutterBottom
59
+ >
60
+ {t('titles:login')}
61
+ </Typography>
62
+ }
63
+ />
64
+ );
65
+ }),
66
+ );
67
+
68
+ 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,62 +0,0 @@
1
- import { get } from 'lodash';
2
- import config from '../gatsby-config';
3
-
4
- const CANONICAL = 'gatsby-plugin-canonical-urls';
5
- const ROBOTS = 'gatsby-plugin-robots-txt';
6
-
7
- const ENV = {
8
- contentfulSpaceID: 1,
9
- contentfulAccessToken: 1,
10
- };
11
-
12
- const containsResolver = (plugins = [], name) =>
13
- plugins.find(
14
- (p) => typeof p === 'object' && p.resolve === name,
15
- );
16
-
17
- const checkPlugins = (args = {}, plugin) => {
18
- const { plugins } = config({ ...ENV, ...args });
19
- const statement = expect(
20
- containsResolver(plugins, plugin),
21
- );
22
-
23
- return {
24
- has: () => statement.not.toBeUndefined(),
25
- hasNot: () => statement.toBeUndefined(),
26
- };
27
- };
28
-
29
- describe('gatsby-config', () => {
30
- describe('plugins', () => {
31
- it('should include conditional plugins', () =>
32
- [CANONICAL].forEach((name) =>
33
- checkPlugins(
34
- {
35
- brandingColor: '#FFF',
36
- title: 'Foo',
37
- siteUrl: 'https://google.ca',
38
- },
39
- name,
40
- ).has(),
41
- ));
42
-
43
- it('should exclude conditional plugins', () =>
44
- [CANONICAL].forEach((name) =>
45
- checkPlugins({}, name).hasNot(),
46
- ));
47
-
48
- it.each([
49
- ['https://dev.netlify.3merge.com', 'disallow'],
50
- ['https://3merge.com', 'allow'],
51
- ])('should disable indexing', (url, key) => {
52
- process.env.URL = url;
53
- const { plugins } = config({ ...ENV });
54
- const res = containsResolver(plugins, ROBOTS);
55
- const prod = get(
56
- res,
57
- 'options.env.production.policy',
58
- )[0];
59
- expect(prod).toHaveProperty(key, '/');
60
- });
61
- });
62
- });
@@ -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
- });
@@ -1,139 +0,0 @@
1
- const {
2
- genCursor,
3
- appendSiblingsToContext,
4
- getPreviousArchiveUrl,
5
- getNextArchiveUrl,
6
- getNumberOfPages,
7
- paginateArchiveContext,
8
- } = require('../pagination');
9
-
10
- const genEntries = () => {
11
- const entries = [];
12
- for (let i = 0; i < 30; i += 1) entries.push(i);
13
- return entries;
14
- };
15
-
16
- describe('pagination', () => {
17
- describe('"genCursor"', () => {
18
- const stub = ['foo', 'bar', 'quuz', 'garply'];
19
- const cursor = genCursor(stub, 2);
20
- // current index targets "quuz"
21
-
22
- it('should identify first item', () => {
23
- expect(cursor.first).toMatch('foo');
24
- });
25
-
26
- it('should identify last item', () => {
27
- expect(cursor.last).toMatch('garply');
28
- });
29
-
30
- it('should identify next item', () => {
31
- expect(cursor.next).toMatch('garply');
32
- });
33
-
34
- it('should identify previous item', () => {
35
- expect(cursor.prev).toMatch('bar');
36
- });
37
-
38
- it('should identify first position', () => {
39
- expect(cursor.isFirst).toBeFalsy();
40
- expect(genCursor(stub, 0).isFirst).toBeTruthy();
41
- });
42
-
43
- it('should identify last position', () => {
44
- expect(cursor.isLast).toBeFalsy();
45
- expect(genCursor(stub, 3).isLast).toBeTruthy();
46
- });
47
- });
48
-
49
- describe('"appendSiblingsToContext"', () => {
50
- const mockContentfulEntry = (id) => ({
51
- contentful_id: id,
52
- });
53
-
54
- const stubWithContentful = [
55
- mockContentfulEntry(1),
56
- mockContentfulEntry(2),
57
- mockContentfulEntry(3),
58
- ];
59
-
60
- it('should map contentful entries using cursor', () => {
61
- const entries = appendSiblingsToContext(
62
- stubWithContentful,
63
- );
64
- expect(entries[0]).toMatchObject({
65
- prev: 3,
66
- next: 2,
67
- });
68
- expect(entries[2]).toMatchObject({
69
- prev: 2,
70
- next: 1,
71
- });
72
- });
73
- });
74
-
75
- describe('"getPreviousArchiveUrl"', () => {
76
- it('should return null', () => {
77
- expect(getPreviousArchiveUrl('/foo', 1)).toBeNull();
78
- });
79
-
80
- it('should return archive', () => {
81
- expect(getPreviousArchiveUrl('/foo', 2)).toEqual(
82
- '/foo',
83
- );
84
- });
85
-
86
- it('should return archive sub-directory', () => {
87
- expect(getPreviousArchiveUrl('/foo', 3)).toEqual(
88
- '/foo/2',
89
- );
90
- });
91
- });
92
-
93
- describe('"getNextArchiveUrl"', () => {
94
- it('should return sub-directory', () => {
95
- expect(getNextArchiveUrl('/foo', 8, 9)).toEqual(
96
- '/foo/9',
97
- );
98
- });
99
-
100
- it('should return null', () => {
101
- expect(getNextArchiveUrl('/foo', 9, 9)).toBeNull();
102
- });
103
- });
104
-
105
- describe('"getNumberOfPages"', () => {
106
- it('should return number divisible by', () => {
107
- expect(getNumberOfPages(genEntries(), 5)).toBe(6);
108
- });
109
- });
110
-
111
- describe('"paginateArchiveContext"', () => {
112
- it('should return pagination meta', () => {
113
- // default 15 per page
114
- const res = paginateArchiveContext(
115
- genEntries(),
116
- '/foo',
117
- );
118
-
119
- expect(res).toHaveLength(2);
120
- expect(res[0]).toMatchObject({
121
- path: '/foo',
122
- limit: 15,
123
- skip: 0,
124
- pageNum: 0,
125
- prev: null,
126
- next: '/foo/2',
127
- });
128
-
129
- expect(res[1]).toMatchObject({
130
- path: '/foo/2',
131
- limit: 15,
132
- skip: 15,
133
- pageNum: 1,
134
- prev: '/foo',
135
- next: null,
136
- });
137
- });
138
- });
139
- });