gatsby-theme-q3 3.2.0 → 3.2.4

Sign up to get free protection for your applications and to get access to all the features.
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
- });