├── .babelrc ├── .browserslistrc ├── .dockerignore ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .istanbul.yml ├── .nycrc ├── .travis.yml ├── Dockerfile ├── Dockerfile.test ├── Jenkinsfile ├── LICENSE ├── README.md ├── jenkins ├── Dockerfile └── docker-compose.yml ├── package-lock.json ├── package.json ├── postcss.config.js ├── server ├── index.js ├── schema │ ├── index.js │ ├── mutation.js │ ├── query.js │ └── user.js └── storage.js ├── src ├── favicon.ico ├── index.html ├── index.jsx ├── modules │ ├── actions │ │ ├── actions │ │ │ └── index.js │ │ ├── components │ │ │ ├── index.jsx │ │ │ └── style.scss │ │ ├── constants │ │ │ └── index.js │ │ ├── containers │ │ │ └── index.js │ │ ├── epics │ │ │ ├── index.js │ │ │ └── init.js │ │ ├── index.jsx │ │ └── reducers │ │ │ └── index.js │ ├── home │ │ ├── index.jsx │ │ └── logo.png │ └── layout │ │ ├── actions │ │ └── index.js │ │ ├── components │ │ ├── bar │ │ │ └── index.jsx │ │ ├── drawer │ │ │ ├── index.jsx │ │ │ └── style.scss │ │ ├── index.jsx │ │ ├── link │ │ │ ├── index.jsx │ │ │ └── style.scss │ │ ├── menu │ │ │ ├── index.jsx │ │ │ └── style.scss │ │ ├── placeholder │ │ │ └── index.jsx │ │ └── style.scss │ │ ├── constants │ │ └── index.js │ │ ├── containers │ │ └── index.js │ │ ├── epics │ │ ├── hideDrawer.js │ │ └── index.js │ │ ├── index.jsx │ │ └── reducers │ │ └── index.js ├── routes.js └── service-worker.js ├── tests ├── server │ └── index.js └── src │ ├── helper.js │ └── modules │ └── actions │ ├── components │ └── index.jsx │ ├── epics │ ├── index.js │ └── init.js │ └── redux │ ├── actions.js │ ├── constants.js │ └── index.js ├── webpack.config.js └── webpack ├── dev_server.js ├── devtool.js ├── index.js ├── plugins.js ├── rules.js └── vendor.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", { 5 | "useBuiltIns": true 6 | } 7 | ], 8 | "react" 9 | ], 10 | "plugins": [ 11 | "transform-class-properties", 12 | "transform-object-rest-spread", 13 | "react-hot-loader/babel", 14 | "syntax-dynamic-import", 15 | "lodash" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | # Browsers that we support 2 | 3 | > 1% 4 | Last 2 versions 5 | IE 10 # sorry 6 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | coverage 4 | .nyc_output 5 | npm-debug.log 6 | jenkins 7 | 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | 11 | # Set default charse 12 | charset = utf-8 13 | 14 | # 2 space indentation 15 | indent_style = space 16 | indent_size = 2 17 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | webpack/*.js 2 | dist/*.js 3 | src/service-worker.js 4 | coverage 5 | tests/src/helper.js 6 | jenkins 7 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": "airbnb", 4 | "env": { 5 | "browser": true, 6 | "node": true, 7 | "mocha": true 8 | } 9 | } 10 | 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | dist 4 | !dist/.gitkeep 5 | coverage 6 | npm-debug.log 7 | .nyc_output 8 | jenkins/jenkins_home 9 | -------------------------------------------------------------------------------- /.istanbul.yml: -------------------------------------------------------------------------------- 1 | instrumentation: 2 | default-excludes: true 3 | excludes: ['**/dist/**', '**/coverage/**'] 4 | include-all-sources: true 5 | root: . 6 | extensions: ['.js', '.jsx'] 7 | check: 8 | global: 9 | statements: 80 10 | lines: 80 11 | branches: 80 12 | functions: 80 13 | -------------------------------------------------------------------------------- /.nycrc: -------------------------------------------------------------------------------- 1 | { 2 | "extension": [".js",".jsx"], 3 | "require": ["./tests/src/helper.js"], 4 | "exclude": [ 5 | "node_modules", 6 | "dist", 7 | "webpack", 8 | "coverage", 9 | "tests", 10 | "src/service-worker.js", 11 | "postcss.config.js", 12 | "webpack.config.js", 13 | "jenkins" 14 | ], 15 | "check-coverage": true, 16 | "per-file": true, 17 | "lines": 0, 18 | "statements": 0, 19 | "functions": 0, 20 | "branches": 0, 21 | "reporter": [ 22 | "lcov", 23 | "text", 24 | "text-summary", 25 | "html" 26 | ], 27 | "all": true 28 | } 29 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8" 4 | - "9" 5 | script: 6 | - npm test 7 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Extending image 2 | FROM node:carbon 3 | 4 | RUN apt-get update 5 | RUN apt-get upgrade -y 6 | RUN apt-get -y install autoconf automake libtool nasm make pkg-config git apt-utils 7 | 8 | # Create app directory 9 | RUN mkdir -p /usr/src/app 10 | WORKDIR /usr/src/app 11 | 12 | # Versions 13 | RUN npm -v 14 | RUN node -v 15 | 16 | # Install app dependencies 17 | COPY package.json /usr/src/app/ 18 | COPY package-lock.json /usr/src/app/ 19 | 20 | RUN npm install 21 | 22 | # Bundle app source 23 | COPY . /usr/src/app 24 | 25 | # Port to listener 26 | EXPOSE 3000 27 | 28 | # Environment variables 29 | ENV NODE_ENV production 30 | ENV PORT 3000 31 | ENV PUBLIC_PATH "/" 32 | 33 | RUN npm run start:build 34 | 35 | # Main command 36 | CMD [ "npm", "run", "start:server" ] 37 | -------------------------------------------------------------------------------- /Dockerfile.test: -------------------------------------------------------------------------------- 1 | # Extending image 2 | FROM node:carbon 3 | 4 | RUN apt-get update 5 | RUN apt-get upgrade -y 6 | RUN apt-get -y install autoconf automake libtool nasm make pkg-config git apt-utils 7 | 8 | # Create app directory 9 | RUN mkdir -p /usr/src/app 10 | WORKDIR /usr/src/app 11 | 12 | # Versions 13 | RUN npm -v 14 | RUN node -v 15 | 16 | # Install app dependencies 17 | COPY package.json /usr/src/app/ 18 | COPY package-lock.json /usr/src/app/ 19 | 20 | RUN npm install 21 | 22 | # Bundle app source 23 | COPY . /usr/src/app 24 | 25 | # Environment variables 26 | ENV NODE_ENV test 27 | 28 | # Main command 29 | CMD [ "npm", "test" ] 30 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | node { 2 | try { 3 | stage('Checkout') { 4 | checkout scm 5 | } 6 | stage('Environment') { 7 | sh 'git --version' 8 | echo "Branch: ${env.BRANCH_NAME}" 9 | sh 'docker -v' 10 | sh 'printenv' 11 | } 12 | stage('Build Docker test'){ 13 | sh 'docker build -t react-test -f Dockerfile.test --no-cache . ' 14 | } 15 | stage('Docker test'){ 16 | sh 'docker run --rm react-test' 17 | } 18 | stage('Clean Docker test'){ 19 | sh 'docker rmi react-test' 20 | } 21 | stage('Deploy'){ 22 | if(env.BRANCH_NAME == 'master'){ 23 | sh 'docker build -t react-app --no-cache .' 24 | sh 'docker tag react-app localhost:5000/react-app' 25 | sh 'docker push localhost:5000/react-app' 26 | sh 'docker rmi -f react-app localhost:5000/react-app' 27 | } 28 | } 29 | } 30 | catch (err) { 31 | throw err 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Evheniy Bystrov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React app from scratch 2 | 3 | It's repository for code from article 4 | [React app from scratch](https://medium.com/@evheniybystrov/react-app-from-scratch-d694300d1631) 5 | 6 | ## Docker 7 | 8 | To build 9 | 10 | npm run docker:build 11 | 12 | To run 13 | 14 | npm run docker:run 15 | 16 | To stop 17 | 18 | docker:stop 19 | 20 | ## Run 21 | 22 | npm start 23 | 24 | ## Development 25 | 26 | npm run dev 27 | 28 | ## Testing 29 | 30 | npm test 31 | 32 | -------------------------------------------------------------------------------- /jenkins/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM jenkins/jenkins:lts 2 | 3 | USER root 4 | -------------------------------------------------------------------------------- /jenkins/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | 5 | jenkins: 6 | build: . 7 | container_name: jenkins 8 | privileged: true 9 | restart: always 10 | ports: 11 | - 8080:8080 12 | volumes: 13 | - ./jenkins_home:/var/jenkins_home 14 | - /var/run/docker.sock:/var/run/docker.sock 15 | - /usr/local/bin/docker:/usr/bin/docker 16 | 17 | registry: 18 | image: registry 19 | container_name: registry 20 | restart: always 21 | ports: 22 | - 5000:5000 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "webpack-dev-server", 8 | "test": "NODE_ENV=test PORT=4000 npm-run-all test:*", 9 | "start": "NODE_ENV=production npm-run-all start:*", 10 | "start:build": "webpack -p", 11 | "start:server": "nodemon server/index.js", 12 | "test:security": "nsp check", 13 | "test:lint": "eslint .", 14 | "test:server": "NODE_ENV=test mocha tests/server/**/*.js", 15 | "test:src": "mocha --require tests/src/helper.js tests/src/**/*.{js,jsx}", 16 | "test:coverage": "NODE_ENV=test nyc mocha tests/**/**/*.{js,jsx}", 17 | "test:build": "NODE_ENV=production npm run start:build", 18 | "precommit": "npm t", 19 | "prepush": "npm t", 20 | "docker:build": "docker build -t react .", 21 | "docker:run": "docker run -d -p 3000:3000 --name react react", 22 | "docker:stop": "docker rm -f react" 23 | }, 24 | "keywords": [], 25 | "author": "", 26 | "license": "MIT", 27 | "devDependencies": { 28 | "babel-core": "^6.26.0", 29 | "babel-eslint": "^8.2.1", 30 | "babel-loader": "^7.1.2", 31 | "babel-plugin-lodash": "^3.3.2", 32 | "babel-plugin-react-hot-loader": "^3.0.0-beta.6", 33 | "babel-plugin-syntax-dynamic-import": "^6.18.0", 34 | "babel-plugin-transform-class-properties": "^6.24.1", 35 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 36 | "babel-preset-env": "^1.6.1", 37 | "babel-preset-react": "^6.24.1", 38 | "chai": "^4.1.2", 39 | "chai-http": "^3.0.0", 40 | "clean-webpack-plugin": "^0.1.18", 41 | "copy-webpack-plugin": "^4.4.1", 42 | "css-loader": "^0.28.9", 43 | "cssnano": "^3.10.0", 44 | "dirty-chai": "^2.0.1", 45 | "enzyme": "^3.3.0", 46 | "enzyme-adapter-react-16": "^1.1.1", 47 | "eslint": "^4.18.0", 48 | "eslint-config-airbnb": "^16.1.0", 49 | "eslint-plugin-import": "^2.8.0", 50 | "eslint-plugin-jsx-a11y": "^6.0.3", 51 | "eslint-plugin-react": "^7.6.1", 52 | "extract-text-webpack-plugin": "^3.0.2", 53 | "favicons-webpack-plugin": "0.0.7", 54 | "file-loader": "^1.1.6", 55 | "html-webpack-plugin": "^2.30.1", 56 | "husky": "^0.14.3", 57 | "ignore-styles": "^5.0.1", 58 | "image-webpack-loader": "^4.1.0", 59 | "lodash-webpack-plugin": "^0.11.4", 60 | "mocha": "^5.0.1", 61 | "node-sass": "^4.7.2", 62 | "nodemon": "^1.15.0", 63 | "npm-install-webpack-plugin": "^4.0.5", 64 | "npm-run-all": "^4.1.2", 65 | "nsp": "^3.2.1", 66 | "nyc": "^11.4.1", 67 | "postcss": "^6.0.19", 68 | "postcss-cssnext": "^3.1.0", 69 | "postcss-loader": "^2.1.0", 70 | "preload-webpack-plugin": "^2.2.0", 71 | "react-test-renderer": "^16.2.0", 72 | "sass-loader": "^6.0.6", 73 | "sinon": "^4.3.0", 74 | "style-loader": "^0.20.2", 75 | "webpack": "^3.11.0", 76 | "webpack-bundle-analyzer": "^2.10.0", 77 | "webpack-dev-server": "^2.11.1", 78 | "webpack-pwa-manifest": "^3.5.0", 79 | "workbox-sw": "^2.1.2", 80 | "workbox-webpack-plugin": "^2.1.2" 81 | }, 82 | "dependencies": { 83 | "@redux-offline/redux-offline": "2.2.1", 84 | "babel-polyfill": "^6.26.0", 85 | "debug": "^3.1.0", 86 | "graphql": "^0.13.1", 87 | "history": "^4.7.2", 88 | "localforage": "^1.5.6", 89 | "lodash": "^4.17.5", 90 | "mime-types": "^2.1.18", 91 | "moment": "^2.20.1", 92 | "mz": "^2.7.0", 93 | "prop-types": "^15.6.0", 94 | "react": "^16.2.0", 95 | "react-dom": "^16.2.0", 96 | "react-hot-loader": "^4.0.0-beta.14", 97 | "react-loadable": "^5.3.1", 98 | "react-loader": "^2.4.2", 99 | "react-media": "^1.8.0", 100 | "react-perf-devtool": "^3.0.2", 101 | "react-proxy": "^1.1.8", 102 | "react-redux": "^5.0.7", 103 | "react-router": "^4.2.0", 104 | "react-router-config": "^1.0.0-beta.4", 105 | "react-router-dom": "^4.2.2", 106 | "react-router-redux": "^5.0.0-alpha.9", 107 | "react-toolbox": "^2.0.0-beta.12", 108 | "react-transition-group": "^2.2.1", 109 | "react-virtualized": "^9.18.5", 110 | "redux": "^3.7.2", 111 | "redux-logger": "^3.0.6", 112 | "redux-observable": "^0.18.0", 113 | "redux-thunk": "^2.2.0", 114 | "resolve-path": "^1.4.0", 115 | "rxjs": "^5.5.6", 116 | "wpb": "0.0.11", 117 | "yeps": "^1.1.1", 118 | "yeps-error": "^1.3.1", 119 | "yeps-graphql": "^0.1.1", 120 | "yeps-index": "0.0.3", 121 | "yeps-router": "^1.2.0", 122 | "yeps-server": "^1.1.2", 123 | "yeps-static": "^1.4.3" 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | 'postcss-cssnext': { 4 | warnForDuplicates: false, 5 | }, 6 | cssnano: {}, 7 | }, 8 | }; 9 | 10 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | const App = require('yeps'); 2 | 3 | const error = require('yeps-error'); 4 | const serve = require('yeps-static'); 5 | const index = require('yeps-index'); 6 | const graphql = require('yeps-graphql'); 7 | const Router = require('yeps-router'); 8 | const server = require('yeps-server'); 9 | 10 | const { resolve } = require('path'); 11 | const { createReadStream } = require('fs'); 12 | 13 | const schema = require('./schema'); 14 | 15 | const root = resolve(__dirname, '..', 'dist'); 16 | const graphiql = true; 17 | 18 | const app = new App(); 19 | 20 | app.all([ 21 | error(), 22 | serve({ root, index: false }), 23 | index({ root }), 24 | ]); 25 | 26 | const router = new Router(); 27 | 28 | router.all('/graphql').then(graphql({ schema, graphiql })); 29 | 30 | app.then(router.resolve()); 31 | 32 | app.then(ctx => new Promise((res, rej) => { 33 | createReadStream(resolve(__dirname, '..', 'dist', 'index.html')) 34 | .pipe(ctx.res) 35 | .on('error', rej) 36 | .on('close', rej) 37 | .on('finish', res); 38 | })); 39 | 40 | module.exports = server.createHttpServer(app); 41 | -------------------------------------------------------------------------------- /server/schema/index.js: -------------------------------------------------------------------------------- 1 | const { GraphQLSchema } = require('yeps-graphql/graphql'); 2 | 3 | const QueryType = require('./query'); 4 | const MutationType = require('./mutation'); 5 | 6 | const schema = new GraphQLSchema({ 7 | query: QueryType, 8 | mutation: MutationType, 9 | }); 10 | 11 | module.exports = schema; 12 | -------------------------------------------------------------------------------- /server/schema/mutation.js: -------------------------------------------------------------------------------- 1 | const { 2 | GraphQLObjectType, 3 | GraphQLInt, 4 | GraphQLString, 5 | GraphQLNonNull, 6 | } = require('yeps-graphql/graphql'); 7 | 8 | const UserType = require('./user'); 9 | const storage = require('../storage'); 10 | 11 | 12 | const MutationType = new GraphQLObjectType({ 13 | name: 'MutationType', 14 | fields: () => ({ 15 | createUser: { 16 | type: UserType, 17 | description: 'User creating', 18 | args: { 19 | id: { 20 | type: new GraphQLNonNull(GraphQLInt), 21 | }, 22 | name: { 23 | type: new GraphQLNonNull(GraphQLString), 24 | }, 25 | }, 26 | resolve: (value, { id, name }) => { 27 | const index = storage.findIndex(user => user.id === parseInt(id, 10)); 28 | 29 | if (index === -1) { 30 | storage.push({ id, name }); 31 | return { id, name }; 32 | } 33 | 34 | return Promise.reject(new Error('User exists!')); 35 | }, 36 | }, 37 | updateUser: { 38 | type: UserType, 39 | description: 'User updating', 40 | args: { 41 | id: { 42 | type: new GraphQLNonNull(GraphQLInt), 43 | }, 44 | name: { 45 | type: new GraphQLNonNull(GraphQLString), 46 | }, 47 | }, 48 | resolve: (value, { id, name }) => { 49 | const index = storage.findIndex(user => user.id === parseInt(id, 10)); 50 | 51 | if (index !== -1) { 52 | Object.assign(storage[index], { name }); 53 | return storage[index]; 54 | } 55 | 56 | return Promise.reject(new Error('User not found!')); 57 | }, 58 | }, 59 | deleteUser: { 60 | type: UserType, 61 | description: 'User deleting', 62 | args: { 63 | id: { 64 | type: new GraphQLNonNull(GraphQLInt), 65 | }, 66 | }, 67 | resolve: (value, { id }) => { 68 | const index = storage.findIndex(user => user.id === parseInt(id, 10)); 69 | 70 | if (index !== -1) { 71 | storage.splice(index, 1); 72 | return { id }; 73 | } 74 | 75 | return Promise.reject(new Error('User not found!')); 76 | }, 77 | }, 78 | }), 79 | }); 80 | 81 | module.exports = MutationType; 82 | -------------------------------------------------------------------------------- /server/schema/query.js: -------------------------------------------------------------------------------- 1 | const { 2 | GraphQLObjectType, 3 | GraphQLList, 4 | GraphQLNonNull, 5 | GraphQLID, 6 | } = require('yeps-graphql/graphql'); 7 | 8 | const UserType = require('./user'); 9 | const storage = require('../storage'); 10 | 11 | const QueryType = new GraphQLObjectType({ 12 | name: 'QueryType', 13 | description: 'User information', 14 | fields: { 15 | users: { 16 | type: new GraphQLList(UserType), 17 | resolve() { 18 | return storage; 19 | }, 20 | }, 21 | user: { 22 | type: UserType, 23 | args: { 24 | id: { 25 | type: new GraphQLNonNull(GraphQLID), 26 | }, 27 | }, 28 | resolve(parent, { id }) { 29 | const index = storage.findIndex(user => user.id === parseInt(id, 10)); 30 | 31 | if (index !== -1) { 32 | return storage[index]; 33 | } 34 | 35 | return Promise.reject(new Error('User not found!')); 36 | }, 37 | }, 38 | }, 39 | }); 40 | 41 | module.exports = QueryType; 42 | -------------------------------------------------------------------------------- /server/schema/user.js: -------------------------------------------------------------------------------- 1 | const { 2 | GraphQLObjectType, 3 | GraphQLInt, 4 | GraphQLString, 5 | } = require('yeps-graphql/graphql'); 6 | 7 | const UserType = new GraphQLObjectType({ 8 | name: 'UserType', 9 | description: 'User type', 10 | fields: { 11 | id: { 12 | type: GraphQLInt, 13 | }, 14 | name: { 15 | type: GraphQLString, 16 | }, 17 | }, 18 | }); 19 | 20 | module.exports = UserType; 21 | -------------------------------------------------------------------------------- /server/storage.js: -------------------------------------------------------------------------------- 1 | const storage = []; 2 | 3 | module.exports = storage; 4 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evheniy/react-app/420570cd2050672e7b14d4894f7b17e7ed454d57/src/favicon.ico -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | PWA APP 8 | 9 | 10 | 145 | 146 | 147 |
148 |
149 |
150 | 151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 | 173 | 174 | -------------------------------------------------------------------------------- /src/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import app from 'wpb/lib/app'; 3 | import { renderRoutes } from 'react-router-config'; 4 | import Layout from './modules/layout'; 5 | import routes from './routes'; 6 | 7 | app(() => ( 8 | 9 | {renderRoutes(routes)} 10 | 11 | )); 12 | 13 | if (module.hot) { 14 | module.hot.accept(); 15 | } 16 | 17 | if (process.env.NODE_ENV !== 'production') { 18 | require('react-perf-devtool')(); 19 | } 20 | 21 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 22 | window.addEventListener('load', () => { 23 | navigator.serviceWorker.register('service-worker.js').catch(() => {}); 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /src/modules/actions/actions/index.js: -------------------------------------------------------------------------------- 1 | import * as constants from '../constants'; 2 | 3 | export const initActions = () => ({ 4 | type: constants.ACTIONS_INIT, 5 | }); 6 | 7 | export const idleActions = () => ({ 8 | type: constants.ACTIONS_IDLE, 9 | }); 10 | 11 | export const clearActions = () => ({ 12 | type: constants.ACTIONS_CLEAR, 13 | }); 14 | -------------------------------------------------------------------------------- /src/modules/actions/components/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { CSSTransition, TransitionGroup } from 'react-transition-group'; 4 | import style from './style.scss'; 5 | 6 | const classNames = { 7 | appear: style.appear, 8 | appearActive: style['appear-active'], 9 | enter: style.enter, 10 | enterActive: style['enter-active'], 11 | exit: style.exit, 12 | exitActive: style['exit-active'], 13 | }; 14 | 15 | const transitionProps = { 16 | timeout: 1000, 17 | classNames, 18 | }; 19 | 20 | const Component = ({ status, initActions, clearActions }) => ( 21 | 22 |

Status: {status}

23 | 24 | {status !== 'idle' && ( 25 | 26 | 27 | 28 | )} 29 | {status === 'idle' && ( 30 | 31 | 32 | 33 | )} 34 | 35 |
36 | ); 37 | 38 | Component.propTypes = { 39 | status: PropTypes.string.isRequired, 40 | initActions: PropTypes.func.isRequired, 41 | clearActions: PropTypes.func.isRequired, 42 | }; 43 | 44 | export default Component; 45 | -------------------------------------------------------------------------------- /src/modules/actions/components/style.scss: -------------------------------------------------------------------------------- 1 | .enter { 2 | opacity: 0; 3 | } 4 | 5 | .enter.enter-active { 6 | opacity: 0; 7 | transition: opacity 300ms ease-in; 8 | } 9 | 10 | .exit { 11 | opacity: 0; 12 | } 13 | 14 | .exit.exit-active { 15 | opacity: 0.01; 16 | transition: opacity 200ms ease-in; 17 | } 18 | 19 | .appear { 20 | opacity: 0; 21 | } 22 | 23 | .appear.appear-active { 24 | opacity: 0.01; 25 | transition: opacity 100ms ease-in; 26 | } 27 | -------------------------------------------------------------------------------- /src/modules/actions/constants/index.js: -------------------------------------------------------------------------------- 1 | export const ACTIONS = '@@actions/ACTIONS'; 2 | export const ACTIONS_INIT = '@@actions/INIT'; 3 | export const ACTIONS_IDLE = '@@actions/IDLE'; 4 | export const ACTIONS_CLEAR = '@@actions/CLEAR'; 5 | -------------------------------------------------------------------------------- /src/modules/actions/containers/index.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import Component from '../components'; 3 | import { initActions, clearActions } from '../actions'; 4 | import { ACTIONS } from '../constants'; 5 | 6 | const mapStateToProps = state => ({ status: state[ACTIONS].status }); 7 | const mapDispatchToProps = { initActions, clearActions }; 8 | 9 | export default connect(mapStateToProps, mapDispatchToProps)(Component); 10 | -------------------------------------------------------------------------------- /src/modules/actions/epics/index.js: -------------------------------------------------------------------------------- 1 | import { combineEpics } from 'redux-observable'; 2 | 3 | import initEpic from './init'; 4 | 5 | export default combineEpics(initEpic); 6 | -------------------------------------------------------------------------------- /src/modules/actions/epics/init.js: -------------------------------------------------------------------------------- 1 | import { ACTIONS_INIT } from '../constants'; 2 | import { idleActions } from '../actions'; 3 | 4 | export default action$ => action$.ofType(ACTIONS_INIT).mapTo(idleActions()); 5 | -------------------------------------------------------------------------------- /src/modules/actions/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { injectReducer, injectEpic } from 'wpb/lib/store'; 3 | import Container from './containers'; 4 | import redux from './reducers'; 5 | import epics from './epics'; 6 | import { ACTIONS } from './constants'; 7 | 8 | injectEpic(ACTIONS, epics); 9 | injectReducer(ACTIONS, redux); 10 | 11 | export default props => ( 12 | 13 | ); 14 | -------------------------------------------------------------------------------- /src/modules/actions/reducers/index.js: -------------------------------------------------------------------------------- 1 | import * as constants from '../constants'; 2 | 3 | const status = 'null'; 4 | 5 | const defaultState = { 6 | status, 7 | }; 8 | 9 | export default (state = defaultState, action) => { 10 | switch (action.type) { 11 | case constants.ACTIONS_INIT: 12 | return { ...state, status: 'init' }; 13 | case constants.ACTIONS_IDLE: 14 | return { ...state, status: 'idle' }; 15 | case constants.ACTIONS_CLEAR: 16 | return { ...state, status }; 17 | default: 18 | return state; 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /src/modules/home/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from 'react'; 2 | import logo from './logo.png'; 3 | 4 | export default () => ( 5 | 6 | logo 7 |

8 | 9 | Read more... 10 | 11 |

12 |
13 | ); 14 | -------------------------------------------------------------------------------- /src/modules/home/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evheniy/react-app/420570cd2050672e7b14d4894f7b17e7ed454d57/src/modules/home/logo.png -------------------------------------------------------------------------------- /src/modules/layout/actions/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | LAYOUT_SHOW_DRAWER, 3 | LAYOUT_HIDE_DRAWER, 4 | } from '../constants'; 5 | 6 | export const showDrawerAction = () => ({ 7 | type: LAYOUT_SHOW_DRAWER, 8 | }); 9 | 10 | export const hideDrawerAction = () => ({ 11 | type: LAYOUT_HIDE_DRAWER, 12 | }); 13 | -------------------------------------------------------------------------------- /src/modules/layout/components/bar/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import Media from 'react-media'; 4 | import { AppBar } from 'react-toolbox'; 5 | 6 | const Bar = ({ children, showDrawerAction, title }) => ( 7 | 8 | { 9 | matches => matches ? ( 10 | 15 | ) : ( 16 | 17 | {children} 18 | 19 | ) 20 | } 21 | 22 | ); 23 | 24 | Bar.propTypes = { 25 | children: PropTypes.node, 26 | title: PropTypes.string.isRequired, 27 | showDrawerAction: PropTypes.func.isRequired, 28 | }; 29 | 30 | Bar.defaultProps = { 31 | children: null, 32 | }; 33 | 34 | export default Bar; 35 | -------------------------------------------------------------------------------- /src/modules/layout/components/drawer/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { NavDrawer } from 'react-toolbox'; 4 | import style from './style.scss'; 5 | 6 | const Drawer = ({ children, isDrawerActive, hideDrawerAction }) => ( 7 | 12 | {children} 13 | 14 | ); 15 | 16 | Drawer.propTypes = { 17 | children: PropTypes.node, 18 | isDrawerActive: PropTypes.bool.isRequired, 19 | hideDrawerAction: PropTypes.func.isRequired, 20 | }; 21 | 22 | Drawer.defaultProps = { 23 | children: null, 24 | }; 25 | 26 | export default Drawer; 27 | -------------------------------------------------------------------------------- /src/modules/layout/components/drawer/style.scss: -------------------------------------------------------------------------------- 1 | .drawer { 2 | background-color: grey; 3 | } 4 | -------------------------------------------------------------------------------- /src/modules/layout/components/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Layout, Panel } from 'react-toolbox'; 4 | import Drawer from './drawer'; 5 | import Menu from './menu'; 6 | import Bar from './bar'; 7 | import style from './style.scss'; 8 | 9 | const Component = ({ children, isDrawerActive, showDrawerAction, hideDrawerAction, routes, title }) => ( 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | {children} 20 |
21 | 22 | 23 | ); 24 | 25 | Component.propTypes = { 26 | children: PropTypes.node, 27 | isDrawerActive: PropTypes.bool.isRequired, 28 | showDrawerAction: PropTypes.func.isRequired, 29 | hideDrawerAction: PropTypes.func.isRequired, 30 | routes: PropTypes.arrayOf(PropTypes.object), 31 | title: PropTypes.string.isRequired, 32 | }; 33 | 34 | Component.defaultProps = { 35 | children: null, 36 | routes: [], 37 | }; 38 | 39 | export default Component; 40 | -------------------------------------------------------------------------------- /src/modules/layout/components/link/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Button } from 'react-toolbox'; 4 | import { NavLink } from 'react-router-dom'; 5 | import style from './style.scss'; 6 | 7 | const Link = ({ url, label, icon }) => ( 8 | 14 |