├── .editorconfig ├── .gitignore ├── .travis.yml ├── .vscode ├── launch.json └── settings.json ├── CHANGELOG.md ├── README.md ├── assets ├── Paypal-button.png ├── Paypal-button@2x.png └── Paypal-button@3x.png ├── front ├── .editorconfig ├── .eslintrc.json ├── .nvmrc ├── .prettierignore ├── .prettierrc ├── .yarnclean ├── coverage │ ├── clover.xml │ ├── coverage-final.json │ ├── lcov-report │ │ ├── base.css │ │ ├── components │ │ │ ├── backToTop │ │ │ │ ├── BackToTop.tsx.html │ │ │ │ ├── backToTopButton │ │ │ │ │ ├── BackToTopButton.tsx.html │ │ │ │ │ ├── UpIcon.tsx.html │ │ │ │ │ ├── index.html │ │ │ │ │ └── styled │ │ │ │ │ │ ├── WithRightMargin.tsx.html │ │ │ │ │ │ └── index.html │ │ │ │ └── index.html │ │ │ ├── loadingContent │ │ │ │ ├── LoadingContent.tsx.html │ │ │ │ └── index.html │ │ │ ├── mainLayout │ │ │ │ ├── MainLayout.tsx.html │ │ │ │ └── index.html │ │ │ ├── navigation │ │ │ │ ├── NavigationBar.tsx.html │ │ │ │ ├── index.html │ │ │ │ └── index.ts.html │ │ │ └── scrollToTop │ │ │ │ ├── ScrollToTop.tsx.html │ │ │ │ └── index.html │ │ ├── config │ │ │ ├── index.html │ │ │ └── navigation.ts.html │ │ ├── contexts │ │ │ ├── auth │ │ │ │ ├── index.html │ │ │ │ └── index.tsx.html │ │ │ └── withDevTools │ │ │ │ ├── index.html │ │ │ │ └── index.ts.html │ │ ├── index.html │ │ ├── prettify.css │ │ ├── prettify.js │ │ ├── services │ │ │ └── auth │ │ │ │ ├── index.html │ │ │ │ └── index.ts.html │ │ ├── sort-arrow-sprite.png │ │ ├── sorter.js │ │ ├── src │ │ │ ├── components │ │ │ │ ├── backToTop │ │ │ │ │ ├── BackToTop.tsx.html │ │ │ │ │ ├── backToTopButton │ │ │ │ │ │ ├── BackToTopButton.tsx.html │ │ │ │ │ │ ├── UpIcon.tsx.html │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── styled │ │ │ │ │ │ │ ├── WithRightMargin.tsx.html │ │ │ │ │ │ │ └── index.html │ │ │ │ │ └── index.html │ │ │ │ ├── fadeInEntrance │ │ │ │ │ ├── FadeInEntrance.tsx.html │ │ │ │ │ ├── index.html │ │ │ │ │ ├── index.ts.html │ │ │ │ │ └── styled │ │ │ │ │ │ ├── FadeInDiv.tsx.html │ │ │ │ │ │ └── index.html │ │ │ │ ├── loadingContent │ │ │ │ │ ├── LoadingContent.tsx.html │ │ │ │ │ └── index.html │ │ │ │ ├── logoutRoute │ │ │ │ │ ├── LogoutRoute.tsx.html │ │ │ │ │ ├── index.html │ │ │ │ │ └── index.ts.html │ │ │ │ ├── mainLayout │ │ │ │ │ ├── MainLayout.tsx.html │ │ │ │ │ └── index.html │ │ │ │ ├── navigation │ │ │ │ │ ├── NavigationBar.tsx.html │ │ │ │ │ ├── index.html │ │ │ │ │ └── index.ts.html │ │ │ │ ├── privateRoute │ │ │ │ │ ├── PrivateRoute.tsx.html │ │ │ │ │ └── index.html │ │ │ │ └── scrollToTop │ │ │ │ │ ├── ScrollToTop.tsx.html │ │ │ │ │ ├── hooks │ │ │ │ │ └── useScrollToTopOnLocationChange │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── index.ts.html │ │ │ │ │ └── index.html │ │ │ ├── config │ │ │ │ ├── appConfig.ts.html │ │ │ │ ├── index.html │ │ │ │ └── navigation.ts.html │ │ │ ├── contexts │ │ │ │ ├── auth │ │ │ │ │ ├── hook │ │ │ │ │ │ ├── authContextHook.ts.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── index.html │ │ │ │ │ └── index.tsx.html │ │ │ │ └── withDevTools │ │ │ │ │ ├── index.html │ │ │ │ │ └── index.ts.html │ │ │ ├── mock │ │ │ │ ├── index.html │ │ │ │ └── userInfo.ts.html │ │ │ ├── pages │ │ │ │ ├── about │ │ │ │ │ ├── About.tsx.html │ │ │ │ │ └── index.html │ │ │ │ ├── home │ │ │ │ │ ├── Home.tsx.html │ │ │ │ │ ├── index.html │ │ │ │ │ └── styled │ │ │ │ │ │ ├── HomeInfo.tsx.html │ │ │ │ │ │ ├── LightNote.tsx.html │ │ │ │ │ │ ├── MainTitle.tsx.html │ │ │ │ │ │ └── index.html │ │ │ │ ├── login │ │ │ │ │ ├── Login.tsx.html │ │ │ │ │ └── index.html │ │ │ │ ├── pageNotFound │ │ │ │ │ ├── PageNotFound.tsx.html │ │ │ │ │ └── index.html │ │ │ │ └── protected │ │ │ │ │ ├── Protected.tsx.html │ │ │ │ │ └── index.html │ │ │ └── services │ │ │ │ ├── API │ │ │ │ ├── fetchTools.ts.html │ │ │ │ └── index.html │ │ │ │ └── auth │ │ │ │ ├── index.html │ │ │ │ └── index.ts.html │ │ └── test │ │ │ ├── index.html │ │ │ └── setupTests.ts.html │ └── lcov.info ├── index.html ├── jest.config.js ├── package.json ├── src │ ├── Root.tsx │ ├── components │ │ ├── backToTop │ │ │ ├── BackToTop.tsx │ │ │ ├── __tests__ │ │ │ │ ├── BackToTop.test.tsx │ │ │ │ └── __snapshots__ │ │ │ │ │ └── BackToTop.test.tsx.snap │ │ │ └── backToTopButton │ │ │ │ ├── BackToTopButton.tsx │ │ │ │ ├── UpIcon.tsx │ │ │ │ ├── __tests__ │ │ │ │ ├── UpIcon.test.tsx │ │ │ │ ├── __snapshots__ │ │ │ │ │ ├── UpIcon.test.tsx.snap │ │ │ │ │ └── backToTopButton.test.tsx.snap │ │ │ │ └── backToTopButton.test.tsx │ │ │ │ └── styled │ │ │ │ └── WithRightMargin.tsx │ │ ├── fadeInEntrance │ │ │ ├── FadeInEntrance.tsx │ │ │ ├── __tests__ │ │ │ │ ├── FadeInEntreance.spec.tsx │ │ │ │ └── __snapshots__ │ │ │ │ │ └── FadeInEntreance.spec.tsx.snap │ │ │ ├── index.ts │ │ │ └── styled │ │ │ │ └── FadeInDiv.tsx │ │ ├── loadingContent │ │ │ ├── LoadingContent.tsx │ │ │ ├── __tests__ │ │ │ │ ├── LoadingContent.test.tsx │ │ │ │ └── __snapshots__ │ │ │ │ │ └── LoadingContent.test.tsx.snap │ │ │ └── index.ts │ │ ├── logoutRoute │ │ │ ├── LogoutRoute.tsx │ │ │ ├── __tests__ │ │ │ │ ├── LogoutRoute.test.tsx │ │ │ │ └── __snapshots__ │ │ │ │ │ └── LogoutRoute.test.tsx.snap │ │ │ └── index.ts │ │ ├── mainLayout │ │ │ ├── MainLayout.tsx │ │ │ ├── __tests__ │ │ │ │ ├── MainLayout.test.tsx │ │ │ │ └── __snapshots__ │ │ │ │ │ └── MainLayout.test.tsx.snap │ │ │ └── index.ts │ │ ├── navigation │ │ │ ├── NavigationBar.tsx │ │ │ ├── __tests__ │ │ │ │ ├── NavigationBar.test.tsx │ │ │ │ └── __snapshots__ │ │ │ │ │ └── NavigationBar.test.tsx.snap │ │ │ └── index.ts │ │ ├── privateRoute │ │ │ ├── PrivateRoute.tsx │ │ │ ├── __tests__ │ │ │ │ ├── PrivateRoute.test.tsx │ │ │ │ └── __snapshots__ │ │ │ │ │ └── PrivateRoute.test.tsx.snap │ │ │ └── index.ts │ │ ├── scrollToTop │ │ │ ├── ScrollToTop.tsx │ │ │ ├── ___tests___ │ │ │ │ ├── ScrolToTop.test.tsx │ │ │ │ └── __snapshots__ │ │ │ │ │ └── ScrolToTop.test.tsx.snap │ │ │ ├── hooks │ │ │ │ └── useScrollToTopOnLocationChange │ │ │ │ │ └── index.ts │ │ │ └── index.ts │ │ └── withSuspense │ │ │ ├── WithSuspense.tsx │ │ │ └── index.ts │ ├── config │ │ ├── appConfig.ts │ │ └── navigation.ts │ ├── contexts │ │ ├── auth │ │ │ ├── hook │ │ │ │ ├── __tests__ │ │ │ │ │ └── authContextHook.spec.ts │ │ │ │ └── authContextHook.ts │ │ │ └── index.tsx │ │ └── withDevTools │ │ │ └── index.ts │ ├── index.html │ ├── index.tsx │ ├── mock │ │ └── userInfo.ts │ ├── pages │ │ ├── about │ │ │ ├── About.tsx │ │ │ ├── __tests__ │ │ │ │ ├── About.test.tsx │ │ │ │ └── __snapshots__ │ │ │ │ │ └── About.test.tsx.snap │ │ │ └── index.ts │ │ ├── home │ │ │ ├── Home.tsx │ │ │ ├── __tests__ │ │ │ │ ├── Home.test.tsx │ │ │ │ └── __snapshots__ │ │ │ │ │ └── Home.test.tsx.snap │ │ │ ├── index.ts │ │ │ └── styled │ │ │ │ ├── HomeInfo.tsx │ │ │ │ ├── LightNote.tsx │ │ │ │ └── MainTitle.tsx │ │ ├── index.ts │ │ ├── login │ │ │ ├── Login.tsx │ │ │ ├── __tests__ │ │ │ │ ├── Login.test.tsx │ │ │ │ └── __snapshots__ │ │ │ │ │ └── Login.test.tsx.snap │ │ │ └── index.ts │ │ ├── pageNotFound │ │ │ ├── PageNotFound.tsx │ │ │ ├── __tests__ │ │ │ │ ├── PageNotFound.test.tsx │ │ │ │ └── __snapshots__ │ │ │ │ │ └── PageNotFound.test.tsx.snap │ │ │ └── index.ts │ │ └── protected │ │ │ ├── Protected.tsx │ │ │ ├── __tests__ │ │ │ ├── Protected.test.tsx │ │ │ └── __snapshots__ │ │ │ │ └── Protected.test.tsx.snap │ │ │ └── index.ts │ ├── routes │ │ ├── MainRoutes.tsx │ │ └── routes.ts │ ├── services │ │ ├── API │ │ │ ├── example.ts │ │ │ └── fetchTools.ts │ │ ├── auth │ │ │ ├── __tests__ │ │ │ │ └── auth.test.ts │ │ │ └── index.ts │ │ ├── getLocationOrigin │ │ │ └── getLocationOrigin.ts │ │ └── sw │ │ │ └── registerServiceWorker.ts │ ├── statics │ │ ├── index-raw.html │ │ └── index.html │ ├── style │ │ └── GlobalStyles.ts │ └── types │ │ ├── auth │ │ └── index.d.ts │ │ └── user │ │ └── index.d.ts ├── test │ ├── __mocks__ │ │ └── fileMock.ts │ ├── mockedRouter.ts │ └── setupTests.ts ├── tsconfig.json ├── webpack.analyze.config.js ├── webpack.dev.config.js ├── webpack.hot.reload.config.js └── webpack.production.config.js ├── preview └── preview.png ├── scripts └── prepareIndexHtml.js ├── server ├── .editorconfig ├── .nvmrc ├── .prettierrc ├── .yarnclean ├── jest.config.js ├── out │ ├── index.js │ ├── lib │ │ └── expressServer.js │ └── middleware │ │ └── errors.js ├── package.json ├── src │ ├── index.ts │ ├── lib │ │ ├── asyncWrap.ts │ │ └── expressServer.ts │ └── middleware │ │ └── errors.ts ├── test │ └── setupTests.ts └── tsconfig.json └── typings ├── globals ├── axios │ ├── index.d.ts │ └── typings.json ├── classnames │ ├── index.d.ts │ └── typings.json ├── jest │ ├── index.d.ts │ └── typings.json ├── js-base64 │ ├── index.d.ts │ └── typings.json ├── modernizr │ ├── index.d.ts │ └── typings.json ├── node │ ├── index.d.ts │ └── typings.json ├── popper.js │ ├── index.d.ts │ └── typings.json ├── react-router-dom │ ├── index.d.ts │ └── typings.json └── recompose │ ├── index.d.ts │ └── typings.json ├── index.d.ts └── modules ├── enzyme ├── index.d.ts └── typings.json ├── react-bootstrap ├── index.d.ts └── typings.json ├── react-motion ├── index.d.ts └── typings.json └── react ├── index.d.ts └── typings.json /.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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .idea/ 3 | *.log 4 | 5 | build/ 6 | dist/ 7 | public/assets/ 8 | /coverage 9 | /.nyc_output 10 | 11 | # lock files 12 | # 13 | yarn.lock 14 | package-lock.json 15 | 16 | .DS_Store 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: node_js 3 | node_js: 4 | - 12 5 | sudo: false 6 | cache: 7 | npm: true 8 | directories: 9 | - node_modules 10 | services: 11 | - xvfb 12 | before_install: 13 | - cd front 14 | before_script: 15 | - npm install 16 | after_success: npm run test 17 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "node", 6 | "request": "launch", 7 | "name": "Launch minimalist prod like server", 8 | "program": "${workspaceRoot}/src/server/index.js" 9 | }, 10 | { 11 | "type": "node", 12 | "request": "launch", 13 | "name": "Launch hot reload server", 14 | "program": "${workspaceRoot}/server.hot.reload.js" 15 | }, 16 | { 17 | "type": "chrome", 18 | "request": "launch", 19 | "name": "Launch Chrome (for dev/prod bundles)", 20 | "url": "http://localhost:8081", 21 | "webRoot": "${workspaceRoot}" 22 | }, 23 | { 24 | "type": "chrome", 25 | "request": "launch", 26 | "name": "Launch Chrome (for hot reload)", 27 | "url": "http://localhost:3000", 28 | "webRoot": "${workspaceRoot}" 29 | } 30 | ] 31 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib" 3 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v8.0.0 2 | 3 | - Typescript migration 4 | - Upgrade React to v16.9+ 5 | - react-testing-library for test 6 | - use react-router 5+ hooks 7 | - `reactstrap` upgrade 8 | 9 | # v7.1.0 10 | 11 | - connect React Context API to redux DevTools (_also works for a React-Native app_) for dev only. 12 | 13 | # v7.0.0 14 | 15 | - migrated to `babel 7` 16 | - update to `React 16.4.x` 17 | 18 | # v6.1.0 19 | 20 | - add `React 16.3.x` new context API use case (_you may no more need redux_) 21 | - migrated to bootstrap 4 22 | 23 | # v6.0.0 24 | 25 | - upgrade to `React 16.3.x` 26 | - upgrade to `webpack 4` 27 | - upgrade to `react-hot-loader v4` 28 | - drop `CSS Module` in favor of `styled-components` (_scoped style, theme support, better scaling in huge applications, simplify toolchain and keep nearly SASS syntax_) 29 | - add `flow types` (_even a little typing at least for better dev experience_) 30 | - drop `prop-types`(_static and dynamic typing apart, flow type does far more so avoid writing 2 differents typing system_) 31 | - `workbox-webpack-plugin` (_service worker caching powerful tool from Google_) 32 | - [loadable-components](https://github.com/smooth-code/loadable-components) (_split your code: here splitted just by routes, by you can split a component level if you feel the need_) 33 | - `webpack-bundle-analyzer`: analyze your bundle size (_maybe you should split or lazy load some part of your application: you will see clearly how to fix that_) 34 | - drop `moment` for `date-fns` (_since far smaller size and job's done_) 35 | 36 | # v5.0.0 37 | 38 | - upgrade to React 16.x 39 | - `react-router 4+` (_read this [nice article about migrating from react-router 3 to react-router 4](https://codeburst.io/react-router-v4-unofficial-migration-guide-5a370b8905a)_) 40 | - add few flow types (_still keep propTypes_) 41 | - updated hot reload (_[read hot reload starter](https://gaearon.github.io/react-hot-loader/getstarted/)_) 42 | - use `CSS module` (_keep coding style with SASS but get benefit of css module for a more peasant coding_) 43 | 44 | # 3.0.0 45 | 46 | - upgrade to `React 15.6.x` 47 | - upgrade to `webpack v3` 48 | - add `JWT authentication` (protected route, logout components...) 49 | 50 | # 2.1.0 51 | 52 | - upgrade to `react-router v4` 53 | 54 | # 2.0.0 55 | 56 | - upgrade to `webpack 2` 57 | - remove autoprefixer in favor of `postcss` and `cssnext` 58 | - upgrade `React` to `15.5.4+` 59 | - `PropTypes` comes now from `prop-types` 60 | - upgrade dependencies 61 | 62 | > If you prefer a `webpack 1` version, [version 1.1.0](https://github.com/MacKentoch/react-bootstrap-webpack-starter/tree/v1.1.0) is what you are looking for. 63 | -------------------------------------------------------------------------------- /assets/Paypal-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacKentoch/react-bootstrap-webpack-starter/aeca4d212ac312378b5b311b2c78ebc92a0a4000/assets/Paypal-button.png -------------------------------------------------------------------------------- /assets/Paypal-button@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacKentoch/react-bootstrap-webpack-starter/aeca4d212ac312378b5b311b2c78ebc92a0a4000/assets/Paypal-button@2x.png -------------------------------------------------------------------------------- /assets/Paypal-button@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacKentoch/react-bootstrap-webpack-starter/aeca4d212ac312378b5b311b2c78ebc92a0a4000/assets/Paypal-button@3x.png -------------------------------------------------------------------------------- /front/.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 | -------------------------------------------------------------------------------- /front/.nvmrc: -------------------------------------------------------------------------------- 1 | 12 2 | -------------------------------------------------------------------------------- /front/.prettierignore: -------------------------------------------------------------------------------- 1 | **/*.json 2 | **/*.txt 3 | **/*.xml 4 | **/*.svg 5 | -------------------------------------------------------------------------------- /front/.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": "typescript" 12 | } 13 | -------------------------------------------------------------------------------- /front/.yarnclean: -------------------------------------------------------------------------------- 1 | @types/react-native 2 | -------------------------------------------------------------------------------- /front/coverage/lcov-report/components/backToTop/backToTopButton/styled/WithRightMargin.tsx.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for components/backToTop/backToTopButton/styled/WithRightMargin.tsx 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files / components/backToTop/backToTopButton/styled WithRightMargin.tsx 20 |

