├── .babelrc
├── .dockerignore
├── .editorconfig
├── .eslintignore
├── .eslintrc.json
├── .flowconfig
├── .gitignore
├── .nvmrc
├── .prettierignore
├── .prettierrc
├── .vscode
└── launch.json
├── CHANGELOG.md
├── ISSUE_TEMPLATE.md
├── LICENSE.md
├── README.md
├── assets
├── Paypal-button.png
├── Paypal-button@2x.png
└── Paypal-button@3x.png
├── components
├── header
│ ├── Header.js
│ └── index.js
├── layout
│ ├── Layout.js
│ └── index.js
└── privateRoute
│ ├── PrivateRoute.js
│ └── index.js
├── config
├── appConfig.js
└── theme.js
├── docs
├── _next
│ └── static
│ │ ├── Q0JVIO_wUTVKoda1tiL4i
│ │ └── pages
│ │ │ ├── _app.js
│ │ │ ├── _error.js
│ │ │ ├── index.js
│ │ │ ├── login.js
│ │ │ ├── page1.js
│ │ │ └── private1.js
│ │ ├── chunks
│ │ ├── 0.js
│ │ ├── 0.js.map
│ │ └── commons.b192840d4295db06bd92.js
│ │ ├── development
│ │ ├── dll
│ │ │ └── dll_0a5735af42686c2a38b2.js
│ │ └── pages
│ │ │ ├── _app.js
│ │ │ ├── _app.js.map
│ │ │ ├── _error.js
│ │ │ ├── _error.js.map
│ │ │ ├── index.js
│ │ │ ├── index.js.map
│ │ │ ├── login.js
│ │ │ ├── login.js.map
│ │ │ ├── page1.js
│ │ │ ├── page1.js.map
│ │ │ ├── private1.js
│ │ │ └── private1.js.map
│ │ ├── runtime
│ │ ├── main-4888d1fc3b6b523c2e7d.js
│ │ ├── main.js
│ │ ├── main.js.map
│ │ ├── webpack-42652fa8b82c329c0559.js
│ │ ├── webpack.js
│ │ └── webpack.js.map
│ │ └── webpack
│ │ ├── 2b67f5f537bd59709cfa.hot-update.json
│ │ ├── 80544a9968c67138357d.hot-update.json
│ │ ├── ae14abc119ea9d1e3a5a.hot-update.json
│ │ └── b0dd31550cc436381fd4.hot-update.json
├── index.html
├── login
│ └── index.html
├── page1
│ └── index.html
├── private1
│ └── index.html
├── service-worker.js
└── static
│ ├── android-chrome-192x192.png
│ ├── android-chrome-512x512.png
│ ├── apple-touch-icon.png
│ ├── browserconfig.xml
│ ├── css
│ └── bootstrap.min.css
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── manifest.json
│ ├── mstile-150x150.png
│ ├── robot.txt
│ ├── safari-pinned-tab.svg
│ └── splashscreen-icon-512x512.png
├── flow-typed
└── npm
│ ├── babel-plugin-transform-async-to-generator_vx.x.x.js
│ ├── babel-plugin-transform-regenerator_vx.x.x.js
│ ├── babel-preset-es2015_vx.x.x.js
│ ├── babel-preset-flow_vx.x.x.js
│ ├── babel-preset-react_vx.x.x.js
│ ├── babel-preset-stage-2_vx.x.x.js
│ ├── eslint-plugin-flowtype_vx.x.x.js
│ ├── eslint-plugin-react_vx.x.x.js
│ ├── eslint_vx.x.x.js
│ ├── flow-bin_v0.x.x.js
│ └── next_vx.x.x.js
├── lighthouse-0.1.1.png
├── lighthouse-test.png
├── mock
├── fakeAPI.json
└── userInfosMock.json
├── next.config.js
├── out
├── 404.html
├── _next
│ └── static
│ │ ├── 8Ch3BaHLClZes2TshMjtO
│ │ └── pages
│ │ │ ├── _app.js
│ │ │ ├── _error.js
│ │ │ ├── index.js
│ │ │ ├── login.js
│ │ │ ├── page1.js
│ │ │ └── private1.js
│ │ ├── E8A3lEOVgy6tVT4xFsF3A
│ │ └── pages
│ │ │ ├── _app.js
│ │ │ ├── _error.js
│ │ │ ├── index.js
│ │ │ ├── login.js
│ │ │ ├── page1.js
│ │ │ └── private1.js
│ │ ├── ETV0yduCoHZQSRvXa1kES
│ │ └── pages
│ │ │ ├── _app.js
│ │ │ ├── _error.js
│ │ │ ├── dynamicPage
│ │ │ └── [counter].js
│ │ │ ├── index.js
│ │ │ ├── login.js
│ │ │ ├── page1.js
│ │ │ └── private1.js
│ │ ├── Jk9jgxKuHpiIQbnW1eVrf
│ │ └── pages
│ │ │ ├── _app.js
│ │ │ ├── _error.js
│ │ │ ├── dynamicPage
│ │ │ └── [counter].js
│ │ │ ├── index.js
│ │ │ ├── login.js
│ │ │ ├── page1.js
│ │ │ └── private1.js
│ │ ├── OZL-5jOmfz85RjoqpvO_b
│ │ └── pages
│ │ │ ├── _app.js
│ │ │ ├── _error.js
│ │ │ ├── dynamicPage
│ │ │ └── [counter].js
│ │ │ ├── index.js
│ │ │ ├── login.js
│ │ │ ├── page1.js
│ │ │ └── private1.js
│ │ ├── USOnSTBlp3bpEJu1grJrb
│ │ └── pages
│ │ │ ├── _app.js
│ │ │ ├── _error.js
│ │ │ ├── dynamicPage
│ │ │ └── [counter].js
│ │ │ ├── index.js
│ │ │ ├── login.js
│ │ │ ├── page1.js
│ │ │ └── private1.js
│ │ ├── ZTxuxVox1tjFqEKeVXToj
│ │ └── pages
│ │ │ ├── _app.js
│ │ │ ├── _error.js
│ │ │ ├── dynamicPage
│ │ │ └── [counter].js
│ │ │ ├── index.js
│ │ │ ├── login.js
│ │ │ ├── page1.js
│ │ │ └── private1.js
│ │ ├── cHnXSbNTyPGpdBtPjBW9K
│ │ └── pages
│ │ │ ├── _app.js
│ │ │ ├── _error.js
│ │ │ ├── dynamicPage
│ │ │ └── [counter].js
│ │ │ ├── index.js
│ │ │ ├── login.js
│ │ │ ├── page1.js
│ │ │ └── private1.js
│ │ ├── chunks
│ │ ├── 0.js
│ │ ├── 0.js.map
│ │ └── commons.cbbc5cda5d9c1e5f29c2.js
│ │ ├── development
│ │ ├── dll
│ │ │ ├── dll_b9dcb1e8da809739c7e4.js
│ │ │ └── dll_b9dcb1e8da809739c7e4.js.map
│ │ └── pages
│ │ │ ├── _app.js
│ │ │ ├── _app.js.map
│ │ │ ├── _error.js
│ │ │ ├── _error.js.map
│ │ │ ├── index.js
│ │ │ ├── index.js.map
│ │ │ ├── login.js
│ │ │ ├── login.js.map
│ │ │ ├── page1.js
│ │ │ └── page1.js.map
│ │ ├── o3_mrJnwnQsdrtluok-i1
│ │ └── pages
│ │ │ ├── _app.js
│ │ │ ├── _error.js
│ │ │ ├── dynamicPage
│ │ │ └── [counter].js
│ │ │ ├── index.js
│ │ │ ├── login.js
│ │ │ ├── page1.js
│ │ │ └── private1.js
│ │ ├── pg_jSjSEGmeN3hq7f8YoC
│ │ └── pages
│ │ │ ├── _app.js
│ │ │ ├── _error.js
│ │ │ ├── dynamicPage
│ │ │ └── [counter].js
│ │ │ ├── index.js
│ │ │ ├── login.js
│ │ │ ├── page1.js
│ │ │ └── private1.js
│ │ ├── runtime
│ │ ├── amp.js
│ │ ├── amp.js.map
│ │ ├── main-5676d46b107b105eec22.js
│ │ ├── main.js
│ │ ├── main.js.map
│ │ ├── webpack-3df6523e264ff2ac6548.js
│ │ ├── webpack.js
│ │ └── webpack.js.map
│ │ ├── uqEdtzFbZBMDEf_37_jDb
│ │ └── pages
│ │ │ ├── _app.js
│ │ │ ├── _error.js
│ │ │ ├── index.js
│ │ │ ├── login.js
│ │ │ ├── page1.js
│ │ │ └── private1.js
│ │ └── webpack
│ │ ├── 712641909e13af060bf1.hot-update.json
│ │ ├── cf4f576164d133d12b4a.hot-update.json
│ │ └── fee1c80e30f9756052df.hot-update.json
├── dynamicPage
│ └── [counter].html
├── index.html
├── login.html
├── page1.html
├── private1.html
├── service-worker.js
└── static
│ ├── android-chrome-192x192.png
│ ├── android-chrome-512x512.png
│ ├── apple-touch-icon.png
│ ├── browserconfig.xml
│ ├── css
│ └── bootstrap.min.css
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── manifest.json
│ ├── mstile-150x150.png
│ ├── robot.txt
│ ├── safari-pinned-tab.svg
│ └── splashscreen-icon-512x512.png
├── package.json
├── pages
├── _app.js
├── _document.js
├── _error.js
├── dynamicPage
│ └── [counter].js
├── index.js
├── login.js
├── page1.js
└── private1.js
├── preview.png
├── redux
├── middleware
│ └── fetchMiddleware.js
├── modules
│ ├── fakeModuleWithFetch.js
│ ├── persistStore.js
│ ├── reducers.js
│ └── userAuth.js
└── store
│ ├── configureStore.dev.js
│ ├── configureStore.js
│ └── configureStore.prod.js
├── server
├── index.js
└── static.js
├── static
├── android-chrome-192x192.png
├── android-chrome-512x512.png
├── apple-touch-icon.png
├── browserconfig.xml
├── css
│ └── bootstrap.min.css
├── favicon-16x16.png
├── favicon-32x32.png
├── manifest.json
├── mstile-150x150.png
├── robot.txt
├── safari-pinned-tab.svg
└── splashscreen-icon-512x512.png
├── style
└── globalStyle.js
├── types
├── nextjs
│ └── index.js
└── redux
│ ├── modules
│ ├── fakeModuleWithFetch
│ │ └── index.js
│ └── userAuth
│ │ └── index.js
│ └── redux-thunk
│ └── index.js
├── typings.json
├── typings
├── globals
│ ├── axios
│ │ ├── index.d.ts
│ │ └── typings.json
│ ├── bootstrap
│ │ ├── index.d.ts
│ │ └── typings.json
│ ├── classnames
│ │ ├── index.d.ts
│ │ └── typings.json
│ ├── localforage
│ │ ├── index.d.ts
│ │ └── typings.json
│ ├── modernizr
│ │ ├── index.d.ts
│ │ └── typings.json
│ ├── popper.js
│ │ ├── index.d.ts
│ │ └── typings.json
│ ├── react-ga
│ │ ├── index.d.ts
│ │ └── typings.json
│ ├── recompose
│ │ ├── index.d.ts
│ │ └── typings.json
│ ├── redux-thunk
│ │ ├── index.d.ts
│ │ └── typings.json
│ ├── redux
│ │ ├── index.d.ts
│ │ └── typings.json
│ └── reselect
│ │ ├── index.d.ts
│ │ └── typings.json
└── index.d.ts
├── utils
├── auth
│ └── index.js
├── fetchTools
│ └── index.js
└── sw
│ └── index.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["@babel/preset-env", {
4 | "modules": false,
5 | "targets": "> 0.25%, not dead"
6 | }],
7 | "@babel/preset-react",
8 | "next/babel",
9 | "@babel/preset-flow"
10 | ],
11 | "plugins": [
12 | "@babel/plugin-proposal-class-properties",
13 | "@babel/plugin-proposal-object-rest-spread",
14 | "@babel/plugin-transform-regenerator",
15 | "@babel/plugin-transform-async-to-generator",
16 | "@babel/plugin-syntax-dynamic-import"
17 | ],
18 | "env": {
19 | "test": {
20 | "presets": [
21 | "@babel/preset-env",
22 | "@babel/preset-react",
23 | "next/babel",
24 | "@babel/preset-flow"
25 | ],
26 | "plugins": [
27 | "@babel/plugin-transform-modules-commonjs",
28 | "@babel/plugin-proposal-class-properties",
29 | "@babel/plugin-proposal-object-rest-spread",
30 | "@babel/plugin-transform-regenerator",
31 | "@babel/plugin-transform-async-to-generator",
32 | "@babel/plugin-syntax-dynamic-import"
33 | ]
34 | },
35 | "production": {
36 | "plugins": ["transform-remove-console"],
37 | "comments": false
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .git
3 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | coverage/*
3 | .next/*
4 | .vscode/*
5 |
--------------------------------------------------------------------------------
/.flowconfig:
--------------------------------------------------------------------------------
1 | [ignore]
2 |
3 | [include]
4 |
5 | [libs]
6 |
7 | [lints]
8 |
9 | [options]
10 | module.file_ext=.css
11 | module.name_mapper='.*\(.css\)' -> 'CSSModule'
12 | module.system=haste
13 | suppress_comment= \\(.\\|\n\\)*\\$FlowIgnore
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OS X
2 | .DS_Store*
3 | Icon?
4 | ._*
5 |
6 | # Windows
7 | Thumbs.db
8 | ehthumbs.db
9 | Desktop.ini
10 |
11 | # Linux
12 | .directory
13 | *~
14 |
15 | # npm
16 | node_modules
17 | *.log
18 | *.gz
19 |
20 | # intelliJ
21 | .idea/
22 |
23 | # Coveralls
24 | coverage
25 |
26 | # next
27 | .next/
28 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 10
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | **/*.json
2 | **/*.txt
3 | **/*.xml
4 | **/*.svg
5 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": true,
3 | "trailingComma": "all",
4 | "bracketSpacing": true,
5 | "jsxBracketSameLine": false,
6 | "singleQuote": true,
7 | "overrides": [],
8 | "printWidth": 80,
9 | "useTabs": false,
10 | "tabWidth": 2,
11 | "parser": "babylon"
12 | }
13 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Utilisez IntelliSense pour en savoir plus sur les attributs possibles.
3 | // Pointez pour afficher la description des attributs existants.
4 | // Pour plus d'informations, visitez : https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "type": "chrome",
9 | "request": "launch",
10 | "name": "Launch Chrome localhost:3000",
11 | "url": "http://localhost:3000",
12 | "webRoot": "${workspaceRoot}"
13 | }
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # CHANGELOG
2 |
3 | ## 1.0.0 (RELEASED)
4 |
5 | - upgade to `NextJS 9+`
6 | - move to `pure function` + `hook` (_as much as possible since some components like pages/_App needs to extends NextJS class App_)
7 | - add `styled-components`
8 | - improved `seo` (_= just better use of NextJS Head_)
9 | - improved `Flow types` (_Typescript is available in NextJS 9 but this can be **too much** for **little application**_)
10 |
11 | ## 0.1.1 (RELEASED)
12 |
13 | - feature browser cookie disabled
14 | - feature nojavascript
15 |
16 | ## 0.1.0 (RELEASED)
17 |
18 | - better PWA (*add to home screen...*)
19 |
20 | ## 0.0.1 (RELEASED)
21 |
22 | - first bricks of this starter
23 |
--------------------------------------------------------------------------------
/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | # ISSUES TEMPLATE
2 |
3 | ## Version
4 |
5 | 1.2.0
6 |
7 | ## Node JS
8 |
9 | v7.x | v8.X
10 |
11 | ## Browser
12 |
13 | Chrome 64.x | Safari 11.x
14 |
15 | ## OS version
16 |
17 | macOS | windows 10 | linux Ubuntu...
18 |
19 | ## Steps to reproduce
20 |
21 | 1. explantion 1...
22 |
23 | 2. explantion 2...
24 |
25 | 3. explantion 3...
26 |
27 | ## Expected behavior
28 |
29 | What should happen
30 |
31 | ## Actual behavior
32 |
33 | What is happening
34 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | # LICENSE
2 |
3 | The MIT License (MIT)
4 |
5 | Copyright (c) 2019 Erwan DATIN
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8 |
9 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10 |
11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
12 |
--------------------------------------------------------------------------------
/assets/Paypal-button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MacKentoch/react-redux-nextjs-bootstrap-pwa-starter/a93fb9713100e8ecdfbbcca26e669ff0286f7a70/assets/Paypal-button.png
--------------------------------------------------------------------------------
/assets/Paypal-button@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MacKentoch/react-redux-nextjs-bootstrap-pwa-starter/a93fb9713100e8ecdfbbcca26e669ff0286f7a70/assets/Paypal-button@2x.png
--------------------------------------------------------------------------------
/assets/Paypal-button@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MacKentoch/react-redux-nextjs-bootstrap-pwa-starter/a93fb9713100e8ecdfbbcca26e669ff0286f7a70/assets/Paypal-button@3x.png
--------------------------------------------------------------------------------
/components/header/Header.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import React, { useState, useCallback } from 'react';
4 | import { useRouter } from 'next/router';
5 | import { connect } from 'react-redux';
6 | import { bindActionCreators } from 'redux';
7 | import {
8 | Collapse,
9 | Navbar,
10 | NavbarToggler,
11 | NavbarBrand,
12 | Nav,
13 | NavItem,
14 | NavLink,
15 | } from 'reactstrap';
16 | import * as userAuthActions from '../../redux/modules/userAuth';
17 |
18 | // #region types
19 | type Props = {
20 | // userAuth:
21 | isAuthenticated: boolean,
22 | disconnectUser: (...any) => any,
23 | };
24 | // #endregion
25 |
26 | function Header({ isAuthenticated, disconnectUser }: Props) {
27 | const [isOpen, setIsOpen] = useState(false);
28 | const { push, replace } = useRouter();
29 |
30 | // #region callbacks
31 | const toggle = useCallback(() => setIsOpen(!isOpen), [isOpen]);
32 |
33 | const navigateTo = useCallback((to: string = '/') => () => push(to), []);
34 |
35 | const handlesDisconnectUser = useCallback((event?: SyntheticEvent<>) => {
36 | event && event.preventDefault();
37 | disconnectUser();
38 | replace('/login');
39 | }, []);
40 | // #endregion
41 |
42 | return (
43 | <>
44 |
45 | react-redux-next-bootstrap starter
46 |
47 |
48 |
75 |
76 |
77 | >
78 | );
79 | }
80 |
81 | // #region statics
82 | Header.defaultProps = {
83 | isAuthenticated: false,
84 | disconnectUser: () => {},
85 | };
86 |
87 | Header.displayName = 'Header';
88 | // #endregion
89 |
90 | // #region redux
91 | const mapStateToProps = state => ({
92 | isAuthenticated: state.userAuth.isAuthenticated,
93 | });
94 |
95 | const mapDispatchToProps = dispatch => {
96 | return {
97 | ...bindActionCreators({ ...userAuthActions }, dispatch),
98 | };
99 | };
100 | // #endregion
101 |
102 | export default connect(
103 | mapStateToProps,
104 | mapDispatchToProps,
105 | )(Header);
106 |
--------------------------------------------------------------------------------
/components/header/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import Header from './Header';
4 |
5 | export default Header;
6 |
--------------------------------------------------------------------------------
/components/layout/Layout.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | /* eslint-disable quotes */
3 |
4 | import React, { useEffect } from 'react';
5 | import Head from 'next/head';
6 | import theme from '../../config/theme';
7 | import appConfig from '../../config/appConfig';
8 | import { registerBeforeinstallprompt } from '../../utils/sw';
9 |
10 | // #region types
11 | type Props = {
12 | children: any,
13 | };
14 | // #endregion
15 |
16 | // #region constants
17 | const flexibilityJsForIE = `
18 |
19 |
20 |
21 | `;
22 | const { accent } = theme;
23 | const { og, seo, viewport } = appConfig.metas;
24 | // #endregion
25 |
26 | function Layout({ children }: Props) {
27 | useEffect(() => registerBeforeinstallprompt(), []);
28 |
29 | return (
30 |
31 |
32 |
{seo.title}
33 |
34 |
35 |
36 |
37 |
38 |
39 | {/*
*/}
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
55 |
56 |
61 |
62 |
68 |
74 |
75 |
80 |
81 |
86 |
90 |
91 |
92 |
102 | {children}
103 |
104 |
105 | );
106 | }
107 |
108 | // #region statics
109 | Layout.displayName = 'Layout';
110 | // #endregion
111 |
112 | export default Layout;
113 |
--------------------------------------------------------------------------------
/components/layout/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import Layout from './Layout';
4 |
5 | export default Layout;
6 |
--------------------------------------------------------------------------------
/components/privateRoute/PrivateRoute.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | // #region imports
4 | import React, { useEffect } from 'react';
5 | import { useRouter } from 'next/router';
6 | import auth from '../../utils/auth';
7 | // #endregion
8 |
9 | // #region flow types
10 | type Props = {
11 | fromPath: string,
12 | children: any,
13 | };
14 | // #endregion
15 |
16 | // #region constants
17 | function checkIsAuthenticated(): boolean {
18 | // $FlowIgnore
19 | const checkUserHasId = user => user && user.id;
20 | const user = auth.getUserInfo() ? auth.getUserInfo() : null;
21 | const isAuthenticated =
22 | auth.getToken() && checkUserHasId(user) ? true : false;
23 | return isAuthenticated;
24 | }
25 |
26 | function checkIsExpired(): boolean {
27 | return auth.isExpiredToken(auth.getToken());
28 | }
29 |
30 | // #endregiobn
31 |
32 | function Private({ fromPath = '/', children }: Props) {
33 | const { replace } = useRouter();
34 |
35 | useEffect(() => {
36 | const userIsAuthenticated = checkIsAuthenticated();
37 | const userTokenExpired = checkIsExpired();
38 | const RoutePayload = {
39 | pathname: '/login',
40 | query: { from: fromPath },
41 | };
42 |
43 | if (!userIsAuthenticated) {
44 | replace(RoutePayload);
45 | }
46 |
47 | if (userTokenExpired) {
48 | replace(RoutePayload);
49 | }
50 | }, []);
51 |
52 | return {children}
;
53 | }
54 |
55 | // #region statics
56 | Private.displayName = 'Private';
57 | // #endregion
58 |
59 | export default Private;
60 |
--------------------------------------------------------------------------------
/components/privateRoute/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import PrivateRoute from './PrivateRoute';
4 |
5 | export default PrivateRoute;
6 |
--------------------------------------------------------------------------------
/config/appConfig.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | export const appConfig = Object.freeze({
4 | // #region flag: set fetch mock or real fetch
5 | DEV_MODE: true,
6 | // #endregion
7 |
8 | // #region API
9 | api: {
10 | fakeEndPoint: 'api/somewhere',
11 | },
12 | // #endregion
13 |
14 | // #region Meta / SEO
15 | metas: {
16 | viewport: 'width=device-width, initial-scale=1, viewport-fit=cover',
17 | seo: {
18 | title: 'react-nextjs-pwa-starter',
19 | name: 'react-nextjs-pwa-starter',
20 | description: 'ReactJS + Redux NextJS Progressive webapp starter',
21 | },
22 | og: {
23 | title: 'react-nextjs-pwa-starter',
24 | url: './',
25 | type: 'website',
26 | description: 'ReactJS + Redux NextJS Progressive webapp starter',
27 | image: 'static/android-chrome-192x192.png',
28 | site_name: 'react-nextjs-pwa-starter',
29 | locale: 'en_US',
30 | },
31 | },
32 | // #endregion
33 | });
34 |
35 | export default appConfig;
36 |
--------------------------------------------------------------------------------
/config/theme.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | export const theme = Object.freeze({
4 | colors: {
5 | accent: '#1967be',
6 | },
7 | fonts: {},
8 | accent: '#1967be',
9 | });
10 |
11 | export default theme;
12 |
--------------------------------------------------------------------------------
/docs/_next/static/Q0JVIO_wUTVKoda1tiL4i/pages/_error.js:
--------------------------------------------------------------------------------
1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[4],{470:function(e,t,r){__NEXT_REGISTER_PAGE("/_error",function(){return e.exports=r(471),{page:e.exports.default}})},471:function(e,t,r){"use strict";r.r(t);var n=r(0),o=r.n(n),u=r(74),a=r.n(u);function l(e){return(l="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function s(e,t){for(var r=0;rNext PWA Starter Cookies are disabled on your browser!
Cookies are necessary to ensure application delivers the best experience and security.
You can't signin or signout this application until you enable cookie in your navigator.
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/docs/page1/index.html:
--------------------------------------------------------------------------------
1 | Next PWA Starter
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/docs/private1/index.html:
--------------------------------------------------------------------------------
1 | Next PWA StarterPrivate1 here
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/docs/service-worker.js:
--------------------------------------------------------------------------------
1 | self.__precacheManifest = [
2 | {
3 | "url": "/_next/static/Q0JVIO_wUTVKoda1tiL4i/pages/index.js"
4 | },
5 | {
6 | "url": "/_next/static/chunks/commons.b192840d4295db06bd92.js"
7 | },
8 | {
9 | "url": "/_next/static/runtime/main-4888d1fc3b6b523c2e7d.js"
10 | },
11 | {
12 | "url": "/_next/static/runtime/webpack-42652fa8b82c329c0559.js"
13 | },
14 | {
15 | "url": "/_next/static/Q0JVIO_wUTVKoda1tiL4i/pages/private1.js"
16 | },
17 | {
18 | "url": "/_next/static/Q0JVIO_wUTVKoda1tiL4i/pages/page1.js"
19 | },
20 | {
21 | "url": "/_next/static/Q0JVIO_wUTVKoda1tiL4i/pages/login.js"
22 | },
23 | {
24 | "url": "/_next/static/Q0JVIO_wUTVKoda1tiL4i/pages/_error.js"
25 | },
26 | {
27 | "url": "/_next/static/Q0JVIO_wUTVKoda1tiL4i/pages/_app.js"
28 | }
29 | ];
30 |
31 | /**
32 | * Welcome to your Workbox-powered service worker!
33 | *
34 | * You'll need to register this file in your web app and you should
35 | * disable HTTP caching for this file too.
36 | * See https://goo.gl/nhQhGp
37 | *
38 | * The rest of the code is auto-generated. Please don't update this file
39 | * directly; instead, make changes to your Workbox build configuration
40 | * and re-run your build process.
41 | * See https://goo.gl/2aRDsh
42 | */
43 |
44 | importScripts("https://storage.googleapis.com/workbox-cdn/releases/3.6.3/workbox-sw.js");
45 |
46 | importScripts(
47 |
48 | );
49 |
50 | /**
51 | * The workboxSW.precacheAndRoute() method efficiently caches and responds to
52 | * requests for URLs in the manifest.
53 | * See https://goo.gl/S9QRab
54 | */
55 | self.__precacheManifest = [
56 | {
57 | "url": "static/android-chrome-192x192.png",
58 | "revision": "530141b9ba7c48c046b7be068992fbea"
59 | },
60 | {
61 | "url": "static/android-chrome-512x512.png",
62 | "revision": "03e1e69e355a1e67830d9d6eb73c1529"
63 | },
64 | {
65 | "url": "static/apple-touch-icon.png",
66 | "revision": "c85de09057f2c9e7f28b4e12b21db83a"
67 | },
68 | {
69 | "url": "static/browserconfig.xml",
70 | "revision": "a493ba0aa0b8ec8068d786d7248bb92c"
71 | },
72 | {
73 | "url": "static/css/bootstrap.min.css",
74 | "revision": "8166a7305b3f33070aa61650c9efaaed"
75 | },
76 | {
77 | "url": "static/favicon-16x16.png",
78 | "revision": "6f32c8d064cb9227a542beca31b7706e"
79 | },
80 | {
81 | "url": "static/favicon-32x32.png",
82 | "revision": "ff0f064d4f31dc5f75cbd864a695811f"
83 | },
84 | {
85 | "url": "static/manifest.json",
86 | "revision": "2110c4a029711d3f60be3c7d0332c268"
87 | },
88 | {
89 | "url": "static/mstile-150x150.png",
90 | "revision": "bcbcd87df04932e11eef6faf134a2593"
91 | },
92 | {
93 | "url": "static/robot.txt",
94 | "revision": "6978a616c585d03cb5b542a891995efb"
95 | },
96 | {
97 | "url": "static/safari-pinned-tab.svg",
98 | "revision": "940da6a2fdfb2efae0c14326fc431ed6"
99 | },
100 | {
101 | "url": "static/splashscreen-icon-512x512.png",
102 | "revision": "0be5f9bfd8d4e977a3a3293ed3f1ce2c"
103 | }
104 | ].concat(self.__precacheManifest || []);
105 | workbox.precaching.suppressWarnings();
106 | workbox.precaching.precacheAndRoute(self.__precacheManifest, {});
107 |
108 | workbox.routing.registerRoute(/^https?.*/, workbox.strategies.networkFirst(), 'GET');
109 |
--------------------------------------------------------------------------------
/docs/static/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MacKentoch/react-redux-nextjs-bootstrap-pwa-starter/a93fb9713100e8ecdfbbcca26e669ff0286f7a70/docs/static/android-chrome-192x192.png
--------------------------------------------------------------------------------
/docs/static/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MacKentoch/react-redux-nextjs-bootstrap-pwa-starter/a93fb9713100e8ecdfbbcca26e669ff0286f7a70/docs/static/android-chrome-512x512.png
--------------------------------------------------------------------------------
/docs/static/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MacKentoch/react-redux-nextjs-bootstrap-pwa-starter/a93fb9713100e8ecdfbbcca26e669ff0286f7a70/docs/static/apple-touch-icon.png
--------------------------------------------------------------------------------
/docs/static/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #da532c
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/docs/static/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MacKentoch/react-redux-nextjs-bootstrap-pwa-starter/a93fb9713100e8ecdfbbcca26e669ff0286f7a70/docs/static/favicon-16x16.png
--------------------------------------------------------------------------------
/docs/static/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MacKentoch/react-redux-nextjs-bootstrap-pwa-starter/a93fb9713100e8ecdfbbcca26e669ff0286f7a70/docs/static/favicon-32x32.png
--------------------------------------------------------------------------------
/docs/static/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-redux-nextjs-bootstrap-pwa-starter",
3 | "short_name": "Next-PWA",
4 | "icons": [
5 | {
6 | "src": "android-chrome-192x192.png",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "android-chrome-512x512.png",
12 | "sizes": "512x512",
13 | "type": "image/png"
14 | },
15 | {
16 | "src": "splashscreen-icon-512x512.png",
17 | "sizes": "512x512",
18 | "type": "image/png"
19 | }
20 | ],
21 | "theme_color": "#1967be",
22 | "background_color": "#1967be",
23 | "display": "standalone",
24 | "start_url": "/"
25 | }
26 |
--------------------------------------------------------------------------------
/docs/static/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MacKentoch/react-redux-nextjs-bootstrap-pwa-starter/a93fb9713100e8ecdfbbcca26e669ff0286f7a70/docs/static/mstile-150x150.png
--------------------------------------------------------------------------------
/docs/static/robot.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 |
--------------------------------------------------------------------------------
/docs/static/splashscreen-icon-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MacKentoch/react-redux-nextjs-bootstrap-pwa-starter/a93fb9713100e8ecdfbbcca26e669ff0286f7a70/docs/static/splashscreen-icon-512x512.png
--------------------------------------------------------------------------------
/flow-typed/npm/babel-plugin-transform-async-to-generator_vx.x.x.js:
--------------------------------------------------------------------------------
1 | // flow-typed signature: b566883b98bb79a75cbf392f24988c4f
2 | // flow-typed version: <>/babel-plugin-transform-async-to-generator_v^6.24.1/flow_v0.57.2
3 |
4 | /**
5 | * This is an autogenerated libdef stub for:
6 | *
7 | * 'babel-plugin-transform-async-to-generator'
8 | *
9 | * Fill this stub out by replacing all the `any` types.
10 | *
11 | * Once filled out, we encourage you to share your work with the
12 | * community by sending a pull request to:
13 | * https://github.com/flowtype/flow-typed
14 | */
15 |
16 | declare module 'babel-plugin-transform-async-to-generator' {
17 | declare module.exports: any;
18 | }
19 |
20 | /**
21 | * We include stubs for each file inside this npm package in case you need to
22 | * require those files directly. Feel free to delete any files that aren't
23 | * needed.
24 | */
25 | declare module 'babel-plugin-transform-async-to-generator/lib/index' {
26 | declare module.exports: any;
27 | }
28 |
29 | // Filename aliases
30 | declare module 'babel-plugin-transform-async-to-generator/lib/index.js' {
31 | declare module.exports: $Exports<'babel-plugin-transform-async-to-generator/lib/index'>;
32 | }
33 |
--------------------------------------------------------------------------------
/flow-typed/npm/babel-plugin-transform-regenerator_vx.x.x.js:
--------------------------------------------------------------------------------
1 | // flow-typed signature: 104fda36b08cda34bbac121973e64b1d
2 | // flow-typed version: <>/babel-plugin-transform-regenerator_v^6.26.0/flow_v0.57.2
3 |
4 | /**
5 | * This is an autogenerated libdef stub for:
6 | *
7 | * 'babel-plugin-transform-regenerator'
8 | *
9 | * Fill this stub out by replacing all the `any` types.
10 | *
11 | * Once filled out, we encourage you to share your work with the
12 | * community by sending a pull request to:
13 | * https://github.com/flowtype/flow-typed
14 | */
15 |
16 | declare module 'babel-plugin-transform-regenerator' {
17 | declare module.exports: any;
18 | }
19 |
20 | /**
21 | * We include stubs for each file inside this npm package in case you need to
22 | * require those files directly. Feel free to delete any files that aren't
23 | * needed.
24 | */
25 | declare module 'babel-plugin-transform-regenerator/lib/index' {
26 | declare module.exports: any;
27 | }
28 |
29 | // Filename aliases
30 | declare module 'babel-plugin-transform-regenerator/lib/index.js' {
31 | declare module.exports: $Exports<'babel-plugin-transform-regenerator/lib/index'>;
32 | }
33 |
--------------------------------------------------------------------------------
/flow-typed/npm/babel-preset-es2015_vx.x.x.js:
--------------------------------------------------------------------------------
1 | // flow-typed signature: 8722dd2bea14809f8f45293872c8f6cf
2 | // flow-typed version: <>/babel-preset-es2015_v^6.24.1/flow_v0.57.2
3 |
4 | /**
5 | * This is an autogenerated libdef stub for:
6 | *
7 | * 'babel-preset-es2015'
8 | *
9 | * Fill this stub out by replacing all the `any` types.
10 | *
11 | * Once filled out, we encourage you to share your work with the
12 | * community by sending a pull request to:
13 | * https://github.com/flowtype/flow-typed
14 | */
15 |
16 | declare module 'babel-preset-es2015' {
17 | declare module.exports: any;
18 | }
19 |
20 | /**
21 | * We include stubs for each file inside this npm package in case you need to
22 | * require those files directly. Feel free to delete any files that aren't
23 | * needed.
24 | */
25 | declare module 'babel-preset-es2015/lib/index' {
26 | declare module.exports: any;
27 | }
28 |
29 | // Filename aliases
30 | declare module 'babel-preset-es2015/lib/index.js' {
31 | declare module.exports: $Exports<'babel-preset-es2015/lib/index'>;
32 | }
33 |
--------------------------------------------------------------------------------
/flow-typed/npm/babel-preset-flow_vx.x.x.js:
--------------------------------------------------------------------------------
1 | // flow-typed signature: 2f58597102bce11ff3a4482d6607d60f
2 | // flow-typed version: <>/babel-preset-flow_v^6.23.0/flow_v0.57.2
3 |
4 | /**
5 | * This is an autogenerated libdef stub for:
6 | *
7 | * 'babel-preset-flow'
8 | *
9 | * Fill this stub out by replacing all the `any` types.
10 | *
11 | * Once filled out, we encourage you to share your work with the
12 | * community by sending a pull request to:
13 | * https://github.com/flowtype/flow-typed
14 | */
15 |
16 | declare module 'babel-preset-flow' {
17 | declare module.exports: any;
18 | }
19 |
20 | /**
21 | * We include stubs for each file inside this npm package in case you need to
22 | * require those files directly. Feel free to delete any files that aren't
23 | * needed.
24 | */
25 | declare module 'babel-preset-flow/lib/index' {
26 | declare module.exports: any;
27 | }
28 |
29 | // Filename aliases
30 | declare module 'babel-preset-flow/lib/index.js' {
31 | declare module.exports: $Exports<'babel-preset-flow/lib/index'>;
32 | }
33 |
--------------------------------------------------------------------------------
/flow-typed/npm/babel-preset-react_vx.x.x.js:
--------------------------------------------------------------------------------
1 | // flow-typed signature: 1421db38882c7eb42bf18e8dbb388727
2 | // flow-typed version: <>/babel-preset-react_v^6.24.1/flow_v0.57.2
3 |
4 | /**
5 | * This is an autogenerated libdef stub for:
6 | *
7 | * 'babel-preset-react'
8 | *
9 | * Fill this stub out by replacing all the `any` types.
10 | *
11 | * Once filled out, we encourage you to share your work with the
12 | * community by sending a pull request to:
13 | * https://github.com/flowtype/flow-typed
14 | */
15 |
16 | declare module 'babel-preset-react' {
17 | declare module.exports: any;
18 | }
19 |
20 | /**
21 | * We include stubs for each file inside this npm package in case you need to
22 | * require those files directly. Feel free to delete any files that aren't
23 | * needed.
24 | */
25 | declare module 'babel-preset-react/lib/index' {
26 | declare module.exports: any;
27 | }
28 |
29 | // Filename aliases
30 | declare module 'babel-preset-react/lib/index.js' {
31 | declare module.exports: $Exports<'babel-preset-react/lib/index'>;
32 | }
33 |
--------------------------------------------------------------------------------
/flow-typed/npm/babel-preset-stage-2_vx.x.x.js:
--------------------------------------------------------------------------------
1 | // flow-typed signature: a801d20d6e50d29432634751cde9135d
2 | // flow-typed version: <>/babel-preset-stage-2_v^6.24.1/flow_v0.57.2
3 |
4 | /**
5 | * This is an autogenerated libdef stub for:
6 | *
7 | * 'babel-preset-stage-2'
8 | *
9 | * Fill this stub out by replacing all the `any` types.
10 | *
11 | * Once filled out, we encourage you to share your work with the
12 | * community by sending a pull request to:
13 | * https://github.com/flowtype/flow-typed
14 | */
15 |
16 | declare module 'babel-preset-stage-2' {
17 | declare module.exports: any;
18 | }
19 |
20 | /**
21 | * We include stubs for each file inside this npm package in case you need to
22 | * require those files directly. Feel free to delete any files that aren't
23 | * needed.
24 | */
25 | declare module 'babel-preset-stage-2/lib/index' {
26 | declare module.exports: any;
27 | }
28 |
29 | // Filename aliases
30 | declare module 'babel-preset-stage-2/lib/index.js' {
31 | declare module.exports: $Exports<'babel-preset-stage-2/lib/index'>;
32 | }
33 |
--------------------------------------------------------------------------------
/flow-typed/npm/flow-bin_v0.x.x.js:
--------------------------------------------------------------------------------
1 | // flow-typed signature: 6a5610678d4b01e13bbfbbc62bdaf583
2 | // flow-typed version: 3817bc6980/flow-bin_v0.x.x/flow_>=v0.25.x
3 |
4 | declare module "flow-bin" {
5 | declare module.exports: string;
6 | }
7 |
--------------------------------------------------------------------------------
/lighthouse-0.1.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MacKentoch/react-redux-nextjs-bootstrap-pwa-starter/a93fb9713100e8ecdfbbcca26e669ff0286f7a70/lighthouse-0.1.1.png
--------------------------------------------------------------------------------
/lighthouse-test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MacKentoch/react-redux-nextjs-bootstrap-pwa-starter/a93fb9713100e8ecdfbbcca26e669ff0286f7a70/lighthouse-test.png
--------------------------------------------------------------------------------
/mock/fakeAPI.json:
--------------------------------------------------------------------------------
1 | [{
2 | "id": 1,
3 | "label": "item 1"
4 | },
5 | {
6 | "id": 2,
7 | "label": "item 2"
8 | }
9 | ]
10 |
--------------------------------------------------------------------------------
/mock/userInfosMock.json:
--------------------------------------------------------------------------------
1 | {
2 | "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJkZW1vIiwiaWF0IjoxNTAyMzA3MzU0LCJleHAiOjE3MjMyMzIxNTQsImF1ZCI6ImRlbW8tZGVtbyIsInN1YiI6ImRlbW8iLCJHaXZlbk5hbWUiOiJKb2huIiwiU3VybmFtZSI6IkRvZSIsIkVtYWlsIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJSb2xlIjpbIlN1cGVyIGNvb2wgZGV2IiwibWFnaWMgbWFrZXIiXX0.6FjgLCypaqmRp4tDjg_idVKIzQw16e-z_rjA3R94IqQ",
3 | "user": {
4 | "id": 111,
5 | "login": "john.doe@fake.mail",
6 | "firstname": "John",
7 | "lastname": "Doe",
8 | "isAdmin": true
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | const withOffline = require('next-offline');
2 |
3 | const nextConfig = {
4 | // next-offline options:
5 | dontAutoRegisterSw: true, // since we want runtime registration
6 | };
7 |
8 | module.exports = withOffline(nextConfig);
9 |
--------------------------------------------------------------------------------
/out/404.html:
--------------------------------------------------------------------------------
1 | Error...Sorry but this time... It threw an error...
Error code: --
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/out/_next/static/8Ch3BaHLClZes2TshMjtO/pages/_error.js:
--------------------------------------------------------------------------------
1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[3],{"1HF/":function(e,t,r){(window.__NEXT_P=window.__NEXT_P||[]).push(["/_error",function(){var e=r("Y0NT");return{page:e.default||e}}])},Y0NT:function(e,t,r){"use strict";r.r(t),r.d(t,"Error",function(){return N});var n=r("ln6h"),a=r.n(n),o=r("O40h"),u=r("rt45"),l=r("q1tI"),c=r.n(l),s=r("m/Pd"),i=r.n(s),d=r("vOnD"),f=r("o+Wc"),p=r.n(f);function m(){var e=Object(u.a)([""]);return m=function(){return e},e}var v=r("y0A3").a.metas,E=v.og,w=E.description,g=E.locale,b=v.seo.description,h=d.c.div(m());function N(e){var t=e.errorCode,r=void 0===t?null:t;return c.a.createElement(h,null,c.a.createElement(i.a,null,c.a.createElement("title",null,"Error..."),c.a.createElement("meta",{name:"description",content:b}),c.a.createElement("meta",{property:"og:description",content:w}),c.a.createElement("meta",{property:"og:locale",content:g})),c.a.createElement(p.a,null,c.a.createElement("h1",null,"Sorry but this time... It threw an error..."),c.a.createElement("code",null,"Error code: ",r||"--")))}N.getInitialProps=function(){var e=Object(o.default)(a.a.mark(function e(t){var r,n,o;return a.a.wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return r=t.res,n=t.xhr,o=r?r.statusCode:n.status,e.abrupt("return",{errorCode:o});case 3:case"end":return e.stop()}},e)}));return function(t){return e.apply(this,arguments)}}(),N.displayName="Error",t.default=N},"o+Wc":function(e,t,r){"use strict";var n=r("TqRt");t.__esModule=!0,t.default=void 0;var a=n(r("pVnL")),o=n(r("8OQS")),u=n(r("q1tI")),l=n(r("17x9")),c=n(r("TSYQ")),s=r("KnAW"),i={tag:s.tagPropType,fluid:l.default.bool,className:l.default.string,cssModule:l.default.object},d=function(e){var t=e.className,r=e.cssModule,n=e.tag,l=e.fluid,i=(0,o.default)(e,["className","cssModule","tag","fluid"]),d=(0,s.mapToCssModules)((0,c.default)(t,"jumbotron",!!l&&"jumbotron-fluid"),r);return u.default.createElement(n,(0,a.default)({},i,{className:d}))};d.propTypes=i,d.defaultProps={tag:"div"};var f=d;t.default=f}},[["1HF/",1,0]]]);
--------------------------------------------------------------------------------
/out/_next/static/8Ch3BaHLClZes2TshMjtO/pages/page1.js:
--------------------------------------------------------------------------------
1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[6],{WC0q:function(e,n,t){"use strict";t.r(n),t.d(n,"Page1",function(){return M});var r=t("hfKm"),a=t.n(r),u=t("2Eek"),c=t.n(u),o=t("XoMD"),i=t.n(o),l=t("Jo+v"),f=t.n(l),p=t("4mXO"),m=t.n(p),s=t("pLtp"),d=t.n(s),h=t("vYYK"),E=t("buZk"),b=t("rt45"),v=t("q1tI"),w=t.n(v),g=t("/MKj"),O=t("nOHt"),j=t("m/Pd"),k=t.n(j),y=t("ANjH"),P=t("vOnD"),_=t("8LhB"),A=t.n(_),C=t("3W4t"),N=t.n(C),X=t("QyV/"),q=t("h+Qx");function D(e,n){var t=d()(e);return m.a&&t.push.apply(t,m()(e)),n&&(t=t.filter(function(n){return f()(e,n).enumerable})),t}function J(e){for(var n=1;n {
64 | if (event.data && event.data.type === 'SKIP_WAITING') {
65 | self.skipWaiting();
66 | }
67 | });
68 |
69 | /**
70 | * The workboxSW.precacheAndRoute() method efficiently caches and responds to
71 | * requests for URLs in the manifest.
72 | * See https://goo.gl/S9QRab
73 | */
74 | self.__precacheManifest = [
75 | {
76 | "url": "static/android-chrome-192x192.png",
77 | "revision": "530141b9ba7c48c046b7be068992fbea"
78 | },
79 | {
80 | "url": "static/android-chrome-512x512.png",
81 | "revision": "03e1e69e355a1e67830d9d6eb73c1529"
82 | },
83 | {
84 | "url": "static/apple-touch-icon.png",
85 | "revision": "c85de09057f2c9e7f28b4e12b21db83a"
86 | },
87 | {
88 | "url": "static/browserconfig.xml",
89 | "revision": "a493ba0aa0b8ec8068d786d7248bb92c"
90 | },
91 | {
92 | "url": "static/css/bootstrap.min.css",
93 | "revision": "8166a7305b3f33070aa61650c9efaaed"
94 | },
95 | {
96 | "url": "static/favicon-16x16.png",
97 | "revision": "6f32c8d064cb9227a542beca31b7706e"
98 | },
99 | {
100 | "url": "static/favicon-32x32.png",
101 | "revision": "ff0f064d4f31dc5f75cbd864a695811f"
102 | },
103 | {
104 | "url": "static/manifest.json",
105 | "revision": "2110c4a029711d3f60be3c7d0332c268"
106 | },
107 | {
108 | "url": "static/mstile-150x150.png",
109 | "revision": "bcbcd87df04932e11eef6faf134a2593"
110 | },
111 | {
112 | "url": "static/robot.txt",
113 | "revision": "6978a616c585d03cb5b542a891995efb"
114 | },
115 | {
116 | "url": "static/safari-pinned-tab.svg",
117 | "revision": "940da6a2fdfb2efae0c14326fc431ed6"
118 | },
119 | {
120 | "url": "static/splashscreen-icon-512x512.png",
121 | "revision": "0be5f9bfd8d4e977a3a3293ed3f1ce2c"
122 | }
123 | ].concat(self.__precacheManifest || []);
124 | workbox.precaching.precacheAndRoute(self.__precacheManifest, {});
125 |
126 | workbox.routing.registerRoute(/^https?.*/, new workbox.strategies.NetworkFirst({ "cacheName":"offlineCache", plugins: [new workbox.expiration.Plugin({ maxEntries: 200, purgeOnQuotaError: false })] }), 'GET');
127 |
--------------------------------------------------------------------------------
/out/static/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MacKentoch/react-redux-nextjs-bootstrap-pwa-starter/a93fb9713100e8ecdfbbcca26e669ff0286f7a70/out/static/android-chrome-192x192.png
--------------------------------------------------------------------------------
/out/static/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MacKentoch/react-redux-nextjs-bootstrap-pwa-starter/a93fb9713100e8ecdfbbcca26e669ff0286f7a70/out/static/android-chrome-512x512.png
--------------------------------------------------------------------------------
/out/static/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MacKentoch/react-redux-nextjs-bootstrap-pwa-starter/a93fb9713100e8ecdfbbcca26e669ff0286f7a70/out/static/apple-touch-icon.png
--------------------------------------------------------------------------------
/out/static/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #da532c
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/out/static/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MacKentoch/react-redux-nextjs-bootstrap-pwa-starter/a93fb9713100e8ecdfbbcca26e669ff0286f7a70/out/static/favicon-16x16.png
--------------------------------------------------------------------------------
/out/static/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MacKentoch/react-redux-nextjs-bootstrap-pwa-starter/a93fb9713100e8ecdfbbcca26e669ff0286f7a70/out/static/favicon-32x32.png
--------------------------------------------------------------------------------
/out/static/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-redux-nextjs-bootstrap-pwa-starter",
3 | "short_name": "Next-PWA",
4 | "icons": [
5 | {
6 | "src": "android-chrome-192x192.png",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "android-chrome-512x512.png",
12 | "sizes": "512x512",
13 | "type": "image/png"
14 | },
15 | {
16 | "src": "splashscreen-icon-512x512.png",
17 | "sizes": "512x512",
18 | "type": "image/png"
19 | }
20 | ],
21 | "theme_color": "#1967be",
22 | "background_color": "#1967be",
23 | "display": "standalone",
24 | "start_url": "/"
25 | }
26 |
--------------------------------------------------------------------------------
/out/static/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MacKentoch/react-redux-nextjs-bootstrap-pwa-starter/a93fb9713100e8ecdfbbcca26e669ff0286f7a70/out/static/mstile-150x150.png
--------------------------------------------------------------------------------
/out/static/robot.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 |
--------------------------------------------------------------------------------
/out/static/splashscreen-icon-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MacKentoch/react-redux-nextjs-bootstrap-pwa-starter/a93fb9713100e8ecdfbbcca26e669ff0286f7a70/out/static/splashscreen-icon-512x512.png
--------------------------------------------------------------------------------
/pages/_app.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import React, { Fragment } from 'react';
4 | import App, { Container } from 'next/app';
5 | import { register, unregister } from 'next-offline/runtime';
6 | import { Provider } from 'react-redux';
7 | import { compose } from 'redux';
8 | import { ThemeProvider } from 'styled-components';
9 | import withRedux from 'next-redux-wrapper';
10 | import smoothScrollPolyfill from 'smoothscroll-polyfill';
11 | import configureStore from '../redux/store/configureStore';
12 | import Layout from '../components/layout';
13 | import { theme } from '../config/theme';
14 | import GlobalStyle from '../style/globalStyle';
15 |
16 | // #region types
17 | type Props = any;
18 | // #endregion
19 |
20 | // #region constants
21 | const store = configureStore();
22 |
23 | if (typeof window !== 'undefined') {
24 | // #region smoothscroll polyfill
25 | smoothScrollPolyfill.polyfill();
26 | // forces polyfill (even if browser partially implements it)
27 | window.__forceSmoothScrollPolyfill__ = true;
28 | // #endregion
29 | }
30 | // #endregion
31 |
32 | export class MyApp extends App {
33 | static async getInitialProps({ Component, ctx }) {
34 | let pageProps = {};
35 |
36 | if (Component.getInitialProps) {
37 | pageProps = await Component.getInitialProps(ctx);
38 | }
39 |
40 | return { pageProps };
41 | }
42 |
43 | componentDidMount() {
44 | register();
45 | }
46 |
47 | componentWillUnmount() {
48 | unregister();
49 | }
50 |
51 | render() {
52 | const { Component, pageProps } = this.props;
53 |
54 | return (
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | );
68 | }
69 | }
70 |
71 | // #region statics
72 | MyApp.displayName = 'MyApp';
73 | // #endregion
74 |
75 | export default compose(withRedux(configureStore))(MyApp);
76 |
--------------------------------------------------------------------------------
/pages/_document.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import React, { Fragment } from 'react';
4 | import Document from 'next/document';
5 | import { ServerStyleSheet } from 'styled-components';
6 |
7 | export class MyDocument extends Document {
8 | static async getInitialProps(ctx) {
9 | const sheet = new ServerStyleSheet();
10 | const originalRenderPage = ctx.renderPage;
11 |
12 | try {
13 | ctx.renderPage = () =>
14 | originalRenderPage({
15 | enhanceApp: App => props => sheet.collectStyles(),
16 | });
17 |
18 | const initialProps = await Document.getInitialProps(ctx);
19 |
20 | return {
21 | ...initialProps,
22 | styles: (
23 |
24 | {initialProps.styles}
25 | {sheet.getStyleElement()}
26 |
27 | ),
28 | };
29 | } finally {
30 | sheet.seal();
31 | }
32 | }
33 | }
34 |
35 | export default MyDocument;
36 |
--------------------------------------------------------------------------------
/pages/_error.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import React from 'react';
4 | import Head from 'next/head';
5 | import styled from 'styled-components';
6 | import Jumbotron from 'reactstrap/lib/Jumbotron';
7 | import { type GetInitialPropsParams } from '../types/nextjs';
8 | import appConfig from '../config/appConfig';
9 |
10 | // #region types
11 | type Props = {
12 | errorCode?: number,
13 | };
14 |
15 | type NextInitialProps = {
16 | res?: {
17 | statusCode: number,
18 | },
19 | xhr: {
20 | status: number,
21 | },
22 | };
23 | // #endregion
24 |
25 | // #region constants
26 | const {
27 | og: { description: ogDescription, locale: ogLang },
28 | seo: { description: seoDescription },
29 | } = appConfig.metas;
30 | // #endregion
31 |
32 | // #region styled components
33 | const Page = styled.div``;
34 | // #endregion
35 |
36 | export function Error({ errorCode = null }: Props) {
37 | return (
38 |
39 |
40 | Error...
41 |
42 |
43 |
44 |
45 |
46 | Sorry but this time... It threw an error...
47 | Error code: {errorCode ? errorCode : '--'}
48 |
49 |
50 | );
51 | }
52 |
53 | Error.getInitialProps = async function({
54 | res,
55 | xhr,
56 | }: GetInitialPropsParams & NextInitialProps) {
57 | const errorCode = res ? res.statusCode : xhr.status;
58 | return { errorCode };
59 | };
60 |
61 | Error.displayName = 'Error';
62 |
63 | export default Error;
64 |
--------------------------------------------------------------------------------
/pages/dynamicPage/[counter].js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import React, { useCallback } from 'react';
4 | import { connect } from 'react-redux';
5 | import { bindActionCreators, compose } from 'redux';
6 | import { useRouter } from 'next/router';
7 | import styled from 'styled-components';
8 | import Head from 'next/head';
9 | import Container from 'reactstrap/lib/Container';
10 | import Jumbotron from 'reactstrap/lib/Jumbotron';
11 | import Button from 'reactstrap/lib/Button';
12 | import Header from '../../components/header';
13 | import appConfig from '../../config/appConfig';
14 |
15 | // #region types
16 | type Props = {
17 | counter: number,
18 | };
19 | // #endregion
20 |
21 | // #region constants
22 | const {
23 | og: { description: ogDescription, locale: ogLang },
24 | seo: { description: seoDescription },
25 | } = appConfig.metas;
26 | // #endregion
27 |
28 | // #region styled components
29 | const Page = styled.div``;
30 | // #endregion
31 |
32 | export function DynamicPage({ isFetching }: Props) {
33 | const { push, query } = useRouter();
34 | const { counter = 0 } = query;
35 |
36 | // # region callbacks
37 | const goHome = useCallback((event: SyntheticEvent<>) => {
38 | event && event.preventDefault();
39 | push('/');
40 | }, []);
41 | // #endregion
42 |
43 | return (
44 |
45 |
46 | Dynamic page #{counter}
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | Dynamic page #{counter}
55 |
56 |
57 |
58 |
59 | );
60 | }
61 |
62 | // #region statics
63 | DynamicPage.displayName = 'DynamicPage';
64 | // #endregion
65 |
66 | // #region redux
67 | const mapStateToProps = state => ({});
68 |
69 | const mapDispatchToProps = dispatch => {
70 | return {
71 | ...bindActionCreators({}, dispatch),
72 | };
73 | };
74 | // #endregion
75 |
76 | export default compose(
77 | connect(
78 | mapStateToProps,
79 | mapDispatchToProps,
80 | ),
81 | )(DynamicPage);
82 |
--------------------------------------------------------------------------------
/pages/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import React, { useCallback } from 'react';
4 | import { connect } from 'react-redux';
5 | import { bindActionCreators, compose } from 'redux';
6 | import { useRouter } from 'next/router';
7 | import Link from 'next/link';
8 | import styled from 'styled-components';
9 | import Head from 'next/head';
10 | import Container from 'reactstrap/lib/Container';
11 | import Jumbotron from 'reactstrap/lib/Jumbotron';
12 | import Button from 'reactstrap/lib/Button';
13 | import * as fakeFetchActions from '../redux/modules/fakeModuleWithFetch';
14 | import * as userAuthActions from '../redux/modules/userAuth';
15 | import Header from '../components/header';
16 | import { type GetInitialPropsParams } from '../types/nextjs';
17 | import appConfig from '../config/appConfig';
18 |
19 | // #region types
20 | type Props = {
21 | // fakeModuleWithFetch:
22 | isFetching: boolean,
23 | fakeData: any,
24 | fakeFetchIfNeeded: () => Promise,
25 | // userAuth:
26 | isAuthenticated: boolean,
27 | disconnectUser: () => any,
28 | };
29 | // #endregion
30 |
31 | // #region constants
32 | const {
33 | og: { description: ogDescription, locale: ogLang },
34 | seo: { description: seoDescription },
35 | } = appConfig.metas;
36 | // #endregion
37 |
38 | // #region styled components
39 | const Page = styled.div``;
40 | // #endregion
41 |
42 | export function IndexPage({ isFetching }: Props) {
43 | const { push } = useRouter();
44 |
45 | // # region callbacks
46 | const goLogin = useCallback((event: SyntheticEvent<>) => {
47 | event && event.preventDefault();
48 | push('/login');
49 | }, []);
50 | // #endregion
51 |
52 | const counter = 2;
53 |
54 | return (
55 |
56 |
57 | Index page
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | PWA: Next JS + Redux + Bootstrap STARTER
66 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 | );
77 | }
78 |
79 | // #region statics
80 | IndexPage.getInitialProps = async function({
81 | isServer,
82 | store,
83 | }: GetInitialPropsParams) {
84 | const SIDE = isServer ? 'SERVER SIDE' : 'FRONT SIDE';
85 |
86 | try {
87 | const response = await store.dispatch(fakeFetchActions.fakeFetchIfNeeded());
88 | const {
89 | payload: { data },
90 | } = response;
91 | // NOTE: you will see this log in your server console (where you `npm run dev`):
92 | /* eslint-disable no-console */
93 | console.log(`getInitialProps - ${SIDE} - fake fetch result: `, data);
94 |
95 | return { data };
96 | } catch (error) {
97 | console.error(`getInitialProps - ${SIDE} - fake fetch failed: `, error);
98 | /* eslint-enable no-console */
99 | return { data: null };
100 | }
101 | };
102 |
103 | IndexPage.displayName = 'IndexPage';
104 | // #endregion
105 |
106 | // #region redux
107 | const mapStateToProps = state => ({
108 | isFetching: state.fakeModuleWithFetch.isFetching,
109 | fakeData: state.fakeModuleWithFetch.data,
110 | isAuthenticated: state.userAuth.isAuthenticated,
111 | });
112 |
113 | const mapDispatchToProps = dispatch => {
114 | return {
115 | ...bindActionCreators(
116 | {
117 | ...fakeFetchActions,
118 | ...userAuthActions,
119 | },
120 | dispatch,
121 | ),
122 | };
123 | };
124 | // #endregion
125 |
126 | export default compose(
127 | connect(
128 | mapStateToProps,
129 | mapDispatchToProps,
130 | ),
131 | )(IndexPage);
132 |
--------------------------------------------------------------------------------
/pages/page1.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import React from 'react';
4 | import { connect } from 'react-redux';
5 | import { useRouter } from 'next/router';
6 | import Head from 'next/head';
7 | import { bindActionCreators, compose } from 'redux';
8 | import styled from 'styled-components';
9 | import Container from 'reactstrap/lib/Container';
10 | import Button from 'reactstrap/lib/Button';
11 | import Header from '../components/header';
12 | import * as userAuthActions from '../redux/modules/userAuth';
13 | import appConfig from '../config/appConfig';
14 |
15 | // #region types
16 | type Props = {
17 | // userAuth:
18 | isAuthenticated: boolean,
19 | disconnectUser: () => any,
20 | };
21 | // #endregion
22 |
23 | // #region constants
24 | const {
25 | og: { description: ogDescription, locale: ogLang },
26 | seo: { description: seoDescription },
27 | } = appConfig.metas;
28 | // #endregion
29 |
30 | // #region styled components
31 | const Page = styled.div``;
32 | // #endregion
33 |
34 | export function Page1({ }: Props) {
35 | const { push } = useRouter();
36 |
37 | // #region callbacks
38 | const goBackHome = (event: SyntheticEvent<>): void => {
39 | event && event.preventDefault();
40 | push('/');
41 | };
42 | // #endregion
43 |
44 | return (
45 |
46 |
47 | Page 1
48 |
49 |
50 |
51 |
52 |
53 |
54 | Page1 here
55 |
58 |
59 |
60 | );
61 | }
62 |
63 | // #region statics
64 | Page1.displayName = 'Page1';
65 | // #endregion
66 |
67 | // #region redux
68 | const mapStateToProps = state => ({
69 | isAuthenticated: state.userAuth.isAuthenticated,
70 | });
71 |
72 | const mapDispatchToProps = dispatch => {
73 | return {
74 | ...bindActionCreators({ ...userAuthActions }, dispatch),
75 | };
76 | };
77 | // #endregion
78 |
79 | export default compose(
80 | connect(
81 | mapStateToProps,
82 | mapDispatchToProps,
83 | ),
84 | )(Page1);
85 |
--------------------------------------------------------------------------------
/pages/private1.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import React from 'react';
4 | import Head from 'next/head';
5 | import styled from 'styled-components';
6 | import { connect } from 'react-redux';
7 | import { bindActionCreators, compose } from 'redux';
8 | import { useRouter } from 'next/router';
9 | import Container from 'reactstrap/lib/Container';
10 | import Button from 'reactstrap/lib/Button';
11 | import * as userAuthActions from '../redux/modules/userAuth';
12 | import Header from '../components/header';
13 | import Private from '../components/privateRoute';
14 | import appConfig from '../config/appConfig';
15 |
16 | // #region types
17 | type Props = {
18 | // userAuth:
19 | isAuthenticated: boolean,
20 | disconnectUser: () => any,
21 | };
22 | // #endregion
23 |
24 | // #region constants
25 | const {
26 | og: { description: ogDescription, locale: ogLang },
27 | seo: { description: seoDescription },
28 | } = appConfig.metas;
29 | // #endregion
30 |
31 | // #region styled components
32 | const Page = styled.div``;
33 | // #endregion
34 |
35 | export function Private1({ }: Props) {
36 | const { asPath, push } = useRouter();
37 |
38 | // #region callbacks
39 | const goBackHome = (event: SyntheticEvent<>): void => {
40 | event && event.preventDefault();
41 | push('/');
42 | };
43 | // #endregion
44 |
45 | return (
46 |
47 |
48 |
49 | Private page1
50 |
51 |
52 |
53 |
54 |
55 |
56 | Private1 here
57 |
60 |
61 |
62 |
63 | );
64 | }
65 |
66 | // #region statics
67 | Private1.displayName = 'Private1';
68 | // #nedregion
69 |
70 | // #region redux
71 | const mapStateToProps = state => ({
72 | isAuthenticated: state.userAuth.isAuthenticated,
73 | });
74 |
75 | const mapDispatchToProps = dispatch => {
76 | return {
77 | ...bindActionCreators({ ...userAuthActions }, dispatch),
78 | };
79 | };
80 | // #endregion
81 |
82 | export default compose(
83 | connect(
84 | mapStateToProps,
85 | mapDispatchToProps,
86 | ),
87 | )(Private1);
88 |
--------------------------------------------------------------------------------
/preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MacKentoch/react-redux-nextjs-bootstrap-pwa-starter/a93fb9713100e8ecdfbbcca26e669ff0286f7a70/preview.png
--------------------------------------------------------------------------------
/redux/middleware/fetchMiddleware.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | // #region imports
4 | import axios from 'axios';
5 | // #endregion
6 |
7 | // #region constants
8 | export const FETCH_MOCK = 'FETCH_MOCK';
9 | export const FETCH = 'FETCH';
10 | // #endregion
11 |
12 | //
13 | // FETCH_MOCK mode
14 | // in any action just add fetch object like:
15 | // {
16 | // fetch: {
17 | // type: 'FETCH_MOCK',
18 | // actionTypes: {
19 | // request: 'TYPE_FOR_REQUEST',
20 | // success: 'TYPE_FOR_RECEIVED',
21 | // fail: 'TYPE_FOR_ERROR',
22 | // },
23 | // mockResult: any
24 | // }
25 | // }
26 | //
27 |
28 | // FETCH mode
29 | // in any action just add fetch object like:
30 | // {
31 | // fetch: {
32 | // type: 'FETCH',
33 | // actionTypes: {
34 | // request: 'TYPE_FOR_REQUEST',
35 | // success: 'TYPE_FOR_RECEIVED',
36 | // fail: 'TYPE_FOR_ERROR',
37 | // },
38 | // url: 'an url',
39 | // method: 'get', // lower case, one of 'get', 'post'...
40 | // headers: {} // OPTIONAL CONTENT like: data: { someprop: 'value ...}
41 | // options: {} // OPTIONAL CONTENT like: Authorization: 'Bearer _A_TOKEN_'
42 | // }
43 | // }
44 |
45 | const fetchMiddleware = (store: any) => (next: (action: any) => any) => (
46 | action: any,
47 | ) => {
48 | if (!action.fetch) {
49 | return next(action);
50 | }
51 |
52 | if (
53 | !action.fetch.type ||
54 | !action.fetch.type === FETCH_MOCK ||
55 | !action.fetch.type === FETCH
56 | ) {
57 | return next(action);
58 | }
59 |
60 | if (!action.fetch.actionTypes) {
61 | return next(action);
62 | }
63 |
64 | /**
65 | * fetch mock
66 | * @type {[type]}
67 | */
68 | if (action.fetch.type === FETCH_MOCK) {
69 | if (!action.fetch.mockResult) {
70 | throw new Error(
71 | 'Fetch middleware require a mockResult payload when type is "FETCH_MOCK"',
72 | );
73 | }
74 |
75 | const {
76 | actionTypes: { request, success },
77 | mockResult,
78 | } = action.fetch;
79 |
80 | // request
81 | store.dispatch({ type: request });
82 |
83 | // received successful for mock
84 | return Promise.resolve(
85 | store.dispatch({
86 | type: success,
87 | payload: {
88 | status: 200,
89 | data: mockResult,
90 | },
91 | }),
92 | );
93 | }
94 |
95 | if (action.fetch.type === FETCH) {
96 | const {
97 | actionTypes: { request, success, fail },
98 | url,
99 | method,
100 | headers,
101 | options,
102 | } = action.fetch;
103 |
104 | // request
105 | store.dispatch({ type: request });
106 |
107 | // fetch server (success or fail)
108 | // returns a Promise
109 | return axios
110 | .request({
111 | method,
112 | url,
113 | withCredentials: true,
114 | headers: {
115 | Accept: 'application/json',
116 | 'Content-Type': 'application/json',
117 | 'Acces-Control-Allow-Origin': '*',
118 | ...headers,
119 | },
120 | ...options,
121 | })
122 | .then(data => store.dispatch({ type: success, payload: data }))
123 | .catch(err => {
124 | store.dispatch({ type: fail, error: err.response });
125 | return Promise.reject(err.response);
126 | });
127 | }
128 | return next(action);
129 | };
130 |
131 | export default fetchMiddleware;
132 |
--------------------------------------------------------------------------------
/redux/modules/fakeModuleWithFetch.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import { format } from 'date-fns';
4 | import { type Dispatch } from 'redux';
5 | import fakeData from '../../mock/fakeAPI.json';
6 | import appConfig from '../../config/appConfig';
7 | import { getLocationOrigin } from '../../utils/fetchTools';
8 | import { type State } from '../../types/redux/modules/fakeModuleWithFetch';
9 |
10 | // #region CONSTANTS
11 | const REQUEST_FAKE_FETCH = 'REQUEST_FAKE_FETCH';
12 | const RECEIVED_FAKE_FETCH = 'RECEIVED_FAKE_FETCH';
13 | const ERROR_FAKE_FETCH = 'ERROR_FAKE_FETCH';
14 | // #endregion
15 |
16 | // #region types
17 | type PartialState = $Shape;
18 | type ActionType =
19 | | 'REQUEST_FAKE_FETCH'
20 | | 'RECEIVED_FAKE_FETCH'
21 | | 'ERROR_FAKE_FETCH';
22 |
23 | type Action = {
24 | type: ActionType,
25 |
26 | isFetching?: boolean,
27 | actionTime?: string,
28 | data?: { ...any } | Array,
29 | payload?: any,
30 | error: { ...any },
31 | } & PartialState;
32 | // #endregion
33 |
34 | // #region REDUCER
35 |
36 | // #region initial state
37 | const initialState: State = {
38 | isFetching: false,
39 | actionTime: '',
40 | data: [],
41 | error: {},
42 | };
43 | // #endregion
44 |
45 | // #region reducer
46 | export default function(state: State = initialState, action: Action): State {
47 | const currentTime = format(new Date());
48 |
49 | switch (action.type) {
50 | // #region request (fake)
51 | case REQUEST_FAKE_FETCH: {
52 | return {
53 | ...state,
54 | actionTime: currentTime,
55 | isFetching: true,
56 | };
57 | }
58 |
59 | case RECEIVED_FAKE_FETCH: {
60 | // $FlowIgnore
61 | const { data } = action.payload;
62 |
63 | return {
64 | ...state,
65 | actionTime: currentTime,
66 | isFetching: false,
67 | data: [...data],
68 | };
69 | }
70 |
71 | case ERROR_FAKE_FETCH: {
72 | return {
73 | ...state,
74 | actionTime: currentTime,
75 | isFetching: false,
76 | error: action.error ? { ...action.error } : {},
77 | };
78 | }
79 | // #endregion
80 |
81 | default:
82 | return state;
83 | }
84 | }
85 | // #endregion
86 |
87 | // #endregion
88 |
89 | // #region ACTIONS CREATORS
90 |
91 | // #region fetch example
92 | function fakeFetch() {
93 | return (dispatch: Dispatch) => {
94 | const shouldFetchMock = appConfig.DEV_MODE;
95 | const fetchType = shouldFetchMock ? 'FETCH_MOCK' : 'FETCH';
96 | const mockResult = fakeData;
97 |
98 | const url = `${getLocationOrigin()}/${appConfig.api.fakeEndPoint}`;
99 | const method = 'get';
100 | const options = {};
101 |
102 | // fetch middleware
103 | // -> you handles pure front or with back-end asyncs just by disaptching a single object
104 | // -> just change config: appConfig.DEV_MODE
105 | return Promise.resolve(
106 | dispatch({
107 | // type name is not important here since fetchMiddleware will intercept this action:
108 | type: 'FETCH_MIDDLEWARE',
109 | // here are fetch middleware props:
110 | fetch: {
111 | type: fetchType,
112 | actionTypes: {
113 | request: REQUEST_FAKE_FETCH,
114 | success: RECEIVED_FAKE_FETCH,
115 | fail: ERROR_FAKE_FETCH,
116 | },
117 | // props only used when type = FETCH_MOCK:
118 | mockResult,
119 | // props only used when type = FETCH:
120 | url,
121 | method,
122 | options,
123 | },
124 | }),
125 | );
126 | };
127 | }
128 |
129 | export function fakeFetchIfNeeded() {
130 | return (
131 | dispatch: Dispatch,
132 | getState: () => { fakeModuleWithFetch: State },
133 | ) => {
134 | if (shouldFakeFetch(getState())) {
135 | return dispatch(fakeFetch());
136 | }
137 | return Promise.resolve();
138 | };
139 | }
140 |
141 | function shouldFakeFetch(state: { fakeModuleWithFetch: State }): boolean {
142 | const { isFetching } = state.fakeModuleWithFetch;
143 | return !isFetching;
144 | }
145 | // #endregion
146 |
147 | // #endregion
148 |
--------------------------------------------------------------------------------
/redux/modules/persistStore.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | // #region imports
4 | import { REHYDRATE } from 'redux-persist/constants';
5 | // #endregion
6 |
7 | const initialState = {};
8 |
9 | /**
10 | * redux-persist reducer rehydration logic
11 | *
12 | * NOTE: you need to write on your own!!!
13 | *
14 | * @export
15 | * @param {any} [state=initialState] state
16 | * @param {any} action action
17 | * @returns {any} state
18 | */
19 | export default function(state: any = initialState, action: any) {
20 | switch (action.type) {
21 | case REHYDRATE: {
22 | const incoming = action.payload.myReducer;
23 | if (incoming) {
24 | return {
25 | ...state,
26 | ...incoming,
27 | // specialKey: processSpecial(incoming.specialKey)
28 | };
29 | }
30 | return state;
31 | }
32 |
33 | default:
34 | return state;
35 | }
36 | }
37 | // #endregion
38 |
--------------------------------------------------------------------------------
/redux/modules/reducers.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import { combineReducers } from 'redux';
4 | import fakeModuleWithFetch from './fakeModuleWithFetch';
5 | import userAuth from './userAuth';
6 |
7 | export const reducers = {
8 | fakeModuleWithFetch,
9 | userAuth,
10 | };
11 |
12 | export default combineReducers({
13 | ...reducers,
14 | });
15 |
--------------------------------------------------------------------------------
/redux/store/configureStore.dev.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import { createStore, applyMiddleware } from 'redux';
4 | import { createLogger } from 'redux-logger';
5 | import thunkMiddleware from 'redux-thunk';
6 | import { composeWithDevTools } from 'redux-devtools-extension';
7 | import { persistReducer } from 'redux-persist';
8 | import storage from 'redux-persist/lib/storage'; // defaults to localStorage for web
9 | import reducer from '../modules/reducers';
10 | import fetchMiddleware from '../middleware/fetchMiddleware';
11 |
12 | // #region configure logger middleware
13 | const loggerMiddleware = createLogger({
14 | level: 'info',
15 | collapsed: true,
16 | });
17 | // #endregion
18 |
19 | // #region createStore : enhancer
20 | const enhancer = composeWithDevTools(
21 | applyMiddleware(thunkMiddleware, fetchMiddleware, loggerMiddleware),
22 | );
23 | // #endregion
24 |
25 | // #region persisted reducer
26 | const persistConfig = {
27 | key: 'root',
28 | storage,
29 | };
30 |
31 | const persistedReducer = persistReducer(persistConfig, reducer);
32 | // #endregion
33 |
34 | // #region store initialization
35 | export default function configureStore(initialState) {
36 | const store = createStore(persistedReducer, initialState, enhancer);
37 |
38 | // we won't need PersistGate since server rendered with nextJS:
39 | // const persistor = persistStore(store);
40 | // return { store, persistor };
41 |
42 | return store;
43 | }
44 | // #endregion
45 |
--------------------------------------------------------------------------------
/redux/store/configureStore.js:
--------------------------------------------------------------------------------
1 | /* eslint no-process-env:0 */
2 |
3 | if (process.env.NODE_ENV === 'production') {
4 | module.exports = require('./configureStore.prod');
5 | } else {
6 | module.exports = require('./configureStore.dev');
7 | }
8 |
--------------------------------------------------------------------------------
/redux/store/configureStore.prod.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import { createStore, compose, applyMiddleware } from 'redux';
4 | import thunkMiddleware from 'redux-thunk';
5 | import { persistReducer } from 'redux-persist';
6 | import storage from 'redux-persist/lib/storage'; // defaults to localStorage for web
7 | import reducer from '../modules/reducers';
8 | import fetchMiddleware from '../middleware/fetchMiddleware';
9 |
10 | // #region createStore : enhancer
11 | const enhancer = compose(applyMiddleware(thunkMiddleware, fetchMiddleware));
12 | // #endregion
13 |
14 | // #region persisted reducer
15 | const persistConfig = {
16 | key: 'root',
17 | storage,
18 | };
19 |
20 | const persistedReducer = persistReducer(persistConfig, reducer);
21 | // #endregion
22 |
23 | // #region store initialization
24 | export default function configureStore(initialState) {
25 | const store = createStore(persistedReducer, initialState, enhancer);
26 |
27 | // we won't need PersistGate since server rendered with nextJS:
28 | // const persistor = persistStore(store);
29 | // return { store, persistor };
30 |
31 | return store;
32 | }
33 | // #endregion
34 |
--------------------------------------------------------------------------------
/server/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | /* eslint-disable no-process-env */
3 |
4 | // #region imports
5 | const express = require('express');
6 | const { join } = require('path');
7 | const chalk = require('chalk');
8 | const next = require('next');
9 | // #endregion
10 |
11 | // #region variables/constants initialization
12 | const port = parseInt(process.env.PORT, 10) || 3000;
13 | const ipAdress = 'localhost';
14 | const dev = process.env.NODE_ENV !== 'production';
15 | const app = next({ dev });
16 | const handle = app.getRequestHandler();
17 | // #endregion
18 |
19 | (async () => {
20 | try {
21 | await app.prepare();
22 |
23 | const server = express();
24 |
25 | // example of custom request handlers:
26 | // server.get('/a', (req, res) => app.render(req, res, '/b', req.query));
27 | // server.get('/b', (req, res) => app.render(req, res, '/a', req.query));
28 |
29 | // handles service worker file request:
30 | server.get('/service-worker.js', (req, res) =>
31 | res.sendFile(join('.next', '/service-worker.js'), { root: '.' }),
32 | );
33 |
34 | // default request handler by next handler:
35 | server.get('*', (req, res) => handle(req, res));
36 |
37 | server.listen(port, err => {
38 | if (err) {
39 | throw err;
40 | }
41 |
42 | /* eslint-disable no-console */
43 | console.log(`
44 | =====================================================
45 | -> Server (${chalk.bgBlue(
46 | 'NextJS PWA',
47 | )}) 🏃 (running) on ${chalk.green(ipAdress)}:${chalk.green(`${port}`)}
48 | =====================================================
49 | `);
50 | /* eslint-enable no-console */
51 | });
52 | } catch (error) {
53 | console.error(error);
54 | }
55 | })();
56 |
--------------------------------------------------------------------------------
/server/static.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | // #region imports
4 | const express = require('express');
5 | const path = require('path');
6 | const chalk = require('chalk');
7 | // #endregion
8 |
9 | // #region constants
10 | const port = parseInt(process.env.PORT, 10) || 3000;
11 | const ipAdress = 'localhost';
12 | const DOCS_PATH = '../out/';
13 | // #endregion
14 |
15 | (async () => {
16 | try {
17 | const app = express();
18 |
19 | app.set('port', port);
20 | app.set('ipAdress', ipAdress);
21 |
22 | app.use(
23 | '/static',
24 | express.static(path.join(__dirname, DOCS_PATH, 'static/')),
25 | );
26 |
27 | app.use(
28 | '/_next',
29 | express.static(path.join(__dirname, DOCS_PATH, '_next/')),
30 | );
31 |
32 | app.get('/*', (req, res) =>
33 | res.sendFile(path.join(__dirname, DOCS_PATH, 'index.html')),
34 | );
35 |
36 | app.listen(port, err => {
37 | if (err) {
38 | throw err;
39 | }
40 |
41 | /* eslint-disable no-console */
42 | console.log(`
43 | =====================================================
44 | -> Server (${chalk.bgBlue('NextJS PWA')}) 🏃 (running) on ${chalk.green(
45 | ipAdress,
46 | )}:${chalk.green(`${port}`)}
47 | =====================================================
48 | `);
49 | /* eslint-enable no-console */
50 | });
51 |
52 | return app;
53 | } catch (error) {
54 | console.log('server error: ', error);
55 | }
56 | })();
57 |
--------------------------------------------------------------------------------
/static/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MacKentoch/react-redux-nextjs-bootstrap-pwa-starter/a93fb9713100e8ecdfbbcca26e669ff0286f7a70/static/android-chrome-192x192.png
--------------------------------------------------------------------------------
/static/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MacKentoch/react-redux-nextjs-bootstrap-pwa-starter/a93fb9713100e8ecdfbbcca26e669ff0286f7a70/static/android-chrome-512x512.png
--------------------------------------------------------------------------------
/static/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MacKentoch/react-redux-nextjs-bootstrap-pwa-starter/a93fb9713100e8ecdfbbcca26e669ff0286f7a70/static/apple-touch-icon.png
--------------------------------------------------------------------------------
/static/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #da532c
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/static/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MacKentoch/react-redux-nextjs-bootstrap-pwa-starter/a93fb9713100e8ecdfbbcca26e669ff0286f7a70/static/favicon-16x16.png
--------------------------------------------------------------------------------
/static/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MacKentoch/react-redux-nextjs-bootstrap-pwa-starter/a93fb9713100e8ecdfbbcca26e669ff0286f7a70/static/favicon-32x32.png
--------------------------------------------------------------------------------
/static/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-redux-nextjs-bootstrap-pwa-starter",
3 | "short_name": "Next-PWA",
4 | "icons": [
5 | {
6 | "src": "android-chrome-192x192.png",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "android-chrome-512x512.png",
12 | "sizes": "512x512",
13 | "type": "image/png"
14 | },
15 | {
16 | "src": "splashscreen-icon-512x512.png",
17 | "sizes": "512x512",
18 | "type": "image/png"
19 | }
20 | ],
21 | "theme_color": "#1967be",
22 | "background_color": "#1967be",
23 | "display": "standalone",
24 | "start_url": "/"
25 | }
26 |
--------------------------------------------------------------------------------
/static/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MacKentoch/react-redux-nextjs-bootstrap-pwa-starter/a93fb9713100e8ecdfbbcca26e669ff0286f7a70/static/mstile-150x150.png
--------------------------------------------------------------------------------
/static/robot.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 |
--------------------------------------------------------------------------------
/static/splashscreen-icon-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MacKentoch/react-redux-nextjs-bootstrap-pwa-starter/a93fb9713100e8ecdfbbcca26e669ff0286f7a70/static/splashscreen-icon-512x512.png
--------------------------------------------------------------------------------
/style/globalStyle.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import { createGlobalStyle } from 'styled-components';
3 |
4 | export const GlobalStyle = createGlobalStyle`
5 | html, body {
6 | margin: 0;
7 | height: 100%;
8 | -webkit-font-smoothing: antialiased;
9 | }
10 |
11 | .navbar {
12 | border-radius: 0;
13 | }
14 | `;
15 |
16 | export default GlobalStyle;
17 |
--------------------------------------------------------------------------------
/types/nextjs/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | export type GetInitialPropsParams = {
4 | pathname?: string, // path section of URL
5 | query?: Object, // query string section of URL parsed as an object
6 | asPath?: string, // the actual url path
7 | req?: Object, // HTTP request object (server only)
8 | res?: Object, // HTTP response object (server only)
9 | jsonPageRes?: Object, // Fetch Response object (client only)
10 | err?: Object, // Error object if any error is encountered during the rendering
11 | isServer?: boolean, // Is rendering server side flag
12 | store?: Object, // redux store passed
13 | };
14 |
15 | // //NOTE: useRouter hook = (alternative to withRouter HOC):
16 | // const {
17 | // // `String` of the actual path (including the query) shows in the browser
18 | // asPath,
19 | // // `String` Current route
20 | // route
21 | // // `Function` navigate back
22 | // back,
23 | // // `Function` prefetch a specific page
24 | // prefetch,
25 | // // `Function` navigate to a specific page (adds entry to history)
26 | // push,
27 | // // `Function` navigate to a specific page
28 | // replace,
29 | // // `Object` current query
30 | // query,
31 | // // `Function` Reload current page
32 | // reload
33 | // } = useRouter()
34 |
--------------------------------------------------------------------------------
/types/redux/modules/fakeModuleWithFetch/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | export type State = {
4 | isFetching: boolean,
5 | actionTime: string,
6 | data: Array,
7 | error: ?any,
8 |
9 | ...any,
10 | };
11 |
12 | export type FakeModuleWithFetchActions = {
13 | fakeFetchIfNeeded: () => Promise,
14 | };
15 |
--------------------------------------------------------------------------------
/types/redux/modules/userAuth/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | export type State = {
4 | ...any,
5 | };
6 |
7 | export type FakeModuleWithFetchActions = {
8 | fakeFetchIfNeeded: () => Promise,
9 | };
10 |
--------------------------------------------------------------------------------
/types/redux/redux-thunk/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | export type PromiseAction = Promise;
4 |
5 | export type ThunkAction = (
6 | dispatch: Dispatch,
7 | getState: GetState,
8 | ) => any;
9 |
10 | export type Dispatch = (
11 | action: Action | ThunkAction | PromiseAction,
12 | ) => any;
13 |
14 | export type GetState = () => { ...any } & State;
15 |
--------------------------------------------------------------------------------
/typings.json:
--------------------------------------------------------------------------------
1 | {
2 | "globalDevDependencies": {},
3 | "globalDependencies": {
4 | "axios": "registry:dt/axios#0.9.1+20161016142654",
5 | "bootstrap": "registry:dt/bootstrap#3.3.5+20160726204056",
6 | "classnames": "registry:dt/classnames#0.0.0+20161113184211",
7 | "localforage": "registry:dt/localforage#0.0.0+20161102134758",
8 | "modernizr": "registry:dt/modernizr#3.3.0+20170302103631",
9 | "popper.js": "registry:dt/popper.js#0.4.0+20161005184000",
10 | "react-ga": "registry:dt/react-ga#1.4.1+20160930194758",
11 | "recompose": "registry:dt/recompose#0.22.0+20170306203754",
12 | "redux": "registry:dt/redux#3.5.2+20160703092728",
13 | "redux-thunk": "registry:dt/redux-thunk#2.1.0+20160703120921",
14 | "reselect": "registry:dt/reselect#2.0.2+20160510002910"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/typings/globals/axios/typings.json:
--------------------------------------------------------------------------------
1 | {
2 | "resolution": "main",
3 | "tree": {
4 | "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/e6215d4444ef44a38cd760817e955ce19b522032/axios/axios.d.ts",
5 | "raw": "registry:dt/axios#0.9.1+20161016142654",
6 | "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/e6215d4444ef44a38cd760817e955ce19b522032/axios/axios.d.ts"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/typings/globals/bootstrap/index.d.ts:
--------------------------------------------------------------------------------
1 | // Generated by typings
2 | // Source: https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/e94e9a86308b7306bb74a973c4e18f37895f7298/bootstrap/index.d.ts
3 | interface ModalOptions {
4 | backdrop?: boolean|string;
5 | keyboard?: boolean;
6 | show?: boolean;
7 | remote?: string;
8 | }
9 |
10 | interface ModalOptionsBackdropString {
11 | backdrop?: string; // for "static"
12 | keyboard?: boolean;
13 | show?: boolean;
14 | remote?: string;
15 | }
16 |
17 | interface ScrollSpyOptions {
18 | offset?: number;
19 | target?: string;
20 | }
21 |
22 | interface TooltipOptions {
23 | animation?: boolean;
24 | html?: boolean;
25 | placement?: string | Function;
26 | selector?: string;
27 | title?: string | Function;
28 | trigger?: string;
29 | template?: string;
30 | delay?: number | Object;
31 | container?: string | boolean;
32 | viewport?: string | Function | Object;
33 | }
34 |
35 | interface PopoverOptions {
36 | animation?: boolean;
37 | html?: boolean;
38 | placement?: string | Function;
39 | selector?: string;
40 | trigger?: string;
41 | title?: string | Function;
42 | template?: string;
43 | content?: any;
44 | delay?: number | Object;
45 | container?: string | boolean;
46 | viewport?: string | Function | Object;
47 | }
48 |
49 | interface CollapseOptions {
50 | parent?: any;
51 | toggle?: boolean;
52 | }
53 |
54 | interface CarouselOptions {
55 | interval?: number;
56 | pause?: string;
57 | wrap?: boolean;
58 | keybord?: boolean;
59 | }
60 |
61 | interface TypeaheadOptions {
62 | source?: any;
63 | items?: number;
64 | minLength?: number;
65 | matcher?: (item: any) => boolean;
66 | sorter?: (items: any[]) => any[];
67 | updater?: (item: any) => any;
68 | highlighter?: (item: any) => string;
69 | }
70 |
71 | interface AffixOptions {
72 | offset?: number | Function | Object;
73 | target?: any;
74 | }
75 |
76 | interface TransitionEventNames {
77 | end: string;
78 | }
79 |
80 | interface JQuery {
81 | modal(options?: ModalOptions): JQuery;
82 | modal(options?: ModalOptionsBackdropString): JQuery;
83 | modal(command: string): JQuery;
84 |
85 | dropdown(): JQuery;
86 | dropdown(command: string): JQuery;
87 |
88 | scrollspy(command: string): JQuery;
89 | scrollspy(options?: ScrollSpyOptions): JQuery;
90 |
91 | tab(): JQuery;
92 | tab(command: string): JQuery;
93 |
94 | tooltip(options?: TooltipOptions): JQuery;
95 | tooltip(command: string): JQuery;
96 |
97 | popover(options?: PopoverOptions): JQuery;
98 | popover(command: string): JQuery;
99 |
100 | alert(): JQuery;
101 | alert(command: string): JQuery;
102 |
103 | button(): JQuery;
104 | button(command: string): JQuery;
105 |
106 | collapse(options?: CollapseOptions): JQuery;
107 | collapse(command: string): JQuery;
108 |
109 | carousel(options?: CarouselOptions): JQuery;
110 | carousel(command: string): JQuery;
111 |
112 | typeahead(options?: TypeaheadOptions): JQuery;
113 |
114 | affix(options?: AffixOptions): JQuery;
115 |
116 | emulateTransitionEnd(duration: number): JQuery;
117 | }
118 |
119 | interface JQuerySupport {
120 | transition: boolean | TransitionEventNames;
121 | }
122 |
123 | declare module "bootstrap" {
124 | }
125 |
--------------------------------------------------------------------------------
/typings/globals/bootstrap/typings.json:
--------------------------------------------------------------------------------
1 | {
2 | "resolution": "main",
3 | "tree": {
4 | "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/e94e9a86308b7306bb74a973c4e18f37895f7298/bootstrap/index.d.ts",
5 | "raw": "registry:dt/bootstrap#3.3.5+20160726204056",
6 | "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/e94e9a86308b7306bb74a973c4e18f37895f7298/bootstrap/index.d.ts"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/typings/globals/classnames/index.d.ts:
--------------------------------------------------------------------------------
1 | // Generated by typings
2 | // Source: https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/cf21303edea9abd919ecdab84698c6ab8513b1c2/classnames/index.d.ts
3 | declare type ClassValue = string | number | ClassDictionary | ClassArray | undefined | null | false;
4 |
5 | interface ClassDictionary {
6 | [id: string]: boolean | undefined | null;
7 | }
8 |
9 | interface ClassArray extends Array { }
10 |
11 | interface ClassNamesFn {
12 | (...classes: ClassValue[]): string;
13 | }
14 |
15 | declare var classNames: ClassNamesFn;
16 |
17 | declare module "classnames" {
18 | export = classNames
19 | }
20 |
--------------------------------------------------------------------------------
/typings/globals/classnames/typings.json:
--------------------------------------------------------------------------------
1 | {
2 | "resolution": "main",
3 | "tree": {
4 | "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/cf21303edea9abd919ecdab84698c6ab8513b1c2/classnames/index.d.ts",
5 | "raw": "registry:dt/classnames#0.0.0+20161113184211",
6 | "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/cf21303edea9abd919ecdab84698c6ab8513b1c2/classnames/index.d.ts"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/typings/globals/localforage/index.d.ts:
--------------------------------------------------------------------------------
1 | // Generated by typings
2 | // Source: https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/20aff2bdb6082f4f51eac9d4e6e64a2be786e346/localForage/localForage.d.ts
3 | interface LocalForageOptions {
4 | driver?: string | LocalForageDriver | LocalForageDriver[];
5 |
6 | name?: string;
7 |
8 | size?: number;
9 |
10 | storeName?: string;
11 |
12 | version?: number;
13 |
14 | description?: string;
15 | }
16 |
17 | interface LocalForageDbMethods {
18 | getItem(key: string): Promise;
19 | getItem(key: string, callback: (err: any, value: T) => void): void;
20 |
21 | setItem(key: string, value: T): Promise;
22 | setItem(key: string, value: T, callback: (err: any, value: T) => void): void;
23 |
24 | removeItem(key: string): Promise;
25 | removeItem(key: string, callback: (err: any) => void): void;
26 |
27 | clear(): Promise;
28 | clear(callback: (err: any) => void): void;
29 |
30 | length(): Promise;
31 | length(callback: (err: any, numberOfKeys: number) => void): void;
32 |
33 | key(keyIndex: number): Promise;
34 | key(keyIndex: number, callback: (err: any, key: string) => void): void;
35 |
36 | keys(): Promise;
37 | keys(callback: (err: any, keys: string[]) => void): void;
38 |
39 | iterate(iteratee: (value: any, key: string, iterationNumber: number) => any): Promise;
40 | iterate(iteratee: (value: any, key: string, iterationNumber: number) => any,
41 | callback: (err: any, result: any) => void): void;
42 | }
43 |
44 | interface LocalForageDriverSupportFunc {
45 | (): Promise;
46 | }
47 |
48 | interface LocalForageDriver extends LocalForageDbMethods {
49 | _driver: string;
50 |
51 | _initStorage(options: LocalForageOptions): void;
52 |
53 | _support?: boolean | LocalForageDriverSupportFunc;
54 | }
55 |
56 | interface LocalForageSerializer {
57 | serialize(value: T | ArrayBuffer | Blob, callback: (value: string, error: any) => void): void;
58 |
59 | deserialize(value: string): T | ArrayBuffer | Blob;
60 |
61 | stringToBuffer(serializedString: string): ArrayBuffer;
62 |
63 | bufferToString(buffer: ArrayBuffer): string;
64 | }
65 |
66 | interface LocalForage extends LocalForageDbMethods {
67 | LOCALSTORAGE: string;
68 | WEBSQL: string;
69 | INDEXEDDB: string;
70 |
71 | /**
72 | * Set and persist localForage options. This must be called before any other calls to localForage are made, but can be called after localForage is loaded.
73 | * If you set any config values with this method they will persist after driver changes, so you can call config() then setDriver()
74 | * @param {LocalForageOptions} options?
75 | */
76 | config(options: LocalForageOptions): boolean;
77 |
78 | /**
79 | * Create a new instance of localForage to point to a different store.
80 | * All the configuration options used by config are supported.
81 | * @param {LocalForageOptions} options
82 | */
83 | createInstance(options: LocalForageOptions): LocalForage;
84 |
85 | driver(): string;
86 | /**
87 | * Force usage of a particular driver or drivers, if available.
88 | * @param {string} driver
89 | */
90 | setDriver(driver: string | string[]): Promise;
91 | setDriver(driver: string | string[], callback: () => void, errorCallback: (error: any) => void): void;
92 | defineDriver(driver: LocalForageDriver): Promise;
93 | defineDriver(driver: LocalForageDriver, callback: () => void, errorCallback: (error: any) => void): void;
94 | /**
95 | * Return a particular driver
96 | * @param {string} driver
97 | */
98 | getDriver(driver: string): Promise;
99 |
100 | getSerializer(): Promise;
101 | getSerializer(callback: (serializer: LocalForageSerializer) => void): void;
102 |
103 | supports(driverName: string): boolean;
104 |
105 | ready(callback: () => void): void;
106 | ready(): Promise;
107 | }
108 |
109 | declare module "localforage" {
110 | let localforage: LocalForage;
111 | export = localforage;
112 | }
113 |
--------------------------------------------------------------------------------
/typings/globals/localforage/typings.json:
--------------------------------------------------------------------------------
1 | {
2 | "resolution": "main",
3 | "tree": {
4 | "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/20aff2bdb6082f4f51eac9d4e6e64a2be786e346/localForage/localForage.d.ts",
5 | "raw": "registry:dt/localforage#0.0.0+20161102134758",
6 | "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/20aff2bdb6082f4f51eac9d4e6e64a2be786e346/localForage/localForage.d.ts"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/typings/globals/modernizr/typings.json:
--------------------------------------------------------------------------------
1 | {
2 | "resolution": "main",
3 | "tree": {
4 | "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/f7ab322332c2a6e3d923402cec1db0811ea753f2/modernizr/index.d.ts",
5 | "raw": "registry:dt/modernizr#3.3.0+20170302103631",
6 | "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/f7ab322332c2a6e3d923402cec1db0811ea753f2/modernizr/index.d.ts"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/typings/globals/popper.js/index.d.ts:
--------------------------------------------------------------------------------
1 | // Generated by typings
2 | // Source: https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/80060c94ef549c077a011977c2b5461bd0fd8947/popper.js/index.d.ts
3 | declare namespace Popper {
4 | export interface PopperOptions {
5 | placement?: string;
6 | gpuAcceleration?: boolean;
7 | offset?: number;
8 | boundariesElement?: string | Element;
9 | boundariesPadding?: number;
10 | preventOverflowOrder?: ("left" | "right" | "top" | "bottom")[];
11 | flipBehavior?: string | string[];
12 | modifiers?: string[];
13 | modifiersIgnored?: string[];
14 | removeOnDestroy?: boolean;
15 | arrowElement?: string | Element;
16 | }
17 | export class Modifiers {
18 | applyStyle(data: Object): Object;
19 | shift(data: Object): Object;
20 | preventOverflow(data: Object): Object;
21 | keepTogether(data: Object): Object;
22 | flip(data: Object): Object;
23 | offset(data: Object): Object;
24 | arrow(data: Object): Object;
25 | }
26 | export interface Data {
27 | placement: string;
28 | offsets: {
29 | popper: {
30 | position: string;
31 | top: number;
32 | left: number;
33 | };
34 | };
35 | }
36 | }
37 |
38 | declare class Popper {
39 | public modifiers: Popper.Modifiers;
40 | public placement: string;
41 |
42 | constructor(reference: Element, popper: Element | Object, options?: Popper.PopperOptions);
43 |
44 | destroy(): void;
45 | update(): void;
46 | onCreate(cb: (data: Popper.Data) => void): this;
47 | onUpdate(cb: (data: Popper.Data) => void): this;
48 | parse(config: Object): Element;
49 | runModifiers(data: Object, modifiers: string[], ends: Function): void;
50 | isModifierRequired(): boolean;
51 | }
52 |
53 | declare module "popper.js" {
54 | export = Popper;
55 | }
56 |
--------------------------------------------------------------------------------
/typings/globals/popper.js/typings.json:
--------------------------------------------------------------------------------
1 | {
2 | "resolution": "main",
3 | "tree": {
4 | "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/80060c94ef549c077a011977c2b5461bd0fd8947/popper.js/index.d.ts",
5 | "raw": "registry:dt/popper.js#0.4.0+20161005184000",
6 | "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/80060c94ef549c077a011977c2b5461bd0fd8947/popper.js/index.d.ts"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/typings/globals/react-ga/index.d.ts:
--------------------------------------------------------------------------------
1 | // Generated by typings
2 | // Source: https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/724ceafbde3450d4afb3797c6c73141e862e75e0/react-ga/react-ga.d.ts
3 | declare namespace __reactGA {
4 | export interface EventArgs {
5 | category: string;
6 | action: string;
7 | label?: string;
8 | value?: number;
9 | nonInteraction?: boolean;
10 | }
11 |
12 | export interface InitializeOptions {
13 | debug?: boolean;
14 | }
15 |
16 | export interface FieldsObject {
17 | [i: string]: any;
18 | }
19 |
20 | export function initialize(trackingCode: string, options?: InitializeOptions): void;
21 | export function pageview(path: string): void;
22 | export function modalview(name: string): void;
23 | export function event(args: EventArgs): void;
24 | export function set(fieldsObject: FieldsObject): void;
25 | }
26 |
27 | declare module 'react-ga' {
28 | export = __reactGA;
29 | }
30 |
--------------------------------------------------------------------------------
/typings/globals/react-ga/typings.json:
--------------------------------------------------------------------------------
1 | {
2 | "resolution": "main",
3 | "tree": {
4 | "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/724ceafbde3450d4afb3797c6c73141e862e75e0/react-ga/react-ga.d.ts",
5 | "raw": "registry:dt/react-ga#1.4.1+20160930194758",
6 | "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/724ceafbde3450d4afb3797c6c73141e862e75e0/react-ga/react-ga.d.ts"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/typings/globals/recompose/typings.json:
--------------------------------------------------------------------------------
1 | {
2 | "resolution": "main",
3 | "tree": {
4 | "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/66bd4290746e62eea9af862b46c22756c61e853a/recompose/index.d.ts",
5 | "raw": "registry:dt/recompose#0.22.0+20170306203754",
6 | "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/66bd4290746e62eea9af862b46c22756c61e853a/recompose/index.d.ts"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/typings/globals/redux-thunk/index.d.ts:
--------------------------------------------------------------------------------
1 | // Generated by typings
2 | // Source: https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/d417f687ab0c81ed72bffbc5dace5f643369bb70/redux-thunk/redux-thunk.d.ts
3 | declare namespace Redux {
4 | type ThunkAction = (dispatch: Dispatch, getState: () => S, extraArgument: E) => R;
5 |
6 | interface Dispatch {
7 | (asyncAction: ThunkAction): R;
8 | }
9 | }
10 |
11 | declare module "redux-thunk" {
12 | import { Middleware } from "redux";
13 |
14 | const thunk: Middleware & {
15 | withExtraArgument(extraArgument: any): Middleware;
16 | };
17 |
18 | export default thunk;
19 | }
20 |
--------------------------------------------------------------------------------
/typings/globals/redux-thunk/typings.json:
--------------------------------------------------------------------------------
1 | {
2 | "resolution": "main",
3 | "tree": {
4 | "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/d417f687ab0c81ed72bffbc5dace5f643369bb70/redux-thunk/redux-thunk.d.ts",
5 | "raw": "registry:dt/redux-thunk#2.1.0+20160703120921",
6 | "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/d417f687ab0c81ed72bffbc5dace5f643369bb70/redux-thunk/redux-thunk.d.ts"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/typings/globals/redux/typings.json:
--------------------------------------------------------------------------------
1 | {
2 | "resolution": "main",
3 | "tree": {
4 | "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/15ddcf312782faf9e7fdfe724a3a29382a5825d7/redux/redux.d.ts",
5 | "raw": "registry:dt/redux#3.5.2+20160703092728",
6 | "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/15ddcf312782faf9e7fdfe724a3a29382a5825d7/redux/redux.d.ts"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/typings/globals/reselect/index.d.ts:
--------------------------------------------------------------------------------
1 | // Generated by typings
2 | // Source: https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/49b1ac6a384fab428e0bad052b47f47ff8306193/reselect/index.d.ts
3 |
4 |
--------------------------------------------------------------------------------
/typings/globals/reselect/typings.json:
--------------------------------------------------------------------------------
1 | {
2 | "resolution": "main",
3 | "tree": {
4 | "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/49b1ac6a384fab428e0bad052b47f47ff8306193/reselect/index.d.ts",
5 | "raw": "registry:dt/reselect#2.0.2+20160510002910",
6 | "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/49b1ac6a384fab428e0bad052b47f47ff8306193/reselect/index.d.ts"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/typings/index.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 | ///
4 | ///
5 | ///
6 | ///
7 | ///
8 | ///
9 | ///
10 | ///
11 | ///
12 |
--------------------------------------------------------------------------------
/utils/fetchTools/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | /**
4 | * getLocationOrigin returns dynamically base url
5 | *
6 | * @export
7 | * @returns {string} location origin
8 | */
9 | export function getLocationOrigin(): string {
10 | if (typeof window === 'undefined') {
11 | return '';
12 | }
13 |
14 | if (!window.location.origin) {
15 | window.location.origin = `${window.location.protocol}//${
16 | window.location.hostname
17 | }${window.location.port ? ':' + window.location.port : ''}`;
18 | }
19 |
20 | return window.location.origin;
21 | }
22 |
--------------------------------------------------------------------------------
/utils/sw/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | export function registerBeforeinstallprompt(): void {
4 | if (typeof window !== 'undefined') {
5 | window.addEventListener('beforeinstallprompt', async e => {
6 | // beforeinstallprompt Event fired
7 | try {
8 | // e.userChoice will return a Promise.
9 | const choiceResult = await e.userChoice;
10 | if (choiceResult.outcome === 'dismissed') {
11 | /* eslint-disable no-console */
12 | console.log('User cancelled home screen install');
13 | /* eslint-enable no-console */
14 | } else {
15 | /* eslint-disable no-console */
16 | console.log('User added to home screen');
17 | /* eslint-enable no-console */
18 | }
19 | } catch (error) {
20 | /* eslint-disable no-console */
21 | console.error(
22 | 'user choice prompt promise failed to resolve, error: ',
23 | error,
24 | );
25 | /* eslint-enable no-console */
26 | }
27 | });
28 | }
29 | }
30 |
--------------------------------------------------------------------------------