├── .babelrc ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── Gruntfile.js ├── LICENSE ├── README.md ├── app ├── actions │ ├── alert.js │ ├── notification.js │ ├── weather.js │ └── weather_list.js ├── app.jsx ├── components │ ├── CSV_Validator │ │ └── CSV_Validator.jsx │ ├── Open_Weather_Search │ │ ├── Open_Weather_Search.jsx │ │ └── Open_Weather_Search.utils.js │ └── dumb │ │ ├── Resize_SubPub_Action.jsx │ │ ├── Show_Page_Visibility_API.jsx │ │ └── Weather_View.jsx ├── config.json ├── config.json.example ├── constants │ ├── components │ │ ├── Open_Weather_Search.constant.js │ │ └── Weather_View.constant.js │ ├── events.constant.js │ ├── keyCodes.constant.js │ └── request.constant.js ├── helpers │ ├── appGlobal.js │ ├── domReady.js │ ├── geolocation.js │ ├── interfaces.js │ ├── uiActions.js │ └── validation.js ├── i18n │ ├── en │ │ └── messages.js │ ├── es │ │ └── messages.js │ ├── fr │ │ └── messages.js │ └── messages.js ├── index.html ├── pages │ ├── About │ │ └── About.jsx │ ├── App.jsx │ ├── CSV_Check │ │ └── CSV_Check.jsx │ ├── Components_Communication │ │ └── Components_Communication.jsx │ ├── Home │ │ └── Home.jsx │ ├── Page_Visibility_API │ │ └── Page_Visibility_API.jsx │ └── Resize_SubPub │ │ └── Resize_SubPub.jsx ├── reducers │ ├── alert.js │ ├── index.js │ ├── notification.js │ ├── weather.js │ └── weather_list.js ├── resources │ ├── Geolocation.resource.js │ ├── Open_Weather.resource.js │ └── ajax.js ├── services │ ├── Alert.jsx │ └── Notification.jsx ├── store.js ├── styles │ ├── _animations.scss │ ├── _fonts.scss │ ├── _main.scss │ ├── _mixins.scss │ ├── _normalize.scss │ ├── _varibles.scss │ ├── components │ │ ├── _open_weather_search.scss │ │ └── dumb │ │ │ └── _weather_view.scss │ ├── pages │ │ ├── _about.scss │ │ └── _home.scss │ ├── services │ │ ├── _alert.scss │ │ └── _notification.scss │ └── styles.scss ├── tests │ ├── __tests__ │ │ ├── components │ │ │ └── Open_Weather_Search │ │ │ │ └── Open_Weather_Search.js │ │ └── configurations │ │ │ ├── setupTests.js │ │ │ └── tempPolyfills.js │ └── e2e │ │ └── cypress │ │ ├── fixtures │ │ └── example.json │ │ ├── integration │ │ └── example_spec.js │ │ └── support │ │ ├── commands.js │ │ └── index.js └── vendor │ ├── core-js.js │ ├── custom-elements-es5-adapter.js │ └── jquery.js ├── cli ├── build.js └── cli.js ├── codecov.yml ├── cypress.json ├── fonts ├── FontAwesome.otf ├── fontawesome-webfont.eot ├── fontawesome-webfont.svg ├── fontawesome-webfont.ttf ├── fontawesome-webfont.woff └── fontawesome-webfont.woff2 ├── gulpfile.js ├── images ├── backtotop.svg ├── cross.svg ├── demo.gif ├── loader.gif └── react+redux.png ├── index.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── server ├── constants │ └── routes.constant.js └── routes │ └── index.js ├── webpack.config.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "react", "env", "stage-0" 4 | ], 5 | "plugins": [ 6 | "transform-async-to-generator", 7 | "transform-custom-element-classes" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | app/tests 2 | app/vendor 3 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": "eslint:recommended", 4 | "env": { 5 | "browser": true, 6 | "node": true, 7 | "es6": true, 8 | "jquery": true 9 | }, 10 | "globals": { 11 | "app": true 12 | }, 13 | "parserOptions": { 14 | "ecmaVersion": 6, 15 | "sourceType": "module" 16 | }, 17 | "rules": { 18 | "no-console": "error", 19 | "curly": "error", 20 | "semi": ["error", "always"], 21 | "no-empty": ["error", { "allowEmptyCatch": true }] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | yarn-error.log 2 | 3 | node_modules 4 | npm-debug* 5 | 6 | *.js.map 7 | 8 | .idea 9 | 10 | #app/config.json 11 | app/tests/__tests__/coverage/* 12 | /cypress/ 13 | 14 | .DS_Store 15 | 16 | build/* 17 | /index.html 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - 8.11.2 5 | 6 | branches: 7 | only: 8 | - master 9 | 10 | install: 11 | - npm install 12 | 13 | script: 14 | - npm run test 15 | - npm run e2e 16 | 17 | after_success: 18 | - npm run report-coverage -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ✨ Thank you for contributing ✨ 4 | 5 | There are just guideliness. 6 | 7 | Please feel free to contribute by submitting PR's for improvements to code snippets, explanations, etc. 8 | 9 | ## Submitting an issue 10 | 11 | Found a problem? Have an enhancement? 12 | 13 | First of all see if your issue or idea has already been [reported](https://github.com/shystruk/create-react-redux-app-structure/issues). 14 | 15 | If do not, open a [new one](https://github.com/shystruk/create-react-redux-app-structure/issues/new). 16 | 17 | ## Submitting a pull request 18 | 19 | - Fork this repository 20 | - Clone fork `git clone ...` 21 | - Navigate to the cloned directory 22 | - Install all dependencies `npm install` 23 | - Crate a new branch for the feature `git checkout -b new-feature` 24 | - Make changes 25 | - Commit changes `git commit -am 'What is feature about? :)'` 26 | - Push to the branch `git push origin new-feature` 27 | - Submit a PR 28 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const config = require('./app/config.json'); 4 | const TARGET = process.env.npm_lifecycle_event; 5 | 6 | module.exports = function(grunt) { 7 | grunt.initConfig({ 8 | pkg: grunt.file.readJSON('package.json'), 9 | 10 | 'cache-busting': { 11 | libsJs: { 12 | replace: ['./index.html'], 13 | replacement: 'libs.js', 14 | file: './build/libs.js', 15 | cleanup: true 16 | }, 17 | vendorJs: { 18 | replace: ['./index.html'], 19 | replacement: 'vendor.js', 20 | file: './build/vendor.js', 21 | cleanup: true 22 | }, 23 | web_components_vendorJs: { 24 | replace: ['./index.html'], 25 | replacement: 'web_components_vendor.js', 26 | file: './build/web_components_vendor.js', 27 | cleanup: true 28 | }, 29 | indexJs: { 30 | replace: ['./index.html'], 31 | replacement: 'index.js', 32 | file: './build/index.js', 33 | cleanup: true 34 | }, 35 | css: { 36 | replace: ['./index.html'], 37 | replacement: 'styles.min.css', 38 | file: './build/styles.min.css', 39 | cleanup: true 40 | } 41 | }, 42 | 43 | strip_code: { 44 | options: { 45 | blocks: [ 46 | { 47 | start_block: '/* staging-code */', 48 | end_block: '/* end-staging-code */' 49 | } 50 | ] 51 | }, 52 | target: { 53 | src: `${config.PATHS.app}/app.jsx`, 54 | dest: `${config.PATHS.app}/app.jsx` 55 | } 56 | }, 57 | 58 | replace: { 59 | js_images: { 60 | options: { 61 | patterns: [ 62 | { 63 | match: new RegExp('src:"./images/', 'g'), 64 | replacement: () => `src:"${config[TARGET].assetHost}/images/` 65 | } 66 | ] 67 | }, 68 | files: [ 69 | { 70 | src: ['./build/index.js'], 71 | dest: './' 72 | } 73 | ] 74 | }, 75 | fonts: { 76 | options: { 77 | patterns: [ 78 | { 79 | match: new RegExp('../fonts/', 'g'), 80 | replacement: () => `${config[TARGET].assetHost}/fonts/` 81 | } 82 | ] 83 | }, 84 | files: [ 85 | { 86 | src: ['./build/styles.min.css'], 87 | dest: './' 88 | } 89 | ] 90 | }, 91 | css_images: { 92 | options: { 93 | patterns: [ 94 | { 95 | match: new RegExp('../images/', 'g'), 96 | replacement: () => `${config[TARGET].assetHost}/images/` 97 | } 98 | ] 99 | }, 100 | files: [ 101 | { 102 | src: ['./build/styles.min.css'], 103 | dest: './' 104 | } 105 | ] 106 | } 107 | } 108 | }); 109 | 110 | grunt.loadNpmTasks('grunt-cache-busting'); 111 | grunt.loadNpmTasks('grunt-strip-code'); 112 | grunt.loadNpmTasks('grunt-replace'); 113 | 114 | grunt.registerTask('remove-code', ['strip_code']); 115 | grunt.registerTask('default', ['replace', 'cache-busting']); 116 | }; 117 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Vasyl Stokolosa 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Create React Redux App Structure [![Twitter URL](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?hashtags=reactjs%20%23redux%20%23javascript&original_referer=https%3A%2F%2Fpublish.twitter.com%2F&ref_src=twsrc%5Etfw&text=Start%20your%20project%20fast%20with%20Create%20React%20Redux%20App%20Structure&tw_p=tweetbutton&url=https%3A%2F%2Fgithub.com%2Fshystruk%2Fcreate-react-redux-app-structure&via=shystrukk) # 2 | 3 | 4 | [![MIT Licence](https://badges.frapsoft.com/os/mit/mit.svg?v=103)](https://opensource.org/licenses/mit-license.php) [![codecov](https://codecov.io/gh/shystruk/create-react-redux-app-structure/branch/master/graph/badge.svg)](https://codecov.io/gh/shystruk/create-react-redux-app-structure) [![Build Status](https://travis-ci.org/shystruk/create-react-redux-app-structure.svg?branch=master)](https://travis-ci.org/shystruk/create-react-redux-app-structure) [![npm version](https://badge.fury.io/js/create-react-redux-app-structure.svg)](https://badge.fury.io/js/create-react-redux-app-structure) 5 | 6 | Create React + Redux app structure with build configurations. 7 | 8 | ## What can I find here? ## 9 | - [Express](https://expressjs.com/), Cors 10 | - [React](https://reactjs.org/) + [Redux](https://redux.js.org/), ES6, async/await 11 | - [Web Components](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements) (Custom Elements) integration 12 | - React Router 13 | - Internationalization 14 | - SASS 15 | - PostCSS (autoprefixer), so you do not need -webkit, -moz or other prefixes 16 | - Build script configuration **Development, Staging, Production** with CDN, [cache-busting](https://www.keycdn.com/support/what-is-cache-busting/) support 17 | - Build script to bundle JS, CSS, with sourcemaps 18 | - Unit tests [Jest](https://jestjs.io/), [Enzyme](http://airbnb.io/enzyme/) 19 | - E2E [Cypress](https://www.cypress.io/) tests 20 | - [ESLint](https://eslint.org/) 21 | - Ghooks (pre-commit with unit tests and eslint validation) 22 | - Code Coverage (https://codecov.io) 23 | - Travis CI runs Unit and E2E tests and report to codecov 24 | 25 | ## Quick Start ## 26 | Create React + Redux app structure works on macOS, Windows, and Linux. 27 | If something doesn’t work, please file an [issue](https://github.com/shystruk/create-react-redux-app-structure/issues/new). 28 | 29 | #### npm 30 | `npm i -g create-react-redux-app-structure` 31 | 32 | #### yarn 33 | `yarn add global create-react-redux-app-structure` 34 | 35 | ``` 36 | create-react-redux-app-structure my-app 37 | cd my-app/ 38 | npm run fast-start 39 | ``` 40 | http://localhost:8080/ will be opened automatically. 41 | 42 | When you are ready to deploy to staging/production please see [Build Scripts](#build-scripts) section. 43 | 44 | ## Getting Started ## 45 | **You will need to have Node >= 6 on your local development machine and [Yarn](https://yarnpkg.com/en/docs/install#mac-stable) installed.** 46 | 47 | Install it once globally: 48 | 49 | #### npm 50 | `npm i -g create-react-redux-app-structure` 51 | 52 | #### yarn 53 | `yarn add global create-react-redux-app-structure` 54 | 55 | > Patience, please. It takes time, most of it is spent installing npm packages. 56 | 57 | ### Creating an App ### 58 | To create a new app, run: 59 | ``` 60 | create-react-redux-app-structure my-app 61 | cd my-app/ 62 | ``` 63 | It will create a directory called my-app inside the current folder. 64 | 65 | ### Prepare config.json for build configurations ### 66 | For running builds you need to have **config.json** in app/ folder. 67 | So you can create new one or rename **app/config.json.example**. 68 | 69 | Inside that file: 70 | - **PATHS** is used in Grunt and Gulp tasks 71 | - **assetHost** CDN path for each build 72 | - **serverHost** is used for running e2e Cypress tests 73 | 74 | ### Installation ### 75 | **`npm install`** or **`yarn install`** 76 | 77 | >You can run **npm run fast-start** script, it will install all npm packages, run dev build, server and open http://localhost:8080/ 78 | 79 | ![](images/demo.gif) 80 | 81 | ### Run build script ### 82 | Have a look at [Build Scripts](#build-scripts) section 83 | 84 | ### Run server ### 85 | **`node index.js`** or **npm run server** 86 | 87 | Then open http://localhost:8080/ to see test weather app :) 88 | 89 | ## Build scripts ## 90 | Development - **`npm run dev`** or **`yarn run dev`** 91 | 92 | Production - **`npm run prod`** or **`yarn run prod`** 93 | 94 | Staging - **`npm run staging`** or **`yarn run staging`** 95 | 96 | 97 | ## Tests ## 98 | Unit - **`npm run test`** or **`yarn run test`** 99 | 100 | Unit with watch - **`npm run test:watch`** or **`yarn run test:watch`** 101 | 102 | E2E - **`npm run e2e`** or **`yarn run e2e`** 103 | 104 | Coverage is here - *app/tests/__tests__/coverage/Icon-report/index.html* 105 | 106 | 107 | ## Automation tests ## 108 | Let's imagine that for automation tests we need to get access to the Redux store. 109 | We can do that by adding to the `window` object property with reference to the store. For e.g. in `app.jsx` file. 110 | Automation tests run only in **staging**, so for production build we remove them out by Grunt task `strip_code` 111 | 112 | ```javascript 113 | /* staging-code */ 114 | window.store = store; 115 | /* end-staging-code */ 116 | ``` 117 | 118 | ## Tips ## 119 | Kill all node processes: 120 | - MacOS `sudo killall -9 node` 121 | - Windows (cmd) `taskkill /f /im node.exe` 122 | 123 | ## Detailed description about features and approaches ## 124 | - [How create-react-redux-app-structure helps you to start a project faster](https://medium.com/@shystruk/how-create-react-redux-app-structure-helps-you-to-start-a-project-faster-cf564c64689c) 125 | - [clearIntervals() when user has a nap](https://codeburst.io/clearintervals-when-user-has-a-nap-3bf8010c986b) 126 | - [Do you still register window event listeners in each component?](https://medium.com/@shystruk/do-you-still-register-window-event-listeners-in-each-component-react-in-example-31a4b1f6f1c8) 127 | - [v4 Create React + Redux app structure with build configurations. What’s new?](https://medium.com/@shystruk/v4-create-react-redux-app-structure-with-build-configurations-whats-new-523bdec328c6) 128 | - [Integrate Custom Elements into React app](https://medium.com/@shystruk/integrate-custom-elements-into-react-app-ef38eba12905) 129 | 130 | ## Contributing ## 131 | 132 | I would love to have your help. 133 | 134 | If you have an idea how to improve or found an issue please read the [Contributions Guidelines](CONTRIBUTING.md) before submitting a PR. 135 | Thanks! 136 | 137 | ## License 138 | 139 | MIT © [Vasyl Stokolosa](https://about.me/shystruk) 140 | -------------------------------------------------------------------------------- /app/actions/alert.js: -------------------------------------------------------------------------------- 1 | export const SHOW_ALERT = 'SHOW_ALERT'; 2 | export const HIDE_ALERT = 'HIDE_ALERT'; 3 | 4 | /** 5 | * @param {String} message 6 | * @return {Object} 7 | */ 8 | export function showAlert(message) { 9 | return { 10 | type: SHOW_ALERT, 11 | message 12 | }; 13 | } 14 | 15 | /** 16 | * @return {Object} 17 | */ 18 | export function hideAlert() { 19 | return { 20 | type: HIDE_ALERT 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /app/actions/notification.js: -------------------------------------------------------------------------------- 1 | export const SHOW_NOTIFICATION = 'SHOW_NOTIFICATION'; 2 | export const REMOVE_NOTIFICATION = 'REMOVE_NOTIFICATION'; 3 | 4 | /** 5 | * @param {String} message 6 | * @return {Object} 7 | */ 8 | export function pushNotification(message) { 9 | return { 10 | type: SHOW_NOTIFICATION, 11 | message 12 | }; 13 | } 14 | 15 | /** 16 | * @return {Object} 17 | */ 18 | export function removeNotification() { 19 | return { 20 | type: REMOVE_NOTIFICATION 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /app/actions/weather.js: -------------------------------------------------------------------------------- 1 | import { getWeatherData } from './../resources/Open_Weather.resource'; 2 | import { parseWeatherResponseForUI } from './../components/Open_Weather_Search/Open_Weather_Search.utils'; 3 | 4 | export const PUSH_WEATHER = 'PUSH_WEATHER'; 5 | export const PUSH_WEATHER_LIST = 'PUSH_WEATHER_LIST'; 6 | export const REMOVE_WEATHER_FROM_LIST = 'REMOVE_WEATHER_FROM_LIST'; 7 | export const REQUEST_WEATHER = 'REQUEST_WEATHER'; 8 | export const RECEIVE_WEATHER = 'RECEIVE_WEATHER'; 9 | 10 | /** 11 | * @param {Object} weather 12 | * @return {Object} 13 | */ 14 | export function pushWeather(weather) { 15 | return { 16 | type: PUSH_WEATHER, 17 | weather 18 | }; 19 | } 20 | 21 | /** 22 | * @return {Object} 23 | */ 24 | export function requestWeather() { 25 | return { 26 | type: REQUEST_WEATHER 27 | }; 28 | } 29 | 30 | /** 31 | * @return {Object} 32 | */ 33 | export function receiveWeather() { 34 | return { 35 | type: RECEIVE_WEATHER 36 | }; 37 | } 38 | 39 | /** 40 | * @param {String} cityName 41 | * @return {Promise} 42 | */ 43 | export const fetchWeather = (cityName) => (dispatch) => { 44 | dispatch(requestWeather()); 45 | 46 | return getWeatherData(cityName) 47 | .then((weather) => { 48 | dispatch(pushWeather(parseWeatherResponseForUI(weather))); 49 | }) 50 | .finally(() => { 51 | dispatch(receiveWeather()); 52 | }); 53 | }; 54 | -------------------------------------------------------------------------------- /app/actions/weather_list.js: -------------------------------------------------------------------------------- 1 | export const PUSH_WEATHER_LIST = 'PUSH_WEATHER_LIST'; 2 | export const REMOVE_WEATHER_FROM_LIST = 'REMOVE_WEATHER_FROM_LIST'; 3 | 4 | /** 5 | * @param {Object} weather 6 | * @return {Object} 7 | */ 8 | export function pushWeatherList(weather) { 9 | return { 10 | type: PUSH_WEATHER_LIST, 11 | weather 12 | }; 13 | } 14 | 15 | /** 16 | * @param {Number} index 17 | * @return {Object} 18 | */ 19 | export function removeWeatherFromList(index) { 20 | return { 21 | type: REMOVE_WEATHER_FROM_LIST, 22 | index 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /app/app.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDom from 'react-dom'; 3 | import { BrowserRouter as Router } from 'react-router-dom'; 4 | import { Provider } from 'react-redux'; 5 | import store from './store'; 6 | import _find from 'lodash/find'; 7 | import DOMReady from './helpers/domReady'; 8 | DOMReady(); 9 | 10 | import App from './pages/App'; 11 | 12 | // Localization 13 | import messages from './i18n/messages'; 14 | import { addLocaleData, IntlProvider } from 'react-intl'; 15 | import en from 'react-intl/locale-data/en'; 16 | import es from 'react-intl/locale-data/es'; 17 | import fr from 'react-intl/locale-data/fr'; 18 | addLocaleData([...en, ...es, ...fr]); 19 | 20 | let locale = _find(['en', 'es', 'fr'], (locale) => { 21 | return app.locale.indexOf(locale) !== -1; 22 | }); 23 | 24 | 25 | ReactDom.render( 26 | 27 | 28 | 29 | 30 | 31 | 32 | , 33 | 34 | document.getElementById('app') 35 | ); 36 | -------------------------------------------------------------------------------- /app/components/CSV_Validator/CSV_Validator.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import CSVFileValidator from 'csv-file-validator'; 3 | 4 | const CSV_Config = { 5 | headers: [] 6 | } 7 | 8 | export default class CSV_Validator extends React.Component { 9 | constructor() { 10 | super(); 11 | } 12 | 13 | /** 14 | * @param {Object} event 15 | */ 16 | handleChange = (event) => { 17 | this.validateFile(event.target.files[0]); 18 | 19 | // allow to upload the same file twice in a row 20 | event.target.value = ''; 21 | } 22 | 23 | /** 24 | * @param {File} file 25 | */ 26 | validateFile = (file) => { 27 | CSVFileValidator(file, CSV_Config) 28 | .then(csvData => { 29 | console.log('DATA: ', csvData.data) 30 | console.log('MESSAGES: ', csvData.inValidMessages) 31 | }) 32 | .catch(err => { 33 | console.log('ERROR: ', err) 34 | }) 35 | } 36 | 37 | render() { 38 | return ( 39 |
40 | 41 |
42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/components/Open_Weather_Search/Open_Weather_Search.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import store from './../../store'; 3 | import { pushWeather, fetchWeather } from './../../actions/weather'; 4 | import { pushWeatherList } from './../../actions/weather_list'; 5 | import { showAlert } from './../../actions/alert'; 6 | import { getWeatherData } from './../../resources/Open_Weather.resource'; 7 | import { getGeoLocation } from './../../resources/Geolocation.resource'; 8 | import { Open_Weather_Interface } from './../../helpers/interfaces'; 9 | import { getLocation, getCityNameFromGeocode } from './../../helpers/geolocation'; 10 | import { parseWeatherResponseForUI } from './Open_Weather_Search.utils'; 11 | import { KEY_CODES } from './../../constants/keyCodes.constant'; 12 | import { ERROR_MESSAGES } from './../../constants/request.constant'; 13 | 14 | export default class Open_Weather extends React.Component { 15 | constructor() { 16 | super(); 17 | 18 | this.state = Open_Weather_Interface; 19 | } 20 | 21 | componentDidMount() { 22 | if (!this.props.weather.loaded) { 23 | this.setState(() => ({preload: true})); 24 | 25 | getLocation() 26 | .then(position => { 27 | return getGeoLocation(`${position.latitude},${position.longitude}`); 28 | }) 29 | .then(location => { 30 | return store.dispatch(fetchWeather(getCityNameFromGeocode(location))); 31 | }) 32 | .catch(error => { 33 | store.dispatch(showAlert((error && error.message) || ERROR_MESSAGES.GEO_LOCATION_UNAVAILABLE)); 34 | }) 35 | .finally(() => { 36 | this.setState(() => ({preload: false})); 37 | }); 38 | } 39 | } 40 | 41 | /** 42 | * @param {Object} event 43 | */ 44 | handleKeyUp = (event) => { 45 | if (event.keyCode === KEY_CODES.ENTER) { 46 | this.handleSearch(); 47 | } 48 | }; 49 | 50 | /** 51 | * @param {Object} event 52 | */ 53 | handleChange = (event) => { 54 | const target = event.target; 55 | const value = target.type === 'checkbox' ? target.checked : target.value; 56 | 57 | this.setState(() => ({[target.name]: value})); 58 | }; 59 | 60 | handleSearch = async () => { 61 | let weather = {}; 62 | 63 | this.setState(() => ({preload: true})); 64 | 65 | try { 66 | weather = await getWeatherData(this.state.cityName, this.state.isCityList); 67 | 68 | Open_Weather.addWeatherToStore(weather); 69 | 70 | } catch(error) { 71 | // do not handle Weather API 'find' endpoint error :) 72 | if (!this.state.isCityList) { 73 | store.dispatch(showAlert(error.message)); 74 | } 75 | } finally { 76 | this.setState(() => ({preload: false})); 77 | } 78 | }; 79 | 80 | /** 81 | * @param {Object} weather 82 | */ 83 | static addWeatherToStore(weather) { 84 | if (weather.list) { 85 | for (let item of weather.list) { 86 | store.dispatch(pushWeatherList(parseWeatherResponseForUI(item))); 87 | } 88 | } else { 89 | store.dispatch(pushWeather(parseWeatherResponseForUI(weather))); 90 | } 91 | } 92 | 93 | render() { 94 | return ( 95 |
96 | 97 |

Current weather in your city

98 | 99 | 101 | 105 | 106 | 107 | 108 | 110 | 111 |
112 | ); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /app/components/Open_Weather_Search/Open_Weather_Search.utils.js: -------------------------------------------------------------------------------- 1 | import _lowerCase from 'lodash/lowerCase'; 2 | 3 | /** 4 | * @param {Object} weather 5 | * @return {Object} 6 | */ 7 | export const parseWeatherResponseForUI = function (weather) { 8 | return { 9 | location: `${weather.name}, ${weather.sys.country}`, 10 | flag: _lowerCase(weather.sys.country), 11 | description: weather.weather[0].description, 12 | temperature: `${weather.main.temp}°С`, 13 | wind: `wind ${weather.wind.speed} m/s`, 14 | clouds: `clouds ${weather.clouds.all} %` 15 | }; 16 | }; 17 | -------------------------------------------------------------------------------- /app/components/dumb/Resize_SubPub_Action.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { FormattedHTMLMessage } from 'react-intl'; 3 | import PublishSubscribe from 'publish-subscribe-js'; 4 | import { PUB_SUB } from './../../constants/events.constant'; 5 | import { Resize_SubPub_Action_Interface } from './../../helpers/interfaces'; 6 | 7 | export default class Resize_SubPub_Action extends React.Component { 8 | constructor() { 9 | super(); 10 | 11 | this.state = Resize_SubPub_Action_Interface; 12 | 13 | this.resizeSubKey = 0; 14 | } 15 | 16 | updateSizes = (data = {}) => { 17 | this.setState({ 18 | width: data.width || window.innerWidth, 19 | height: data.height || window.innerHeight 20 | }); 21 | }; 22 | 23 | componentDidMount() { 24 | this.updateSizes(); 25 | this.resizeSubKey = PublishSubscribe.subscribe(PUB_SUB.RESIZE, this.updateSizes); 26 | } 27 | 28 | componentWillUnmount() { 29 | PublishSubscribe.unsubscribe(PUB_SUB.RESIZE, this.resizeSubKey); 30 | } 31 | 32 | render() { 33 | return ( 34 |
35 |
36 |
37 |
38 |
39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/components/dumb/Show_Page_Visibility_API.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PublishSubscribe from 'publish-subscribe-js'; 3 | import { Show_Page_Visibility_API_Interface } from './../../helpers/interfaces'; 4 | import { PUB_SUB } from './../../constants/events.constant'; 5 | import SetInterval from 'set-interval'; 6 | import store from './../../store'; 7 | import { showAlert } from './../../actions/alert'; 8 | import { FormattedMessage } from 'react-intl'; 9 | 10 | export default class Show_Page_Visibility_API extends React.Component { 11 | constructor() { 12 | super(); 13 | 14 | this.state = Show_Page_Visibility_API_Interface; 15 | this.visibilityChangeSubKey = 0; 16 | } 17 | 18 | simulateHTTPRequest = () => { 19 | setTimeout(() => { 20 | if (!this.unmount) { 21 | this.setState({amountOfNewUsers: ++this.state.amountOfNewUsers}); 22 | } 23 | }, 500); 24 | }; 25 | 26 | /** 27 | * @param {Object} data 28 | */ 29 | handleVisibilityChange = (data) => { 30 | if (data.action === 'continue') { 31 | SetInterval.start(this.simulateHTTPRequest, 1000, 'page_visibility'); 32 | store.dispatch(showAlert('Seems like the page was not visible. Do not worry, we keep working :)')); 33 | } else { 34 | SetInterval.clear('page_visibility'); 35 | } 36 | }; 37 | 38 | componentDidMount() { 39 | SetInterval.start(this.simulateHTTPRequest, 1000, 'page_visibility'); 40 | this.visibilityChangeSubKey = PublishSubscribe.subscribe(PUB_SUB.PAGE_VISIBILITY, this.handleVisibilityChange); 41 | } 42 | 43 | componentWillUnmount() { 44 | this.unmount = true; 45 | SetInterval.clear('page_visibility'); 46 | PublishSubscribe.unsubscribe(PUB_SUB.PAGE_VISIBILITY, this.visibilityChangeSubKey); 47 | } 48 | 49 | render() { 50 | return ( 51 |
52 |
53 |

54 | 55 |

56 |
57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/components/dumb/Weather_View.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import _isEmpty from 'lodash/isEmpty'; 3 | import { FLAG_IMAGE_URL } from '../../constants/components/Weather_View.constant'; 4 | import { scrollTo } from '../../helpers/uiActions'; 5 | 6 | export default class Weather_View extends React.Component { 7 | 8 | /** 9 | * @param {Object} weather 10 | * @return {XML} 11 | */ 12 | static weatherTemplate(weather) { 13 | return
14 |
15 | {weather.location} 16 | 17 | {weather.description} 18 |
19 |
20 | {weather.temperature}, 21 | {weather.wind}, 22 | {weather.clouds} 23 |
24 |
; 25 | } 26 | 27 | render() { 28 | let oneCity = ''; 29 | let weather = this.props.weather; 30 | let weatherList = this.props.weatherList; 31 | 32 | if (!_isEmpty(weather.data)) { 33 | oneCity =
  • {Weather_View.weatherTemplate(weather.data)}
  • ; 34 | } 35 | 36 | return ( 37 |
    38 | 53 |
    54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "staging": { 3 | "assetHost": "." 4 | }, 5 | "prod": { 6 | "assetHost": "." 7 | }, 8 | "dev": { 9 | "assetHost": "." 10 | }, 11 | "PATHS": { 12 | "app": "./app", 13 | "sass_index": "./app/styles/styles.scss", 14 | "sass": ["./app/styles/*.scss", "./app/styles/**/*.scss", "./app/styles/**/**/*.scss"], 15 | "build": "./build", 16 | "vendor": "./app/vendor", 17 | "styles": "./build/styles.css", 18 | "external": ["./app/helpers/appGlobal.js", "./app/helpers/domReady.js"] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/config.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "staging": { 3 | "assetHost": "" 4 | }, 5 | "prod": { 6 | "assetHost": "" 7 | }, 8 | "dev": { 9 | "assetHost": ".", 10 | "serverHost": "http://localhost:8080/" 11 | }, 12 | "PATHS": { 13 | "app": "./app", 14 | "sass_index": "./app/styles/styles.scss", 15 | "sass": ["./app/styles/*.scss", "./app/styles/**/*.scss", "./app/styles/**/**/*.scss"], 16 | "build": "./build", 17 | "vendor": "./app/vendor", 18 | "styles": "./build/styles.css", 19 | "external": ["./app/helpers/globalService.js", "./app/helpers/domReady.js"] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/constants/components/Open_Weather_Search.constant.js: -------------------------------------------------------------------------------- 1 | import { URL } from '../request.constant'; 2 | 3 | export const API_KEY = '21f7dd457b739d8e583a82c904c09054'; 4 | 5 | /** 6 | * @param {String} cityName 7 | * @return {String} 8 | */ 9 | export const GET_URL_FOR_ONE_CITY = function (cityName) { 10 | return `${URL.WEATHER}q=${cityName}&appid=${API_KEY}&units=metric`; 11 | }; 12 | 13 | /** 14 | * @param {String} cityName 15 | * @return {String} 16 | */ 17 | export const GET_URL_FOR_LIST = function (cityName) { 18 | return `${URL.WEATHER_LIST}q=${cityName}&type=like&sort=population&cnt=30&appid=${API_KEY}&units=metric`; 19 | }; 20 | -------------------------------------------------------------------------------- /app/constants/components/Weather_View.constant.js: -------------------------------------------------------------------------------- 1 | export const FLAG_IMAGE_URL = 'http://openweathermap.org/images/flags/'; 2 | -------------------------------------------------------------------------------- /app/constants/events.constant.js: -------------------------------------------------------------------------------- 1 | export const PUB_SUB = { 2 | RESIZE: 'resizeEvent', 3 | PAGE_VISIBILITY: 'visibilityChangeEvent' 4 | }; 5 | -------------------------------------------------------------------------------- /app/constants/keyCodes.constant.js: -------------------------------------------------------------------------------- 1 | export const KEY_CODES = { 2 | ENTER: 13 3 | }; 4 | -------------------------------------------------------------------------------- /app/constants/request.constant.js: -------------------------------------------------------------------------------- 1 | export const URL = { 2 | WEATHER: 'weather/', 3 | WEATHER_LIST: 'weather_list/', 4 | LOCATION: 'location/' 5 | }; 6 | 7 | export const ERROR_MESSAGES = { 8 | GEO_LOCATION_UNAVAILABLE: 'Location information is unavailable' 9 | }; 10 | -------------------------------------------------------------------------------- /app/helpers/appGlobal.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @doc 3 | * Global object for sharing functional helpers 4 | */ 5 | 6 | var app = app || {}; 7 | 8 | (function () { 9 | /** 10 | * A function that performs no operations. 11 | */ 12 | app.noop = Object.freeze(function(){}); 13 | 14 | app.locale = 15 | (navigator.languages && navigator.languages[0]) 16 | || navigator.language 17 | || navigator.userLanguage 18 | || 'en'; 19 | 20 | }()); 21 | -------------------------------------------------------------------------------- /app/helpers/domReady.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @doc 3 | * Listen when the DOM is fully loaded and have window event listeners in one place. 4 | */ 5 | 6 | import PublishSubscribe from 'publish-subscribe-js'; 7 | import jQuery from 'jquery'; 8 | import { PUB_SUB } from './../constants/events.constant'; 9 | 10 | export default function DOMReady() { 11 | jQuery(document).ready(function() { 12 | const toTopBtn = jQuery('#toTop'); 13 | 14 | 15 | // Window Visibilitychange Listener 16 | initPageVisibilityAPI(); 17 | 18 | // Window Scroll Listener 19 | jQuery(window).scroll(function() { 20 | showHideToTopBtn(); 21 | }); 22 | 23 | // Window Resize Listener 24 | jQuery(window).resize(function() { 25 | PublishSubscribe.publish(PUB_SUB.RESIZE, {width: window.innerWidth, height: window.innerHeight}); 26 | }); 27 | 28 | 29 | toTopBtn.click(function () { 30 | jQuery('html, body').animate({scrollTop:0}, 'slow'); 31 | }); 32 | 33 | function showHideToTopBtn() { 34 | if (jQuery(window).scrollTop() < 200) { 35 | toTopBtn.fadeOut(); 36 | } else if (jQuery(window).scrollTop() > 200) { 37 | toTopBtn.fadeIn(); 38 | } 39 | } 40 | 41 | function initPageVisibilityAPI() { 42 | let hidden, visibilityChange; 43 | 44 | // Opera 12.10 and Firefox 18 and later support 45 | if (typeof document.hidden !== 'undefined') { 46 | hidden = 'hidden'; 47 | visibilityChange = 'visibilitychange'; 48 | } else if (typeof document.msHidden !== 'undefined') { 49 | hidden = 'msHidden'; 50 | visibilityChange = 'msvisibilitychange'; 51 | } else if (typeof document.webkitHidden !== 'undefined') { 52 | hidden = 'webkitHidden'; 53 | visibilityChange = 'webkitvisibilitychange'; 54 | } 55 | 56 | // Warn if the browser doesn't support addEventListener or the Page Visibility API 57 | if (typeof document.addEventListener === 'undefined' || typeof document.hidden === 'undefined') { 58 | alert('This demo requires a browser, such as Google Chrome or Firefox, that supports the Page Visibility API.'); 59 | } else { 60 | document.addEventListener(visibilityChange, _handleVisibilityChange, false); 61 | } 62 | 63 | function _handleVisibilityChange(event) { 64 | if (document[hidden]) { 65 | PublishSubscribe.publish(PUB_SUB.PAGE_VISIBILITY, {event}); 66 | } else { 67 | // page is visible you may continue doing what was stopped 68 | PublishSubscribe.publish(PUB_SUB.PAGE_VISIBILITY, {event, action: 'continue'}); 69 | } 70 | } 71 | } 72 | }); 73 | } 74 | -------------------------------------------------------------------------------- /app/helpers/geolocation.js: -------------------------------------------------------------------------------- 1 | import _find from 'lodash/find'; 2 | import _includes from 'lodash/includes'; 3 | 4 | /** 5 | * @return {Promise} 6 | */ 7 | export const getLocation = () => { 8 | return new Promise((resolve, reject) => { 9 | if (navigator.geolocation) { 10 | return void navigator.geolocation.getCurrentPosition((position) => { 11 | resolve({ 12 | latitude: position.coords.latitude, 13 | longitude: position.coords.longitude 14 | }); 15 | }, (error) => { 16 | switch(error.code) { 17 | case error.PERMISSION_DENIED: 18 | return reject({message: 'User denied the request for Geolocation'}); 19 | case error.POSITION_UNAVAILABLE: 20 | return reject({message: 'Location information is unavailable'}); 21 | case error.TIMEOUT: 22 | return reject({message: 'The request to get user location timed out'}); 23 | case error.UNKNOWN_ERROR: 24 | return reject({message: 'An unknown error occurred'}); 25 | } 26 | }); 27 | } 28 | 29 | reject({message: 'Geolocation is not supported by this browser'}); 30 | }); 31 | }; 32 | 33 | /** 34 | * @param {Object} location 35 | * @return {String} 36 | */ 37 | export const getCityNameFromGeocode = (location) => { 38 | const address = _find((location.results[0] && location.results[0].address_components) || [], (address) => { 39 | return _includes(address.types, 'locality'); 40 | }); 41 | 42 | if (address) { 43 | switch (address.long_name) { 44 | case 'Kyiv': 45 | return 'Kiev'; 46 | default: 47 | return address.long_name; 48 | } 49 | } 50 | 51 | return 'Kiev'; 52 | }; 53 | -------------------------------------------------------------------------------- /app/helpers/interfaces.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @doc 3 | * Interfaces that describes component state object 4 | */ 5 | 6 | export const Open_Weather_Interface = { 7 | cityName: '', 8 | isCityList: false, 9 | preload: false 10 | }; 11 | 12 | export const Resize_SubPub_Action_Interface = { 13 | width: 0, 14 | height: 0 15 | }; 16 | 17 | export const Show_Page_Visibility_API_Interface = { 18 | amountOfNewUsers: 0 19 | }; 20 | -------------------------------------------------------------------------------- /app/helpers/uiActions.js: -------------------------------------------------------------------------------- 1 | import jQuery from 'jquery'; 2 | 3 | /** 4 | * @param {String} element 5 | * @param {Number=} additionalSpace 6 | * @param {Object=} event 7 | */ 8 | export const scrollTo = (element, additionalSpace = 0, event) => { 9 | event && event.preventDefault(); 10 | event && event.stopPropagation(); 11 | 12 | jQuery('html, body').animate({ 13 | scrollTop: jQuery(element).offset().top + additionalSpace 14 | }, 1000); 15 | }; 16 | -------------------------------------------------------------------------------- /app/helpers/validation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @doc 3 | * Validation helpers 4 | */ 5 | 6 | /** 7 | * @param {String} email 8 | */ 9 | export const isEmailValid = email => { 10 | const reqExp = /[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,3}$/; 11 | return reqExp.test(email); 12 | }; 13 | -------------------------------------------------------------------------------- /app/i18n/en/messages.js: -------------------------------------------------------------------------------- 1 | export default { 2 | // PAGES 3 | 'pages.home': 'Home', 4 | 'pages.about': 'About', 5 | 'pages.resize_subpub': 'Resize SubPub', 6 | 'pages.page_visibility_api': 'Page Visibility API', 7 | 'pages.components_communication': 'Components Communication', 8 | 'pages.csv_check': 'CSV Validator', 9 | 10 | // COMPONENTS 11 | 'dumb.amount_of_new_users': 'Amount of new users: {amountOfUsers}', 12 | 'dumb.display_width': 'Width: {width}', 13 | 'dumb.display_height': 'Height: {height}' 14 | }; 15 | -------------------------------------------------------------------------------- /app/i18n/es/messages.js: -------------------------------------------------------------------------------- 1 | export default { 2 | // PAGES 3 | 'pages.home': 'Casa', 4 | 'pages.about': 'Acerca de', 5 | 'pages.resize_subpub': 'Cambiar el tamaño SubPub', 6 | 'pages.page_visibility_api': 'API de visibilidad de la página', 7 | 'pages.components_communication': 'Comunicación de componentes', 8 | 'pages.csv_check': 'CSV Validator', 9 | 10 | // COMPONENTS 11 | 'dumb.amount_of_new_users': 'Amount of new users: {amountOfUsers}', 12 | 'dumb.display_width': 'Anchura: {width}', 13 | 'dumb.display_height': 'Altura: {height}' 14 | }; 15 | -------------------------------------------------------------------------------- /app/i18n/fr/messages.js: -------------------------------------------------------------------------------- 1 | export default { 2 | // PAGES 3 | 'pages.home': 'Maison', 4 | 'pages.about': 'Sur', 5 | 'pages.resize_subpub': 'Redimensionner SubPub', 6 | 'pages.page_visibility_api': 'L’API Page Visibility', 7 | 'pages.components_communication': 'Composants Communication', 8 | 'pages.csv_check': 'CSV Validator', 9 | 10 | // COMPONENTS 11 | 'dumb.amount_of_new_users': 'Amount of new users: {amountOfUsers}', 12 | 'dumb.display_width': 'Largeur: {width}', 13 | 'dumb.display_height': 'La taille: {height}' 14 | }; 15 | -------------------------------------------------------------------------------- /app/i18n/messages.js: -------------------------------------------------------------------------------- 1 | import en from './en/messages'; 2 | import es from './es/messages'; 3 | import fr from './fr/messages'; 4 | 5 | export default { 6 | 'en': en, 7 | 'es': es, 8 | 'fr': fr 9 | }; 10 | -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Create React + Redux App Structure 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
    17 | 18 |
    19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/pages/About/About.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { FormattedMessage } from 'react-intl'; 3 | 4 | import Open_Weather_Search from './../../components/Open_Weather_Search/Open_Weather_Search'; 5 | import Weather_View from './../../components/dumb/Weather_View'; 6 | import CustomEvent from "custom-event-js"; 7 | 8 | export default class About extends React.Component { 9 | constructor() { 10 | super(); 11 | } 12 | 13 | componentDidMount() { 14 | CustomEvent.DISPATCH('WEB_COMP_SHOW_NOTIFICATION', { 15 | type: 'warning', 16 | message: 'Hello again! ;) \n I\'m notification-service based on Custom Element' 17 | }); 18 | } 19 | 20 | render() { 21 | let weatherStore = this.props.weather; 22 | let weatherListStore = this.props.weatherCities; 23 | 24 | return ( 25 |
    26 | 27 |

    28 | 29 |

    You are not able to remove weather from the list on this page :)

    30 | 31 | 32 | 33 | 34 |
    35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/pages/App.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route, Link, withRouter } from 'react-router-dom'; 3 | import { connect } from 'react-redux'; 4 | import { FormattedMessage } from 'react-intl'; 5 | import store from './../store'; 6 | import 'notification-service-js'; 7 | import CustomEvent from 'custom-event-js'; 8 | 9 | import noInternet from 'no-internet'; 10 | import isEmpty from 'lodash/isEmpty'; 11 | 12 | import { pushNotification, removeNotification } from './../actions/notification'; 13 | 14 | // Services 15 | import Alert from './../services/Alert'; 16 | import Notification from './../services/Notification'; 17 | 18 | // Pages 19 | import Home from './Home/Home'; 20 | import About from './About/About'; 21 | import Resize_SubPub from './Resize_SubPub/Resize_SubPub'; 22 | import Page_Visibility_API from './Page_Visibility_API/Page_Visibility_API'; 23 | import Components_Communication from './Components_Communication/Components_Communication'; 24 | import CSV_Check from './CSV_Check/CSV_Check'; 25 | 26 | function mapStateToProps(store, props) { 27 | return { 28 | alert: store.alert, 29 | weather: store.weather, 30 | weatherCities: store.weatherCities, 31 | notification: store.notification 32 | }; 33 | } 34 | 35 | class App extends React.Component { 36 | constructor() { 37 | super(); 38 | } 39 | 40 | componentDidMount() { 41 | noInternet({callback: (offline) => { 42 | if (offline && isEmpty(this.props.notification.message)) { 43 | store.dispatch(pushNotification('You are offline 😎')); 44 | } else if (!offline && !isEmpty(this.props.notification.message)) { 45 | store.dispatch(removeNotification()); 46 | } 47 | }}); 48 | 49 | CustomEvent.DISPATCH('WEB_COMP_SHOW_NOTIFICATION', { 50 | type: 'success', 51 | message: 'Welcome! I\'m notification-service based on Custom Element' 52 | }); 53 | } 54 | 55 | render() { 56 | let alertStore = this.props.alert; 57 | let notificationStore = this.props.notification; 58 | 59 | return ( 60 |
    61 | 62 | 70 | 71 | } /> 72 | } /> 73 | } /> 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 |
    83 | ); 84 | } 85 | } 86 | 87 | export default withRouter(connect(mapStateToProps)(App)); 88 | -------------------------------------------------------------------------------- /app/pages/CSV_Check/CSV_Check.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { FormattedMessage } from 'react-intl'; 3 | import CSV_Validator from '../../components/CSV_Validator/CSV_Validator'; 4 | 5 | export default class CSV_Check extends React.Component { 6 | constructor() { 7 | super(); 8 | } 9 | 10 | render() { 11 | return ( 12 |
    13 |

    14 | 15 | 16 |
    17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/pages/Components_Communication/Components_Communication.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import jQuery from 'jquery'; 3 | import { FormattedMessage } from 'react-intl'; 4 | import { oneWay, twoWay, oneTime, disconnect, disconnectAll } from 'bind-dom'; 5 | 6 | export default class Components_Communication extends React.Component { 7 | componentDidMount() { 8 | oneWay('inputObserver', document.querySelector('#observer_1'), document.querySelector('#observer_2')); 9 | oneTime('inputObserver_2', document.querySelector('#observer_3'), document.querySelector('#observer_4')); 10 | twoWay('inputObserver_3', document.querySelector('#observer_5'), document.querySelector('#observer_6')); 11 | 12 | setTimeout(() => { 13 | document.querySelector('#observer_3').innerHTML = 'I\'m here :)'; 14 | }, 5000); 15 | } 16 | 17 | componentWillUnmount() { 18 | disconnect('inputObserver'); 19 | disconnectAll(); 20 | } 21 | 22 | onChange = (event) => { 23 | jQuery('#observer_1').attr('value', event.target.value); 24 | } 25 | 26 | onChangeArea = (event) => { 27 | jQuery(event.target).attr('data-bind-dom', event.target.value.length); 28 | } 29 | 30 | render() { 31 | return ( 32 |
    33 |

    34 | 35 |

    Synchronization between two DOM elements (One-Time, One-Way & Two-Way binding) bind-dom

    36 | 37 |
    38 |

    OneWay

    39 |
    40 |
    Input Observer
    41 | 42 |
    43 |
    44 |
    Apply To
    45 | 46 |
    47 |
    48 | 49 |
    50 | 51 |
    52 |

    OneTime

    53 | Loading... 54 | Waiting... 55 |
    56 | 57 |
    58 |

    TwoWay

    59 |
    60 |