21 |
22 |
23 | 100% 24 | Statements 25 | 3/3 26 |
27 |
28 | 100% 29 | Branches 30 | 2/2 31 |
32 |
33 | 100% 34 | Functions 35 | 0/0 36 |
37 |
38 | 100% 39 | Lines 40 | 3/3 41 |
42 |
43 |
44 |
45 |

46 | 
68 | 
1 47 | 2 48 | 3 49 | 4 50 | 5 51 | 6 52 | 7 53 | 83x 54 |   55 | 3x 56 |   57 |   58 |   59 | 3x 60 |  
import styled from 'styled-components';
61 |  
62 | const WithRightMargin = styled.div`
63 |   margin-right: 10px;
64 | `;
65 |  
66 | export default WithRightMargin;
67 |  
69 |
70 |
71 | 75 | 76 | 77 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /front/coverage/lcov-report/components/navigation/index.ts.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for components/navigation/index.ts 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files / components/navigation index.ts 20 |

21 |
22 |
23 | 100% 24 | Statements 25 | 2/2 26 |
27 |
28 | 100% 29 | Branches 30 | 0/0 31 |
32 |
33 | 100% 34 | Functions 35 | 0/0 36 |
37 |
38 | 100% 39 | Lines 40 | 2/2 41 |
42 |
43 |
44 |
45 |

46 | 
56 | 
1 47 | 2 48 | 3 49 | 41x 50 |   51 | 1x 52 |  
import NavigationBar from './NavigationBar';
53 |  
54 | export default NavigationBar;
55 |  
57 |
58 |
59 | 63 | 64 | 65 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /front/coverage/lcov-report/prettify.css: -------------------------------------------------------------------------------- 1 | .pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} 2 | -------------------------------------------------------------------------------- /front/coverage/lcov-report/sort-arrow-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacKentoch/react-bootstrap-webpack-starter/aeca4d212ac312378b5b311b2c78ebc92a0a4000/front/coverage/lcov-report/sort-arrow-sprite.png -------------------------------------------------------------------------------- /front/coverage/lcov-report/src/components/backToTop/backToTopButton/styled/WithRightMargin.tsx.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for src/components/backToTop/backToTopButton/styled/WithRightMargin.tsx 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files / src/components/backToTop/backToTopButton/styled WithRightMargin.tsx 20 |

21 |
22 |
23 |
24 |
25 |

26 | 
48 | 
1 27 | 2 28 | 3 29 | 4 30 | 5 31 | 6 32 | 7 33 | 83x 34 |   35 | 3x 36 |   37 |   38 |   39 | 3x 40 |  
import styled from 'styled-components';
41 |  
42 | const WithRightMargin = styled.div`
43 |   margin-right: 10px;
44 | `;
45 |  
46 | export default WithRightMargin;
47 |  
49 |
50 |
51 | 55 | 56 | 57 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /front/coverage/lcov-report/src/components/backToTop/backToTopButton/styled/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for src/components/backToTop/backToTopButton/styled 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files src/components/backToTop/backToTopButton/styled 20 |

21 |
22 |
23 |
24 |
25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
FileStatementsBranchesFunctionsLines
WithRightMargin.tsx
%/%/%/%/
56 |
57 |
58 | 62 | 63 | 64 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /front/coverage/lcov-report/src/components/backToTop/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for src/components/backToTop 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files src/components/backToTop 20 |

21 |
22 |
23 |
24 |
25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
FileStatementsBranchesFunctionsLines
BackToTop.tsx
%/%/%/%/
56 |
57 |
58 | 62 | 63 | 64 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /front/coverage/lcov-report/src/components/fadeInEntrance/FadeInEntrance.tsx.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for src/components/fadeInEntrance/FadeInEntrance.tsx 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files / src/components/fadeInEntrance FadeInEntrance.tsx 20 |

21 |
22 |
23 |
24 |
25 |

26 | 
69 | 
1 27 | 2 28 | 3 29 | 4 30 | 5 31 | 6 32 | 7 33 | 8 34 | 9 35 | 10 36 | 11 37 | 12 38 | 13 39 | 14 40 | 156x 41 | 6x 42 |   43 |   44 |   45 |   46 |   47 | 6x 48 | 6x 49 |   50 |   51 | 6x 52 |   53 | 6x 54 |  
import React from 'react';
55 | import FadeInDiv from './styled/FadeInDiv';
56 |  
57 | type Props = {
58 |   children: any;
59 | };
60 |  
61 | function FadeInEntrance({ children }: Props) {
62 |   return <FadeInDiv startAnimation>{children}</FadeInDiv>;
63 | }
64 |  
65 | FadeInEntrance.displayName = 'FadeInEntrance';
66 |  
67 | export default FadeInEntrance;
68 |  
70 |
71 |
72 | 76 | 77 | 78 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /front/coverage/lcov-report/src/components/fadeInEntrance/index.ts.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for src/components/fadeInEntrance/index.ts 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files / src/components/fadeInEntrance index.ts 20 |

21 |
22 |
23 |
24 |
25 |

26 | 
33 | 
1 27 | 2 28 | 35x 29 |   30 | 5x
import FadeInEntrance from './FadeInEntrance';
31 |  
32 | export default FadeInEntrance;
34 |
35 |
36 | 40 | 41 | 42 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /front/coverage/lcov-report/src/components/fadeInEntrance/styled/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for src/components/fadeInEntrance/styled 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files src/components/fadeInEntrance/styled 20 |

21 |
22 |
23 |
24 |
25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
FileStatementsBranchesFunctionsLines
FadeInDiv.tsx
%/%/%/%/
56 |
57 |
58 | 62 | 63 | 64 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /front/coverage/lcov-report/src/components/loadingContent/LoadingContent.tsx.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for src/components/loadingContent/LoadingContent.tsx 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files / src/components/loadingContent LoadingContent.tsx 20 |

21 |
22 |
23 |
24 |
25 |

26 | 
60 | 
1 27 | 2 28 | 3 29 | 4 30 | 5 31 | 6 32 | 7 33 | 8 34 | 9 35 | 10 36 | 11 37 | 121x 38 |   39 |   40 |   41 |   42 | 1x 43 |   44 |   45 | 1x 46 |   47 | 1x 48 |  
import React from 'react';
49 |  
50 | type Props = {};
51 |  
52 | function LoadingContent(props: Props) {
53 |   return <div>loading...</div>;
54 | }
55 |  
56 | LoadingContent.displayName = 'LoadingContent';
57 |  
58 | export default LoadingContent;
59 |  
61 |
62 |
63 | 67 | 68 | 69 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /front/coverage/lcov-report/src/components/loadingContent/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for src/components/loadingContent 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files src/components/loadingContent 20 |

21 |
22 |
23 |
24 |
25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
FileStatementsBranchesFunctionsLines
LoadingContent.tsx
%/%/%/%/
56 |
57 |
58 | 62 | 63 | 64 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /front/coverage/lcov-report/src/components/logoutRoute/index.ts.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for src/components/logoutRoute/index.ts 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files / src/components/logoutRoute index.ts 20 |

21 |
22 |
23 |
24 |
25 |

26 | 
36 | 
1 27 | 2 28 | 3 29 | 41x 30 |   31 | 1x 32 |  
import LogoutRoute from './LogoutRoute';
33 |  
34 | export default LogoutRoute;
35 |  
37 |
38 |
39 | 43 | 44 | 45 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /front/coverage/lcov-report/src/components/mainLayout/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for src/components/mainLayout 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files src/components/mainLayout 20 |

21 |
22 |
23 |
24 |
25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
FileStatementsBranchesFunctionsLines
MainLayout.tsx
%/%/%/%/
56 |
57 |
58 | 62 | 63 | 64 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /front/coverage/lcov-report/src/components/navigation/index.ts.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for src/components/navigation/index.ts 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files / src/components/navigation index.ts 20 |

21 |
22 |
23 |
24 |
25 |

26 | 
36 | 
1 27 | 2 28 | 3 29 | 41x 30 |   31 | 1x 32 |  
import NavigationBar from './NavigationBar';
33 |  
34 | export default NavigationBar;
35 |  
37 |
38 |
39 | 43 | 44 | 45 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /front/coverage/lcov-report/src/components/privateRoute/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for src/components/privateRoute 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files src/components/privateRoute 20 |

21 |
22 |
23 |
24 |
25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
FileStatementsBranchesFunctionsLines
PrivateRoute.tsx
%/%/%/%/
56 |
57 |
58 | 62 | 63 | 64 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /front/coverage/lcov-report/src/components/scrollToTop/hooks/useScrollToTopOnLocationChange/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for src/components/scrollToTop/hooks/useScrollToTopOnLocationChange 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files src/components/scrollToTop/hooks/useScrollToTopOnLocationChange 20 |

21 |
22 |
23 |
24 |
25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
FileStatementsBranchesFunctionsLines
index.ts
%/%/%/%/
56 |
57 |
58 | 62 | 63 | 64 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /front/coverage/lcov-report/src/components/scrollToTop/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for src/components/scrollToTop 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files src/components/scrollToTop 20 |

21 |
22 |
23 |
24 |
25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
FileStatementsBranchesFunctionsLines
ScrollToTop.tsx
%/%/%/%/
56 |
57 |
58 | 62 | 63 | 64 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /front/coverage/lcov-report/src/config/appConfig.ts.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for src/config/appConfig.ts 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files / src/config appConfig.ts 20 |

21 |
22 |
23 |
24 |
25 |

26 | 
57 | 
1 27 | 2 28 | 3 29 | 4 30 | 5 31 | 6 32 | 7 33 | 8 34 | 9 35 | 10 36 | 111x 37 |   38 |   39 |   40 |   41 |   42 |   43 |   44 |   45 | 1x 46 |  
export const appConfig = Object.freeze({
47 |   DEV_MODE: true, // block fetch
48 |  
49 |   // sw path
50 |   sw: {
51 |     path: 'public/assets/sw.js',
52 |   },
53 | });
54 |  
55 | export default appConfig;
56 |  
58 |
59 |
60 | 64 | 65 | 66 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /front/coverage/lcov-report/src/contexts/auth/hook/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for src/contexts/auth/hook 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files src/contexts/auth/hook 20 |

21 |
22 |
23 |
24 |
25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
FileStatementsBranchesFunctionsLines
authContextHook.ts
%/%/%/%/
56 |
57 |
58 | 62 | 63 | 64 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /front/coverage/lcov-report/src/contexts/auth/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for src/contexts/auth 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files src/contexts/auth 20 |

21 |
22 |
23 |
24 |
25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
FileStatementsBranchesFunctionsLines
index.tsx
%/%/%/%/
56 |
57 |
58 | 62 | 63 | 64 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /front/coverage/lcov-report/src/contexts/withDevTools/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for src/contexts/withDevTools 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files src/contexts/withDevTools 20 |

21 |
22 |
23 |
24 |
25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
FileStatementsBranchesFunctionsLines
index.ts
%/%/%/%/
56 |
57 |
58 | 62 | 63 | 64 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /front/coverage/lcov-report/src/mock/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for src/mock 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files src/mock 20 |

21 |
22 |
23 |
24 |
25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
FileStatementsBranchesFunctionsLines
userInfo.ts
%/%/%/%/
56 |
57 |
58 | 62 | 63 | 64 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /front/coverage/lcov-report/src/pages/about/About.tsx.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for src/pages/about/About.tsx 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files / src/pages/about About.tsx 20 |

21 |
22 |
23 |
24 |
25 |

 26 | 
 81 | 
1 27 | 2 28 | 3 29 | 4 30 | 5 31 | 6 32 | 7 33 | 8 34 | 9 35 | 10 36 | 11 37 | 12 38 | 13 39 | 14 40 | 15 41 | 16 42 | 17 43 | 18 44 | 191x 45 |   46 | 1x 47 |   48 |   49 |   50 |   51 |   52 | 1x 53 |   54 |   55 |   56 |   57 |   58 |   59 | 1x 60 |   61 | 1x 62 |  
import React from 'react';
 63 | // import {  } from 'react-router-dom';
 64 | import FadeInEntrance from '../../components/fadeInEntrance';
 65 |  
 66 | type OwnProps = any;
 67 | type Props = OwnProps;
 68 |  
 69 | function About() {
 70 |   return (
 71 |     <FadeInEntrance>
 72 |       <h1>About</h1>
 73 |     </FadeInEntrance>
 74 |   );
 75 | }
 76 |  
 77 | About.displayName = 'About';
 78 |  
 79 | export default About;
 80 |  
