22 |
23 |
24 |
25 |
26 | );
27 | };
28 |
29 | export const getStaticProps = async ({ locale }) => ({
30 | props: await getIntlProps(locale),
31 | });
32 |
33 | export default Contacts;
34 |
--------------------------------------------------------------------------------
/docusaurus/static/recipes-assets/dato-cms/next-with-apollo/next-with-apollo.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import withApollo from 'next-with-apollo';
3 | import { ApolloProvider } from '@apollo/react-hooks';
4 |
5 | import createApolloClient from './apollo-client';
6 |
7 | export default withApollo(
8 | ({ initialState, ctx }) => {
9 | if ((typeof ctx !== 'undefined' && Object.hasOwnProperty.call(ctx.query, 'cms-preview')) ||
10 | (typeof window !== 'undefined' && new URLSearchParams(window.location.search).has('cms-preview'))) {
11 | return createApolloClient(initialState, ctx, true);
12 | }
13 |
14 | return createApolloClient(initialState, ctx);
15 | },
16 | {
17 | // eslint-disable-next-line react/prop-types
18 | render: ({ Page, props }) => (
19 | // eslint-disable-next-line react/prop-types
20 |
21 |
22 |
23 | ),
24 | },
25 | );
26 |
--------------------------------------------------------------------------------
/docusaurus/docs/this-document.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: this-document
3 | title: This document
4 | sidebar_label: This document
5 | ---
6 |
7 | This document is the default documentation for [`next-with-moxy`](https://github.com/moxystudio/next-with-moxy) projects.
8 |
9 | **Keep in mind**, if you have received a project developed with [`next-with-moxy`](https://github.com/moxystudio/next-with-moxy), you have received a version of this documentation tailored to your project.
10 | Though much of the information in this document is shared between projects, we recommend you always check the documentation sent with your project for more accurate and tailored instructions.
11 |
12 | The first time you do this, you must install the dependencies of the documentation. From the folder of the project, run:
13 |
14 | ```bash
15 | npm install --prefix docusaurus
16 | ```
17 |
18 | Afterwards, and every time you need to access the documentation again, you can run:
19 |
20 | ```bash
21 | npm run docs
22 | ```
23 |
24 | This script will automatically open your documentation in your default browser.
25 |
--------------------------------------------------------------------------------
/www/shared/react/testing-library/app-tree/AppTree.test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-restricted-imports */
2 |
3 | import React from 'react';
4 | import { FormattedMessage } from 'react-intl';
5 | import { render, screen } from '@testing-library/react';
6 | import AppTree from './AppTree';
7 |
8 | it('should render children correctly', () => {
9 | render(
10 |
11 |
foo
12 | ,
13 | );
14 |
15 | screen.getByText('foo');
16 | });
17 |
18 | it('should correctly setup IntlProvider', () => {
19 | render(
20 |
21 |
22 | ,
23 | );
24 |
25 | screen.getByText('foo');
26 | });
27 |
28 | it('should correctly setup IntlProvider with overrides', () => {
29 | const intlProvider = {
30 | messages: { foo: 'bar' },
31 | };
32 |
33 | render(
34 |
35 |
36 | ,
37 | );
38 |
39 | screen.getByText('bar');
40 | });
41 |
--------------------------------------------------------------------------------
/www/shared/react/main-layout/MainLayout.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render, screen } from '../testing-library';
3 | import MainLayout from './MainLayout';
4 |
5 | const Page = ({ children }) => children;
6 |
7 | it('should render correctly', () => {
8 | const { container } = render(
9 |
10 |
11 | Hello!
12 |
13 | ,
14 | );
15 |
16 | expect(container.querySelector('header')).toBeInTheDocument();
17 | expect(container.querySelector('footer')).toBeInTheDocument();
18 | expect(container.querySelector('main')).toBeInTheDocument();
19 | screen.getByText('Hello!');
20 | });
21 |
22 | it('should respect passed className', () => {
23 | const { container } = render(
24 |
25 |
26 | Hello!
27 |
28 | ,
29 | );
30 |
31 | expect(container.querySelector('.foo')).toBeTruthy();
32 | });
33 |
--------------------------------------------------------------------------------
/docusaurus/docs/what-is-included/breakpoints.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: breakpoints
3 | title: Breakpoints
4 | sidebar_label: Breakpoints
5 | ---
6 |
7 | The boilerplate comes with a standardized set of breakpoints, with the following specs:
8 |
9 | | Name | Breakpoint | Description |
10 | | ---- | ---------- | ----------- |
11 | | xxs | ≥0 | Normal mobile devices |
12 | | xs | ≥480 | Large mobile devices or tiny tablets |
13 | | sm | ≥768 | Small tablets |
14 | | md | ≥1024 | Large tablets or tiny desktops |
15 | | lg | ≥1280 | Small desktops |
16 | | xl | ≥1440 | Normal desktops |
17 | | xxl | ≥1920 | Large desktops |
18 |
19 | ## Targeting breakpoints
20 |
21 | Inside `www/shared/styles/imports/custom-medias.css`, you will find [Custom Media Queries](https://github.com/postcss/postcss-custom-media) to target resolutions higher, higher or equal, lower and lower or equal for each breakpoint.
22 |
23 | Here's an example:
24 |
25 | ```css
26 | @import "../../shared/styles/imports";
27 |
28 | .myPage {
29 | padding: 0 25px;
30 |
31 | @media (--lt-sm) {
32 | padding: 0 10px;
33 | }
34 | }
35 | ```
36 |
--------------------------------------------------------------------------------
/www/shared/react/page-swapper/PageSwapper.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import RawPageSwapper from '@moxy/react-page-swapper'; // eslint-disable-line no-restricted-imports
4 | import { useRouterScroll } from '@moxy/next-router-scroll';
5 | import PageTransition from './page-transition';
6 |
7 | const PageSwapper = ({ children, pageTransitionClassName, ...rest }) => {
8 | const { updateScroll } = useRouterScroll();
9 |
10 | return (
11 |
12 | { ({ node, ...rest }) => {
13 | if (typeof children === 'function') {
14 | node = children({ node, ...rest });
15 | }
16 |
17 | return { node };
18 | } }
19 |
20 | );
21 | };
22 |
23 | PageSwapper.propTypes = {
24 | children: PropTypes.func,
25 | pageTransitionClassName: PropTypes.string,
26 | };
27 |
28 | export default PageSwapper;
29 |
--------------------------------------------------------------------------------
/docusaurus/docs/what-is-included/jest-rtl.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: testing-with-jest-rtl
3 | title: Testing with Jest & RTL
4 | sidebar_label: Testing with Jest & RTL
5 | ---
6 |
7 | For testing, this boilerplate is configured to use [**Jest**](https://jestjs.io/) and [**react-testing-library**](https://github.com/testing-library/react-testing-library), providing strong options for testing varied types of code.
8 |
9 | You can find more details about our implementation of Jest in its configuration file, `jest.config.js`, which uses our own [`@moxy/jest-config`](https://github.com/moxystudio/jest-config).
10 |
11 | ## RTL
12 |
13 | We have historically used **Enzyme** for testing, however, with the introduction of new lifecycle methods and React hooks, we noticed that **Enzyme** did not keep up with their support for these new features.
14 |
15 | Due to the nature of **react-testing-library**, we don't foresee it suffering from the same problems, and, together with moving towards a philosophy of testing with a stronger user perspective in mind, we rethought the tool we use for unit testing and have since then opted for **react-testing-library**.
16 |
--------------------------------------------------------------------------------
/docusaurus/static/recipes-assets/setting-up-redux/redux-logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/www/shared/react/next-router/use-page-router.js:
--------------------------------------------------------------------------------
1 | import { useRef } from 'react';
2 | import { useRouter } from 'next/router'; // eslint-disable-line no-restricted-imports
3 | import usePageKey from './use-page-key';
4 |
5 | // You should use this hook instead of useRouter() provided by Next.js because it
6 | // returns the correct router even during page transitions. During the transition,
7 | // the old page will still be mounted when animating out, but the router has already changed.
8 | // This hook returns the same router your page started with.
9 | // Additionally, you may specify an `pathnames` array to allow the returned router to change only for these pages.
10 |
11 | const usePageRouter = (pathnames) => {
12 | const router = useRouter();
13 | const pageKey = usePageKey();
14 |
15 | const routerRef = useRef(router.pathname);
16 | const pageKeyRef = useRef(pageKey);
17 |
18 | if (pageKeyRef.current === pageKey || pathnames?.includes(router.pathname)) {
19 | pageKeyRef.current = pageKey;
20 | routerRef.current = router;
21 | }
22 |
23 | return routerRef.current;
24 | };
25 |
26 | export default usePageRouter;
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2020 Made With MOXY Lda
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/www/shared/react/testing-library/app-tree/AppTree.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { IntlProvider } from 'react-intl';
4 | import { RouterScrollProvider } from '@moxy/next-router-scroll';
5 | import { LayoutTree } from '@moxy/next-layout';
6 |
7 | export const AppTree = ({ intlProvider, routerScrollProvider, children }) => {
8 | intlProvider = {
9 | locale: 'en-US',
10 | messages: new Proxy({}, {
11 | get: (target, key) => key,
12 | getOwnPropertyDescriptor: () => ({ configurable: true }),
13 | }),
14 | ...intlProvider,
15 | };
16 |
17 | return (
18 |
19 |
20 |
23 |
24 |
25 | );
26 | };
27 |
28 | AppTree.propTypes = {
29 | intlProvider: PropTypes.object,
30 | routerScrollProvider: PropTypes.object,
31 | children: PropTypes.element.isRequired,
32 | };
33 |
34 | export default AppTree;
35 |
--------------------------------------------------------------------------------
/docusaurus/static/recipes-assets/dato-cms/next-with-apollo/next-with-apollo.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render, screen } from '../testing-library';
3 |
4 | beforeEach(() => {
5 | process.env.DATOCMS_TOKEN = 'foo';
6 | });
7 |
8 | afterEach(() => {
9 | jest.resetModules();
10 | });
11 |
12 | const MyComponent = ({ children }) => children;
13 |
14 | it('should render correctly', () => {
15 | const withApollo = require('./next-with-apollo').default;
16 | const MyComponentWithApollo = withApollo(MyComponent);
17 |
18 | render((
19 |
20 |
29 | );
30 |
31 | MainLayout.propTypes = {
32 | children: PropTypes.node,
33 | className: PropTypes.string,
34 | };
35 |
36 | export default MainLayout;
37 |
--------------------------------------------------------------------------------
/www/shared/modules/nprogress/nprogress.js:
--------------------------------------------------------------------------------
1 | import Router from 'next/router'; // eslint-disable-line no-restricted-imports
2 | import NProgress from 'nprogress';
3 |
4 | NProgress.configure({
5 | minimum: 0.2,
6 | speed: 300,
7 | trickleSpeed: 300,
8 | showSpinner: false,
9 | });
10 |
11 | const START_DELAY = 700;
12 |
13 | let refCount = 0;
14 | let startDelayTimeout;
15 |
16 | const start = () => {
17 | refCount += 1;
18 |
19 | if (refCount === 1) {
20 | clearTimeout(startDelayTimeout);
21 | startDelayTimeout = setTimeout(() => NProgress.start(), START_DELAY);
22 | }
23 | };
24 |
25 | const done = () => {
26 | refCount -= 1;
27 |
28 | if (refCount === 0) {
29 | clearTimeout(startDelayTimeout);
30 | NProgress.done();
31 | }
32 | };
33 |
34 | let subscribedToRouter = false;
35 |
36 | const subscribeToRouter = () => {
37 | if (typeof window !== 'undefined' && !subscribedToRouter) {
38 | Router.events.on('routeChangeStart', start);
39 | Router.events.on('routeChangeComplete', done);
40 | Router.events.on('routeChangeError', done);
41 |
42 | subscribedToRouter = true;
43 | }
44 | };
45 |
46 | export default { start, done };
47 | export { subscribeToRouter };
48 |
--------------------------------------------------------------------------------
/docusaurus/docs/what-is-included/everything-else.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: everything-else
3 | title: And everything that comes with Next.js
4 | sidebar_label: And everything from Next.js
5 | ---
6 |
7 | ## File-System Routing
8 |
9 | Next.js comes with a default routing system, which will serve each file in the `/pages` folder with pathname corresponding to the filename. For example, `/pages/some_page.js` would be server at `website.com/some_page`.
10 |
11 | ## Automatic Code Splitting
12 |
13 | Next.js will automatically split pages and common code into separate chunks based on several metrics, improving load time performance.
14 |
15 | ## Server Side Rendering
16 |
17 | Next.js is built to render pages server side, and rehydrate them in the client whenever it's necessary.
18 |
19 | If you're looking for more information about server-side rendering and its benefits, you can read through [this article](https://developers.google.com/web/updates/2019/02/rendering-on-the-web), where rehydration is also covered.
20 |
21 | ## Static Exporting
22 |
23 | Next.js guarantees that static pages also benefit from these features, as well as other things still, like dynamic imports and prefetching.
24 |
25 | ## And more
26 |
27 | Check the [Next.js documentation](https://nextjs.org/#more) for all the features available.
28 |
--------------------------------------------------------------------------------
/www/shared/modules/google-tag-manager/google-tag-manager.js:
--------------------------------------------------------------------------------
1 | const mockIfMissingTrackigId = (fn) => process.env.GTM_CONTAINER_ID ? fn : () => {};
2 |
3 | export const initGTM = mockIfMissingTrackigId(() => {
4 | if (document.getElementById(`gtm-${process.env.GTM_CONTAINER_ID}`)) {
5 | return;
6 | }
7 |
8 | const script = document.createElement('script');
9 |
10 | script.id = `gtm-${process.env.GTM_CONTAINER_ID}`;
11 | script.innerHTML = `(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);})(window,document,'script','dataLayer','${process.env.GTM_CONTAINER_ID}');`;
12 |
13 | document.head.appendChild(script);
14 |
15 | window.dataLayer = [];
16 | });
17 |
18 | export const destroyGTM = mockIfMissingTrackigId(() => {
19 | const script = document.getElementById(`gtm-${process.env.GTM_CONTAINER_ID}`);
20 |
21 | script?.parentNode?.removeChild(script);
22 |
23 | delete window.dataLayer;
24 | });
25 |
26 | export const dataLayer = mockIfMissingTrackigId((dataLayer) => {
27 | window.dataLayer = window.dataLayer ?? [];
28 | window.dataLayer.push(dataLayer);
29 | });
30 |
--------------------------------------------------------------------------------
/www/shared/styles/imports/custom-medias.css:
--------------------------------------------------------------------------------
1 | /* ==========================================================================
2 | Custom media queries - for more information check https://github.com/postcss/postcss-custom-media
3 |
4 | Use it like so: @media (--sm) { ... }
5 | ========================================================================== */
6 |
7 | /* Use these if you are developing for desktop first and want to target lower resolutions */
8 |
9 | @custom-media --lt-xxs (width < 20em); /* 320px */
10 | @custom-media --lt-xs (width < 30em); /* 480px */
11 | @custom-media --lt-sm (width < 48em); /* 768px */
12 | @custom-media --lt-md (width < 64em); /* 1024px */
13 | @custom-media --lt-lg (width < 80em); /* 1280px */
14 | @custom-media --lt-xl (width < 90em); /* 1440px */
15 | @custom-media --lt-xxl (width < 120em); /* 1920px */
16 |
17 | /* Use these if you are developing for mobile first and want to target higher resolutions */
18 |
19 | @custom-media --gte-xxs (width >= 0); /* 0 */
20 | @custom-media --gte-xs (width >= 30em); /* 480px */
21 | @custom-media --gte-sm (width >= 48em); /* 768px */
22 | @custom-media --gte-md (width >= 64em); /* 1024px */
23 | @custom-media --gte-lg (width >= 80em); /* 1280px */
24 | @custom-media --gte-xl (width >= 90em); /* 1440px */
25 | @custom-media --gte-xxl (width >= 120em); /* 1920px */
26 |
--------------------------------------------------------------------------------
/docusaurus/static/recipes-assets/dato-cms/graphql/graphql.js:
--------------------------------------------------------------------------------
1 | import { introspectSchema, makeRemoteExecutableSchema } from 'apollo-server';
2 | import { ApolloServer } from 'apollo-server-micro';
3 | import { setContext } from 'apollo-link-context';
4 | import { HttpLink } from 'apollo-link-http';
5 | import fetch from 'node-fetch';
6 |
7 | const http = new HttpLink({
8 | uri: 'https://graphql.datocms.com/',
9 | fetch,
10 | });
11 |
12 | const link = setContext(() => ({
13 | headers: {
14 | 'Content-Type': 'application/json',
15 | Accept: 'application/json',
16 | Authorization: `Bearer ${process.env.DATOCMS_TOKEN}`,
17 | },
18 | })).concat(http);
19 |
20 | let schema = {};
21 |
22 | const getSchema = async () => {
23 | try {
24 | schema = await introspectSchema(link);
25 | } catch (error) {
26 | console.log(error);
27 | }
28 |
29 | const executableSchema = makeRemoteExecutableSchema({
30 | schema,
31 | link,
32 | });
33 |
34 | return executableSchema;
35 | };
36 |
37 | const apolloServer = new ApolloServer({
38 | schema: getSchema(),
39 | introspection: true,
40 | playground: {
41 | settings: {
42 | 'schema.polling.enable': false,
43 | },
44 | },
45 | });
46 |
47 | export default apolloServer.createHandler({ path: '/api/graphql' });
48 |
--------------------------------------------------------------------------------
/docusaurus/docs/what-is-included/top-progress-bar.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: top-progress-bar
3 | title: Top progress bar
4 | sidebar_label: Top progress bar
5 | ---
6 |
7 | A very subtle progress bar is displayed when Next.js is performing route changes, which can take some time depending on how fast the page assets and data are loaded (via `getInitialProps`, `getServerSideProps` or `getStaticProps` with `revalidate`).
8 |
9 | We use the [`NProgress`](https://github.com/rstacruz/nprogress) library for the heavy-lifting of the progress bar UI. Under `shared/modules/nprogress`, you will find a `NProgress` sub-facade that:
10 |
11 | - Has `start()` and `done()` methods that are ref-count aware, meaning you can call them several times around your code-base; only the first start call and last done call will be accounted for to show / hide the progress bar.
12 | - Adds a built-in start delay, so that the progress bar is not shown right away.
13 |
14 | ## Removing this feature
15 |
16 | If you are going to have another loading solution for Next.js page transitions, follow these steps:
17 |
18 | 1. Uninstall `nprogress`.
19 | 2. Delete `shared/modules/nprogress` folder and its import & usage from `app/App.js`.
20 | 3. Delete `shared/styles/global/nprogress.css` and its import from `shared/styles/global/index.css`.
21 | 4. Remove the `--z-index-nprogress` CSS variable from `shared/styles/imports/variables.css`.
22 |
--------------------------------------------------------------------------------
/intl/en-US.json5:
--------------------------------------------------------------------------------
1 | {
2 | // Base SEO used for all pages.
3 | "seo.title": "{project-name}",
4 | "seo.description": "{project-description}",
5 | "seo.keywords": "{project-keyword-1}, {project-keyword-2}",
6 |
7 | // Home page.
8 | "home.title": "Homepage",
9 |
10 | // Error page.
11 | "error.internal.title": "An error has occurred",
12 | "error.not-found.title": "Page not found",
13 | "error.return-to-home": "Return to home",
14 |
15 | // Contacts page.
16 | "contacts.seo.title": "Contacts",
17 | "contacts.seo.description": "Know how you can contact us",
18 | "contacts.seo.keywords": "contact, email, telephone, telephone number, phone, phone number, address",
19 | "contacts.title": "Contacts",
20 | "contacts.name": "Name: { name }",
21 | "contacts.email": "Email: { email }",
22 |
23 | // Terms page.
24 | "terms.seo.title": "Terms and Conditions",
25 | "terms.seo.description": "Read the Terms and Conditions before using this website",
26 | "terms.seo.keywords": "terms, conditions, read",
27 | "terms.title": "Terms and Conditions",
28 |
29 | // CookieBanner component.
30 | "cookie-banner.text": "We use cookies to personalize content and to collect analytics to improve our website. Please review our terms and conditions for more information.",
31 | "cookie-banner.accept": "Accept",
32 | "cookie-banner.reject": "Reject"
33 | }
34 |
--------------------------------------------------------------------------------
/docusaurus/docs/what-is-included/customizable-page-transitions.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: customizable-page-transitions
3 | title: Customizable page transitions
4 | sidebar_label: Customizable page transitions
5 | ---
6 |
7 | The boilerplate includes [`@moxy/react-page-swapper`](https://github.com/moxystudio/react-page-swapper) which eases out the implementation of page transitions.
8 |
9 | A simple `fade` transition built with `` is included as an example on how you can orchestrate your custom page transitions.
10 |
11 | The `PageSwapper` component also integrates with [`@moxy/next-router-scroll`](https://github.com/moxystudio/next-router-scroll) to handle scroll restoration for you.
12 |
13 | For more details about all the possibilities `@moxy/react-page-swapper` gives you to customize your page transitions, check the [docs](https://github.com/moxystudio/react-page-swapper/blob/master/README.md).
14 |
15 | ## Removing this feature
16 |
17 | If you don't need to include custom transitions to your pages you may want to remove this feature altogether. To do so, take the following steps:
18 |
19 | 1. Uninstall `@moxy/react-page-swapper`, `@moxy/next-router-scroll` and `react-transition-group`.
20 | 2. Delete the folder `www/react/page-swapper`.
21 | 3. Search globally for `react-page-swapper` and `@moxy/next-router-scroll` and remove the corresponding code across the project.
22 | 4. Update your unit tests if necessary so that they all pass!
23 |
--------------------------------------------------------------------------------
/www/shared/styles/global/typography.css:
--------------------------------------------------------------------------------
1 | @import "../imports";
2 |
3 | /* ==========================================================================
4 | Typography Defaults
5 | ========================================================================== */
6 |
7 | html {
8 | font-size: 62.5%; /* 62.5% so that all the REM measurements re based on 10px sizing */
9 | }
10 |
11 | body {
12 | @mixin typography-body-1;
13 | @mixin text-smooth;
14 | @mixin text-wrap;
15 |
16 | color: var(--color-black);
17 | -webkit-tap-highlight-color: transparent; /* Do not show a highlight (rectangle around the link) while tapping it */
18 | }
19 |
20 | /* ==========================================================================
21 | Anchors
22 | ========================================================================== */
23 |
24 | a {
25 | color: inherit;
26 | }
27 |
28 | /* ==========================================================================
29 | Headings
30 | ========================================================================== */
31 |
32 | h1,
33 | h2,
34 | h3,
35 | h4,
36 | h5,
37 | h6,
38 | p {
39 | margin: 0;
40 | }
41 |
42 | h1 {
43 | @mixin typography-h1;
44 | }
45 |
46 | h2 {
47 | @mixin typography-h2;
48 | }
49 |
50 | h3 {
51 | @mixin typography-h3;
52 | }
53 |
54 | h4 {
55 | @mixin typography-h4;
56 | }
57 |
58 | h5 {
59 | @mixin typography-h5;
60 | }
61 |
62 | h6 {
63 | @mixin typography-h6;
64 | }
65 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Next.js with MOXY
4 |
5 | [![Build Status][build-status-image]][build-status-url] [![Coverage Status][codecov-image]][codecov-url] [![Dependency status][david-dm-image]][david-dm-url] [![Dev Dependency status][david-dm-dev-image]][david-dm-dev-url]
6 |
7 | [build-status-url]:https://github.com/moxystudio/next-with-moxy/actions
8 | [build-status-image]:https://img.shields.io/github/workflow/status/moxystudio/next-with-moxy/Node%20CI/master
9 | [codecov-url]:https://codecov.io/gh/moxystudio/next-with-moxy
10 | [codecov-image]:https://img.shields.io/codecov/c/github/moxystudio/next-with-moxy/master.svg
11 | [david-dm-url]:https://david-dm.org/moxystudio/next-with-moxy
12 | [david-dm-image]:https://img.shields.io/david/moxystudio/next-with-moxy.svg
13 | [david-dm-dev-url]:https://david-dm.org/moxystudio/next-with-moxy?type=dev
14 | [david-dm-dev-image]:https://img.shields.io/david/dev/moxystudio/next-with-moxy.svg
15 |
16 | MOXY's boilerplate to accelerate the setup of new [Next.js](https://nextjs.org/) based web applications.
17 |
18 | You may preview the boilerplate at https://next-with-moxy.vercel.app.
19 |
20 | ## Documentation
21 |
22 | Please check out the documentation page at https://next-with.moxy.tech.
23 |
24 | To view the documentation locally, you may run:
25 |
26 | ```bash
27 | npm i --prefix docusaurus
28 | npm run docs
29 | ```
30 |
31 | ## License
32 |
33 | [MIT License](./LICENSE.md)
34 |
--------------------------------------------------------------------------------
/docusaurus/static/img/logo-moxy.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/www/pages/home/Home.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { FormattedMessage } from 'react-intl';
3 | import { getIntlProps } from '@moxy/next-intl';
4 | import { Container } from '../../shared/react/grid';
5 |
6 | import styles from './Home.module.css';
7 |
8 | const Home = () => (
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
typography-h1
17 |
typography-h2
18 |
typography-h3
19 |
typography-h4
20 |
typography-h5
21 |
typography-h6
22 |
23 |
typography-subtitle-1
24 |
typography-subtitle-2
25 |
typography-body-1
26 |
typography-body-2
27 |
typography-text-button
28 |
typography-caption
29 |
typography-overline
30 |
31 |
32 |
33 | );
34 |
35 | export const getStaticProps = async ({ locale }) => ({
36 | props: await getIntlProps(locale),
37 | });
38 |
39 | export default Home;
40 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Dockerfile that produce Lightweight runtime images
2 | # Inspired by https://github.com/zeit/next.js/issues/121#issuecomment-541399420
3 |
4 | # -- BASE STAGE --------------------------------
5 |
6 | FROM node:16-alpine AS base
7 |
8 | WORKDIR /src
9 |
10 | COPY package*.json ./
11 | RUN npm ci --no-audit
12 |
13 | # -- CHECK STAGE --------------------------------
14 |
15 | FROM base AS check
16 |
17 | ARG CI
18 | ENV CI $CI
19 |
20 | COPY . .
21 | RUN npm run lint
22 | RUN npm test
23 |
24 | # -- BUILD STAGE --------------------------------
25 |
26 | FROM base AS build
27 |
28 | # Define build arguments & map them to environment variables
29 | ARG GTM_CONTAINER_ID
30 | ENV GTM_CONTAINER_ID $GTM_CONTAINER_ID
31 | ARG SITE_URL
32 | ENV SITE_URL $SITE_URL
33 |
34 | # Build the project and then dispose files not necessary to run the project
35 | # This will make the runtime image as small as possible
36 | COPY . .
37 | RUN npx next telemetry disable > /dev/null
38 | RUN npm run build
39 | RUN npm prune --production --no-audit
40 | RUN rm -rf .next/cache
41 |
42 | # -- RUNTIME STAGE --------------------------------
43 |
44 | FROM node:16-alpine AS runtime
45 |
46 | WORKDIR /usr/app
47 |
48 | COPY --from=build /src/package.json ./package.json
49 | COPY --from=build /src/node_modules ./node_modules
50 | COPY --from=build /src/.next ./.next
51 | COPY --from=build /src/intl/index.js ./intl/index.js
52 | COPY --from=build /src/public ./public
53 | COPY --from=build /src/next.config.js ./next.config.js
54 |
55 | ENV PORT 3000
56 | EXPOSE $PORT
57 | CMD npm start -- -p $PORT
58 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "env": {
4 | "browser": true,
5 | "node": true
6 | },
7 | "extends": [
8 | "@moxy/eslint-config-base/esm",
9 | "@moxy/eslint-config-babel",
10 | "@moxy/eslint-config-react",
11 | "@moxy/eslint-config-react-web-a11y",
12 | "@moxy/eslint-config-jest"
13 | ],
14 | "rules": {
15 | // Next.js component doesn't play nice with anchor validation.
16 | "jsx-a11y/anchor-is-valid": 0,
17 | // Disallow imports as we have built-in replacements for this project.
18 | "no-restricted-imports": ["error", {
19 | "paths": [
20 | {
21 | "name": "@moxy/react-page-swapper",
22 | "message": "Please import from 'shared/react/page-swapper' instead."
23 | },
24 | {
25 | "name": "next/router",
26 | "importNames": ["useRouter", "withRouter"],
27 | "message": "You most likely want to import 'usePageRouter' or 'withPageRouter' from 'shared/react/next-router' as it preservers the router between page transitions."
28 | },
29 | {
30 | "name": "@testing-library/react",
31 | "message": "Please import from 'shared/react/testing-library' instead."
32 | },
33 | {
34 | "name": "@testing-library/user-event",
35 | "message": "Please import from 'shared/react/testing-library' instead."
36 | }
37 | ]
38 | }]
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/docusaurus/docs/what-is-included/webpack-file-loaders.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: webpack-file-loaders
3 | title: Webpack loaders for common files
4 | sidebar_label: Webpack file loaders
5 | ---
6 |
7 | The boilerplate includes [`@moxy/next-common-files`](https://github.com/moxystudio/next-common-files), a Next.js plugin that exports webpack loader rules for common files like images and fonts.
8 |
9 | This plugin comes with options that give the developer some choice about how their files are loaded, and this boilerplate uses certain **filename conventions** to specify how certain files are loaded into the web application.
10 | We implement these filename conventions through suffixes that we add to the filename itself.
11 |
12 | Here's the list of conventions:
13 |
14 | - `.data-url.`
15 |
16 | This suffix is used when you want a file to be translated into base64 and sent with your bundle instead of being loaded with a standard URL (e.g., for assets above the fold, you might want to use this to avoid [flashing of unstyled content](https://en.wikipedia.org/wiki/Flash_of_unstyled_content)).
17 |
18 | > ⚠️ Be aware that base64 encoding increases file sizes by roughly 33%.
19 |
20 | - `.inline.`
21 |
22 | This suffix is used when you want your `.svg` files to be put as inline HTML on your pages.
23 |
24 | > ℹ️ Support is limited to `.svg` files for now.
25 |
26 | - No suffix
27 |
28 | When none of those suffixes are used, the files will be loaded as URL.
29 |
30 | Keep in mind that though these are preset conventions, they can be changed.
31 | Should you want to change them or implement more of these filename suffixes still, you can do so in the `next.config.js` file.
32 |
--------------------------------------------------------------------------------
/www/shared/react/page-swapper/page-transition/PageTransition.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { CSSTransition } from 'react-transition-group';
4 | import classNames from 'classnames';
5 |
6 | import styles from './PageTransition.module.css';
7 |
8 | // This function might have to be adjusted if you have more than you transition property on animations.
9 | const addEndListener = (node, done) => node.addEventListener('transitionend', done);
10 |
11 | const PageTransition = ({ children, animation, style, in: inProp, onEntered, onExited, className }) => (
12 |
26 |
50 | Hello World
51 | );
52 |
53 | const div = container.querySelector('div');
54 |
55 | expect(div.classList.contains('col-offset-xxs-1')).toBe(true);
56 | expect(div.classList.contains('col-offset-xs-2')).toBe(true);
57 | });
58 |
--------------------------------------------------------------------------------
/docusaurus/docs/what-is-included/docker.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: docker
3 | title: Docker
4 | sidebar_label: Docker
5 | ---
6 |
7 | We provide a `Dockerfile` so that you can easily use [**Docker**](https://www.docker.com/) to run the application you're building in a containerized environment.
8 |
9 | To run this app in Docker, you can use the following commands from the project's folder:
10 |
11 | ```bash
12 | docker build -t {project-name}:latest .
13 | docker run --name={project-name} -p 3000:3000 --restart=unless-stopped -d {project-name}:latest
14 | ```
15 |
16 | Afterwards you'll be able to find your project running at [http://localhost:3000](http://localhost:3000).
17 |
18 | This `Dockerfile` is also how you can pass environment variables to your Docker container running this application. In the `Dockerfile`, you do this with two steps:
19 |
20 | - Define an `ARG` for the variable to be received.
21 | - Pass that `ARG` into an `ENV` argument that will exist during the build process as well as the server-runtime process.
22 |
23 | Example:
24 |
25 | ```bash
26 | # Define build arguments & map them to environment variables
27 | ARG SOME_ARGUMENT
28 | ENV SOME_ENV_VAR $SOME_ARGUMENT
29 | ```
30 |
31 | You can check the existing `Dockerfile` to see how we're configuring `GTM_CONTAINER_ID` and `SITE_URL`.
32 |
33 | ## Multi-stage builds
34 |
35 | The Dockerfile we provide allows for [multi-stage builds](https://docs.docker.com/develop/develop-images/multistage-build/), with the following benefits:
36 |
37 | - The final runtime image is much lighter because it doesn't contain dev dependencies and other intermediate artifacts produced during the build phase.
38 | - It allows to target a specific stage when building, which is useful to create a CI/CD pipeline. As an example, you may run the project tests by specifying `--target check` when calling `docker build`.
39 |
40 | > ℹ️ You may leverage Docker [BuildKit](https://docs.docker.com/develop/develop-images/build_enhancements/) to [skip stages](https://github.com/docker/cli/issues/1134#issuecomment-399005853) that are not needed for the specified target. To do so, set DOCKER_BUILDKIT=1 when running `docker build`.
41 |
--------------------------------------------------------------------------------
/docusaurus/docs/what-is-included/sitemap.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: sitemap-robots
3 | title: Sitemap & robots.txt
4 | sidebar_label: Sitemap & robots.txt
5 | ---
6 |
7 | The boilerplate includes a dynamic sitemap generator and a `robots.txt` file. Don't really know what a sitemap is? Check [this](https://support.google.com/webmasters/answer/156184?hl=en) out. Having a sitemap will help search engines and their crawlers index your whole website, including static and dynamic URL's, giving you better results in SEO ranks. [`@moxy/next-sitemaps`](https://www.npmjs.com/package/@moxy/next-sitemaps) helps make this a very simple task.
8 |
9 | To get started, the only thing you have to do is to specify the dynamic routes, if you have any, in `/pages/api/sitemap.xml.js`. Take a look at the step 2 of the usage section of the [docs](https://www.npmjs.com/package/@moxy/next-sitemaps#usage) to get a feel for it. You don't need to worry about static routes, as they will already be taken care of by the package.
10 |
11 | Also, while optional, it's recommended you check out the `cacheControl` option prop of the package [API](https://www.npmjs.com/package/@moxy/next-sitemaps#api), in order to optimize the creation of your sitemap.
12 |
13 | Access your sitemap at any time, at the `/api/sitemap.xml` route, to check if everything is according to plan. If you see a xml response with all your desired URL's, you're just about ready to go up in those SEO ranks!
14 |
15 | > ℹ️ By default, the rules in the provided `robots.txt` make no restriction for any crawlers or pages, but you can tweak this to your liking in `/public/robots.txt`. Check the guidelines [here](https://support.google.com/webmasters/answer/6062596?hl=en). Also, you don't need to worry about the path to the sitemap, as `@moxy/next-sitemaps` will deal with it for both staging and production environments.
16 |
17 | ## Removing this feature
18 |
19 | If you're not really into search engines indexing your pages, feel free to remove this feature, following these steps:
20 |
21 | 1. Uninstall `@moxy/next-sitemap`.
22 | 2. Remove all the `withSitemap` related code in `next.config.js`
23 | 3. Delete `pages/api/sitemap.xml.js` and `public/robots.txt`.
--------------------------------------------------------------------------------
/www/shared/react/grid/Row.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render, screen } from '../testing-library';
3 | import Row from './Row';
4 |
5 | it('should render correctly', () => {
6 | render(Hello World);
7 |
8 | screen.getByText((content, element) => element.tagName.toLowerCase() === 'div' && content.startsWith('Hello'));
9 | });
10 |
11 | it('should render with passed component', () => {
12 | render(Hello World);
13 |
14 | screen.getByText((content, element) => element.tagName.toLowerCase() === 'span' && content.startsWith('Hello'));
15 | });
16 |
17 | it('should respect passed className', () => {
18 | const { container } = render(Hello World);
19 |
20 | expect(container.querySelector('.foo')).toBeInTheDocument();
21 | });
22 |
23 | it('should respect passed justifyContent (string)', () => {
24 | const { container } = render(Hello World);
25 |
26 | expect(container.querySelector('div').classList.contains('row-justify-content-xxs-flex-end')).toBe(true);
27 | });
28 |
29 | it('should respect passed justifyContent (object)', () => {
30 | const { container } = render(
31 |
32 | Hello World
33 | );
34 |
35 | const div = container.querySelector('div');
36 |
37 | expect(div.classList.contains('row-justify-content-xxs-flex-end')).toBe(true);
38 | expect(div.classList.contains('row-justify-content-xs-flex-start')).toBe(true);
39 | });
40 |
41 | it('should respect passed alignItems (string)', () => {
42 | const { container } = render(Hello World);
43 |
44 | expect(container.querySelector('div').classList.contains('row-align-items-xxs-flex-end')).toBe(true);
45 | });
46 |
47 | it('should respect passed alignItems (object)', () => {
48 | const { container } = render(
49 |
50 | Hello World
51 | );
52 |
53 | const div = container.querySelector('div');
54 |
55 | expect(div.classList.contains('row-align-items-xxs-flex-end')).toBe(true);
56 | expect(div.classList.contains('row-align-items-xs-flex-start')).toBe(true);
57 | });
58 |
--------------------------------------------------------------------------------
/docusaurus/docs/recipes/inner-vh.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: inner-vh
3 | title: Proper vh CSS unit on Mobile
4 | sidebar_label: Inner vh CSS
5 | ---
6 |
7 | Some mobile browsers define a fixed value for the vh based on the maximum height of the screen. By doing so, the user would not experience jumps on the page once the address bar went out of view. However, in some situations, you want to to target the visible viewport (inner), regardless of the address bar being visible or not.
8 |
9 | To address the issue, this recipe allows you to target the inner viewport height using the `--inner-vh` CSS variable.
10 |
11 | You may read more at about this subject in the following article: https://css-tricks.com/the-trick-to-viewport-units-on-mobile/.
12 |
13 | ## Walk-through
14 |
15 | ### 1. Install the necessary dependencies
16 |
17 | ```sh
18 | $ npm i inner-vh detect-it
19 | ```
20 |
21 | ### 2. Setup `inner-vh` module
22 |
23 | ```js
24 | // www/app/App.js
25 | import { innerVh } from 'inner-vh';
26 | import { deviceType } from 'detect-it';
27 |
28 | // ...
29 |
30 | if (typeof window !== 'undefined') {
31 | // Apply module that updates the --inner-vh CSS variable.
32 | innerVh({
33 | customPropertyName: 'inner-vh',
34 | // Update --inner-vh on desktop alike always.
35 | ignoreCollapsibleUi: deviceType === 'touchOnly',
36 | // Seems to be 114px on iOS safari.
37 | maximumCollapsibleUiHeight: 120,
38 | });
39 | }
40 |
41 | // ...
42 | ```
43 |
44 | ### 3. Setup PostCSS to ignore `--inner-vh`
45 |
46 | ```js
47 | 'use strict';
48 |
49 | module.exports = require('@moxy/postcss-preset')({
50 | // ...
51 | cssVariables: {
52 | preserveAtRulesOrder: true,
53 | preserve: (declaration) => {
54 | // Keep --inner-vh usage intact, useful for mobile.
55 | if (!declaration.prop.startsWith('--') && declaration.value.includes('--inner-vh')) {
56 | return true;
57 | }
58 |
59 | return false;
60 | },
61 | },
62 | });
63 | ```
64 |
65 | ### 4. Now use it!
66 |
67 | ```css
68 | .foo {
69 | height: calc(var(--inner-vh, 1vh) * 100);
70 | }
71 | ```
72 |
73 | > ℹ️ The second argument of var is the fallback, which should be `1vh`. This is necessary, because the CSS var is only injected after the module is initialized.
74 |
--------------------------------------------------------------------------------
/docusaurus/docs/what-is-included/grid-system.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: grid-system
3 | title: Grid system
4 | sidebar_label: Grid system
5 | ---
6 |
7 | The boilerplate comes with a standardized Grid system to help you position elements on the screen, based on CSS flex box. It has the following specs:
8 |
9 | | Breakpoint | Columns | Gutters | Width | Max Width |
10 | | ---------- | ------- | ------- | ----- | --------- |
11 | | xxs (≥0) | 4 | 16px | 100% - 20px * 2 | none |
12 | | xs (≥480) | 4 | 16px | 100% - 20px * 2 | 660px |
13 | | sm (≥768) | 8 | 24px | 85% | none |
14 | | md (≥1024) | 12 | 24px | 85% | 1024px |
15 | | lg (≥1280) | 12 | 32px | 85% | none |
16 | | xl (≥1440) | 12 | 32px | 1200px | none |
17 | | xxl (≥1920) | 12 | 32px | 1200px | none |
18 |
19 | > ℹ️ Please note that the Grid has no outside gutters.
20 |
21 | ## Using the Grid
22 |
23 | To use the grid, import `Container`, `Row` and `Col` components and use them like so:
24 |
25 | ```js
26 | import { Container, Row, Col } from '../../shared/react/grid';
27 |
28 | const MyPage = () => (
29 |
30 |
31 |
32 |