82 |
83 |
84 | 88 | 89 | 90 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /front/coverage/lcov-report/src/pages/about/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for src/pages/about 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files src/pages/about 20 |

21 |
22 |
23 |
24 |
25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
FileStatementsBranchesFunctionsLines
About.tsx
%/%/%/%/
56 |
57 |
58 | 62 | 63 | 64 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /front/coverage/lcov-report/src/pages/home/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for src/pages/home 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files src/pages/home 20 |

21 |
22 |
23 |
24 |
25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
FileStatementsBranchesFunctionsLines
Home.tsx
%/%/%/%/
56 |
57 |
58 | 62 | 63 | 64 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /front/coverage/lcov-report/src/pages/home/styled/HomeInfo.tsx.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for src/pages/home/styled/HomeInfo.tsx 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files / src/pages/home/styled HomeInfo.tsx 20 |

21 |
22 |
23 |
24 |
25 |

26 | 
42 | 
1 27 | 2 28 | 3 29 | 4 30 | 5 31 | 61x 32 |   33 | 1x 34 |   35 | 1x 36 |  
import styled from 'styled-components';
37 |  
38 | const HomeInfo = styled.div``;
39 |  
40 | export default HomeInfo;
41 |  
43 |
44 |
45 | 49 | 50 | 51 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /front/coverage/lcov-report/src/pages/home/styled/LightNote.tsx.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for src/pages/home/styled/LightNote.tsx 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files / src/pages/home/styled LightNote.tsx 20 |

21 |
22 |
23 |
24 |
25 |

26 | 
48 | 
1 27 | 2 28 | 3 29 | 4 30 | 5 31 | 6 32 | 7 33 | 81x 34 |   35 | 1x 36 |   37 |   38 |   39 | 1x 40 |  
import styled from 'styled-components';
41 |  
42 | const LightNote = styled.i`
43 |   font-size: 0.7em;
44 | `;
45 |  
46 | export default LightNote;
47 |  
49 |
50 |
51 | 55 | 56 | 57 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /front/coverage/lcov-report/src/pages/home/styled/MainTitle.tsx.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for src/pages/home/styled/MainTitle.tsx 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files / src/pages/home/styled MainTitle.tsx 20 |

21 |
22 |
23 |
24 |
25 |

26 | 
54 | 
1 27 | 2 28 | 3 29 | 4 30 | 5 31 | 6 32 | 7 33 | 8 34 | 9 35 | 101x 36 |   37 |   38 | 1x 39 |   40 |   41 |   42 |   43 | 1x 44 |  
import styled from 'styled-components';
45 |  
46 |  
47 | const MainTitle = styled.h1`
48 |   color: #222 !important;
49 |   font-weight: 800;
50 | `;
51 |  
52 | export default MainTitle;
53 |  
55 |
56 |
57 | 61 | 62 | 63 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /front/coverage/lcov-report/src/pages/login/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for src/pages/login 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files src/pages/login 20 |

21 |
22 |
23 |
24 |
25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
FileStatementsBranchesFunctionsLines
Login.tsx
%/%/%/%/
56 |
57 |
58 | 62 | 63 | 64 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /front/coverage/lcov-report/src/pages/pageNotFound/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for src/pages/pageNotFound 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files src/pages/pageNotFound 20 |

21 |
22 |
23 |
24 |
25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
FileStatementsBranchesFunctionsLines
PageNotFound.tsx
%/%/%/%/
56 |
57 |
58 | 62 | 63 | 64 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /front/coverage/lcov-report/src/pages/protected/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for src/pages/protected 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files src/pages/protected 20 |

21 |
22 |
23 |
24 |
25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
FileStatementsBranchesFunctionsLines
Protected.tsx
%/%/%/%/
56 |
57 |
58 | 62 | 63 | 64 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /front/coverage/lcov-report/src/services/API/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for src/services/API 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files src/services/API 20 |

21 |
22 |
23 |
24 |
25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
FileStatementsBranchesFunctionsLines
fetchTools.ts
%/%/%/%/
56 |
57 |
58 | 62 | 63 | 64 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /front/coverage/lcov-report/src/services/auth/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for src/services/auth 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files src/services/auth 20 |

21 |
22 |
23 |
24 |
25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
FileStatementsBranchesFunctionsLines
index.ts
%/%/%/%/
56 |
57 |
58 | 62 | 63 | 64 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /front/coverage/lcov-report/test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for test 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files test 20 |

21 |
22 |
23 |
24 |
25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
FileStatementsBranchesFunctionsLines
setupTests.ts
%/%/%/%/
56 |
57 |
58 | 62 | 63 | 64 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /front/coverage/lcov-report/test/setupTests.ts.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for test/setupTests.ts 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | All files / test setupTests.ts 20 |

21 |
22 |
23 |
24 |
25 |

26 | 
75 | 
1 27 | 2 28 | 3 29 | 4 30 | 5 31 | 6 32 | 7 33 | 8 34 | 9 35 | 10 36 | 11 37 | 12 38 | 13 39 | 14 40 | 15 41 | 16 42 | 17  43 |   44 | 1x 45 |   46 | 1x 47 |   48 | 1x 49 |   50 | 1x 51 |   52 | 1x 53 |   54 | 1x 55 |   56 |   57 | 1x 58 |  
// to avoid: Warning: React depends on requestAnimationFrame. Make sure that you load a polyfill in older browsers. http://fb.me/react-polyfills
59 | // @ts-ignore
60 | require('raf/polyfill');
61 | // @ts-ignore
62 | require('jest-localstorage-mock');
63 | // @ts-ignore
64 | require('core-js/stable');
65 | // @ts-ignore
66 | require('regenerator-runtime/runtime');
67 | // @ts-ignore
68 | const Enzyme = require('enzyme');
69 | // @ts-ignore
70 | const EnzymeAdapter = require('enzyme-adapter-react-16');
71 |  
72 | // Setup enzyme's react adapter
73 | Enzyme.configure({ adapter: new EnzymeAdapter() });
74 |  
76 |
77 |
78 | 82 | 83 | 84 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /front/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ReactJS Redux Bootstrap Starter 6 | 7 | 8 | 9 | 10 | 11 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /front/jest.config.js: -------------------------------------------------------------------------------- 1 | const { jsWithBabel: tsjPreset } = require('ts-jest/presets'); 2 | 3 | module.exports = { 4 | preset: 'ts-jest', 5 | transform: { ...tsjPreset.transform }, 6 | testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$', 7 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], 8 | globals: { 9 | 'ts-jest': { 10 | tsConfig: './tsconfig.json', 11 | babelConfig: false, 12 | }, 13 | }, 14 | // testEnvironment: 'node', 15 | testEnvironment: 'jest-environment-jsdom-fifteen', 16 | verbose: true, 17 | roots: ['/src/', '/test'], 18 | setupFiles: ['/test/setupTests.ts'], 19 | moduleNameMapper: { 20 | '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': 21 | '/src/test/__mocks__/fileMock.js', 22 | '\\.(css|less|sass|scss)$': 'identity-obj-proxy', 23 | }, 24 | coverageDirectory: './coverage/', 25 | collectCoverage: true, 26 | }; 27 | -------------------------------------------------------------------------------- /front/src/Root.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component, Fragment } from 'react'; 2 | import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'; 3 | import { hot } from 'react-hot-loader/root'; 4 | import MainRoutes from './routes/MainRoutes'; 5 | import ScrollToTop from './components/scrollToTop/ScrollToTop'; 6 | import LogoutRoute from './components/logoutRoute'; 7 | import { AuthProvider } from './contexts/auth'; 8 | import { devToolsStore } from './contexts/withDevTools'; 9 | import Login from './pages/login'; 10 | import GlobalStyle from './style/GlobalStyles'; 11 | import registerServiceWorker from './services/sw/registerServiceWorker'; 12 | 13 | type Props = any; 14 | type State = any; 15 | 16 | class Root extends Component { 17 | componentDidMount() { 18 | // init devTools (so that will be visible in Chrome redux devtools tab): 19 | devToolsStore && devToolsStore?.init(); 20 | // register service worker (no worry about multiple attempts to register, browser will ignore when already registered) 21 | registerServiceWorker(); 22 | } 23 | 24 | componentDidCatch(error: any, info: any) { 25 | console.log('App error: ', error); 26 | console.log('App error info: ', info); 27 | // 28 | } 29 | 30 | render() { 31 | return ( 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | {/* Application with main layout (could have multiple applications with different layouts) */} 42 | 43 | {/* logout: just redirects to login (App will take care of removing the token) */} 44 | 45 | 46 | 47 | 48 | 49 | 50 | ); 51 | } 52 | } 53 | 54 | export default hot(Root); 55 | -------------------------------------------------------------------------------- /front/src/components/backToTop/BackToTop.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undefined */ 2 | import React, { useState, useEffect } from 'react'; 3 | import BackToTopButton from './backToTopButton/BackToTopButton'; 4 | import { Motion, spring, presets } from 'react-motion'; 5 | 6 | type Props = { 7 | minScrollY: number; 8 | scrollTo?: string; 9 | onScrollDone?: () => any; 10 | }; 11 | 12 | function BackToTop({ minScrollY = 120, onScrollDone = () => {} }: Props) { 13 | const [showBackButton, setShowBackButton] = useState(false); 14 | const [windowScrollY, setWindowScrollY] = useState(0); 15 | const [tickingScollObserve, setTickingScollObserve] = useState(false); 16 | 17 | // #region on windows scroll callback 18 | const handleWindowScroll = () => { 19 | if (!window) { 20 | return; 21 | } 22 | 23 | /* eslint-disable no-undefined */ 24 | const currentWindowScrollY = 25 | window.pageYOffset !== undefined 26 | ? window.pageYOffset 27 | : ( 28 | document.documentElement || 29 | document.body.parentNode || 30 | document.body 31 | ).scrollTop; 32 | /* eslint-enable no-undefined */ 33 | 34 | // scroll event fires to often, using window.requestAnimationFrame to limit computations 35 | if (!tickingScollObserve) { 36 | window.requestAnimationFrame(() => { 37 | if (windowScrollY !== currentWindowScrollY) { 38 | const shouldShowBackButton = 39 | currentWindowScrollY >= minScrollY ? true : false; 40 | 41 | setWindowScrollY(currentWindowScrollY); 42 | setShowBackButton(shouldShowBackButton); 43 | } 44 | setTickingScollObserve(false); 45 | }); 46 | } 47 | 48 | setTickingScollObserve(true); 49 | }; 50 | // #endregion 51 | 52 | // #region on button click (smooth scroll) 53 | const handlesOnBackButtonClick = ( 54 | event: React.MouseEvent, 55 | ) => { 56 | event && event.preventDefault(); 57 | if (window && windowScrollY && windowScrollY > minScrollY) { 58 | // using here smoothscroll-polyfill 59 | window.scroll({ top: 0, left: 0, behavior: 'smooth' }); 60 | typeof onScrollDone === 'function' && onScrollDone(); 61 | } 62 | }; 63 | // #endregion 64 | 65 | // #region mount and unmount subscrubstions 66 | useEffect(() => { 67 | if (typeof window !== 'undefined') { 68 | window.addEventListener('scroll', handleWindowScroll); 69 | } 70 | 71 | return function unsubscribeEvents() { 72 | if (typeof window !== 'undefined') { 73 | window.removeEventListener('scroll', handleWindowScroll); 74 | } 75 | }; 76 | }); 77 | // #endregion 78 | 79 | return ( 80 | // @ts-ignore 81 | 82 | {({ x }) => ( 83 | 91 | )} 92 | 93 | ); 94 | } 95 | 96 | BackToTop.displayName = 'BackToTop'; 97 | 98 | export default BackToTop; 99 | -------------------------------------------------------------------------------- /front/src/components/backToTop/__tests__/BackToTop.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import BackToTop from '../BackToTop'; 4 | 5 | describe('BackToTop component', () => { 6 | let rootElement: any = null; 7 | 8 | const defaultProps = { 9 | minScrollY: 10, 10 | onScrollDone: jest.fn(), 11 | }; 12 | 13 | beforeEach(() => { 14 | rootElement = document.createElement('div'); 15 | document.body.appendChild(rootElement); 16 | }); 17 | 18 | afterEach(() => { 19 | rootElement && document.body.removeChild(rootElement); 20 | rootElement = null; 21 | }); 22 | 23 | it('renders as expected', () => { 24 | const props = { ...defaultProps }; 25 | const { container } = render(, rootElement); 26 | expect(container.firstChild).toMatchSnapshot(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /front/src/components/backToTop/__tests__/__snapshots__/BackToTop.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`BackToTop component renders as expected 1`] = ` 4 | 24 | `; 25 | -------------------------------------------------------------------------------- /front/src/components/backToTop/backToTopButton/BackToTopButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import cx from 'classnames'; 3 | import UpIcon from './UpIcon'; 4 | import WithRightMargin from './styled/WithRightMargin'; 5 | 6 | export type BackButtonPosition = 'bottom-left' | 'bottom-right'; 7 | type Props = { 8 | position: BackButtonPosition; 9 | onClick: (event: React.MouseEvent) => any; 10 | children?: any; 11 | motionStyle: any; 12 | }; 13 | 14 | // #region constants 15 | const defaultBackGroundColor = '#4A4A4A'; 16 | const sideOffset = '-10px'; 17 | const bottomOffset = '40px'; 18 | const defaultWidth = '100px'; 19 | const defaultZindex = 10; 20 | const defaultOpacity = 0.5; 21 | const defaultStyle = { 22 | position: 'fixed', 23 | right: sideOffset, 24 | left: '', 25 | bottom: bottomOffset, 26 | width: defaultWidth, 27 | zIndex: defaultZindex, 28 | opacity: defaultOpacity, 29 | backgroundColor: defaultBackGroundColor, 30 | }; 31 | 32 | function setPosition(position = 'bottom-right', refStyle = defaultStyle): any { 33 | const style = { ...refStyle }; 34 | 35 | switch (position) { 36 | case 'bottom-right': 37 | style.right = sideOffset; 38 | style.left = ''; 39 | return style; 40 | 41 | case 'bottom-left': 42 | style.right = ''; 43 | style.left = sideOffset; 44 | return style; 45 | 46 | default: 47 | return refStyle; 48 | } 49 | } 50 | // #endregion 51 | 52 | const BackToTopButton = ({ 53 | onClick, 54 | position = 'bottom-right', 55 | children, 56 | motionStyle, 57 | }: Props) => { 58 | const buttonStyle = setPosition(position, { 59 | ...motionStyle, 60 | ...defaultStyle, 61 | }); 62 | 63 | return ( 64 | 78 | ); 79 | }; 80 | 81 | BackToTopButton.displayName = 'BackToTopButton'; 82 | 83 | export default BackToTopButton; 84 | -------------------------------------------------------------------------------- /front/src/components/backToTop/backToTopButton/UpIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | type Props = { 4 | color: string; 5 | }; 6 | 7 | const UpIcon = ({ color }: Props) => { 8 | return ( 9 | 10 | 14 | 15 | ); 16 | }; 17 | 18 | // #region statics 19 | UpIcon.defaultProps = { 20 | color: '#F1F1F1', 21 | }; 22 | 23 | UpIcon.displayName = 'UpIcon'; 24 | // #endregion 25 | 26 | export default UpIcon; 27 | -------------------------------------------------------------------------------- /front/src/components/backToTop/backToTopButton/__tests__/UpIcon.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import UpIcon from '../UpIcon'; 4 | 5 | describe('UpIcon component', () => { 6 | let rootElement: any = null; 7 | 8 | const defaultProps = { 9 | color: '', 10 | }; 11 | 12 | beforeEach(() => { 13 | rootElement = document.createElement('div'); 14 | document.body.appendChild(rootElement); 15 | }); 16 | 17 | afterEach(() => { 18 | rootElement && document.body.removeChild(rootElement); 19 | rootElement = null; 20 | }); 21 | 22 | it('renders as expected', () => { 23 | const props = { ...defaultProps }; 24 | const { container } = render(, rootElement); 25 | expect(container.firstChild).toMatchSnapshot(); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /front/src/components/backToTop/backToTopButton/__tests__/__snapshots__/UpIcon.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`UpIcon component renders as expected 1`] = ` 4 | 10 | 14 | 15 | `; 16 | -------------------------------------------------------------------------------- /front/src/components/backToTop/backToTopButton/__tests__/__snapshots__/backToTopButton.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`BackToTopButton component renders as expected 1`] = ` 4 | 12 | `; 13 | -------------------------------------------------------------------------------- /front/src/components/backToTop/backToTopButton/__tests__/backToTopButton.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import BackToTopButton, { BackButtonPosition } from '../BackToTopButton'; 4 | 5 | describe('BackToTopButton component', () => { 6 | let rootElement: any = null; 7 | 8 | beforeEach(() => { 9 | rootElement = document.createElement('div'); 10 | document.body.appendChild(rootElement); 11 | }); 12 | 13 | afterEach(() => { 14 | rootElement && document.body.removeChild(rootElement); 15 | rootElement = null; 16 | }); 17 | 18 | it('renders as expected', () => { 19 | const position: BackButtonPosition = 'bottom-left'; 20 | const props = { 21 | position, 22 | onClick: () => {}, 23 | motionStyle: {}, 24 | }; 25 | 26 | const { container } = render( 27 | 28 |

a child

29 |
, 30 | rootElement, 31 | ); 32 | expect(container.firstChild).toMatchSnapshot(); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /front/src/components/backToTop/backToTopButton/styled/WithRightMargin.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const WithRightMargin = styled.div` 4 | margin-right: 10px; 5 | `; 6 | 7 | export default WithRightMargin; 8 | -------------------------------------------------------------------------------- /front/src/components/fadeInEntrance/FadeInEntrance.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import FadeInDiv from './styled/FadeInDiv'; 3 | 4 | type Props = { 5 | children: any; 6 | }; 7 | 8 | function FadeInEntrance({ children }: Props) { 9 | return {children}; 10 | } 11 | 12 | FadeInEntrance.displayName = 'FadeInEntrance'; 13 | 14 | export default FadeInEntrance; 15 | -------------------------------------------------------------------------------- /front/src/components/fadeInEntrance/__tests__/FadeInEntreance.spec.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import FadeInEntrance from '../FadeInEntrance'; 4 | 5 | 6 | describe('FadeInEntrance component', () => { 7 | let rootElement: any = null; 8 | 9 | beforeEach(() => { 10 | rootElement = document.createElement('div'); 11 | document.body.appendChild(rootElement); 12 | }); 13 | 14 | afterEach(() => { 15 | rootElement && document.body.removeChild(rootElement); 16 | rootElement = null; 17 | }); 18 | 19 | it('renders as expected', () => { 20 | const props = {}; 21 | 22 | const { container } = render( 23 | 24 |

a child

25 |
, 26 | rootElement, 27 | ); 28 | expect(container.firstChild).toMatchSnapshot(); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /front/src/components/fadeInEntrance/__tests__/__snapshots__/FadeInEntreance.spec.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`FadeInEntrance component renders as expected 1`] = ` 4 |
7 |

8 | a child 9 |

10 |
11 | `; 12 | -------------------------------------------------------------------------------- /front/src/components/fadeInEntrance/index.ts: -------------------------------------------------------------------------------- 1 | import FadeInEntrance from './FadeInEntrance'; 2 | 3 | export default FadeInEntrance; -------------------------------------------------------------------------------- /front/src/components/fadeInEntrance/styled/FadeInDiv.tsx: -------------------------------------------------------------------------------- 1 | import styled, { keyframes, css } from 'styled-components'; 2 | 3 | const fadeIn = keyframes` 4 | from { 5 | opacity: 0; 6 | } 7 | to { 8 | opacity: 1; 9 | transform: none; 10 | } 11 | `; 12 | 13 | const fadeInAnimationCss = css` 14 | opacity: 0; 15 | animation-name: ${fadeIn}; 16 | animation-timing-function: ease-in; 17 | animation-duration: 0.7s; 18 | animation-delay: 0s; 19 | animation-fill-mode: both; 20 | `; 21 | 22 | type FadeInProps = { 23 | startAnimation: boolean; 24 | }; 25 | 26 | const FadeInDiv = styled.div` 27 | ${({ startAnimation }) => startAnimation && fadeInAnimationCss}; 28 | `; 29 | 30 | export default FadeInDiv; 31 | -------------------------------------------------------------------------------- /front/src/components/loadingContent/LoadingContent.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | type Props = {}; 4 | 5 | function LoadingContent(props: Props) { 6 | return
loading...
; 7 | } 8 | 9 | LoadingContent.displayName = 'LoadingContent'; 10 | 11 | export default LoadingContent; 12 | -------------------------------------------------------------------------------- /front/src/components/loadingContent/__tests__/LoadingContent.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import LoadingContent from '../LoadingContent'; 4 | 5 | describe('LoadingContent component', () => { 6 | let rootElement: any = null; 7 | 8 | beforeEach(() => { 9 | rootElement = document.createElement('div'); 10 | document.body.appendChild(rootElement); 11 | }); 12 | 13 | afterEach(() => { 14 | rootElement && document.body.removeChild(rootElement); 15 | rootElement = null; 16 | }); 17 | 18 | it('renders as expected', () => { 19 | const { container } = render(, rootElement); 20 | expect(container.firstChild).toMatchSnapshot(); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /front/src/components/loadingContent/__tests__/__snapshots__/LoadingContent.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`LoadingContent component renders as expected 1`] = ` 4 |
5 | loading... 6 |
7 | `; 8 | -------------------------------------------------------------------------------- /front/src/components/loadingContent/index.ts: -------------------------------------------------------------------------------- 1 | import LoadingContent from './LoadingContent'; 2 | 3 | export default LoadingContent; 4 | -------------------------------------------------------------------------------- /front/src/components/logoutRoute/LogoutRoute.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useEffect, useContext } from 'react'; 3 | import { Route, Redirect } from 'react-router-dom'; 4 | // import { } from 'react-router-dom'; 5 | import { AuthProviderState, AuthContext } from '../../contexts/auth'; 6 | 7 | type OwnProps = { 8 | path?: string; 9 | }; 10 | type Props = OwnProps; 11 | 12 | function LogoutRoute(props: Props) { 13 | const auth = useContext(AuthContext); 14 | 15 | useEffect(() => { 16 | auth && auth.disconnectUser(); 17 | }, []); 18 | 19 | return ( 20 | 21 | 22 | 23 | ); 24 | } 25 | 26 | LogoutRoute.displayName = 'LogoutRoute'; 27 | 28 | export default LogoutRoute; 29 | -------------------------------------------------------------------------------- /front/src/components/logoutRoute/__tests__/LogoutRoute.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import { MemoryRouter } from 'react-router'; 4 | import { AuthProvider } from '../../../contexts/auth'; 5 | import LogoutRoute from '../index'; 6 | 7 | describe('LogoutRoute component', () => { 8 | let rootElement: any = null; 9 | 10 | beforeEach(() => { 11 | rootElement = document.createElement('div'); 12 | document.body.appendChild(rootElement); 13 | }); 14 | 15 | afterEach(() => { 16 | rootElement && document.body.removeChild(rootElement); 17 | rootElement = null; 18 | }); 19 | 20 | it('renders as expected', () => { 21 | const { container } = render( 22 | 23 | 24 | 25 | 26 | , 27 | rootElement, 28 | ); 29 | expect(container.firstChild).toMatchSnapshot(); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /front/src/components/logoutRoute/__tests__/__snapshots__/LogoutRoute.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`LogoutRoute component renders as expected 1`] = `null`; 4 | -------------------------------------------------------------------------------- /front/src/components/logoutRoute/index.ts: -------------------------------------------------------------------------------- 1 | import LogoutRoute from './LogoutRoute'; 2 | 3 | export default LogoutRoute; 4 | -------------------------------------------------------------------------------- /front/src/components/mainLayout/MainLayout.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useCallback } from 'react'; 2 | import { withRouter } from 'react-router'; 3 | import { RouteComponentProps } from 'react-router'; 4 | import Container from 'reactstrap/lib/Container'; 5 | import NavigationBar from '../../components/navigation'; 6 | import BackToTop from '../../components/backToTop/BackToTop'; 7 | import navigationModel from '../../config/navigation'; 8 | import { Navigation } from '../../config/navigation'; 9 | 10 | type Props = { 11 | children: any; 12 | } & RouteComponentProps; 13 | 14 | function MainLayout({ children }: Props) { 15 | const [navModel] = useState(navigationModel); 16 | 17 | // #region callbacks 18 | /* eslint-disable no-unused-vars*/ 19 | const handleLeftNavItemClick = useCallback( 20 | (event?: any, viewName?: string) => { 21 | // something to do here? 22 | }, 23 | [], 24 | ); 25 | 26 | const handleRightNavItemClick = useCallback( 27 | (event?: any, viewName?: string) => { 28 | // something to do here? 29 | }, 30 | [], 31 | ); 32 | /* eslint-enable no-unused-vars*/ 33 | // #endregion 34 | return ( 35 |
36 | 42 | {children} 43 | 44 |
45 | ); 46 | } 47 | 48 | MainLayout.displayName = 'MainLayout'; 49 | 50 | export default withRouter(MainLayout); 51 | -------------------------------------------------------------------------------- /front/src/components/mainLayout/__tests__/MainLayout.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import { MemoryRouter } from 'react-router'; 4 | import { AuthProvider } from '../../../contexts/auth'; 5 | import MainLayout from '../MainLayout'; 6 | 7 | jest.mock('../../navigation'); 8 | 9 | describe('MainLayout component', () => { 10 | let rootElement: any = null; 11 | 12 | beforeEach(() => { 13 | rootElement = document.createElement('div'); 14 | document.body.appendChild(rootElement); 15 | 16 | jest.restoreAllMocks(); 17 | }); 18 | 19 | afterEach(() => { 20 | rootElement && document.body.removeChild(rootElement); 21 | rootElement = null; 22 | }); 23 | 24 | it('renders as expected', () => { 25 | const NavigationBar = require('../../navigation'); 26 | // React component is default exported: 27 | NavigationBar.default.mockImplementationOnce(() => navbar); 28 | 29 | const { container } = render( 30 | 31 | 32 | 33 |

children here

34 |
35 |
36 |
, 37 | rootElement, 38 | ); 39 | expect(container.firstChild).toMatchSnapshot(); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /front/src/components/mainLayout/__tests__/__snapshots__/MainLayout.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`MainLayout component renders as expected 1`] = ` 4 |
7 | 8 | navbar 9 | 10 |
13 |

14 | children here 15 |

16 |
17 | 37 |
38 | `; 39 | -------------------------------------------------------------------------------- /front/src/components/mainLayout/index.ts: -------------------------------------------------------------------------------- 1 | import MainLayout from './MainLayout'; 2 | 3 | export default MainLayout; 4 | -------------------------------------------------------------------------------- /front/src/components/navigation/NavigationBar.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext } from 'react'; 2 | import { 3 | Collapse, 4 | Navbar, 5 | NavbarToggler, 6 | NavbarBrand, 7 | Nav, 8 | NavItem, 9 | NavLink, 10 | } from 'reactstrap'; 11 | import { useHistory } from 'react-router-dom'; 12 | import { Link } from '../../config/navigation'; 13 | import { AuthContext, AuthProviderState } from '../../contexts/auth'; 14 | import { Navigation } from '../../config/navigation'; 15 | 16 | export type OwnProps = { 17 | brand: string; 18 | navModel: Partial; 19 | leftNavItemClick?: (event?: any, viewName?: string) => any; 20 | rightNavItemClick?: (event?: any, viewName?: string) => any; 21 | }; 22 | type Props = OwnProps; 23 | 24 | function NavigationBar({ 25 | brand, 26 | navModel: { rightLinks }, 27 | leftNavItemClick, 28 | rightNavItemClick, 29 | }: Props) { 30 | const [isOpen, setIsOpen] = useState(false); 31 | const auth = useContext(AuthContext); 32 | const history = useHistory(); 33 | 34 | // #region navigation bar toggle 35 | const toggle = (event: React.MouseEvent) => { 36 | event && event.preventDefault(); 37 | setIsOpen(!isOpen); 38 | }; 39 | // #endregion 40 | 41 | // #region handlesNavItemClick event 42 | const handlesNavItemClick = (link: string = '/') => ( 43 | event: React.MouseEvent, 44 | ) => { 45 | event?.preventDefault(); 46 | history.push(link); 47 | }; 48 | // #endregion 49 | 50 | // #region disconnect 51 | const handlesDisconnect = ( 52 | event: React.MouseEvent, 53 | ) => { 54 | event?.preventDefault(); 55 | auth?.disconnectUser(); 56 | history.push('/'); 57 | }; 58 | // #endregion 59 | 60 | return ( 61 | 62 | {brand} 63 | 64 | 65 | 81 | 82 | 83 | ); 84 | } 85 | 86 | NavigationBar.displayName = 'NavigationBar'; 87 | 88 | export default NavigationBar; 89 | -------------------------------------------------------------------------------- /front/src/components/navigation/__tests__/NavigationBar.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import { MemoryRouter } from 'react-router'; 4 | import NavigationBar from '../NavigationBar'; 5 | import { AuthProvider } from '../../../contexts/auth'; 6 | 7 | describe('NavigationBar component', () => { 8 | let rootElement: any = null; 9 | 10 | beforeEach(() => { 11 | rootElement = document.createElement('div'); 12 | document.body.appendChild(rootElement); 13 | }); 14 | 15 | afterEach(() => { 16 | rootElement && document.body.removeChild(rootElement); 17 | rootElement = null; 18 | }); 19 | 20 | it('renders as expected', async () => { 21 | const props = { 22 | brand: 'test', 23 | leftNavItemClick: jest.fn(), 24 | rightNavItemClick: jest.fn(), 25 | navModel: { 26 | brand: 'test', 27 | leftLinks: [ 28 | { 29 | link: '/', 30 | label: 'home', 31 | viewName: 'home', 32 | onClick: jest.fn(), 33 | }, 34 | ], 35 | rightLinks: [ 36 | { 37 | link: '/', 38 | label: 'home', 39 | viewName: 'home', 40 | onClick: jest.fn(), 41 | }, 42 | ], 43 | }, 44 | }; 45 | 46 | const { container } = await render( 47 | 48 | 49 | 50 | 51 | , 52 | rootElement, 53 | ); 54 | expect(container.firstChild).toMatchSnapshot(); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /front/src/components/navigation/__tests__/__snapshots__/NavigationBar.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`NavigationBar component renders as expected 1`] = ` 4 | 41 | `; 42 | -------------------------------------------------------------------------------- /front/src/components/navigation/index.ts: -------------------------------------------------------------------------------- 1 | import NavigationBar from './NavigationBar'; 2 | 3 | export default NavigationBar; 4 | -------------------------------------------------------------------------------- /front/src/components/privateRoute/PrivateRoute.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect, useState } from 'react'; 2 | import { Route, Redirect } from 'react-router-dom'; 3 | import { AuthContext, AuthProviderState } from '../../contexts/auth'; 4 | 5 | type OwnProps = { 6 | path: string; 7 | children: any; 8 | }; 9 | type Props = OwnProps; 10 | 11 | function PrivateRoute({ children, ...rest }: Props) { 12 | const auth = useContext(AuthContext); 13 | 14 | return ( 15 | { 18 | return auth?.isAuthenticated && !auth?.isExpiredToken ? ( 19 | children 20 | ) : ( 21 | 22 | ); 23 | }} 24 | /> 25 | ); 26 | } 27 | 28 | PrivateRoute.displayName = 'PrivateRoute'; 29 | 30 | export default PrivateRoute; 31 | -------------------------------------------------------------------------------- /front/src/components/privateRoute/__tests__/PrivateRoute.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { mount, shallow } from 'enzyme'; 3 | import { Router, Switch } from 'react-router'; 4 | import { Route } from 'react-router'; 5 | import { createHashHistory } from 'history'; 6 | import PrivateRoute from '../PrivateRoute'; 7 | import { AuthProvider } from '../../../contexts/auth'; 8 | 9 | // #region constants 10 | const history = createHashHistory(); 11 | 12 | const Home = (p: any) => { 13 | p.history.push('/protected'); 14 | return

home

; 15 | }; 16 | // #enregion 17 | 18 | describe('PrivateRoute component', () => { 19 | it('renders as expected', () => { 20 | const props = {}; 21 | const Child = () =>

private

; 22 | 23 | const component = shallow( 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |

login

} /> 34 |
35 |
36 | , 37 |
, 38 | ); 39 | expect(component).toMatchSnapshot(); 40 | }); 41 | 42 | it('redirects to login when not authenticated', () => { 43 | const props = {}; 44 | const PrivatePage = () =>

private

; 45 | const LoginPage = () =>

login

; 46 | 47 | const wrapper = mount( 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | , 61 | , 62 | ); 63 | 64 | expect(wrapper.find('#login')).toHaveLength(1); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /front/src/components/privateRoute/index.ts: -------------------------------------------------------------------------------- 1 | import PrivateRoute from './PrivateRoute'; 2 | 3 | export default PrivateRoute; 4 | -------------------------------------------------------------------------------- /front/src/components/scrollToTop/ScrollToTop.tsx: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from 'react'; 2 | import { useLocation } from 'react-router-dom'; 3 | import { useScrollToTopOnLocationChange } from './hooks/useScrollToTopOnLocationChange'; 4 | type OwnProps = { 5 | children: any; 6 | }; 7 | type Props = OwnProps; 8 | 9 | function ScrollToTop({ children }: Props) { 10 | const location = useLocation(); 11 | useScrollToTopOnLocationChange(location); 12 | 13 | return {children}; 14 | } 15 | 16 | ScrollToTop.displayName = 'ScrollToTop'; 17 | 18 | export default ScrollToTop; 19 | -------------------------------------------------------------------------------- /front/src/components/scrollToTop/___tests___/ScrolToTop.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import { MemoryRouter } from 'react-router'; 4 | import ScrollToTop from '../ScrollToTop'; 5 | 6 | describe('ScrollToTop component', () => { 7 | let rootElement: any = null; 8 | 9 | beforeEach(() => { 10 | rootElement = document.createElement('div'); 11 | document.body.appendChild(rootElement); 12 | }); 13 | 14 | afterEach(() => { 15 | rootElement && document.body.removeChild(rootElement); 16 | rootElement = null; 17 | }); 18 | 19 | it('renders as expected', () => { 20 | const props = { 21 | brand: 'test', 22 | leftNavItemClick: jest.fn(), 23 | rightNavItemClick: jest.fn(), 24 | navModel: { 25 | brand: 'test', 26 | leftLinks: [ 27 | { 28 | link: '/', 29 | label: 'home', 30 | viewName: 'home', 31 | onClick: jest.fn(), 32 | }, 33 | ], 34 | rightLinks: [ 35 | { 36 | link: '/', 37 | label: 'home', 38 | viewName: 'home', 39 | onClick: jest.fn(), 40 | }, 41 | ], 42 | }, 43 | }; 44 | 45 | const { container } = render( 46 | 47 | 48 |

a child

49 |
50 |
, 51 | rootElement, 52 | ); 53 | expect(container.firstChild).toMatchSnapshot(); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /front/src/components/scrollToTop/___tests___/__snapshots__/ScrolToTop.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`ScrollToTop component renders as expected 1`] = ` 4 |

5 | a child 6 |

7 | `; 8 | -------------------------------------------------------------------------------- /front/src/components/scrollToTop/hooks/useScrollToTopOnLocationChange/index.ts: -------------------------------------------------------------------------------- 1 | import { useRef, useEffect } from 'react'; 2 | import { Location } from 'history'; 3 | 4 | export function useScrollToTopOnLocationChange(location: any) { 5 | const prevLocation = useRef(null); 6 | 7 | useEffect(() => { 8 | prevLocation.current = location; 9 | }, []); 10 | 11 | useEffect(() => { 12 | if (prevLocation.current !== location) { 13 | window && window.scrollTo(0, 0); 14 | prevLocation.current = location; 15 | } 16 | }, [location]); 17 | } 18 | -------------------------------------------------------------------------------- /front/src/components/scrollToTop/index.ts: -------------------------------------------------------------------------------- 1 | import ScrollToTop from './ScrollToTop' 2 | 3 | export default ScrollToTop -------------------------------------------------------------------------------- /front/src/components/withSuspense/WithSuspense.tsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense } from 'react'; 2 | import LoadingContent from '../loadingContent'; 3 | type Props = { 4 | children: any; 5 | }; 6 | function WithSuspense({ children }: Props) { 7 | return }>{children}; 8 | } 9 | 10 | WithSuspense.displayName = 'WithSuspense'; 11 | 12 | export default WithSuspense; 13 | -------------------------------------------------------------------------------- /front/src/components/withSuspense/index.ts: -------------------------------------------------------------------------------- 1 | import WithSuspense from './WithSuspense'; 2 | 3 | export default WithSuspense; 4 | -------------------------------------------------------------------------------- /front/src/config/appConfig.ts: -------------------------------------------------------------------------------- 1 | export const appConfig = Object.freeze({ 2 | DEV_MODE: true, // block fetch 3 | 4 | // sw path 5 | sw: { 6 | path: 'public/assets/sw.js', 7 | }, 8 | }); 9 | 10 | export default appConfig; 11 | -------------------------------------------------------------------------------- /front/src/config/navigation.ts: -------------------------------------------------------------------------------- 1 | // #region types 2 | export type Link = { 3 | label: string; 4 | viewName?: string; 5 | link: string; 6 | view?: string; 7 | isRouteBtn?: boolean; 8 | onClick?: () => any; 9 | }; 10 | 11 | export type Navigation = { 12 | brand: string; 13 | leftLinks?: Array; 14 | rightLinks?: Array; 15 | }; 16 | // #endregion 17 | 18 | const navigation = Object.freeze({ 19 | brand: 'React Bootstrap Starter', 20 | leftLinks: [], 21 | rightLinks: [ 22 | { 23 | label: 'Home', 24 | link: '/', 25 | }, 26 | { 27 | label: 'Protected', 28 | link: '/protected', 29 | view: 'protected', 30 | isRouteBtn: true, 31 | }, 32 | { 33 | label: 'About', 34 | link: '/about', 35 | view: 'about', 36 | isRouteBtn: true, 37 | }, 38 | ], 39 | }); 40 | 41 | export default navigation; 42 | -------------------------------------------------------------------------------- /front/src/contexts/auth/hook/__tests__/authContextHook.spec.ts: -------------------------------------------------------------------------------- 1 | import { renderHook, act } from '@testing-library/react-hooks'; 2 | import { initialContextState, authContextHook } from '../authContextHook'; 3 | import auth from '../../../../services/auth'; 4 | 5 | jest.mock('../../../../services/auth'); 6 | jest.mock('../../../withDevTools'); 7 | 8 | describe('authContextHook', () => { 9 | test('authcontext state is equal to initialState by default', () => { 10 | const { result } = renderHook(() => authContextHook()); 11 | 12 | const { 13 | isAuthenticated, 14 | isExpiredToken, 15 | token, 16 | lastAuthDate, 17 | user, 18 | } = result.current; 19 | 20 | expect(isAuthenticated).toBe(initialContextState.isAuthenticated); 21 | expect(isExpiredToken).toBe(initialContextState.isExpiredToken); 22 | expect(token).toBe(initialContextState.token); 23 | expect(lastAuthDate).toBe(initialContextState.lastAuthDate); 24 | expect(user).toBe(initialContextState.user); 25 | }); 26 | 27 | test('checkTokenIsExpired should update matching state property isExpiredToken according to user information and token', async () => { 28 | // #region mock 29 | // @ts-ignore 30 | auth.getUserInfo.mockImplementationOnce(() => ({ 31 | id: 'some user fake id', 32 | })); 33 | // @ts-ignore 34 | auth.getToken.mockImplementationOnce(() => 'fake auth token'); 35 | // @ts-ignore 36 | auth.isExpiredToken.mockImplementationOnce(() => false); 37 | // #endregion 38 | 39 | const { result } = renderHook(() => authContextHook()); 40 | expect(result.current.isExpiredToken).toBe( 41 | initialContextState.isExpiredToken, 42 | ); 43 | 44 | act(() => { 45 | result.current.checkTokenIsExpired(); 46 | }); 47 | expect(result.current.isExpiredToken).toBe(false); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /front/src/contexts/auth/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { 2 | ReactNode, 3 | useState, 4 | useCallback, 5 | useMemo, 6 | createContext, 7 | } from 'react'; 8 | import { authContextHook } from './hook/authContextHook'; 9 | 10 | // #region types 11 | export type AuthProviderState = { 12 | isAuthenticated: boolean; 13 | isExpiredToken: boolean; 14 | lastAuthDate?: Date; 15 | token: string; 16 | user?: User; 17 | checkIsAuthenticated: () => boolean; 18 | checkTokenIsExpired: () => boolean; 19 | setToken: (token: string) => void; 20 | setUserInfo: (newUser: User) => void; 21 | disconnectUser: () => void; 22 | }; 23 | 24 | type Props = { 25 | children: ReactNode; 26 | }; 27 | // #endregion 28 | 29 | // #region context 30 | export const AuthContext = createContext(null); 31 | AuthContext.displayName = 'AuthContext'; // NOTE: displayName will be sugar ;) if you use react context devtools in your browser 32 | // #endregion 33 | 34 | export function AuthProvider({ children }: Props) { 35 | const { 36 | isAuthenticated, 37 | isExpiredToken, 38 | token, 39 | lastAuthDate, 40 | user, 41 | 42 | checkIsAuthenticated, 43 | checkTokenIsExpired, 44 | setToken, 45 | setUserInfo, 46 | disconnectUser, 47 | } = authContextHook(); 48 | 49 | const value = useMemo( 50 | () => ({ 51 | isAuthenticated, 52 | isExpiredToken, 53 | token, 54 | lastAuthDate, 55 | user, 56 | 57 | checkIsAuthenticated, 58 | checkTokenIsExpired, 59 | setToken, 60 | setUserInfo, 61 | disconnectUser, 62 | }), 63 | [ 64 | isAuthenticated, 65 | isExpiredToken, 66 | lastAuthDate, 67 | token, 68 | user, 69 | 70 | checkIsAuthenticated, 71 | checkTokenIsExpired, 72 | setToken, 73 | setUserInfo, 74 | disconnectUser, 75 | ], 76 | ); 77 | 78 | return {children}; 79 | } 80 | 81 | export default AuthProvider; 82 | -------------------------------------------------------------------------------- /front/src/contexts/withDevTools/index.ts: -------------------------------------------------------------------------------- 1 | // #region types 2 | export type DevToolsMessageType = 'DISPATCH' | string; 3 | 4 | export type DevToolsMessagePayload = { 5 | type?: string; 6 | state?: any; 7 | }; 8 | 9 | export type DevToolsMessage = { 10 | type?: DevToolsMessageType; 11 | payload?: DevToolsMessagePayload; 12 | }; 13 | 14 | export type DevTools = { 15 | init: () => void; 16 | connect: () => any; 17 | subscribe: (message: DevToolsMessage) => any; 18 | send: (action: { type: string; state?: any }, newState: any) => any; 19 | unsubscribe: () => any; 20 | dispatch: (action: { type: string }) => any; 21 | disconnect: () => any; 22 | }; 23 | // #endregion 24 | 25 | // #region constants 26 | // @ts-ignore 27 | const isDEV = process.env.NODE_ENV === 'development'; 28 | 29 | export const withDevTools = 30 | isDEV && 31 | typeof window !== 'undefined' && 32 | (window as any).__REDUX_DEVTOOLS_EXTENSION__; 33 | 34 | const devTools: DevTools = !withDevTools 35 | ? null 36 | : (window as any).__REDUX_DEVTOOLS_EXTENSION__.connect(); 37 | // #endregion 38 | 39 | // #region devtools reducer 40 | type State = { 41 | auth: any; 42 | }; 43 | type Action = { 44 | type: string; 45 | state?: any; 46 | }; 47 | 48 | const initialState: State = { 49 | auth: {}, 50 | }; 51 | 52 | export const reducer = (state: State = initialState, action: Action) => { 53 | /* eslint-disable no-unused-vars */ 54 | switch (action.type) { 55 | // #region auth context 56 | case 'AUTH_CHECK_IS_AUTHENTICATED': 57 | case 'AUTH_CHECK_TOKEN_IS_EXPIRED': 58 | case 'AUTH_SET_TOKEN': 59 | case 'AUTH_SET_USER_INFO': 60 | case 'AUTH_DISCONNECT_USER': { 61 | const { type, state: context, ...rest } = action; 62 | return { ...state, user: { context, ...rest } }; 63 | } 64 | // #endregion 65 | 66 | default: 67 | return state; 68 | } 69 | /* eslint-enable no-unused-vars */ 70 | }; 71 | // #endregion 72 | 73 | // #region singleton devtools local state 74 | let state: State; 75 | // #endregion 76 | 77 | // #region devToolsStore (redux like) 78 | 79 | export const devToolsStore = !withDevTools 80 | ? null 81 | : { 82 | ...devTools, 83 | dispatch: (action: Action) => { 84 | // #region action validation 85 | if (!action) { 86 | throw new Error('devTools dispatched action should be defined'); 87 | } 88 | if (typeof action === 'function') { 89 | throw new Error('devTools dispatched action should be an object'); 90 | } 91 | if (typeof action !== 'object') { 92 | throw new Error('devTools dispatched action should be an object'); 93 | } 94 | if (Array.isArray(action)) { 95 | throw new Error('devTools dispatched action should be an object'); 96 | } 97 | // #endregion 98 | 99 | const newState = reducer(state, action); 100 | state = newState; 101 | 102 | devTools && devTools.send({ ...action }, newState); 103 | }, 104 | }; 105 | // #endregion 106 | -------------------------------------------------------------------------------- /front/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ReactJS Redux Bootstrap Starter 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /front/src/index.tsx: -------------------------------------------------------------------------------- 1 | import 'core-js/stable'; 2 | import 'regenerator-runtime/runtime'; 3 | import 'bootstrap/dist/css/bootstrap.min.css'; 4 | import React from 'react'; 5 | import { hydrate, render } from 'react-dom'; 6 | import smoothScrollPolyfill from 'smoothscroll-polyfill'; 7 | import { loadComponents, getState } from 'loadable-components'; 8 | import Root from './Root'; 9 | 10 | // #region constants 11 | const ELEMENT_TO_BOOTSTRAP = 'root'; 12 | const bootstrapedElement = document.getElementById(ELEMENT_TO_BOOTSTRAP); 13 | // #endregion 14 | 15 | // #region globals (styles, polyfill ...) 16 | // smoothscroll polyfill 17 | smoothScrollPolyfill.polyfill(); 18 | // force polyfill (even if browser partially implements it) 19 | (window as any).__forceSmoothScrollPolyfill__ = true; 20 | (window as any).snapSaveState = () => getState(); 21 | 22 | (async () => { 23 | console.log( 24 | 'You have async support if you read this instead of "ReferenceError: regeneratorRuntime is not defined" error.', 25 | ); 26 | })(); 27 | // #endregion 28 | 29 | // render app (hydrate for react-snap): 30 | function renderApp(RootComponent: any) { 31 | const Application = RootComponent; 32 | // needed for react-snap: 33 | bootstrapedElement && bootstrapedElement.hasChildNodes() 34 | ? loadComponents().then(() => hydrate(, bootstrapedElement)) 35 | : render(, bootstrapedElement); 36 | } 37 | 38 | renderApp(Root); 39 | // #endregion 40 | -------------------------------------------------------------------------------- /front/src/mock/userInfo.ts: -------------------------------------------------------------------------------- 1 | export const userMock = Object.freeze({ 2 | token: 3 | 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJkZW1vIiwiaWF0IjoxNTAyMzA3MzU0LCJleHAiOjE3MjMyMzIxNTQsImF1ZCI6ImRlbW8tZGVtbyIsInN1YiI6ImRlbW8iLCJHaXZlbk5hbWUiOiJKb2huIiwiU3VybmFtZSI6IkRvZSIsIkVtYWlsIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJSb2xlIjpbIlN1cGVyIGNvb2wgZGV2IiwibWFnaWMgbWFrZXIiXX0.6FjgLCypaqmRp4tDjg_idVKIzQw16e-z_rjA3R94IqQ', 4 | user: { 5 | id: 111, 6 | login: 'john.doe@fake.mail', 7 | firstname: 'John', 8 | lastname: 'Doe', 9 | isAdmin: true, 10 | token: 11 | 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJkZW1vIiwiaWF0IjoxNTAyMzA3MzU0LCJleHAiOjE3MjMyMzIxNTQsImF1ZCI6ImRlbW8tZGVtbyIsInN1YiI6ImRlbW8iLCJHaXZlbk5hbWUiOiJKb2huIiwiU3VybmFtZSI6IkRvZSIsIkVtYWlsIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJSb2xlIjpbIlN1cGVyIGNvb2wgZGV2IiwibWFnaWMgbWFrZXIiXX0.6FjgLCypaqmRp4tDjg_idVKIzQw16e-z_rjA3R94IqQ', 12 | }, 13 | }); 14 | 15 | export default userMock; 16 | -------------------------------------------------------------------------------- /front/src/pages/about/About.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | // import { } from 'react-router-dom'; 3 | import FadeInEntrance from '../../components/fadeInEntrance'; 4 | 5 | type OwnProps = any; 6 | type Props = OwnProps; 7 | 8 | function About() { 9 | return ( 10 | 11 |

About

12 |
13 | ); 14 | } 15 | 16 | About.displayName = 'About'; 17 | 18 | export default About; 19 | -------------------------------------------------------------------------------- /front/src/pages/about/__tests__/About.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { MemoryRouter } from 'react-router'; 3 | import { render } from '@testing-library/react'; 4 | import About from '../About'; 5 | 6 | describe('About page', () => { 7 | let rootElement: any = null; 8 | 9 | beforeEach(() => { 10 | rootElement = document.createElement('div'); 11 | document.body.appendChild(rootElement); 12 | }); 13 | 14 | afterEach(() => { 15 | rootElement && document.body.removeChild(rootElement); 16 | rootElement = null; 17 | }); 18 | 19 | it('renders as expected', () => { 20 | const { container } = render( 21 | 22 | 23 | , 24 | rootElement, 25 | ); 26 | expect(container.firstChild).toMatchSnapshot(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /front/src/pages/about/__tests__/__snapshots__/About.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`About page renders as expected 1`] = ` 4 |
7 |

8 | About 9 |

10 |
11 | `; 12 | -------------------------------------------------------------------------------- /front/src/pages/about/index.ts: -------------------------------------------------------------------------------- 1 | import About from './About'; 2 | 3 | export default About; 4 | -------------------------------------------------------------------------------- /front/src/pages/home/Home.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | // import { } from 'react-router-dom'; 3 | import Jumbotron from 'reactstrap/lib/Jumbotron'; 4 | import { Link } from 'react-router-dom'; 5 | import HomeInfo from './styled/HomeInfo'; 6 | import MainTitle from './styled/MainTitle'; 7 | import LightNote from './styled/LightNote'; 8 | import FadeInEntrance from '../../components/fadeInEntrance'; 9 | 10 | type OwnProps = any; 11 | type Props = OwnProps; 12 | 13 | function Home() { 14 | return ( 15 | 16 | 17 | 18 | ReactJS 16.11+ Bootstrap 4 19 |

20 | with Hot Reload (react-hot-loader 4+ 21 | )!!! 22 |

23 |

and React 16+ Context API

24 |

and React Router v5

25 |

and webpack 4.x

26 |

and Typescript

27 |

28 | and styled-components ( 29 | 30 | so keep using SCSS like styles and benefit performant css-in-js 31 | 32 | ) 33 |

34 |

Starter

35 |

36 | 37 | 38 |   go to about 39 | 40 |

41 |
42 |
43 |
44 | ); 45 | } 46 | 47 | Home.displayName = 'Home'; 48 | 49 | export default Home; 50 | -------------------------------------------------------------------------------- /front/src/pages/home/__tests__/Home.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { MemoryRouter } from 'react-router'; 3 | import { render } from '@testing-library/react'; 4 | import Home from '../Home'; 5 | 6 | describe('Home page', () => { 7 | let rootElement: any = null; 8 | 9 | beforeEach(() => { 10 | rootElement = document.createElement('div'); 11 | document.body.appendChild(rootElement); 12 | }); 13 | 14 | afterEach(() => { 15 | rootElement && document.body.removeChild(rootElement); 16 | rootElement = null; 17 | }); 18 | 19 | it('renders as expected', () => { 20 | const { container } = render( 21 | 22 | 23 | , 24 | rootElement, 25 | ); 26 | expect(container.firstChild).toMatchSnapshot(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /front/src/pages/home/__tests__/__snapshots__/Home.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Home page renders as expected 1`] = ` 4 |
7 |
10 |
13 |

16 | ReactJS 16.11+ Bootstrap 4 17 |

18 |

19 | with Hot Reload ( 20 | 21 | react-hot-loader 4+ 22 | 23 | )!!! 24 |

25 |

26 | and React 16+ Context API 27 |

28 |

29 | and React Router v5 30 |

31 |

32 | and webpack 4.x 33 |

34 |

35 | and Typescript 36 |

37 |

38 | and styled-components ( 39 | 42 | so keep using SCSS like styles and benefit performant css-in-js 43 | 44 | ) 45 |

46 |

47 | Starter 48 |

49 |

50 | 54 | 57 |   go to about 58 | 59 |

60 |
61 |
62 |
63 | `; 64 | -------------------------------------------------------------------------------- /front/src/pages/home/index.ts: -------------------------------------------------------------------------------- 1 | import Home from './Home'; 2 | 3 | export default Home; 4 | -------------------------------------------------------------------------------- /front/src/pages/home/styled/HomeInfo.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const HomeInfo = styled.div``; 4 | 5 | export default HomeInfo; 6 | -------------------------------------------------------------------------------- /front/src/pages/home/styled/LightNote.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const LightNote = styled.i` 4 | font-size: 0.7em; 5 | `; 6 | 7 | export default LightNote; 8 | -------------------------------------------------------------------------------- /front/src/pages/home/styled/MainTitle.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | 4 | const MainTitle = styled.h1` 5 | color: #222 !important; 6 | font-weight: 800; 7 | `; 8 | 9 | export default MainTitle; 10 | -------------------------------------------------------------------------------- /front/src/pages/index.ts: -------------------------------------------------------------------------------- 1 | export { default as About } from './about/About'; 2 | export { default as Home } from './home/Home'; 3 | export { default as PageNotFound } from './pageNotFound/PageNotFound'; 4 | export { default as Login } from './login/Login'; 5 | -------------------------------------------------------------------------------- /front/src/pages/login/__tests__/Login.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { MemoryRouter } from 'react-router'; 3 | import { render } from '@testing-library/react'; 4 | import Login from '../Login'; 5 | 6 | describe('Login page', () => { 7 | let rootElement: any = null; 8 | 9 | beforeEach(() => { 10 | rootElement = document.createElement('div'); 11 | document.body.appendChild(rootElement); 12 | }); 13 | 14 | afterEach(() => { 15 | rootElement && document.body.removeChild(rootElement); 16 | rootElement = null; 17 | }); 18 | 19 | it('renders as expected', () => { 20 | const props = { 21 | disconnectUser: jest.fn(), 22 | setToken: jest.fn(), 23 | setUserInfo: jest.fn(), 24 | checkIsAuthenticated: jest.fn(), 25 | }; 26 | 27 | const { container } = render( 28 | 29 | 30 | , 31 | rootElement, 32 | ); 33 | expect(container.firstChild).toMatchSnapshot(); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /front/src/pages/login/__tests__/__snapshots__/Login.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Login page renders as expected 1`] = ` 4 |
7 |
10 |
13 |
16 |
20 |
21 | 22 | Login 23 | 24 |
27 | 33 |
36 | 44 |
45 |
46 |
49 | 55 |
58 | 66 |
67 |
68 |
71 |
74 | 82 |
83 |
84 |
85 |
86 |
87 |
88 |
91 |
94 |
97 | 103 |
104 |
105 |
106 |
107 |
108 | `; 109 | -------------------------------------------------------------------------------- /front/src/pages/login/index.ts: -------------------------------------------------------------------------------- 1 | 2 | import Login from './Login'; 3 | 4 | export default Login; 5 | -------------------------------------------------------------------------------- /front/src/pages/pageNotFound/PageNotFound.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Jumbotron from 'reactstrap/lib/Jumbotron'; 3 | import FadeInEntrance from '../../components/fadeInEntrance'; 4 | 5 | type OwnProps = any; 6 | type Props = OwnProps; 7 | 8 | function PageNotFound() { 9 | return ( 10 | 11 | 12 |

Sorry this page does not exists...

13 |
14 |
15 | ); 16 | } 17 | 18 | PageNotFound.displayName = 'PageNotFound'; 19 | 20 | export default PageNotFound; 21 | -------------------------------------------------------------------------------- /front/src/pages/pageNotFound/__tests__/PageNotFound.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { MemoryRouter } from 'react-router'; 3 | import { render } from '@testing-library/react'; 4 | import PageNotFound from '../PageNotFound'; 5 | 6 | describe('PageNotFound page', () => { 7 | let rootElement: any = null; 8 | 9 | beforeEach(() => { 10 | rootElement = document.createElement('div'); 11 | document.body.appendChild(rootElement); 12 | }); 13 | 14 | afterEach(() => { 15 | rootElement && document.body.removeChild(rootElement); 16 | rootElement = null; 17 | }); 18 | 19 | it('renders as expected', () => { 20 | const { container } = render( 21 | 22 | 23 | , 24 | rootElement, 25 | ); 26 | expect(container.firstChild).toMatchSnapshot(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /front/src/pages/pageNotFound/__tests__/__snapshots__/PageNotFound.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`PageNotFound page renders as expected 1`] = ` 4 |
7 |
10 |

11 | Sorry this page does not exists... 12 |

13 |
14 |
15 | `; 16 | -------------------------------------------------------------------------------- /front/src/pages/pageNotFound/index.ts: -------------------------------------------------------------------------------- 1 | import PageNotFound from './PageNotFound'; 2 | 3 | 4 | export default PageNotFound; 5 | -------------------------------------------------------------------------------- /front/src/pages/protected/Protected.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import FadeInEntrance from '../../components/fadeInEntrance'; 3 | 4 | type OwnProps = any; 5 | type Props = OwnProps; 6 | 7 | function Protected() { 8 | return ( 9 | 10 |

Protected view

11 |

If you can read, it means you are authenticated

12 |
13 | ); 14 | } 15 | 16 | Protected.displayName = 'Protected'; 17 | 18 | export default Protected; 19 | -------------------------------------------------------------------------------- /front/src/pages/protected/__tests__/Protected.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { MemoryRouter } from 'react-router'; 3 | import { render } from '@testing-library/react'; 4 | import Protected from '../Protected'; 5 | 6 | describe('Protected page', () => { 7 | let rootElement: any = null; 8 | 9 | beforeEach(() => { 10 | rootElement = document.createElement('div'); 11 | document.body.appendChild(rootElement); 12 | }); 13 | 14 | afterEach(() => { 15 | rootElement && document.body.removeChild(rootElement); 16 | rootElement = null; 17 | }); 18 | 19 | it('renders as expected', () => { 20 | const { container } = render( 21 | 22 | 23 | , 24 | rootElement, 25 | ); 26 | expect(container.firstChild).toMatchSnapshot(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /front/src/pages/protected/__tests__/__snapshots__/Protected.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Protected page renders as expected 1`] = ` 4 |
7 |

8 | Protected view 9 |

10 |

11 | If you can read, it means you are authenticated 12 |

13 |
14 | `; 15 | -------------------------------------------------------------------------------- /front/src/pages/protected/index.ts: -------------------------------------------------------------------------------- 1 | import Protected from './Protected'; 2 | 3 | export default Protected; 4 | -------------------------------------------------------------------------------- /front/src/routes/MainRoutes.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route, Switch } from 'react-router'; 3 | import PrivateRoute from '../components/privateRoute'; 4 | import MainLayout from '../components/mainLayout'; 5 | import { 6 | Home as AsyncHome, 7 | About as AsyncAbout, 8 | PageNotFound as AsyncPageNotFound, 9 | Protected as AsyncProtected, 10 | } from './routes'; 11 | 12 | const MainRoutes = () => { 13 | return ( 14 | 15 | 16 | {/* public views: */} 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | {/* private views: need user to be authenticated */} 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | ); 36 | }; 37 | 38 | export default MainRoutes; 39 | -------------------------------------------------------------------------------- /front/src/routes/routes.ts: -------------------------------------------------------------------------------- 1 | import loadable from 'loadable-components'; 2 | 3 | export const Home = loadable(() => import('../pages/home'), { 4 | modules: ['home'], 5 | }); 6 | export const About = loadable(() => import('../pages/about'), { 7 | modules: ['about'], 8 | }); 9 | export const Login = loadable(() => import('../pages/login'), { 10 | modules: ['login'], 11 | }); 12 | export const Protected = loadable(() => import('../pages/protected'), { 13 | modules: ['protected'], 14 | }); 15 | export const PageNotFound = loadable(() => import('../pages/pageNotFound'), { 16 | modules: ['pageNotFound'], 17 | }); 18 | -------------------------------------------------------------------------------- /front/src/services/API/example.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { Method } from 'axios'; 3 | import { 4 | getMethod, 5 | jsonHeader, 6 | defaultOptions, 7 | getLocationOrigin, 8 | } from './fetchTools'; 9 | 10 | export const getSomething = ( 11 | endpoint = 'api/getSomethingByDefault', 12 | ): Promise => { 13 | const method = getMethod.method as Method; 14 | const headers = jsonHeader; 15 | const url = `${getLocationOrigin()}/${endpoint}`; 16 | const options = { ...defaultOptions }; 17 | 18 | return axios 19 | .request({ 20 | method, 21 | url, 22 | withCredentials: true, 23 | ...headers, 24 | ...options, 25 | }) 26 | .then(data => data) 27 | .catch(error => Promise.reject(error)); 28 | }; 29 | -------------------------------------------------------------------------------- /front/src/services/API/fetchTools.ts: -------------------------------------------------------------------------------- 1 | import { Base64 } from 'js-base64'; 2 | 3 | // #region window.location.origin polyfill 4 | export const getLocationOrigin = (): string => { 5 | if (!window.location.origin) { 6 | // @ts-ignores 7 | window.location.origin = `${window.location.protocol}//${ 8 | window.location.hostname 9 | }${window.location.port ? ':' + window.location.port : ''}`; 10 | } 11 | return window.location.origin; 12 | }; 13 | // #endregion 14 | 15 | // #region query options: 16 | export const getMethod = { 17 | method: 'get', 18 | }; 19 | 20 | export const postMethod = { 21 | method: 'post', 22 | }; 23 | 24 | export const defaultOptions = { 25 | credentials: 'same-origin', 26 | }; 27 | 28 | export const jsonHeader = { 29 | headers: { 30 | Accept: 'application/json', 31 | 'Content-Type': 'application/json', 32 | // 'Access-control-Allow-Origin': '*' 33 | }, 34 | }; 35 | // #endregion 36 | 37 | // #region general helpers 38 | export const encodeBase64 = (stringToEncode: string = ''): string => { 39 | return Base64.encode(stringToEncode); 40 | }; 41 | // #endregion 42 | -------------------------------------------------------------------------------- /front/src/services/auth/__tests__/auth.test.ts: -------------------------------------------------------------------------------- 1 | import auth from '../index'; 2 | 3 | 4 | describe('auth service', () => { 5 | it('localStorage should exist', () => { 6 | expect(localStorage).toBeDefined(); 7 | }); 8 | 9 | it('should set token to localStorage (default store)', () => { 10 | const value = 'something to save'; 11 | const tokenKey = 'test'; 12 | 13 | // set token 14 | auth.setToken(value, 'localStorage', tokenKey); 15 | expect(localStorage.setItem).toHaveBeenLastCalledWith(tokenKey, value); 16 | }); 17 | 18 | it('should get token from localStorage (default store)', () => { 19 | const tokenKey = 'test'; 20 | 21 | // set token 22 | auth.getToken('localStorage', tokenKey); 23 | expect(localStorage.getItem).toHaveBeenLastCalledWith(tokenKey); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /front/src/services/getLocationOrigin/getLocationOrigin.ts: -------------------------------------------------------------------------------- 1 | export const getLocationOrigin = (): string => { 2 | if (!window.location.origin) { 3 | // @ts-ignore 4 | window.location.origin = `${window.location.protocol}//${ 5 | window.location.hostname 6 | }${window.location.port ? ':' + window.location.port : ''}`; 7 | } 8 | return window.location.origin; 9 | }; 10 | 11 | export default getLocationOrigin; 12 | -------------------------------------------------------------------------------- /front/src/services/sw/registerServiceWorker.ts: -------------------------------------------------------------------------------- 1 | import appConfig from '../../config/appConfig'; 2 | 3 | // #region constants 4 | const { path: swPath } = appConfig.sw; 5 | // #endregion 6 | 7 | function registerServiceWorker(): void { 8 | if (typeof window !== undefined) { 9 | if ('serviceWorker' in navigator) { 10 | window.addEventListener('load', () => { 11 | navigator.serviceWorker 12 | .register(swPath) 13 | .then(registration => { 14 | console.log('SW registered: ', registration); 15 | }) 16 | .catch(registrationError => { 17 | console.log('SW registration failed: ', registrationError); 18 | }); 19 | }); 20 | } 21 | } 22 | } 23 | 24 | export default registerServiceWorker; 25 | -------------------------------------------------------------------------------- /front/src/statics/index-raw.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ReactJS Bootstrap Starter 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /front/src/statics/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ReactJS Bootstrap Starter 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /front/src/style/GlobalStyles.ts: -------------------------------------------------------------------------------- 1 | import { createGlobalStyle } from 'styled-components'; 2 | 3 | const GlobalStyle = createGlobalStyle` 4 | html, body { 5 | margin: 0; 6 | height: 100%; 7 | -webkit-font-smoothing: antialiased; 8 | } 9 | 10 | * { 11 | box-sizing: border-box; 12 | } 13 | 14 | a { 15 | text-decoration: none; 16 | color: inherit; 17 | &:hover { 18 | text-decoration: none; 19 | } 20 | } 21 | `; 22 | 23 | export default GlobalStyle; 24 | -------------------------------------------------------------------------------- /front/src/types/auth/index.d.ts: -------------------------------------------------------------------------------- 1 | declare type STORES_TYPES = 'localStorage' | 'sessionStorage'; 2 | declare type TokenKey = string; 3 | declare type UserInfoKey = string; 4 | -------------------------------------------------------------------------------- /front/src/types/user/index.d.ts: -------------------------------------------------------------------------------- 1 | declare type User = { 2 | id: string | number; 3 | login: string; 4 | firstname: string; 5 | lastname: string; 6 | token: string; 7 | isAuthenticated?: boolean; 8 | }; 9 | -------------------------------------------------------------------------------- /front/test/__mocks__/fileMock.ts: -------------------------------------------------------------------------------- 1 | export default 'test-file-stub'; -------------------------------------------------------------------------------- /front/test/mockedRouter.ts: -------------------------------------------------------------------------------- 1 | import { RouteComponentProps } from 'react-router'; 2 | import { UnregisterCallback, Href } from 'history'; 3 | 4 | export function getMockRouterProps

(data: P) { 5 | const location = { 6 | hash: '', 7 | key: '', 8 | pathname: '', 9 | search: '', 10 | state: {}, 11 | }; 12 | 13 | const props: RouteComponentProps

= { 14 | match: { 15 | isExact: true, 16 | params: data, 17 | path: '', 18 | url: '', 19 | }, 20 | location, 21 | history: { 22 | length: 2, 23 | action: 'POP', 24 | location, 25 | push: () => {}, 26 | replace: () => {}, 27 | go: num => {}, 28 | goBack: () => {}, 29 | goForward: () => {}, 30 | block: t => { 31 | const temp: UnregisterCallback = () => {}; 32 | return temp; 33 | }, 34 | createHref: t => { 35 | const temp: Href = ''; 36 | return temp; 37 | }, 38 | listen: t => { 39 | const temp: UnregisterCallback = () => {}; 40 | return temp; 41 | }, 42 | }, 43 | staticContext: {}, 44 | ...data, 45 | }; 46 | 47 | return props; 48 | } 49 | -------------------------------------------------------------------------------- /front/test/setupTests.ts: -------------------------------------------------------------------------------- 1 | // to avoid: Warning: React depends on requestAnimationFrame. Make sure that you load a polyfill in older browsers. http://fb.me/react-polyfills 2 | // @ts-ignore 3 | require('raf/polyfill'); 4 | // @ts-ignore 5 | require('jest-localstorage-mock'); 6 | // @ts-ignore 7 | require('core-js/stable'); 8 | // @ts-ignore 9 | require('regenerator-runtime/runtime'); 10 | // @ts-ignore 11 | const Enzyme = require('enzyme'); 12 | // @ts-ignore 13 | const EnzymeAdapter = require('enzyme-adapter-react-16'); 14 | 15 | // Setup enzyme's react adapter 16 | Enzyme.configure({ adapter: new EnzymeAdapter() }); 17 | -------------------------------------------------------------------------------- /front/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "importHelpers": true, 4 | "target": "es5", 5 | "types": ["react", "jest"], 6 | "lib": ["dom", "es2015", "esnext"], 7 | "module": "commonjs", 8 | "moduleResolution": "node", 9 | "checkJs": false, 10 | "allowSyntheticDefaultImports": true, 11 | "esModuleInterop": true, 12 | "jsx": "react", 13 | "sourceMap": true, 14 | "removeComments": true, 15 | "strict": true, 16 | "noImplicitAny": true, 17 | "noImplicitThis": true, 18 | "resolveJsonModule": true, 19 | "outDir": "../dist/assets" 20 | }, 21 | "include": ["./src"] 22 | } 23 | -------------------------------------------------------------------------------- /front/webpack.dev.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 5 | const workboxPlugin = require('workbox-webpack-plugin'); 6 | const ModernizrWebpackPlugin = require('modernizr-webpack-plugin'); 7 | 8 | // #region constants` 9 | const nodeModulesDir = path.join(__dirname, 'node_modules'); 10 | const indexFile = path.join(__dirname, 'src/index.tsx'); 11 | // #endregion 12 | 13 | const config = { 14 | target: 'web', 15 | mode: 'development', 16 | devtool: 'source-map', 17 | entry: { 18 | app: [indexFile], 19 | }, 20 | output: { 21 | path: path.join(__dirname, '/../docs/assets'), 22 | publicPath: '/assets/', 23 | filename: '[name].[hash].js', 24 | chunkFilename: '[name].[hash].js', 25 | }, 26 | resolve: { 27 | modules: ['node_modules'], 28 | extensions: ['.css', '.json', '.js', '.jsx', '.ts', '.tsx'], 29 | }, 30 | module: { 31 | rules: [ 32 | { 33 | test: /\.ts(x)?$/, 34 | use: ['awesome-typescript-loader'], 35 | exclude: [nodeModulesDir], 36 | }, 37 | { 38 | test: /\.css$/, 39 | use: ['style-loader', 'css-loader'], 40 | }, 41 | { 42 | test: /\.(eot|woff|woff2|ttf|svg|png|jpe?g|gif)(\?\S*)?$/, 43 | use: [ 44 | { 45 | loader: 'url-loader', 46 | options: { 47 | limit: 100000, 48 | name: '[name].[ext]', 49 | }, 50 | }, 51 | ], 52 | }, 53 | ], 54 | }, 55 | optimization: { 56 | runtimeChunk: false, 57 | splitChunks: { 58 | cacheGroups: { 59 | commons: { 60 | test: /[\\/]node_modules[\\/]/, 61 | name: 'vendors', 62 | chunks: 'all', 63 | }, 64 | styles: { 65 | name: 'styles', 66 | test: /\.css$/, 67 | chunks: 'all', 68 | enforce: true, 69 | }, 70 | }, 71 | }, 72 | }, 73 | plugins: [ 74 | new HtmlWebpackPlugin({ 75 | template: 'src/index.html', 76 | filename: '../index.html', // hack since outPut path would place in '/dist/assets/' in place of '/dist/' 77 | }), 78 | new webpack.DefinePlugin({ 79 | 'process.env': { 80 | NODE_ENV: JSON.stringify('development'), 81 | }, 82 | }), 83 | new ModernizrWebpackPlugin({ 84 | htmlWebpackPlugin: true, 85 | }), 86 | new MiniCssExtractPlugin({ 87 | filename: '[name].[hash].css', 88 | chunkFilename: '[id].[hash].css', 89 | }), 90 | new workboxPlugin.GenerateSW({ 91 | swDest: 'sw.js', 92 | clientsClaim: true, 93 | skipWaiting: true, 94 | }), 95 | ], 96 | }; 97 | 98 | module.exports = config; 99 | -------------------------------------------------------------------------------- /front/webpack.hot.reload.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | const ProgressBarPlugin = require('progress-bar-webpack-plugin'); 4 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 5 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 6 | 7 | // #region constants 8 | const nodeModulesDir = path.join(__dirname, 'node_modules'); 9 | const srcInclude = path.join(__dirname, 'src'); 10 | const srcExclude = path.join(__dirname, 'test'); 11 | const indexFile = path.join(__dirname, 'src/index.tsx'); 12 | // #endregion 13 | 14 | const config = { 15 | mode: 'development', 16 | target: 'web', 17 | devtool: 'eval-source-map', 18 | entry: { 19 | app: [indexFile], 20 | }, 21 | output: { 22 | path: path.join(__dirname, 'docs'), 23 | filename: '[name].js', 24 | chunkFilename: '[name].js', 25 | }, 26 | resolve: { 27 | modules: ['node_modules'], 28 | extensions: ['.ts', '.tsx', '.js', '.jsx', '.css', '.json'], 29 | }, 30 | module: { 31 | rules: [ 32 | { 33 | test: /\.ts(x)?$/, 34 | use: ['awesome-typescript-loader'], 35 | exclude: [nodeModulesDir], 36 | }, 37 | { 38 | test: /\.css$/, 39 | use: ['style-loader', 'css-loader'], 40 | }, 41 | { 42 | test: /\.(eot|woff|woff2|ttf|svg|png|jpe?g|gif)(\?\S*)?$/, 43 | use: [ 44 | { 45 | loader: 'url-loader', 46 | options: { 47 | limit: 100000, 48 | name: '[name].[ext]', 49 | }, 50 | }, 51 | ], 52 | }, 53 | ], 54 | }, 55 | optimization: { 56 | runtimeChunk: false, 57 | splitChunks: { 58 | cacheGroups: { 59 | commons: { 60 | test: /[\\/]node_modules[\\/]/, 61 | name: 'vendors', 62 | chunks: 'all', 63 | }, 64 | styles: { 65 | name: 'styles', 66 | test: /\.css$/, 67 | chunks: 'all', 68 | enforce: true, 69 | }, 70 | }, 71 | }, 72 | }, 73 | devServer: { 74 | host: 'localhost', 75 | port: 3001, 76 | historyApiFallback: true, 77 | contentBase: path.join(__dirname, 'temp'), 78 | headers: { 'Access-Control-Allow-Origin': '*' }, 79 | }, 80 | plugins: [ 81 | new webpack.HotModuleReplacementPlugin(), 82 | new HtmlWebpackPlugin({ 83 | template: 'index.html', 84 | }), 85 | new webpack.NamedModulesPlugin(), 86 | new webpack.DefinePlugin({ 87 | 'process.env': { 88 | NODE_ENV: JSON.stringify('development'), 89 | }, 90 | }), 91 | new ProgressBarPlugin({ 92 | format: 'Build [:bar] :percent (:elapsed seconds)', 93 | clear: false, 94 | }), 95 | ], 96 | }; 97 | 98 | module.exports = config; 99 | -------------------------------------------------------------------------------- /preview/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacKentoch/react-bootstrap-webpack-starter/aeca4d212ac312378b5b311b2c78ebc92a0a4000/preview/preview.png -------------------------------------------------------------------------------- /scripts/prepareIndexHtml.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | // #region imports 4 | const { join } = require('path'); 5 | const fs = require('fs'); 6 | const chalk = require('chalk'); 7 | // #endregion 8 | 9 | // #region constants 10 | const indexRaw = join(__dirname, '../', 'src/front/statics/index-raw.html'); 11 | const destHtml = join(__dirname, '../', 'docs/index.html'); 12 | // #endregion 13 | 14 | // #region utils 15 | function copyFile(sourceFilePath, destFilePath) { 16 | fs.createReadStream(sourceFilePath).pipe(fs.createWriteStream(destFilePath)); 17 | } 18 | // #endregion 19 | 20 | // #region make production bundle 21 | function prepareIndexHtml() { 22 | if (fs.existsSync(indexRaw)) { 23 | copyFile(indexRaw, destHtml); 24 | 25 | return console.log( 26 | `${chalk.default.greenBright('==== index.html generated 🏋️‍ ====')}`, 27 | ); 28 | } 29 | 30 | return console.log( 31 | `${chalk.default.red('==== index.html not found... 😢 ====')}`, 32 | ); 33 | } 34 | // #endergion 35 | 36 | prepareIndexHtml(); 37 | -------------------------------------------------------------------------------- /server/.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 | -------------------------------------------------------------------------------- /server/.nvmrc: -------------------------------------------------------------------------------- 1 | 12 2 | -------------------------------------------------------------------------------- /server/.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": "typescript" 12 | } 13 | -------------------------------------------------------------------------------- /server/.yarnclean: -------------------------------------------------------------------------------- 1 | @types/react-native 2 | -------------------------------------------------------------------------------- /server/jest.config.js: -------------------------------------------------------------------------------- 1 | const { jsWithBabel: tsjPreset } = require('ts-jest/presets'); 2 | 3 | module.exports = { 4 | preset: 'ts-jest', 5 | transform: { ...tsjPreset.transform }, 6 | testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$', 7 | moduleFileExtensions: ['ts', 'tsx', 'json', 'node'], 8 | globals: { 9 | 'ts-jest': { 10 | babelConfig: false, 11 | }, 12 | }, 13 | testEnvironment: 'node', 14 | verbose: true, 15 | coverageDirectory: './coverage/', 16 | collectCoverage: true, 17 | }; 18 | -------------------------------------------------------------------------------- /server/out/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | exports.__esModule = true; 3 | var express = require("express"); 4 | var PrettyError = require("pretty-error"); 5 | var expressServer_1 = require("./lib/expressServer"); 6 | // #region constants 7 | var dev = process.env.NODE_ENV !== 'production'; 8 | var pe = new PrettyError(); 9 | // #endregion 10 | (function () { 11 | try { 12 | pe.start(); 13 | var app = express(); 14 | expressServer_1["default"](app, dev); 15 | } 16 | catch (error) { 17 | console.log('server error: ', error); 18 | } 19 | })(); 20 | -------------------------------------------------------------------------------- /server/out/lib/expressServer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | exports.__esModule = true; 3 | var express = require("express"); 4 | var path = require("path"); 5 | var chalk_1 = require("chalk"); 6 | var errors_1 = require("../middleware/errors"); 7 | // #region constants 8 | var DOCS_PATH = '../../../docs/'; 9 | var port = process.env.PORT || 8082; 10 | var host = process.env.SERVER_HOST || 'localhost'; 11 | // #endregion 12 | var expressServer = function (app, isDev) { 13 | if (isDev === void 0) { isDev = false; } 14 | if (!app) { 15 | console.log('Server application instance is undefined'); 16 | throw new Error('Server application instance is undefined'); 17 | } 18 | app.set('port', port); 19 | app.set('ipAdress', host); 20 | app.use('/assets', express.static(path.join(__dirname, DOCS_PATH, 'assets/'))); 21 | app.get('/*', function (req, res) { 22 | return res.sendFile(path.join(__dirname, DOCS_PATH, 'index.html')); 23 | }); 24 | app.use(errors_1.error404); 25 | app.use(errors_1.error500); 26 | /* eslint-disable no-console */ 27 | // @ts-ignore 28 | app.listen(port, host, function () { 29 | return console.log("\n =====================================================\n -> Server (" + chalk_1["default"].bgBlue('SPA') + ") \uD83C\uDFC3 (running) on " + chalk_1["default"].green(host) + ":" + chalk_1["default"].green("" + port) + "\n =====================================================\n "); 30 | }); 31 | /* eslint-enable no-console */ 32 | return app; 33 | }; 34 | exports["default"] = expressServer; 35 | -------------------------------------------------------------------------------- /server/out/middleware/errors.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | exports.__esModule = true; 3 | exports.error404 = function (req, res, next) { 4 | console.log('req.url: ', req.url); 5 | var err = new Error('Not found'); 6 | err.status = 404; 7 | next(err); 8 | }; 9 | exports.error500 = function (err, req, res, next) { 10 | if (err.status === 404) { 11 | res.status(404).send('Sorry nothing here for now...'); 12 | } 13 | console.error(err); 14 | res.status(500).send('internal server error'); 15 | }; 16 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-bootstrap-webpack-starter-server", 3 | "version": "1.0.1", 4 | "author": "Erwan DATIN (MacKentoch)", 5 | "license": "MIT", 6 | "description": "server side of react js + bootstrap + webpack starter", 7 | "main": "out/index.js", 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/MacKentoch/react-bootstrap-webpack-starter.git" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/MacKentoch/react-bootstrap-webpack-starter/issues" 14 | }, 15 | "engines": { 16 | "node": ">=10", 17 | "npm": ">=6.0.0", 18 | "yarn": ">=1.3.0" 19 | }, 20 | "directories": { 21 | "test": "test" 22 | }, 23 | "scripts": { 24 | "test": "cross-env NODE_ENV=test jest", 25 | "build-server": "tsc src/index.ts --outDir out", 26 | "run-server": "npm run build-server && ts-node src/index.ts" 27 | }, 28 | "keywords": [ 29 | "node", 30 | "TS", 31 | "express", 32 | "react", 33 | "react 16", 34 | "redux", 35 | "react-redux", 36 | "ES6", 37 | "ES7", 38 | "ES2015", 39 | "ES2016", 40 | "ES2017", 41 | "ES2018", 42 | "ES2019", 43 | "typescript", 44 | "bootstrap", 45 | "react-router4", 46 | "react-router", 47 | "starter", 48 | "webpack", 49 | "hot-reload", 50 | "redux-devtools-extension", 51 | "devtools", 52 | "webpack4" 53 | ], 54 | "dependencies": { 55 | "body-parser": "^1.19.0", 56 | "chalk": "^2.4.2", 57 | "compression": "^1.7.4", 58 | "convict": "^5.1.0", 59 | "date-fns": "^2.4.1", 60 | "express": "^4.17.1", 61 | "express-promise-router": "^3.0.3", 62 | "express-rate-limit": "^5.0.0", 63 | "pretty-error": "^2.1.1", 64 | "serialize-javascript": "^4.0.0", 65 | "serve-favicon": "^2.5.0" 66 | }, 67 | "devDependencies": { 68 | "@types/body-parser": "^1.17.1", 69 | "@types/chalk": "^2.2.0", 70 | "@types/convict": "^4.2.1", 71 | "@types/express": "^4.17.1", 72 | "@types/express-promise-router": "^2.0.1", 73 | "@types/express-rate-limit": "^3.3.3", 74 | "@types/fetch-mock": "^7.3.1", 75 | "@types/jest": "^24.0.18", 76 | "@types/node": "^12.7.12", 77 | "fetch-mock": "^7.5.1", 78 | "jest": "^24.9.0", 79 | "jest-localstorage-mock": "^2.4.0", 80 | "prettier": "^1.18.2", 81 | "rimraf": "^3.0.0", 82 | "ts-jest": "^24.1.0", 83 | "ts-node": "^8.4.1", 84 | "tslib": "^1.10.0", 85 | "tslint-config-prettier": "^1.18.0", 86 | "typescript": "^3.6.4" 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /server/src/index.ts: -------------------------------------------------------------------------------- 1 | import * as express from 'express'; 2 | import * as PrettyError from 'pretty-error'; 3 | import expressServer from './lib/expressServer'; 4 | 5 | // #region constants 6 | const dev = process.env.NODE_ENV !== 'production'; 7 | const pe = new PrettyError(); 8 | // #endregion 9 | 10 | (() => { 11 | try { 12 | pe.start(); 13 | const app = express(); 14 | expressServer(app, dev); 15 | } catch (error) { 16 | console.log('server error: ', error); 17 | } 18 | })(); 19 | -------------------------------------------------------------------------------- /server/src/lib/asyncWrap.ts: -------------------------------------------------------------------------------- 1 | import * as express from 'express'; 2 | 3 | // #region constants 4 | const shouldLogErrors = process.env.DEBUG || false; 5 | // #endregion 6 | 7 | /** 8 | * Returns a route handler for Express that calls the passed in function 9 | */ 10 | export default function(fn: Function) { 11 | if (fn.length <= 3) { 12 | return function( 13 | req: express.Request, 14 | res: express.Response, 15 | next: express.NextFunction, 16 | ) { 17 | return fn(req, res, next).catch((error: any) => { 18 | if (shouldLogErrors) { 19 | console.log('middleware error: ', error); 20 | } 21 | next(); 22 | }); 23 | }; 24 | } else { 25 | return function( 26 | err: express.Errback, 27 | req: express.Request, 28 | res: express.Response, 29 | next: express.NextFunction, 30 | ) { 31 | return fn(err, req, res, next).catch((error: any) => { 32 | if (shouldLogErrors) { 33 | console.log('middleware error: ', error); 34 | } 35 | next(); 36 | }); 37 | }; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /server/src/lib/expressServer.ts: -------------------------------------------------------------------------------- 1 | import * as express from 'express'; 2 | import * as path from 'path'; 3 | import chalk from 'chalk'; 4 | import { error404, error500 } from '../middleware/errors'; 5 | 6 | // #region constants 7 | const DOCS_PATH = '../../../docs/'; 8 | const port = process.env.PORT || 8082; 9 | const host = process.env.SERVER_HOST || 'localhost'; 10 | // #endregion 11 | 12 | const expressServer = (app: express.Application, isDev = false) => { 13 | if (!app) { 14 | console.log('Server application instance is undefined'); 15 | throw new Error('Server application instance is undefined'); 16 | } 17 | 18 | app.set('port', port); 19 | app.set('ipAdress', host); 20 | 21 | app.use( 22 | '/assets', 23 | express.static(path.join(__dirname, DOCS_PATH, 'assets/')), 24 | ); 25 | 26 | app.get('/*', (req, res) => 27 | res.sendFile(path.join(__dirname, DOCS_PATH, 'index.html')), 28 | ); 29 | 30 | app.use(error404); 31 | app.use(error500); 32 | 33 | /* eslint-disable no-console */ 34 | // @ts-ignore 35 | app.listen(port, host, () => 36 | console.log(` 37 | ===================================================== 38 | -> Server (${chalk.bgBlue('SPA')}) 🏃 (running) on ${chalk.green( 39 | host, 40 | )}:${chalk.green(`${port}`)} 41 | ===================================================== 42 | `), 43 | ); 44 | /* eslint-enable no-console */ 45 | 46 | return app; 47 | }; 48 | 49 | export default expressServer; 50 | -------------------------------------------------------------------------------- /server/src/middleware/errors.ts: -------------------------------------------------------------------------------- 1 | import * as express from 'express'; 2 | 3 | type ErrorWithStatus = { 4 | status?: number, 5 | message?: string, 6 | }; 7 | 8 | export const error404 = ( 9 | req: express.Request, 10 | res: express.Response, 11 | next: express.NextFunction, 12 | ) => { 13 | console.log('req.url: ', req.url); 14 | 15 | const err: ErrorWithStatus = new Error('Not found'); 16 | err.status = 404; 17 | next(err); 18 | }; 19 | 20 | export const error500 = ( 21 | err: express.Errback & ErrorWithStatus, 22 | req: express.Request, 23 | res: express.Response, 24 | next: express.NextFunction, 25 | ) => { 26 | if (err.status === 404) { 27 | res.status(404).send('Sorry nothing here for now...'); 28 | } 29 | 30 | console.error(err); 31 | res.status(500).send('internal server error'); 32 | }; 33 | -------------------------------------------------------------------------------- /server/test/setupTests.ts: -------------------------------------------------------------------------------- 1 | require('jest-localstorage-mock'); 2 | -------------------------------------------------------------------------------- /server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "noImplicitReturns": true, 5 | "noUnusedLocals": true, 6 | "sourceMap": true, 7 | "strict": true, 8 | "target": "es2017", 9 | "resolveJsonModule": true, 10 | "moduleResolution": "node", 11 | "baseUrl": ".", 12 | "outDir": "out" 13 | }, 14 | "include": ["./src/**/*", "./test/**/*"] 15 | } 16 | -------------------------------------------------------------------------------- /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/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/jest/typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "resolution": "main", 3 | "tree": { 4 | "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/545f81a23a1c4f23ffb9707eeb40973bf5f053fe/jest/index.d.ts", 5 | "raw": "registry:dt/jest#19.2.0+20170312200138", 6 | "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/545f81a23a1c4f23ffb9707eeb40973bf5f053fe/jest/index.d.ts" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /typings/globals/js-base64/index.d.ts: -------------------------------------------------------------------------------- 1 | // Generated by typings 2 | // Source: https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/62e37b17e1ec502ad642903d745d1b1ac8ff74d6/js-base64/index.d.ts 3 | declare module 'js-base64' { 4 | namespace JSBase64 { 5 | const Base64: Base64Static 6 | interface Base64Static { 7 | /** 8 | * .encode 9 | * @param {String} string 10 | * @return {String} 11 | */ 12 | encode(base64: string): string; 13 | 14 | /** 15 | * .encodeURI 16 | * @param {String} string 17 | * @return {String} 18 | */ 19 | encodeURI(base64: string): string 20 | 21 | /** 22 | * .decode 23 | * @param {String} string 24 | * @return {String} 25 | */ 26 | decode(base64: string): string 27 | 28 | /** 29 | * Library version 30 | */ 31 | VERSION:string 32 | } 33 | } 34 | export = JSBase64 35 | } 36 | -------------------------------------------------------------------------------- /typings/globals/js-base64/typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "resolution": "main", 3 | "tree": { 4 | "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/62e37b17e1ec502ad642903d745d1b1ac8ff74d6/js-base64/index.d.ts", 5 | "raw": "registry:dt/js-base64#2.1.9+20161025033459", 6 | "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/62e37b17e1ec502ad642903d745d1b1ac8ff74d6/js-base64/index.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/node/typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "resolution": "main", 3 | "tree": { 4 | "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/a4a912a0cd1849fa7df0e5d909c8625fba04e49d/node/index.d.ts", 5 | "raw": "registry:dt/node#7.0.0+20170322231424", 6 | "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/a4a912a0cd1849fa7df0e5d909c8625fba04e49d/node/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-router-dom/index.d.ts: -------------------------------------------------------------------------------- 1 | // Generated by typings 2 | // Source: https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/984933df7c89afcb97e42adbe37b6df5dd7b253c/react-router-dom/index.d.ts 3 | declare module 'react-router-dom' { 4 | import { 5 | Prompt, 6 | MemoryRouter, 7 | Redirect, 8 | RouteComponentProps, 9 | RouteProps, 10 | Route, 11 | Router, 12 | StaticRouter, 13 | Switch, 14 | match, 15 | matchPath, 16 | withRouter 17 | } from 'react-router'; 18 | import * as React from 'react'; 19 | import * as H from 'history'; 20 | 21 | 22 | interface BrowserRouterProps { 23 | basename?: string; 24 | getUserConfirmation?: () => void; 25 | forceRefresh?: boolean; 26 | keyLength?: number; 27 | } 28 | class BrowserRouter extends React.Component {} 29 | 30 | 31 | interface HashRouterProps { 32 | basename?: string; 33 | getUserConfirmation?: () => void; 34 | hashType?: 'slash' | 'noslash' | 'hashbang'; 35 | } 36 | class HashRouter extends React.Component {} 37 | 38 | 39 | interface LinkProps extends React.HTMLAttributes { 40 | to: H.LocationDescriptor; 41 | replace?: boolean; 42 | } 43 | class Link extends React.Component {} 44 | 45 | 46 | interface NavLinkProps extends LinkProps { 47 | activeClassName?: string; 48 | activeStyle?: React.CSSProperties; 49 | exact?: boolean; 50 | strict?: boolean; 51 | isActive?: (location: H.Location, props: any) => boolean; 52 | } 53 | class NavLink extends React.Component {} 54 | 55 | 56 | export { 57 | BrowserRouter, 58 | HashRouter, 59 | LinkProps, // TypeScript specific, not from React Router itself 60 | Link, 61 | NavLink, 62 | Prompt, 63 | MemoryRouter, 64 | Redirect, 65 | RouteComponentProps, // TypeScript specific, not from React Router itself 66 | RouteProps, // TypeScript specific, not from React Router itself 67 | Route, 68 | Router, 69 | StaticRouter, 70 | Switch, 71 | match, // TypeScript specific, not from React Router itself 72 | matchPath, 73 | withRouter 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /typings/globals/react-router-dom/typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "resolution": "main", 3 | "tree": { 4 | "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/984933df7c89afcb97e42adbe37b6df5dd7b253c/react-router-dom/index.d.ts", 5 | "raw": "registry:dt/react-router-dom#4.0.0+20170323201651", 6 | "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/984933df7c89afcb97e42adbe37b6df5dd7b253c/react-router-dom/index.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/index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | /// 5 | /// 6 | /// 7 | /// 8 | /// 9 | /// 10 | /// 11 | /// 12 | /// 13 | /// 14 | -------------------------------------------------------------------------------- /typings/modules/enzyme/typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "resolution": "main", 3 | "tree": { 4 | "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/520e5694ccb26dfc7fe555abea584df9f969f412/enzyme/index.d.ts", 5 | "raw": "registry:dt/enzyme#2.7.0+20170320235509", 6 | "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/520e5694ccb26dfc7fe555abea584df9f969f412/enzyme/index.d.ts" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /typings/modules/react-bootstrap/typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "resolution": "main", 3 | "tree": { 4 | "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/0f535159029908a2b5e068391b399e4d9a562d69/react-bootstrap/index.d.ts", 5 | "raw": "registry:dt/react-bootstrap#0.0.0+20170317163524", 6 | "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/0f535159029908a2b5e068391b399e4d9a562d69/react-bootstrap/index.d.ts" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /typings/modules/react-motion/typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "resolution": "main", 3 | "tree": { 4 | "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/9b5329839558a78550bf078c3e5f323c2f0f3b86/react-motion/index.d.ts", 5 | "raw": "registry:dt/react-motion#0.0.0+20170123203653", 6 | "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/9b5329839558a78550bf078c3e5f323c2f0f3b86/react-motion/index.d.ts" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /typings/modules/react/typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "resolution": "main", 3 | "tree": { 4 | "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/6ffca66c9d1edfdfa1f42f8d82db59e4297e302b/react/index.d.ts", 5 | "raw": "registry:dt/react#15.0.0+20170324160437", 6 | "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/6ffca66c9d1edfdfa1f42f8d82db59e4297e302b/react/index.d.ts" 7 | } 8 | } 9 | --------------------------------------------------------------------------------