├── Chapter01 ├── .gitignore ├── .babelrc ├── server │ ├── index.js │ ├── server.js │ └── routes.js ├── src │ ├── actions │ │ └── article.js │ ├── falcorModel.js │ ├── reducers │ │ └── article.js │ ├── app.js │ └── layouts │ │ └── PublishingApp.js ├── dist │ └── index.html ├── initData.js ├── webpack.config.js └── package.json ├── Chapter02 ├── .gitignore ├── .babelrc ├── server │ ├── index.js │ ├── configSecret.js │ ├── server.js │ ├── configMongoose.js │ ├── routes.js │ └── routesSession.js ├── src │ ├── actions │ │ └── article.js │ ├── falcorModel.js │ ├── reducers │ │ ├── index.js │ │ └── article.js │ ├── store │ │ └── configureStore.js │ ├── layouts │ │ ├── CoreLayout.js │ │ └── PublishingApp.js │ ├── app.js │ ├── views │ │ ├── DashboardView.js │ │ ├── RegisterView.js │ │ └── LoginView.js │ ├── routes │ │ └── index.js │ ├── containers │ │ └── Root.js │ └── components │ │ ├── DefaultInput.js │ │ ├── LoginForm.js │ │ └── RegisterForm.js ├── dist │ └── index.html ├── initData.js ├── initPubUsers.js ├── webpack.config.js └── package.json ├── Chapter03 ├── .gitignore ├── .babelrc ├── server │ ├── index.js │ ├── configSecret.js │ ├── fetchServerSide.js │ ├── configMongoose.js │ ├── routes.js │ ├── routesSession.js │ └── server.js ├── src │ ├── actions │ │ └── article.js │ ├── falcorModel.js │ ├── reducers │ │ ├── index.js │ │ └── article.js │ ├── store │ │ └── configureStore.js │ ├── app.js │ ├── containers │ │ └── Root.js │ ├── views │ │ ├── DashboardView.js │ │ ├── RegisterView.js │ │ └── LoginView.js │ ├── layouts │ │ ├── CoreLayout.js │ │ └── PublishingApp.js │ ├── routes │ │ └── index.js │ └── components │ │ ├── DefaultInput.js │ │ ├── LoginForm.js │ │ └── RegisterForm.js ├── dist │ └── index.html ├── initData.js ├── initPubUsers.js ├── webpack.config.js └── package.json ├── Chapter04 ├── .gitignore ├── .babelrc ├── server │ ├── index.js │ ├── configSecret.js │ ├── fetchServerSide.js │ ├── configMongoose.js │ ├── routes.js │ ├── routesSession.js │ └── server.js ├── dist │ ├── placeholder.png │ ├── static │ │ ├── avatar.png │ │ └── placeholder.png │ ├── index.html │ └── styles-draft-js.css ├── src │ ├── falcorModel.js │ ├── reducers │ │ ├── index.js │ │ └── article.js │ ├── store │ │ └── configureStore.js │ ├── actions │ │ └── article.js │ ├── app.js │ ├── containers │ │ └── Root.js │ ├── views │ │ ├── LogoutView.js │ │ ├── RegisterView.js │ │ ├── DashboardView.js │ │ ├── LoginView.js │ │ └── articles │ │ │ └── AddArticleView.js │ ├── utils │ │ └── mapHelpers.js │ ├── components │ │ ├── DefaultInput.js │ │ ├── LoginForm.js │ │ ├── RegisterForm.js │ │ ├── ArticleCard.js │ │ └── articles │ │ │ ├── wysiwyg │ │ │ └── WYSIWYGbuttons.js │ │ │ └── WYSIWYGeditor.js │ ├── routes │ │ └── index.js │ └── layouts │ │ ├── PublishingApp.js │ │ └── CoreLayout.js ├── initData.js ├── initPubUsers.js ├── webpack.config.js └── package.json ├── Chapter05 ├── .gitignore ├── .babelrc ├── server │ ├── index.js │ ├── configSecret.js │ ├── fetchServerSide.js │ ├── configMongoose.js │ ├── routesSession.js │ └── server.js ├── dist │ ├── avatar.png │ ├── placeholder.png │ ├── index.html │ └── styles-draft-js.css ├── src │ ├── reducers │ │ ├── index.js │ │ └── article.js │ ├── store │ │ └── configureStore.js │ ├── actions │ │ └── article.js │ ├── app.js │ ├── containers │ │ └── Root.js │ ├── views │ │ ├── LogoutView.js │ │ ├── RegisterView.js │ │ ├── DashboardView.js │ │ ├── LoginView.js │ │ └── articles │ │ │ └── AddArticleView.js │ ├── utils │ │ └── mapHelpers.js │ ├── falcorModel.js │ ├── components │ │ ├── DefaultInput.js │ │ ├── LoginForm.js │ │ ├── RegisterForm.js │ │ ├── ArticleCard.js │ │ └── articles │ │ │ ├── wysiwyg │ │ │ └── WYSIWYGbuttons.js │ │ │ └── WYSIWYGeditor.js │ ├── routes │ │ └── index.js │ └── layouts │ │ ├── PublishingApp.js │ │ └── CoreLayout.js ├── initData.js ├── initPubUsers.js ├── webpack.config.js └── package.json ├── Chapter06 ├── .gitignore ├── .babelrc ├── server │ ├── configSecret.js │ ├── .env │ ├── index.js │ ├── fetchServerSide.js │ ├── configMongoose.js │ ├── routesSession.js │ └── server.js ├── dist │ ├── avatar.png │ ├── placeholder.png │ ├── index.html │ └── styles-draft-js.css ├── src │ ├── reducers │ │ ├── index.js │ │ └── article.js │ ├── store │ │ └── configureStore.js │ ├── actions │ │ └── article.js │ ├── app.js │ ├── containers │ │ └── Root.js │ ├── views │ │ ├── LogoutView.js │ │ ├── RegisterView.js │ │ ├── DashboardView.js │ │ └── LoginView.js │ ├── utils │ │ └── mapHelpers.js │ ├── falcorModel.js │ ├── components │ │ ├── DefaultInput.js │ │ ├── LoginForm.js │ │ ├── RegisterForm.js │ │ ├── ArticleCard.js │ │ └── articles │ │ │ ├── wysiwyg │ │ │ └── WYSIWYGbuttons.js │ │ │ ├── ImgUploader.js │ │ │ └── WYSIWYGeditor.js │ ├── routes │ │ └── index.js │ └── layouts │ │ ├── PublishingApp.js │ │ └── CoreLayout.js ├── initData.js ├── initPubUsers.js ├── webpack.config.js └── package.json ├── .gitattributes ├── .gitignore ├── LICENSE └── README.md /Chapter01/.gitignore: -------------------------------------------------------------------------------- 1 | **/.DS_Store 2 | node_modules 3 | **/dist/app.js 4 | -------------------------------------------------------------------------------- /Chapter02/.gitignore: -------------------------------------------------------------------------------- 1 | **/.DS_Store 2 | node_modules 3 | **/dist/app.js 4 | -------------------------------------------------------------------------------- /Chapter03/.gitignore: -------------------------------------------------------------------------------- 1 | **/.DS_Store 2 | node_modules 3 | **/dist/app.js 4 | -------------------------------------------------------------------------------- /Chapter04/.gitignore: -------------------------------------------------------------------------------- 1 | **/.DS_Store 2 | node_modules 3 | **/dist/app.js 4 | -------------------------------------------------------------------------------- /Chapter05/.gitignore: -------------------------------------------------------------------------------- 1 | **/.DS_Store 2 | node_modules 3 | **/dist/app.js 4 | -------------------------------------------------------------------------------- /Chapter06/.gitignore: -------------------------------------------------------------------------------- 1 | **/.DS_Store 2 | node_modules 3 | **/dist/app.js 4 | -------------------------------------------------------------------------------- /Chapter01/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "react", 5 | "stage-0" 6 | ] 7 | } -------------------------------------------------------------------------------- /Chapter02/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "react", 5 | "stage-0" 6 | ] 7 | } -------------------------------------------------------------------------------- /Chapter03/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "react", 5 | "stage-0" 6 | ] 7 | } -------------------------------------------------------------------------------- /Chapter04/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "react", 5 | "stage-0" 6 | ] 7 | } -------------------------------------------------------------------------------- /Chapter05/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "react", 5 | "stage-0" 6 | ] 7 | } -------------------------------------------------------------------------------- /Chapter06/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "react", 5 | "stage-0" 6 | ] 7 | } -------------------------------------------------------------------------------- /Chapter01/server/index.js: -------------------------------------------------------------------------------- 1 | require('babel-core/register'); 2 | require('babel-polyfill'); 3 | require('./server'); -------------------------------------------------------------------------------- /Chapter02/server/index.js: -------------------------------------------------------------------------------- 1 | require('babel-core/register'); 2 | require('babel-polyfill'); 3 | require('./server'); -------------------------------------------------------------------------------- /Chapter03/server/index.js: -------------------------------------------------------------------------------- 1 | require('babel-core/register'); 2 | require('babel-polyfill'); 3 | require('./server'); -------------------------------------------------------------------------------- /Chapter04/server/index.js: -------------------------------------------------------------------------------- 1 | require('babel-core/register'); 2 | require('babel-polyfill'); 3 | require('./server'); -------------------------------------------------------------------------------- /Chapter05/server/index.js: -------------------------------------------------------------------------------- 1 | require('babel-core/register'); 2 | require('babel-polyfill'); 3 | require('./server'); -------------------------------------------------------------------------------- /Chapter02/server/configSecret.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'secret': process.env.JWT_SECRET || 'devSecretGoesHere' 3 | } -------------------------------------------------------------------------------- /Chapter03/server/configSecret.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'secret': process.env.JWT_SECRET || 'devSecretGoesHere' 3 | } -------------------------------------------------------------------------------- /Chapter04/server/configSecret.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'secret': process.env.JWT_SECRET || 'devSecretGoesHere' 3 | } -------------------------------------------------------------------------------- /Chapter05/server/configSecret.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'secret': process.env.JWT_SECRET || 'devSecretGoesHere' 3 | } -------------------------------------------------------------------------------- /Chapter06/server/configSecret.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'secret': process.env.JWT_SECRET || 'devSecretGoesHere' 3 | } -------------------------------------------------------------------------------- /Chapter05/dist/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Full-Stack-React-Web-Development/HEAD/Chapter05/dist/avatar.png -------------------------------------------------------------------------------- /Chapter06/dist/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Full-Stack-React-Web-Development/HEAD/Chapter06/dist/avatar.png -------------------------------------------------------------------------------- /Chapter04/dist/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Full-Stack-React-Web-Development/HEAD/Chapter04/dist/placeholder.png -------------------------------------------------------------------------------- /Chapter04/dist/static/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Full-Stack-React-Web-Development/HEAD/Chapter04/dist/static/avatar.png -------------------------------------------------------------------------------- /Chapter05/dist/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Full-Stack-React-Web-Development/HEAD/Chapter05/dist/placeholder.png -------------------------------------------------------------------------------- /Chapter06/dist/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Full-Stack-React-Web-Development/HEAD/Chapter06/dist/placeholder.png -------------------------------------------------------------------------------- /Chapter04/dist/static/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Full-Stack-React-Web-Development/HEAD/Chapter04/dist/static/placeholder.png -------------------------------------------------------------------------------- /Chapter06/server/.env: -------------------------------------------------------------------------------- 1 | AWS_ACCESS_KEY_ID=<> 2 | AWS_SECRET_ACCESS_KEY=<> 3 | AWS_BUCKET_NAME=publishingapp 4 | AWS_REGION_NAME=us-west-2 -------------------------------------------------------------------------------- /Chapter02/src/actions/article.js: -------------------------------------------------------------------------------- 1 | export default { 2 | articlesList: (response) => { 3 | return { 4 | type: 'ARTICLES_LIST_ADD', 5 | payload: {response: response} 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /Chapter01/src/actions/article.js: -------------------------------------------------------------------------------- 1 | export default { 2 | articlesList: (response) => { 3 | return { 4 | type: 'ARTICLES_LIST_ADD', 5 | payload: { response: response } 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /Chapter03/src/actions/article.js: -------------------------------------------------------------------------------- 1 | export default { 2 | articlesList: (response) => { 3 | return { 4 | type: 'ARTICLES_LIST_ADD', 5 | payload: { response: response } 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /Chapter02/src/falcorModel.js: -------------------------------------------------------------------------------- 1 | import falcor from 'falcor'; 2 | import FalcorDataSource from 'falcor-http-datasource'; 3 | 4 | const model = new falcor.Model({ 5 | source: new FalcorDataSource('/model.json') 6 | }); 7 | 8 | export default model; -------------------------------------------------------------------------------- /Chapter01/src/falcorModel.js: -------------------------------------------------------------------------------- 1 | import falcor from 'falcor'; 2 | import FalcorDataSource from 'falcor-http-datasource'; 3 | 4 | const model = new falcor.Model({ 5 | source: new FalcorDataSource('/model.json') 6 | }); 7 | 8 | 9 | export default model; -------------------------------------------------------------------------------- /Chapter02/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import {combineReducers} from 'redux'; 2 | import {routeReducer} from 'redux-simple-router'; 3 | import article from './article'; 4 | 5 | export default combineReducers({ 6 | routing: routeReducer, 7 | article 8 | }); -------------------------------------------------------------------------------- /Chapter03/src/falcorModel.js: -------------------------------------------------------------------------------- 1 | import falcor from 'falcor'; 2 | import FalcorDataSource from 'falcor-http-datasource'; 3 | 4 | const model = new falcor.Model({ 5 | source: new FalcorDataSource('/model.json') 6 | }); 7 | 8 | 9 | export default model; -------------------------------------------------------------------------------- /Chapter04/src/falcorModel.js: -------------------------------------------------------------------------------- 1 | import falcor from 'falcor'; 2 | import FalcorDataSource from 'falcor-http-datasource'; 3 | 4 | const model = new falcor.Model({ 5 | source: new FalcorDataSource('/model.json') 6 | }); 7 | 8 | 9 | export default model; -------------------------------------------------------------------------------- /Chapter06/server/index.js: -------------------------------------------------------------------------------- 1 | var env = require('node-env-file'); 2 | // Load any undefined ENV variables form a specified file. 3 | env(__dirname + '/.env'); 4 | 5 | require("babel-core/register"); 6 | require("babel-polyfill"); 7 | require('./server'); -------------------------------------------------------------------------------- /Chapter03/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { routeReducer } from 'redux-simple-router'; 3 | 4 | import article from './article'; 5 | 6 | export default combineReducers({ 7 | routing: routeReducer, 8 | article 9 | }); -------------------------------------------------------------------------------- /Chapter04/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { routeReducer } from 'redux-simple-router'; 3 | 4 | import article from './article'; 5 | 6 | export default combineReducers({ 7 | routing: routeReducer, 8 | article 9 | }); -------------------------------------------------------------------------------- /Chapter05/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { routeReducer } from 'redux-simple-router'; 3 | 4 | import article from './article'; 5 | 6 | export default combineReducers({ 7 | routing: routeReducer, 8 | article 9 | }); -------------------------------------------------------------------------------- /Chapter06/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { routeReducer } from 'redux-simple-router'; 3 | 4 | import article from './article'; 5 | 6 | export default combineReducers({ 7 | routing: routeReducer, 8 | article 9 | }); -------------------------------------------------------------------------------- /Chapter01/dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Publishing App 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Chapter02/dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Publishing App 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Chapter03/dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Publishing App 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Chapter04/dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Publishing App 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Chapter05/dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Publishing App 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Chapter06/dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Publishing App 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Chapter02/initData.js: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | articleId: '987654', 4 | articleTitle: 'Lorem ipsum - article one', 5 | articleContent: 'Here goes the content of the article' 6 | }, 7 | { 8 | articleId: '123456', 9 | articleTitle: 'Lorem ipsum - article two', 10 | articleContent: 'Sky is the limit, the content goes here.' 11 | } 12 | ] -------------------------------------------------------------------------------- /Chapter04/server/fetchServerSide.js: -------------------------------------------------------------------------------- 1 | import configMongoose from './configMongoose'; 2 | let Article = configMongoose.Article; 3 | 4 | export default () => { 5 | return Article.find({}, function(err, articlesDocs) { 6 | return articlesDocs; 7 | }).then ((articlesArrayFromDB) => { 8 | return articlesArrayFromDB; 9 | }); 10 | } 11 | 12 | 13 | -------------------------------------------------------------------------------- /Chapter05/server/fetchServerSide.js: -------------------------------------------------------------------------------- 1 | import configMongoose from './configMongoose'; 2 | let Article = configMongoose.Article; 3 | 4 | export default () => { 5 | return Article.find({}, function(err, articlesDocs) { 6 | return articlesDocs; 7 | }).then ((articlesArrayFromDB) => { 8 | return articlesArrayFromDB; 9 | }); 10 | } 11 | 12 | 13 | -------------------------------------------------------------------------------- /Chapter06/server/fetchServerSide.js: -------------------------------------------------------------------------------- 1 | import configMongoose from './configMongoose'; 2 | let Article = configMongoose.Article; 3 | 4 | export default () => { 5 | return Article.find({}, function(err, articlesDocs) { 6 | return articlesDocs; 7 | }).then ((articlesArrayFromDB) => { 8 | return articlesArrayFromDB; 9 | }); 10 | } 11 | 12 | 13 | -------------------------------------------------------------------------------- /Chapter01/initData.js: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | articleId: '987654', 4 | articleTitle: 'Lorem ipsum - article one', 5 | articleContent: 'Here goes the content of the article' 6 | }, 7 | { 8 | articleId: '123456', 9 | articleTitle: 'Lorem ipsum - article two', 10 | articleContent: 'Sky is the limit, the content goes here.' 11 | } 12 | ] 13 | -------------------------------------------------------------------------------- /Chapter03/initData.js: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | articleId: '987654', 4 | articleTitle: 'Lorem ipsum - article one', 5 | articleContent: 'Here goes the content of the article' 6 | }, 7 | { 8 | articleId: '123456', 9 | articleTitle: 'Lorem ipsum - article two', 10 | articleContent: 'Sky is the limit, the content goes here.' 11 | } 12 | ] 13 | -------------------------------------------------------------------------------- /Chapter04/initData.js: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | articleId: '987654', 4 | articleTitle: 'Lorem ipsum - article one', 5 | articleContent: 'Here goes the content of the article' 6 | }, 7 | { 8 | articleId: '123456', 9 | articleTitle: 'Lorem ipsum - article two', 10 | articleContent: 'Sky is the limit, the content goes here.' 11 | } 12 | ] 13 | -------------------------------------------------------------------------------- /Chapter05/initData.js: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | articleId: '987654', 4 | articleTitle: 'Lorem ipsum - article one', 5 | articleContent: 'Here goes the content of the article' 6 | }, 7 | { 8 | articleId: '123456', 9 | articleTitle: 'Lorem ipsum - article two', 10 | articleContent: 'Sky is the limit, the content goes here.' 11 | } 12 | ] 13 | -------------------------------------------------------------------------------- /Chapter06/initData.js: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | articleId: '987654', 4 | articleTitle: 'Lorem ipsum - article one', 5 | articleContent: 'Here goes the content of the article' 6 | }, 7 | { 8 | articleId: '123456', 9 | articleTitle: 'Lorem ipsum - article two', 10 | articleContent: 'Sky is the limit, the content goes here.' 11 | } 12 | ] 13 | -------------------------------------------------------------------------------- /Chapter01/src/reducers/article.js: -------------------------------------------------------------------------------- 1 | const article = (state = {}, action) => { 2 | switch (action.type) { 3 | case 'RETURN_ALL_ARTICLES': 4 | return Object.assign({}, state); 5 | case 'ARTICLES_LIST_ADD': 6 | return Object.assign({}, action.payload.response); 7 | default: 8 | return state; 9 | } 10 | } 11 | 12 | export default article; -------------------------------------------------------------------------------- /Chapter02/initPubUsers.js: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | username: 'admin', 4 | password: 'c5a0df4e293953d6048e78bd9849ec0ddce811f0b29f72564714e474615a7852', 5 | firstName: 'Kamil', 6 | lastName: 'Przeorski', 7 | email: 'kamil@mobilewebpro.pl', 8 | role: 'admin', 9 | verified: false, 10 | imageUrl: 'http://lorempixel.com/100/100/people/' 11 | } 12 | ] -------------------------------------------------------------------------------- /Chapter02/src/reducers/article.js: -------------------------------------------------------------------------------- 1 | const article = (state = {}, action) => { 2 | switch (action.type) { 3 | case 'RETURN_ALL_ARTICLES': 4 | return Object.assign({}, state); 5 | case 'ARTICLES_LIST_ADD': 6 | return Object.assign({}, action.payload.response); 7 | default: 8 | return state; 9 | } 10 | } 11 | 12 | export default article; -------------------------------------------------------------------------------- /Chapter03/src/reducers/article.js: -------------------------------------------------------------------------------- 1 | const article = (state = {}, action) => { 2 | switch (action.type) { 3 | case 'RETURN_ALL_ARTICLES': 4 | return Object.assign({}, state); 5 | case 'ARTICLES_LIST_ADD': 6 | return Object.assign({}, action.payload.response); 7 | default: 8 | return state; 9 | } 10 | } 11 | 12 | export default article; -------------------------------------------------------------------------------- /Chapter03/initPubUsers.js: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "username" : "admin", 4 | "password" : 5 | "c5a0df4e293953d6048e78bd9849ec0ddce811f0b29f72564714e474615a7852", 6 | "firstName" : "Kamil", 7 | "lastName" : "Przeorski", 8 | "email" : "kamil@mobilewebpro.pl", 9 | "role" : "admin", 10 | "verified" : false, 11 | "imageUrl" : "http://lorempixel.com/100/100/people/" 12 | } 13 | ] -------------------------------------------------------------------------------- /Chapter04/initPubUsers.js: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "username" : "admin", 4 | "password" : 5 | "c5a0df4e293953d6048e78bd9849ec0ddce811f0b29f72564714e474615a7852", 6 | "firstName" : "Kamil", 7 | "lastName" : "Przeorski", 8 | "email" : "kamil@mobilewebpro.pl", 9 | "role" : "admin", 10 | "verified" : false, 11 | "imageUrl" : "http://lorempixel.com/100/100/people/" 12 | } 13 | ] -------------------------------------------------------------------------------- /Chapter05/initPubUsers.js: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "username" : "admin", 4 | "password" : 5 | "c5a0df4e293953d6048e78bd9849ec0ddce811f0b29f72564714e474615a7852", 6 | "firstName" : "Kamil", 7 | "lastName" : "Przeorski", 8 | "email" : "kamil@mobilewebpro.pl", 9 | "role" : "admin", 10 | "verified" : false, 11 | "imageUrl" : "http://lorempixel.com/100/100/people/" 12 | } 13 | ] -------------------------------------------------------------------------------- /Chapter06/initPubUsers.js: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "username" : "admin", 4 | "password" : 5 | "c5a0df4e293953d6048e78bd9849ec0ddce811f0b29f72564714e474615a7852", 6 | "firstName" : "Kamil", 7 | "lastName" : "Przeorski", 8 | "email" : "kamil@mobilewebpro.pl", 9 | "role" : "admin", 10 | "verified" : false, 11 | "imageUrl" : "http://lorempixel.com/100/100/people/" 12 | } 13 | ] -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /Chapter01/src/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import { Provider } from 'react-redux'; 4 | import { createStore } from 'redux'; 5 | import article from './reducers/article'; 6 | import PublishingApp from './layouts/PublishingApp'; 7 | 8 | let store = createStore(article) 9 | render( 10 | 11 | 12 | , 13 | document.getElementById('publishingAppRoot') 14 | ); -------------------------------------------------------------------------------- /Chapter03/server/fetchServerSide.js: -------------------------------------------------------------------------------- 1 | export default () => { 2 | return { 3 | "article": 4 | { 5 | "0": { 6 | "articleTitle": "SERVER-SIDE Lorem ipsum - article one", 7 | "articleContent":"SERVER-SIDE Here goes the content of the article" 8 | }, 9 | "1": { 10 | "articleTitle":"SERVER-SIDE Lorem ipsum - article two", 11 | "articleContent":"SERVER-SIDE Sky is the limit, the content goes here." 12 | } 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /Chapter02/src/store/configureStore.js: -------------------------------------------------------------------------------- 1 | import rootReducer from '../reducers'; 2 | import thunk from 'redux-thunk'; 3 | import {applyMiddleware, compose, createStore} from 'redux'; 4 | 5 | 6 | export default function configureStore (initialState, debug = false) { 7 | const middleware = applyMiddleware(thunk); 8 | const createStoreWithMiddleware = compose(middleware); 9 | 10 | const store = createStoreWithMiddleware(createStore)( 11 | rootReducer, initialState 12 | ); 13 | 14 | return store; 15 | } -------------------------------------------------------------------------------- /Chapter01/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: ['babel-polyfill', './src/app.js'], 3 | output: { 4 | path: './dist', 5 | filename: 'app.js', 6 | publicPath: '/' 7 | }, 8 | devServer: { 9 | inline: true, 10 | port: 3000, 11 | contentBase: './dist' 12 | }, 13 | module: { 14 | loaders: [ 15 | { 16 | test: /\.js$/, 17 | exclude: /(node_modules|bower_components)/, 18 | loader: 'babel', 19 | query: { 20 | presets: ['es2015', 'stage-0', 'react'] 21 | } 22 | }] 23 | } 24 | } -------------------------------------------------------------------------------- /Chapter02/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: ['babel-polyfill', './src/app.js'], 3 | output: { 4 | path: './dist', 5 | filename: 'app.js', 6 | publicPath: '/' 7 | }, 8 | devServer: { 9 | inline: true, 10 | port: 3000, 11 | contentBase: './dist' 12 | }, 13 | module: { 14 | loaders: [ 15 | { 16 | test: /\.js$/, 17 | exclude: /(node_modules|bower_components)/, 18 | loader: 'babel', 19 | query: { 20 | presets: ['es2015', 'stage-0', 'react'] 21 | } 22 | }] 23 | } 24 | } -------------------------------------------------------------------------------- /Chapter03/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: ['babel-polyfill', './src/app.js'], 3 | output: { 4 | path: './dist', 5 | filename: 'app.js', 6 | publicPath: '/' 7 | }, 8 | devServer: { 9 | inline: true, 10 | port: 3000, 11 | contentBase: './dist' 12 | }, 13 | module: { 14 | loaders: [ 15 | { 16 | test: /\.js$/, 17 | exclude: /(node_modules|bower_components)/, 18 | loader: 'babel', 19 | query: { 20 | presets: ['es2015', 'stage-0', 'react'] 21 | } 22 | }] 23 | } 24 | } -------------------------------------------------------------------------------- /Chapter04/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: ['babel-polyfill', './src/app.js'], 3 | output: { 4 | path: './dist', 5 | filename: 'app.js', 6 | publicPath: '/' 7 | }, 8 | devServer: { 9 | inline: true, 10 | port: 3000, 11 | contentBase: './dist' 12 | }, 13 | module: { 14 | loaders: [ 15 | { 16 | test: /\.js$/, 17 | exclude: /(node_modules|bower_components)/, 18 | loader: 'babel', 19 | query: { 20 | presets: ['es2015', 'stage-0', 'react'] 21 | } 22 | }] 23 | } 24 | } -------------------------------------------------------------------------------- /Chapter05/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: ['babel-polyfill', './src/app.js'], 3 | output: { 4 | path: './dist', 5 | filename: 'app.js', 6 | publicPath: '/' 7 | }, 8 | devServer: { 9 | inline: true, 10 | port: 3000, 11 | contentBase: './dist' 12 | }, 13 | module: { 14 | loaders: [ 15 | { 16 | test: /\.js$/, 17 | exclude: /(node_modules|bower_components)/, 18 | loader: 'babel', 19 | query: { 20 | presets: ['es2015', 'stage-0', 'react'] 21 | } 22 | }] 23 | } 24 | } -------------------------------------------------------------------------------- /Chapter06/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: ['babel-polyfill', './src/app.js'], 3 | output: { 4 | path: './dist', 5 | filename: 'app.js', 6 | publicPath: '/' 7 | }, 8 | devServer: { 9 | inline: true, 10 | port: 3000, 11 | contentBase: './dist' 12 | }, 13 | module: { 14 | loaders: [ 15 | { 16 | test: /\.js$/, 17 | exclude: /(node_modules|bower_components)/, 18 | loader: 'babel', 19 | query: { 20 | presets: ['es2015', 'stage-0', 'react'] 21 | } 22 | }] 23 | } 24 | } -------------------------------------------------------------------------------- /Chapter03/src/store/configureStore.js: -------------------------------------------------------------------------------- 1 | import rootReducer from '../reducers'; 2 | import thunk from 'redux-thunk'; 3 | import { 4 | applyMiddleware, 5 | compose, 6 | createStore 7 | }from 'redux'; 8 | 9 | 10 | export default function configureStore (initialState, debug = false) { 11 | let createStoreWithMiddleware; 12 | 13 | const middleware = applyMiddleware(thunk); 14 | 15 | createStoreWithMiddleware = compose(middleware); 16 | 17 | const store = createStoreWithMiddleware(createStore)( 18 | rootReducer, initialState 19 | ); 20 | return store; 21 | } -------------------------------------------------------------------------------- /Chapter04/src/store/configureStore.js: -------------------------------------------------------------------------------- 1 | import rootReducer from '../reducers'; 2 | import thunk from 'redux-thunk'; 3 | import { 4 | applyMiddleware, 5 | compose, 6 | createStore 7 | }from 'redux'; 8 | 9 | 10 | export default function configureStore (initialState, debug = false) { 11 | let createStoreWithMiddleware; 12 | 13 | const middleware = applyMiddleware(thunk); 14 | 15 | createStoreWithMiddleware = compose(middleware); 16 | 17 | const store = createStoreWithMiddleware(createStore)( 18 | rootReducer, initialState 19 | ); 20 | return store; 21 | } -------------------------------------------------------------------------------- /Chapter05/src/store/configureStore.js: -------------------------------------------------------------------------------- 1 | import rootReducer from '../reducers'; 2 | import thunk from 'redux-thunk'; 3 | import { 4 | applyMiddleware, 5 | compose, 6 | createStore 7 | }from 'redux'; 8 | 9 | 10 | export default function configureStore (initialState, debug = false) { 11 | let createStoreWithMiddleware; 12 | 13 | const middleware = applyMiddleware(thunk); 14 | 15 | createStoreWithMiddleware = compose(middleware); 16 | 17 | const store = createStoreWithMiddleware(createStore)( 18 | rootReducer, initialState 19 | ); 20 | return store; 21 | } -------------------------------------------------------------------------------- /Chapter06/src/store/configureStore.js: -------------------------------------------------------------------------------- 1 | import rootReducer from '../reducers'; 2 | import thunk from 'redux-thunk'; 3 | import { 4 | applyMiddleware, 5 | compose, 6 | createStore 7 | }from 'redux'; 8 | 9 | 10 | export default function configureStore (initialState, debug = false) { 11 | let createStoreWithMiddleware; 12 | 13 | const middleware = applyMiddleware(thunk); 14 | 15 | createStoreWithMiddleware = compose(middleware); 16 | 17 | const store = createStoreWithMiddleware(createStore)( 18 | rootReducer, initialState 19 | ); 20 | return store; 21 | } -------------------------------------------------------------------------------- /Chapter02/src/layouts/CoreLayout.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | 4 | class CoreLayout extends React.Component { 5 | static propTypes = { 6 | children : React.PropTypes.element 7 | } 8 | 9 | render () { 10 | return ( 11 |
12 | Links: Register 13 | Login 14 | Home Page 15 | 16 |
17 | {this.props.children} 18 |
19 | ); 20 | } 21 | } 22 | 23 | export default CoreLayout; -------------------------------------------------------------------------------- /Chapter04/src/actions/article.js: -------------------------------------------------------------------------------- 1 | export default { 2 | articlesList: (response) => { 3 | return { 4 | type: 'ARTICLES_LIST_ADD', 5 | payload: { response: response } 6 | } 7 | }, 8 | pushNewArticle: (response) => { 9 | return { 10 | type: 'PUSH_NEW_ARTICLE', 11 | payload: { response: response } 12 | } 13 | }, 14 | editArticle: (response) => { 15 | return { 16 | type: 'EDIT_ARTICLE', 17 | payload: { response: response } 18 | } 19 | }, 20 | deleteArticle: (response) => { 21 | return { 22 | type: 'DELETE_ARTICLE', 23 | payload: { response: response } 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /Chapter05/src/actions/article.js: -------------------------------------------------------------------------------- 1 | export default { 2 | articlesList: (response) => { 3 | return { 4 | type: 'ARTICLES_LIST_ADD', 5 | payload: { response: response } 6 | } 7 | }, 8 | pushNewArticle: (response) => { 9 | return { 10 | type: 'PUSH_NEW_ARTICLE', 11 | payload: { response: response } 12 | } 13 | }, 14 | editArticle: (response) => { 15 | return { 16 | type: 'EDIT_ARTICLE', 17 | payload: { response: response } 18 | } 19 | }, 20 | deleteArticle: (response) => { 21 | return { 22 | type: 'DELETE_ARTICLE', 23 | payload: { response: response } 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /Chapter06/src/actions/article.js: -------------------------------------------------------------------------------- 1 | export default { 2 | articlesList: (response) => { 3 | return { 4 | type: 'ARTICLES_LIST_ADD', 5 | payload: { response: response } 6 | } 7 | }, 8 | pushNewArticle: (response) => { 9 | return { 10 | type: 'PUSH_NEW_ARTICLE', 11 | payload: { response: response } 12 | } 13 | }, 14 | editArticle: (response) => { 15 | return { 16 | type: 'EDIT_ARTICLE', 17 | payload: { response: response } 18 | } 19 | }, 20 | deleteArticle: (response) => { 21 | return { 22 | type: 'DELETE_ARTICLE', 23 | payload: { response: response } 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /Chapter02/src/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import createBrowserHistory from 'history/lib/createBrowserHistory'; 4 | import {syncReduxAndRouter} from 'redux-simple-router'; 5 | import Root from './containers/Root'; 6 | import configureStore from './store/configureStore'; 7 | 8 | const target = document.getElementById('publishingAppRoot'); 9 | const history = createBrowserHistory(); 10 | 11 | export const store = configureStore(window.__INITIAL_STATE__); 12 | 13 | syncReduxAndRouter(history, store); 14 | const node = ( 15 | 18 | ); 19 | 20 | ReactDOM.render(node, target); -------------------------------------------------------------------------------- /Chapter03/src/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import createBrowserHistory from 'history/lib/createBrowserHistory'; 4 | import { syncReduxAndRouter } from 'redux-simple-router'; 5 | import Root from './containers/Root'; 6 | import configureStore from './store/configureStore'; 7 | 8 | const target = document.getElementById('publishingAppRoot'); 9 | const history = createBrowserHistory(); 10 | 11 | export const store = configureStore(window.__INITIAL_STATE__); 12 | 13 | syncReduxAndRouter(history, store); 14 | const node = ( 15 | 19 | ); 20 | 21 | ReactDOM.render(node, target); -------------------------------------------------------------------------------- /Chapter04/src/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import createBrowserHistory from 'history/lib/createBrowserHistory'; 4 | import { syncReduxAndRouter } from 'redux-simple-router'; 5 | import Root from './containers/Root'; 6 | import configureStore from './store/configureStore'; 7 | 8 | const target = document.getElementById('publishingAppRoot'); 9 | const history = createBrowserHistory(); 10 | 11 | export const store = configureStore(window.__INITIAL_STATE__); 12 | 13 | syncReduxAndRouter(history, store); 14 | const node = ( 15 | 19 | ); 20 | 21 | ReactDOM.render(node, target); -------------------------------------------------------------------------------- /Chapter05/src/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import createBrowserHistory from 'history/lib/createBrowserHistory'; 4 | import { syncReduxAndRouter } from 'redux-simple-router'; 5 | import Root from './containers/Root'; 6 | import configureStore from './store/configureStore'; 7 | 8 | const target = document.getElementById('publishingAppRoot'); 9 | const history = createBrowserHistory(); 10 | 11 | export const store = configureStore(window.__INITIAL_STATE__); 12 | 13 | syncReduxAndRouter(history, store); 14 | const node = ( 15 | 19 | ); 20 | 21 | ReactDOM.render(node, target); -------------------------------------------------------------------------------- /Chapter06/src/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import createBrowserHistory from 'history/lib/createBrowserHistory'; 4 | import { syncReduxAndRouter } from 'redux-simple-router'; 5 | import Root from './containers/Root'; 6 | import configureStore from './store/configureStore'; 7 | 8 | const target = document.getElementById('publishingAppRoot'); 9 | const history = createBrowserHistory(); 10 | 11 | export const store = configureStore(window.__INITIAL_STATE__); 12 | 13 | syncReduxAndRouter(history, store); 14 | const node = ( 15 | 19 | ); 20 | 21 | ReactDOM.render(node, target); -------------------------------------------------------------------------------- /Chapter03/src/containers/Root.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Provider } from 'react-redux'; 3 | import { Router } from 'react-router'; 4 | import routes from '../routes'; 5 | import createHashHistory from 'history/lib/createHashHistory'; 6 | 7 | export default class Root extends React.Component { 8 | static propTypes = { 9 | history : React.PropTypes.object.isRequired, 10 | store : React.PropTypes.object.isRequired 11 | } 12 | render () { 13 | return ( 14 | 15 |
16 | 17 | {routes} 18 | 19 |
20 |
21 | ); 22 | } 23 | } -------------------------------------------------------------------------------- /Chapter04/src/containers/Root.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Provider } from 'react-redux'; 3 | import { Router } from 'react-router'; 4 | import routes from '../routes'; 5 | import createHashHistory from 'history/lib/createHashHistory'; 6 | 7 | export default class Root extends React.Component { 8 | static propTypes = { 9 | history : React.PropTypes.object.isRequired, 10 | store : React.PropTypes.object.isRequired 11 | } 12 | render () { 13 | return ( 14 | 15 |
16 | 17 | {routes} 18 | 19 |
20 |
21 | ); 22 | } 23 | } -------------------------------------------------------------------------------- /Chapter05/src/containers/Root.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Provider } from 'react-redux'; 3 | import { Router } from 'react-router'; 4 | import routes from '../routes'; 5 | import createHashHistory from 'history/lib/createHashHistory'; 6 | 7 | export default class Root extends React.Component { 8 | static propTypes = { 9 | history : React.PropTypes.object.isRequired, 10 | store : React.PropTypes.object.isRequired 11 | } 12 | render () { 13 | return ( 14 | 15 |
16 | 17 | {routes} 18 | 19 |
20 |
21 | ); 22 | } 23 | } -------------------------------------------------------------------------------- /Chapter06/src/containers/Root.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Provider } from 'react-redux'; 3 | import { Router } from 'react-router'; 4 | import routes from '../routes'; 5 | import createHashHistory from 'history/lib/createHashHistory'; 6 | 7 | export default class Root extends React.Component { 8 | static propTypes = { 9 | history : React.PropTypes.object.isRequired, 10 | store : React.PropTypes.object.isRequired 11 | } 12 | render () { 13 | return ( 14 | 15 |
16 | 17 | {routes} 18 | 19 |
20 |
21 | ); 22 | } 23 | } -------------------------------------------------------------------------------- /Chapter02/src/views/DashboardView.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Falcor from 'falcor'; 3 | import falcorModel from '../falcorModel.js'; 4 | import {connect} from 'react-redux'; 5 | import {bindActionCreators} from 'redux'; 6 | import {LoginForm} from '../components/LoginForm.js'; 7 | 8 | const mapStateToProps = (state) => ({ 9 | ...state 10 | }); 11 | 12 | // You can add your reducers here 13 | const mapDispatchToProps = (dispatch) => ({ 14 | }); 15 | 16 | class DashboardView extends React.Component { 17 | render () { 18 | return ( 19 |
20 |

Dashboard - logged in!

21 |
22 | ); 23 | } 24 | } 25 | 26 | export default connect(mapStateToProps, mapDispatchToProps)(DashboardView); -------------------------------------------------------------------------------- /Chapter02/src/routes/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Route, IndexRoute} from 'react-router'; 3 | import CoreLayout from '../layouts/CoreLayout'; 4 | import PublishingApp from '../layouts/PublishingApp'; 5 | import LoginView from '../views/LoginView'; 6 | import DashboardView from '../views/DashboardView'; 7 | import RegisterView from '../views/RegisterView'; 8 | 9 | export default ( 10 | 11 | 12 | 13 | 14 | 15 | 16 | ); -------------------------------------------------------------------------------- /Chapter04/src/views/LogoutView.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import React from 'react'; 3 | import { Paper } from 'material-ui'; 4 | class LogoutView extends React.Component { 5 | constructor(props) { 6 | super(props); 7 | } 8 | componentWillMount() { 9 | if(typeof localStorage !== 'undefined' && localStorage.token) { 10 | delete localStorage.token; 11 | delete localStorage.username; 12 | delete localStorage.role; 13 | } 14 | } 15 | 16 | render () { 17 | return ( 18 |
19 | 20 | Logout successful. 21 | 22 |
23 | ); 24 | } 25 | } 26 | export default LogoutView; -------------------------------------------------------------------------------- /Chapter05/src/views/LogoutView.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import React from 'react'; 3 | import { Paper } from 'material-ui'; 4 | class LogoutView extends React.Component { 5 | constructor(props) { 6 | super(props); 7 | } 8 | componentWillMount() { 9 | if(typeof localStorage !== 'undefined' && localStorage.token) { 10 | delete localStorage.token; 11 | delete localStorage.username; 12 | delete localStorage.role; 13 | } 14 | } 15 | 16 | render () { 17 | return ( 18 |
19 | 20 | Logout successful. 21 | 22 |
23 | ); 24 | } 25 | } 26 | export default LogoutView; -------------------------------------------------------------------------------- /Chapter06/src/views/LogoutView.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import React from 'react'; 3 | import { Paper } from 'material-ui'; 4 | class LogoutView extends React.Component { 5 | constructor(props) { 6 | super(props); 7 | } 8 | componentWillMount() { 9 | if(typeof localStorage !== 'undefined' && localStorage.token) { 10 | delete localStorage.token; 11 | delete localStorage.username; 12 | delete localStorage.role; 13 | } 14 | } 15 | 16 | render () { 17 | return ( 18 |
19 | 20 | Logout successful. 21 | 22 |
23 | ); 24 | } 25 | } 26 | export default LogoutView; -------------------------------------------------------------------------------- /Chapter03/src/views/DashboardView.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import React from 'react'; 4 | import Falcor from 'falcor'; 5 | import falcorModel from '../falcorModel.js'; 6 | import { connect } from 'react-redux'; 7 | import { bindActionCreators } from 'redux'; 8 | import { LoginForm } from '../components/LoginForm.js'; 9 | 10 | const mapStateToProps = (state) => ({ 11 | ...state 12 | }); 13 | 14 | const mapDispatchToProps = (dispatch) => ({ 15 | }); 16 | 17 | class DashboardView extends React.Component { 18 | constructor(props) { 19 | super(props); 20 | } 21 | 22 | render () { 23 | return ( 24 |
25 |

Dashboard - loggedin!

26 |
27 | ); 28 | } 29 | } 30 | 31 | export default connect(mapStateToProps, mapDispatchToProps)(DashboardView); -------------------------------------------------------------------------------- /Chapter02/src/containers/Root.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Provider} from 'react-redux'; 3 | import {Router} from 'react-router'; 4 | import routes from '../routes'; 5 | import createHashHistory from 'history/lib/createHashHistory'; 6 | 7 | const noQueryKeyHistory = createHashHistory({ 8 | queryKey: false 9 | }); 10 | 11 | export default class Root extends React.Component { 12 | static propTypes = { 13 | history: React.PropTypes.object.isRequired, 14 | store: React.PropTypes.object.isRequired 15 | } 16 | 17 | render () { 18 | return ( 19 | 20 |
21 | 22 | {routes} 23 | 24 |
25 |
26 | ); 27 | } 28 | } -------------------------------------------------------------------------------- /Chapter03/src/layouts/CoreLayout.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | import themeDecorator from 'material-ui/lib/styles/theme-decorator'; 4 | import getMuiTheme from 'material-ui/lib/styles/getMuiTheme'; 5 | 6 | class CoreLayout extends React.Component { 7 | static propTypes = { 8 | children : React.PropTypes.element 9 | } 10 | constructor(props) { 11 | super(props); 12 | } 13 | 14 | render () { 15 | return ( 16 |
17 | Links: Register | Login | 18 | Home Page 19 |
20 | {this.props.children} 21 |
22 | ); 23 | } 24 | } 25 | 26 | export default themeDecorator(getMuiTheme(null, { userAgent: 'all' }))(CoreLayout); -------------------------------------------------------------------------------- /Chapter03/src/routes/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { Route, IndexRoute } from 'react-router'; 4 | /* wrappers */ 5 | import CoreLayout from '../layouts/CoreLayout'; 6 | 7 | /* home view */ 8 | import PublishingApp from '../layouts/PublishingApp'; 9 | 10 | /* auth views */ 11 | import LoginView from '../views/LoginView'; 12 | 13 | import DashboardView from '../views/DashboardView'; 14 | 15 | import RegisterView from '../views/RegisterView'; 16 | 17 | export default ( 18 | 19 | 20 | 21 | 22 | 23 | 24 | ); -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear in the root of a volume 35 | .DocumentRevisions-V100 36 | .fseventsd 37 | .Spotlight-V100 38 | .TemporaryItems 39 | .Trashes 40 | .VolumeIcon.icns 41 | 42 | # Directories potentially created on remote AFP share 43 | .AppleDB 44 | .AppleDesktop 45 | Network Trash Folder 46 | Temporary Items 47 | .apdisk 48 | -------------------------------------------------------------------------------- /Chapter04/src/reducers/article.js: -------------------------------------------------------------------------------- 1 | import mapHelpers from '../utils/mapHelpers'; 2 | 3 | const article = (state = {}, action) => { 4 | switch (action.type) { 5 | case 'ARTICLES_LIST_ADD': 6 | let articlesList = action.payload.response; 7 | return mapHelpers.addMultipleItems(state, articlesList); 8 | case 'PUSH_NEW_ARTICLE': 9 | let newArticleObject = action.payload.response; 10 | return mapHelpers.addItem(state, newArticleObject['_id'], newArticleObject); 11 | case 'EDIT_ARTICLE': 12 | let editedArticleObject = action.payload.response; 13 | return mapHelpers.addItem(state, editedArticleObject['_id'], editedArticleObject); 14 | case 'DELETE_ARTICLE': 15 | let deleteArticleId = action.payload.response; 16 | return mapHelpers.deleteItem(state, deleteArticleId); 17 | default: 18 | return state; 19 | } 20 | } 21 | 22 | export default article -------------------------------------------------------------------------------- /Chapter05/src/reducers/article.js: -------------------------------------------------------------------------------- 1 | import mapHelpers from '../utils/mapHelpers'; 2 | 3 | const article = (state = {}, action) => { 4 | switch (action.type) { 5 | case 'ARTICLES_LIST_ADD': 6 | let articlesList = action.payload.response; 7 | return mapHelpers.addMultipleItems(state, articlesList); 8 | case 'PUSH_NEW_ARTICLE': 9 | let newArticleObject = action.payload.response; 10 | return mapHelpers.addItem(state, newArticleObject['_id'], newArticleObject); 11 | case 'EDIT_ARTICLE': 12 | let editedArticleObject = action.payload.response; 13 | return mapHelpers.addItem(state, editedArticleObject['_id'], editedArticleObject); 14 | case 'DELETE_ARTICLE': 15 | let deleteArticleId = action.payload.response; 16 | return mapHelpers.deleteItem(state, deleteArticleId); 17 | default: 18 | return state; 19 | } 20 | } 21 | 22 | export default article -------------------------------------------------------------------------------- /Chapter06/src/reducers/article.js: -------------------------------------------------------------------------------- 1 | import mapHelpers from '../utils/mapHelpers'; 2 | 3 | const article = (state = {}, action) => { 4 | switch (action.type) { 5 | case 'ARTICLES_LIST_ADD': 6 | let articlesList = action.payload.response; 7 | return mapHelpers.addMultipleItems(state, articlesList); 8 | case 'PUSH_NEW_ARTICLE': 9 | let newArticleObject = action.payload.response; 10 | return mapHelpers.addItem(state, newArticleObject['_id'], newArticleObject); 11 | case 'EDIT_ARTICLE': 12 | let editedArticleObject = action.payload.response; 13 | return mapHelpers.addItem(state, editedArticleObject['_id'], editedArticleObject); 14 | case 'DELETE_ARTICLE': 15 | let deleteArticleId = action.payload.response; 16 | return mapHelpers.deleteItem(state, deleteArticleId); 17 | default: 18 | return state; 19 | } 20 | } 21 | 22 | export default article -------------------------------------------------------------------------------- /Chapter01/server/server.js: -------------------------------------------------------------------------------- 1 | import http from 'http'; 2 | import express from 'express'; 3 | import cors from 'cors'; 4 | import bodyParser from 'body-parser'; 5 | import falcor from 'falcor'; 6 | import falcorExpress from 'falcor-express'; 7 | import falcorRouter from 'falcor-router'; 8 | import routes from './routes.js'; 9 | 10 | const app = express(); 11 | 12 | app.server = http.createServer(app); 13 | // CORS - 3rd party middleware 14 | app.use(cors()); 15 | // This is required by falcor-express middleware to work correctly with falcor-browser 16 | app.use(bodyParser.json({extended: false})); 17 | 18 | app.use('/model.json', falcorExpress.dataSourceRoute(function(req, res) { 19 | return new falcorRouter(routes); 20 | })); 21 | 22 | app.use(express.static('dist')); 23 | 24 | app.server.listen(process.env.PORT || 3000); 25 | console.log(`Started on port ${app.server.address().port}`); 26 | 27 | export default app; 28 | -------------------------------------------------------------------------------- /Chapter04/src/utils/mapHelpers.js: -------------------------------------------------------------------------------- 1 | const duplicate = (map) => { 2 | const newMap = new Map(); 3 | map.forEach((item, key) => { 4 | if(item['_id']) { 5 | newMap.set(item['_id'], item); 6 | } 7 | }); 8 | return newMap; 9 | }; 10 | 11 | const addMultipleItems = (map, items) => { 12 | const newMap = duplicate(map); 13 | 14 | Object.keys(items).map((itemIndex) => { 15 | let item = items[itemIndex]; 16 | if(item['_id']) { 17 | newMap.set(item['_id'], item); 18 | } 19 | }); 20 | 21 | return newMap; 22 | }; 23 | 24 | const addItem = (map, newKey, newItem) => { 25 | const newMap = duplicate(map); 26 | newMap.set(newKey, newItem); 27 | return newMap; 28 | }; 29 | 30 | const deleteItem = (map, key) => { 31 | const newMap = duplicate(map); 32 | newMap.delete(key); 33 | 34 | return newMap; 35 | }; 36 | 37 | export default { 38 | addItem, 39 | deleteItem, 40 | addMultipleItems 41 | }; -------------------------------------------------------------------------------- /Chapter05/src/utils/mapHelpers.js: -------------------------------------------------------------------------------- 1 | const duplicate = (map) => { 2 | const newMap = new Map(); 3 | map.forEach((item, key) => { 4 | if(item['_id']) { 5 | newMap.set(item['_id'], item); 6 | } 7 | }); 8 | return newMap; 9 | }; 10 | 11 | const addMultipleItems = (map, items) => { 12 | const newMap = duplicate(map); 13 | 14 | Object.keys(items).map((itemIndex) => { 15 | let item = items[itemIndex]; 16 | if(item['_id']) { 17 | newMap.set(item['_id'], item); 18 | } 19 | }); 20 | 21 | return newMap; 22 | }; 23 | 24 | const addItem = (map, newKey, newItem) => { 25 | const newMap = duplicate(map); 26 | newMap.set(newKey, newItem); 27 | return newMap; 28 | }; 29 | 30 | const deleteItem = (map, key) => { 31 | const newMap = duplicate(map); 32 | newMap.delete(key); 33 | 34 | return newMap; 35 | }; 36 | 37 | export default { 38 | addItem, 39 | deleteItem, 40 | addMultipleItems 41 | }; -------------------------------------------------------------------------------- /Chapter06/src/utils/mapHelpers.js: -------------------------------------------------------------------------------- 1 | const duplicate = (map) => { 2 | const newMap = new Map(); 3 | map.forEach((item, key) => { 4 | if(item['_id']) { 5 | newMap.set(item['_id'], item); 6 | } 7 | }); 8 | return newMap; 9 | }; 10 | 11 | const addMultipleItems = (map, items) => { 12 | const newMap = duplicate(map); 13 | 14 | Object.keys(items).map((itemIndex) => { 15 | let item = items[itemIndex]; 16 | if(item['_id']) { 17 | newMap.set(item['_id'], item); 18 | } 19 | }); 20 | 21 | return newMap; 22 | }; 23 | 24 | const addItem = (map, newKey, newItem) => { 25 | const newMap = duplicate(map); 26 | newMap.set(newKey, newItem); 27 | return newMap; 28 | }; 29 | 30 | const deleteItem = (map, key) => { 31 | const newMap = duplicate(map); 32 | newMap.delete(key); 33 | 34 | return newMap; 35 | }; 36 | 37 | export default { 38 | addItem, 39 | deleteItem, 40 | addMultipleItems 41 | }; -------------------------------------------------------------------------------- /Chapter05/src/falcorModel.js: -------------------------------------------------------------------------------- 1 | import falcor from 'falcor'; 2 | import FalcorDataSource from 'falcor-http-datasource'; 3 | import { errorFunc } from './layouts/CoreLayout'; 4 | 5 | class PublishingAppDataSource extends FalcorDataSource { 6 | onBeforeRequest ( config ) { 7 | const token = localStorage.token; 8 | const username = localStorage.username; 9 | const role = localStorage.role; 10 | 11 | if (token && username && role) { 12 | config.headers['token'] = token; 13 | config.headers['username'] = username; 14 | config.headers['role'] = role; 15 | } 16 | } 17 | } 18 | 19 | let falcorOptions = { 20 | source: new PublishingAppDataSource('/model.json'), 21 | errorSelector: function(path, error) { 22 | errorFunc(error.value, path); 23 | error.$expires = -1000 * 60 * 2; 24 | return error; 25 | } 26 | }; 27 | 28 | const model = new falcor.Model(falcorOptions); 29 | 30 | export default model; -------------------------------------------------------------------------------- /Chapter06/src/falcorModel.js: -------------------------------------------------------------------------------- 1 | import falcor from 'falcor'; 2 | import FalcorDataSource from 'falcor-http-datasource'; 3 | import { errorFunc } from './layouts/CoreLayout'; 4 | 5 | class PublishingAppDataSource extends FalcorDataSource { 6 | onBeforeRequest ( config ) { 7 | const token = localStorage.token; 8 | const username = localStorage.username; 9 | const role = localStorage.role; 10 | 11 | if (token && username && role) { 12 | config.headers['token'] = token; 13 | config.headers['username'] = username; 14 | config.headers['role'] = role; 15 | } 16 | } 17 | } 18 | 19 | let falcorOptions = { 20 | source: new PublishingAppDataSource('/model.json'), 21 | errorSelector: function(path, error) { 22 | errorFunc(error.value, path); 23 | error.$expires = -1000 * 60 * 2; 24 | return error; 25 | } 26 | }; 27 | 28 | const model = new falcor.Model(falcorOptions); 29 | 30 | export default model; -------------------------------------------------------------------------------- /Chapter04/dist/styles-draft-js.css: -------------------------------------------------------------------------------- 1 | .RichEditor-root { 2 | background: #fff; 3 | border: 1px solid #ddd; 4 | font-family: 'Georgia', serif; 5 | font-size: 14px; 6 | padding: 15px; 7 | } 8 | 9 | .RichEditor-editor { 10 | border-top: 1px solid #ddd; 11 | cursor: text; 12 | font-size: 16px; 13 | margin-top: 10px; 14 | min-height: 100px; 15 | } 16 | 17 | .RichEditor-editor .RichEditor-blockquote { 18 | border-left: 5px solid #eee; 19 | color: #666; 20 | font-family: 'Hoefler Text', 'Georgia', serif; 21 | font-style: italic; 22 | margin: 16px 0; 23 | padding: 10px 20px; 24 | } 25 | 26 | .RichEditor-controls {font-family: 'Helvetica', sans-serif; 27 | font-size: 14px; 28 | margin-bottom: 5px; 29 | user-select: none; 30 | } 31 | 32 | .RichEditor-styleButton { 33 | color: #999; 34 | cursor: pointer; 35 | margin-right: 16px; 36 | padding: 2px 0; 37 | } 38 | 39 | .RichEditor-activeButton { 40 | color: #5890ff; 41 | } -------------------------------------------------------------------------------- /Chapter05/dist/styles-draft-js.css: -------------------------------------------------------------------------------- 1 | .RichEditor-root { 2 | background: #fff; 3 | border: 1px solid #ddd; 4 | font-family: 'Georgia', serif; 5 | font-size: 14px; 6 | padding: 15px; 7 | } 8 | 9 | .RichEditor-editor { 10 | border-top: 1px solid #ddd; 11 | cursor: text; 12 | font-size: 16px; 13 | margin-top: 10px; 14 | min-height: 100px; 15 | } 16 | 17 | .RichEditor-editor .RichEditor-blockquote { 18 | border-left: 5px solid #eee; 19 | color: #666; 20 | font-family: 'Hoefler Text', 'Georgia', serif; 21 | font-style: italic; 22 | margin: 16px 0; 23 | padding: 10px 20px; 24 | } 25 | 26 | .RichEditor-controls {font-family: 'Helvetica', sans-serif; 27 | font-size: 14px; 28 | margin-bottom: 5px; 29 | user-select: none; 30 | } 31 | 32 | .RichEditor-styleButton { 33 | color: #999; 34 | cursor: pointer; 35 | margin-right: 16px; 36 | padding: 2px 0; 37 | } 38 | 39 | .RichEditor-activeButton { 40 | color: #5890ff; 41 | } -------------------------------------------------------------------------------- /Chapter06/dist/styles-draft-js.css: -------------------------------------------------------------------------------- 1 | .RichEditor-root { 2 | background: #fff; 3 | border: 1px solid #ddd; 4 | font-family: 'Georgia', serif; 5 | font-size: 14px; 6 | padding: 15px; 7 | } 8 | 9 | .RichEditor-editor { 10 | border-top: 1px solid #ddd; 11 | cursor: text; 12 | font-size: 16px; 13 | margin-top: 10px; 14 | min-height: 100px; 15 | } 16 | 17 | .RichEditor-editor .RichEditor-blockquote { 18 | border-left: 5px solid #eee; 19 | color: #666; 20 | font-family: 'Hoefler Text', 'Georgia', serif; 21 | font-style: italic; 22 | margin: 16px 0; 23 | padding: 10px 20px; 24 | } 25 | 26 | .RichEditor-controls {font-family: 'Helvetica', sans-serif; 27 | font-size: 14px; 28 | margin-bottom: 5px; 29 | user-select: none; 30 | } 31 | 32 | .RichEditor-styleButton { 33 | color: #999; 34 | cursor: pointer; 35 | margin-right: 16px; 36 | padding: 2px 0; 37 | } 38 | 39 | .RichEditor-activeButton { 40 | color: #5890ff; 41 | } -------------------------------------------------------------------------------- /Chapter02/server/server.js: -------------------------------------------------------------------------------- 1 | import http from 'http'; 2 | import express from 'express'; 3 | import cors from 'cors'; 4 | import bodyParser from 'body-parser'; 5 | import falcor from 'falcor'; 6 | import falcorExpress from 'falcor-express'; 7 | import falcorRouter from 'falcor-router'; 8 | import routes from './routes.js'; 9 | 10 | const app = express(); 11 | 12 | app.server = http.createServer(app); 13 | // CORS - 3rd party middleware 14 | app.use(cors()); 15 | // This is required by falcor-express middleware to work correctly with falcor-browser 16 | app.use(bodyParser.json({extended: false})); 17 | 18 | app.use(bodyParser.urlencoded({extended: false})); 19 | 20 | app.use('/model.json', falcorExpress.dataSourceRoute((req, res) => { 21 | return new falcorRouter(routes); 22 | })); 23 | 24 | app.use(express.static('dist')); 25 | 26 | app.server.listen(process.env.PORT || 3000); 27 | console.log(`Started on port ${app.server.address().port}`); 28 | 29 | export default app; 30 | -------------------------------------------------------------------------------- /Chapter03/server/configMongoose.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | 3 | var userSchema = { 4 | "username" : { type: String, index: {unique: true, dropDups: true }}, 5 | "password" : String, 6 | "firstName" : String, 7 | "lastName" : String, 8 | "email" : { type: String, index: {unique: true, dropDups: true }}, 9 | "role" : { type: String, default: 'editor' }, 10 | "verified" : Boolean, 11 | "imageUrl" : String 12 | } 13 | 14 | var User = mongoose.model('User', userSchema, 'pubUsers'); 15 | 16 | 17 | const conf = { 18 | hostname: process.env.MONGO_HOSTNAME || 'localhost', 19 | port: process.env.MONGO_PORT || 27017, 20 | env: process.env.MONGO_ENV || 'local', 21 | }; 22 | 23 | mongoose.connect(`mongodb://${conf.hostname}:${conf.port}/${conf.env}`); 24 | 25 | var articleSchema = { 26 | articleTitle:String, 27 | articleContent:String 28 | } 29 | var Article = mongoose.model('Article', articleSchema, 'articles'); 30 | 31 | export default { 32 | Article, 33 | User 34 | } -------------------------------------------------------------------------------- /Chapter04/server/configMongoose.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | 3 | var userSchema = { 4 | "username" : { type: String, index: {unique: true, dropDups: true }}, 5 | "password" : String, 6 | "firstName" : String, 7 | "lastName" : String, 8 | "email" : { type: String, index: {unique: true, dropDups: true }}, 9 | "role" : { type: String, default: 'editor' }, 10 | "verified" : Boolean, 11 | "imageUrl" : String 12 | } 13 | 14 | var User = mongoose.model('User', userSchema, 'pubUsers'); 15 | 16 | 17 | const conf = { 18 | hostname: process.env.MONGO_HOSTNAME || 'localhost', 19 | port: process.env.MONGO_PORT || 27017, 20 | env: process.env.MONGO_ENV || 'local', 21 | }; 22 | 23 | mongoose.connect(`mongodb://${conf.hostname}:${conf.port}/${conf.env}`); 24 | 25 | var articleSchema = { 26 | articleTitle:String, 27 | articleContent:String 28 | } 29 | var Article = mongoose.model('Article', articleSchema, 'articles'); 30 | 31 | export default { 32 | Article, 33 | User 34 | } -------------------------------------------------------------------------------- /Chapter02/server/configMongoose.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | 3 | const articleSchema = { 4 | articleTitle:String, 5 | articleContent:String 6 | }; 7 | 8 | const userSchema = { 9 | "username" : { type: String, index: {unique: true, dropDups: true }}, 10 | "password" : String, 11 | "firstName" : String, 12 | "lastName" : String, 13 | "email" : { type: String, index: {unique: true, dropDups: true }}, 14 | "role" : { type: String, default: 'editor' }, 15 | "verified" : Boolean, 16 | "imageUrl" : String 17 | }; 18 | 19 | const conf = { 20 | hostname: process.env.MONGO_HOSTNAME || 'localhost', 21 | port: process.env.MONGO_PORT || 27017, 22 | env: process.env.MONGO_ENV || 'local', 23 | }; 24 | 25 | const Article = mongoose.model('Article', articleSchema, 'articles'); 26 | const User = mongoose.model('User', userSchema, 'pubUsers'); 27 | 28 | mongoose.connect(`mongodb://${conf.hostname}:${conf.port}/${conf.env}`); 29 | 30 | 31 | export default { 32 | Article, 33 | User 34 | } -------------------------------------------------------------------------------- /Chapter03/src/components/DefaultInput.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {TextField} from 'material-ui'; 3 | import {HOC} from 'formsy-react'; 4 | 5 | class DefaultInput extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | this.changeValue = this.changeValue.bind(this); 9 | this.state = { currentText: null } 10 | } 11 | 12 | changeValue(e) { 13 | this.setState({currentText: e.target.value}) 14 | this.props.setValue(e.target.value); 15 | this.props.onChange(e); 16 | } 17 | 18 | render() { 19 | return (
20 | 28 | {this.props.children} 29 |
); 30 | } 31 | }; 32 | 33 | export default HOC(DefaultInput); -------------------------------------------------------------------------------- /Chapter04/src/components/DefaultInput.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {TextField} from 'material-ui'; 3 | import {HOC} from 'formsy-react'; 4 | 5 | class DefaultInput extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | this.changeValue = this.changeValue.bind(this); 9 | this.state = { currentText: null } 10 | } 11 | 12 | changeValue(e) { 13 | this.setState({currentText: e.target.value}) 14 | this.props.setValue(e.target.value); 15 | this.props.onChange(e); 16 | } 17 | 18 | render() { 19 | return (
20 | 28 | {this.props.children} 29 |
); 30 | } 31 | }; 32 | 33 | export default HOC(DefaultInput); -------------------------------------------------------------------------------- /Chapter05/src/components/DefaultInput.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {TextField} from 'material-ui'; 3 | import {HOC} from 'formsy-react'; 4 | 5 | class DefaultInput extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | this.changeValue = this.changeValue.bind(this); 9 | this.state = { currentText: null } 10 | } 11 | 12 | changeValue(e) { 13 | this.setState({currentText: e.target.value}) 14 | this.props.setValue(e.target.value); 15 | this.props.onChange(e); 16 | } 17 | 18 | render() { 19 | return (
20 | 28 | {this.props.children} 29 |
); 30 | } 31 | }; 32 | 33 | export default HOC(DefaultInput); -------------------------------------------------------------------------------- /Chapter06/src/components/DefaultInput.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {TextField} from 'material-ui'; 3 | import {HOC} from 'formsy-react'; 4 | 5 | class DefaultInput extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | this.changeValue = this.changeValue.bind(this); 9 | this.state = { currentText: null } 10 | } 11 | 12 | changeValue(e) { 13 | this.setState({currentText: e.target.value}) 14 | this.props.setValue(e.target.value); 15 | this.props.onChange(e); 16 | } 17 | 18 | render() { 19 | return (
20 | 28 | {this.props.children} 29 |
); 30 | } 31 | }; 32 | 33 | export default HOC(DefaultInput); -------------------------------------------------------------------------------- /Chapter03/src/components/LoginForm.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Formsy from 'formsy-react'; 3 | import { RaisedButton, Paper } from 'material-ui'; 4 | import DefaultInput from './DefaultInput'; 5 | 6 | export class LoginForm extends React.Component { 7 | constructor() { 8 | super(); 9 | } 10 | 11 | render() { 12 | return ( 13 | 14 | 15 |

Log in

16 | {}} name='username' title='Username (admin)' 17 | required /> 18 | {}} type='password' name='password' 19 | title='Password (123456)' required /> 20 |
21 | 26 |
27 |
28 |
29 | ); 30 | } 31 | } -------------------------------------------------------------------------------- /Chapter04/src/components/LoginForm.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Formsy from 'formsy-react'; 3 | import { RaisedButton, Paper } from 'material-ui'; 4 | import DefaultInput from './DefaultInput'; 5 | 6 | export class LoginForm extends React.Component { 7 | constructor() { 8 | super(); 9 | } 10 | 11 | render() { 12 | return ( 13 | 14 | 15 |

Log in

16 | {}} name='username' title='Username (admin)' 17 | required /> 18 | {}} type='password' name='password' 19 | title='Password (123456)' required /> 20 |
21 | 26 |
27 |
28 |
29 | ); 30 | } 31 | } -------------------------------------------------------------------------------- /Chapter05/src/components/LoginForm.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Formsy from 'formsy-react'; 3 | import { RaisedButton, Paper } from 'material-ui'; 4 | import DefaultInput from './DefaultInput'; 5 | 6 | export class LoginForm extends React.Component { 7 | constructor() { 8 | super(); 9 | } 10 | 11 | render() { 12 | return ( 13 | 14 | 15 |

Log in

16 | {}} name='username' title='Username (admin)' 17 | required /> 18 | {}} type='password' name='password' 19 | title='Password (123456)' required /> 20 |
21 | 26 |
27 |
28 |
29 | ); 30 | } 31 | } -------------------------------------------------------------------------------- /Chapter06/src/components/LoginForm.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Formsy from 'formsy-react'; 3 | import { RaisedButton, Paper } from 'material-ui'; 4 | import DefaultInput from './DefaultInput'; 5 | 6 | export class LoginForm extends React.Component { 7 | constructor() { 8 | super(); 9 | } 10 | 11 | render() { 12 | return ( 13 | 14 | 15 |

Log in

16 | {}} name='username' title='Username (admin)' 17 | required /> 18 | {}} type='password' name='password' 19 | title='Password (123456)' required /> 20 |
21 | 26 |
27 |
28 |
29 | ); 30 | } 31 | } -------------------------------------------------------------------------------- /Chapter01/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "project", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "dev": "webpack-dev-server", 6 | "start": "npm run webpack; node server", 7 | "webpack": "webpack --config ./webpack.config.js" 8 | }, 9 | "dependencies": { 10 | "body-parser": "^1.15.0", 11 | "cors": "^2.7.1", 12 | "express": "^4.13.4", 13 | "falcor": "^0.1.16", 14 | "falcor-express": "^0.1.2", 15 | "falcor-http-datasource": "^0.1.3", 16 | "falcor-router": "0.2.12", 17 | "mongoose": "4.4.5", 18 | "react": "^0.14.7", 19 | "react-dom": "^0.14.7", 20 | "react-redux": "^4.4.0", 21 | "redux": "^3.3.1" 22 | }, 23 | "devDependencies": { 24 | "babel": "^6.5.2", 25 | "babel-core": "^6.6.5", 26 | "babel-loader": "^6.2.4", 27 | "babel-polyfill": "^6.6.1", 28 | "babel-preset-es2015": "^6.6.0", 29 | "babel-preset-react": "^6.5.0", 30 | "babel-preset-stage-0": "^6.5.0", 31 | "webpack": "^1.12.14", 32 | "webpack-dev-server": "^1.14.1" 33 | }, 34 | "jshintConfig": { 35 | "esversion": 6 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Chapter02/src/components/DefaultInput.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {TextField} from 'material-ui'; 3 | import {HOC} from 'formsy-react'; 4 | 5 | class DefaultInput extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | this.changeValue = this.changeValue.bind(this); 9 | this.state = {currentText: null} 10 | } 11 | 12 | changeValue(e) { 13 | this.setState({currentText: e.target.value}) 14 | this.props.setValue(e.target.value); 15 | this.props.onChange(e); 16 | } 17 | 18 | render() { 19 | return ( 20 |
21 | 30 | 31 | {this.props.children} 32 |
33 | ); 34 | } 35 | }; 36 | 37 | export default HOC(DefaultInput); -------------------------------------------------------------------------------- /Chapter05/server/configMongoose.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | var Schema = mongoose.Schema; 3 | 4 | var userSchema = { 5 | "username" : { type: String, index: {unique: true, dropDups: true }}, 6 | "password" : String, 7 | "firstName" : String, 8 | "lastName" : String, 9 | "email" : { type: String, index: {unique: true, dropDups: true }}, 10 | "role" : { type: String, default: 'editor' }, 11 | "verified" : Boolean, 12 | "imageUrl" : String 13 | } 14 | 15 | var User = mongoose.model('User', userSchema, 'pubUsers'); 16 | 17 | 18 | const conf = { 19 | hostname: process.env.MONGO_HOSTNAME || 'localhost', 20 | port: process.env.MONGO_PORT || 27017, 21 | env: process.env.MONGO_ENV || 'local', 22 | }; 23 | 24 | mongoose.connect(`mongodb://${conf.hostname}:${conf.port}/${conf.env}`); 25 | 26 | var articleSchema = new Schema({ 27 | articleTitle:String, 28 | articleContent:String, 29 | articleContentJSON: Object 30 | }, 31 | { 32 | minimize: false 33 | } 34 | ); 35 | var Article = mongoose.model('Article', articleSchema, 'articles'); 36 | 37 | export default { 38 | Article, 39 | User 40 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Packt 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 | -------------------------------------------------------------------------------- /Chapter02/src/components/LoginForm.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Formsy from 'formsy-react'; 3 | import {RaisedButton, Paper} from 'material-ui'; 4 | import DefaultInput from './DefaultInput'; 5 | 6 | export class LoginForm extends React.Component { 7 | constructor() { 8 | super(); 9 | } 10 | 11 | render() { 12 | return ( 13 | 14 | 15 |

Log in

16 | {}} 18 | name='username' 19 | title='Username' 20 | required /> 21 | 22 | {}} 24 | type='password' 25 | name='password' 26 | title='Password' 27 | required /> 28 | 29 |
30 | 35 |
36 |
37 |
38 | ); 39 | } 40 | } -------------------------------------------------------------------------------- /Chapter04/src/routes/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route, IndexRoute } from 'react-router'; 3 | 4 | /* wrappers */ 5 | import CoreLayout from '../layouts/CoreLayout'; 6 | 7 | /* home view */ 8 | import PublishingApp from '../layouts/PublishingApp'; 9 | 10 | /* auth views */ 11 | import LoginView from '../views/LoginView'; 12 | import LogoutView from '../views/LogoutView'; 13 | import RegisterView from '../views/RegisterView'; 14 | 15 | 16 | import DashboardView from '../views/DashboardView'; 17 | import AddArticleView from '../views/articles/AddArticleView'; 18 | import EditArticleView from '../views/articles/EditArticleView'; 19 | 20 | export default ( 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | ); -------------------------------------------------------------------------------- /Chapter05/src/routes/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route, IndexRoute } from 'react-router'; 3 | 4 | /* wrappers */ 5 | import CoreLayout from '../layouts/CoreLayout'; 6 | 7 | /* home view */ 8 | import PublishingApp from '../layouts/PublishingApp'; 9 | 10 | /* auth views */ 11 | import LoginView from '../views/LoginView'; 12 | import LogoutView from '../views/LogoutView'; 13 | import RegisterView from '../views/RegisterView'; 14 | 15 | 16 | import DashboardView from '../views/DashboardView'; 17 | import AddArticleView from '../views/articles/AddArticleView'; 18 | import EditArticleView from '../views/articles/EditArticleView'; 19 | 20 | export default ( 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | ); -------------------------------------------------------------------------------- /Chapter06/src/routes/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route, IndexRoute } from 'react-router'; 3 | 4 | /* wrappers */ 5 | import CoreLayout from '../layouts/CoreLayout'; 6 | 7 | /* home view */ 8 | import PublishingApp from '../layouts/PublishingApp'; 9 | 10 | /* auth views */ 11 | import LoginView from '../views/LoginView'; 12 | import LogoutView from '../views/LogoutView'; 13 | import RegisterView from '../views/RegisterView'; 14 | 15 | 16 | import DashboardView from '../views/DashboardView'; 17 | import AddArticleView from '../views/articles/AddArticleView'; 18 | import EditArticleView from '../views/articles/EditArticleView'; 19 | 20 | export default ( 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | ); -------------------------------------------------------------------------------- /Chapter02/server/routes.js: -------------------------------------------------------------------------------- 1 | import configMongoose from './configMongoose'; 2 | import sessionRoutes from './routesSession'; 3 | 4 | const Article = configMongoose.Article; 5 | 6 | const PublishingAppRoutes = [ 7 | ...sessionRoutes, 8 | { 9 | route: 'articles.length', 10 | get: () => Article.count({}, (err, count) => count) 11 | .then ((articlesCountInDB) => { 12 | return { 13 | path: ['articles', 'length'], 14 | value: articlesCountInDB 15 | }; 16 | }) 17 | }, 18 | { 19 | route: 'articles[{integers}]["id","articleTitle","articleContent"]', 20 | get: (pathSet) => { 21 | const articlesIndex = pathSet[1]; 22 | 23 | return Article.find({}, (err, articlesDocs) => articlesDocs) 24 | .then ((articlesArrayFromDB) => { 25 | let results = []; 26 | 27 | articlesIndex.forEach((index) => { 28 | const singleArticleObject = articlesArrayFromDB[index].toObject(); 29 | const falcorSingleArticleResult = { 30 | path: ['articles', index], 31 | value: singleArticleObject 32 | }; 33 | 34 | results.push(falcorSingleArticleResult); 35 | }); 36 | 37 | return results; 38 | }); 39 | } 40 | } 41 | ]; 42 | 43 | export default PublishingAppRoutes; -------------------------------------------------------------------------------- /Chapter03/server/routes.js: -------------------------------------------------------------------------------- 1 | import configMongoose from './configMongoose'; 2 | 3 | let Article = configMongoose.Article; 4 | import sessionRoutes from './routesSession'; 5 | 6 | const PublishingAppRoutes = [ 7 | ...sessionRoutes, 8 | { 9 | route: 'articles.length', 10 | get: () => Article.count({}, (err, count) => count) 11 | .then ((articlesCountInDB) => { 12 | return { 13 | path: ['articles', 'length'], 14 | value: articlesCountInDB 15 | }; 16 | }) 17 | }, 18 | { 19 | route: 'articles[{integers}]["id","articleTitle","articleContent"]', 20 | get: (pathSet) => { 21 | const articlesIndex = pathSet[1]; 22 | 23 | return Article.find({}, (err, articlesDocs) => articlesDocs) 24 | .then ((articlesArrayFromDB) => { 25 | let results = []; 26 | 27 | articlesIndex.forEach((index) => { 28 | const singleArticleObject = articlesArrayFromDB[index].toObject(); 29 | const falcorSingleArticleResult = { 30 | path: ['articles', index], 31 | value: singleArticleObject 32 | }; 33 | 34 | results.push(falcorSingleArticleResult); 35 | }); 36 | 37 | return results; 38 | }); 39 | } 40 | } 41 | ]; 42 | 43 | export default PublishingAppRoutes; -------------------------------------------------------------------------------- /Chapter04/server/routes.js: -------------------------------------------------------------------------------- 1 | import configMongoose from './configMongoose'; 2 | 3 | let Article = configMongoose.Article; 4 | import sessionRoutes from './routesSession'; 5 | 6 | const PublishingAppRoutes = [ 7 | ...sessionRoutes, 8 | { 9 | route: 'articles.length', 10 | get: () => Article.count({}, (err, count) => count) 11 | .then ((articlesCountInDB) => { 12 | return { 13 | path: ['articles', 'length'], 14 | value: articlesCountInDB 15 | }; 16 | }) 17 | }, 18 | { 19 | route: 'articles[{integers}]["_id","articleTitle","articleContent"]', 20 | get: (pathSet) => { 21 | const articlesIndex = pathSet[1]; 22 | 23 | return Article.find({}, (err, articlesDocs) => articlesDocs) 24 | .then ((articlesArrayFromDB) => { 25 | let results = []; 26 | 27 | articlesIndex.forEach((index) => { 28 | const singleArticleObject = articlesArrayFromDB[index].toObject(); 29 | const falcorSingleArticleResult = { 30 | path: ['articles', index], 31 | value: singleArticleObject 32 | }; 33 | 34 | results.push(falcorSingleArticleResult); 35 | }); 36 | 37 | return results; 38 | }); 39 | } 40 | } 41 | ]; 42 | 43 | export default PublishingAppRoutes; -------------------------------------------------------------------------------- /Chapter02/src/components/RegisterForm.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Formsy from 'formsy-react'; 3 | import { RaisedButton, Paper } from 'material-ui'; 4 | import DefaultInput from './DefaultInput'; 5 | 6 | export class RegisterForm extends React.Component { 7 | constructor() { 8 | super(); 9 | } 10 | 11 | render() { 12 | return ( 13 | 14 | 15 |

Registration form

16 | {}} name='username' title='Username' 17 | required /> 18 | {}} name='firstName' title='Firstname' required 19 | /> 20 | {}} name='lastName' title='Lastname' 21 | required /> 22 | {}} name='email' title='Email' required /> 23 | {}} type='password' name='password' 24 | title='Password' required /> 25 |
26 | 31 |
32 |
33 |
34 | ); 35 | } 36 | } -------------------------------------------------------------------------------- /Chapter03/src/components/RegisterForm.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Formsy from 'formsy-react'; 3 | import { RaisedButton, Paper } from 'material-ui'; 4 | import DefaultInput from './DefaultInput'; 5 | 6 | export class RegisterForm extends React.Component { 7 | constructor() { 8 | super(); 9 | } 10 | 11 | render() { 12 | return ( 13 | 14 | 15 |

Registration form

16 | {}} name='username' title='Username' 17 | required /> 18 | {}} name='firstName' title='Firstname' required 19 | /> 20 | {}} name='lastName' title='Lastname' 21 | required /> 22 | {}} name='email' title='Email' required /> 23 | {}} type='password' name='password' 24 | title='Password' required /> 25 |
26 | 31 |
32 |
33 |
34 | ); 35 | } 36 | } -------------------------------------------------------------------------------- /Chapter04/src/components/RegisterForm.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Formsy from 'formsy-react'; 3 | import { RaisedButton, Paper } from 'material-ui'; 4 | import DefaultInput from './DefaultInput'; 5 | 6 | export class RegisterForm extends React.Component { 7 | constructor() { 8 | super(); 9 | } 10 | 11 | render() { 12 | return ( 13 | 14 | 15 |

Registration form

16 | {}} name='username' title='Username' 17 | required /> 18 | {}} name='firstName' title='Firstname' required 19 | /> 20 | {}} name='lastName' title='Lastname' 21 | required /> 22 | {}} name='email' title='Email' required /> 23 | {}} type='password' name='password' 24 | title='Password' required /> 25 |
26 | 31 |
32 |
33 |
34 | ); 35 | } 36 | } -------------------------------------------------------------------------------- /Chapter05/src/components/RegisterForm.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Formsy from 'formsy-react'; 3 | import { RaisedButton, Paper } from 'material-ui'; 4 | import DefaultInput from './DefaultInput'; 5 | 6 | export class RegisterForm extends React.Component { 7 | constructor() { 8 | super(); 9 | } 10 | 11 | render() { 12 | return ( 13 | 14 | 15 |

Registration form

16 | {}} name='username' title='Username' 17 | required /> 18 | {}} name='firstName' title='Firstname' required 19 | /> 20 | {}} name='lastName' title='Lastname' 21 | required /> 22 | {}} name='email' title='Email' required /> 23 | {}} type='password' name='password' 24 | title='Password' required /> 25 |
26 | 31 |
32 |
33 |
34 | ); 35 | } 36 | } -------------------------------------------------------------------------------- /Chapter06/src/components/RegisterForm.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Formsy from 'formsy-react'; 3 | import { RaisedButton, Paper } from 'material-ui'; 4 | import DefaultInput from './DefaultInput'; 5 | 6 | export class RegisterForm extends React.Component { 7 | constructor() { 8 | super(); 9 | } 10 | 11 | render() { 12 | return ( 13 | 14 | 15 |

Registration form

16 | {}} name='username' title='Username' 17 | required /> 18 | {}} name='firstName' title='Firstname' required 19 | /> 20 | {}} name='lastName' title='Lastname' 21 | required /> 22 | {}} name='email' title='Email' required /> 23 | {}} type='password' name='password' 24 | title='Password' required /> 25 |
26 | 31 |
32 |
33 |
34 | ); 35 | } 36 | } -------------------------------------------------------------------------------- /Chapter02/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "project", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "dev": "webpack-dev-server", 6 | "start": "npm run webpack; node server", 7 | "webpack": "webpack --config ./webpack.config.js" 8 | }, 9 | "dependencies": { 10 | "body-parser": "^1.15.0", 11 | "cors": "^2.7.1", 12 | "crypto": "0.0.3", 13 | "express": "^4.13.4", 14 | "falcor": "^0.1.16", 15 | "falcor-express": "^0.1.2", 16 | "falcor-http-datasource": "^0.1.3", 17 | "falcor-router": "0.2.12", 18 | "formsy-react": "^0.17.0", 19 | "history": "^1.17.0", 20 | "jsonwebtoken": "^7.0.1", 21 | "material-ui": "^0.14.4", 22 | "mongoose": "4.4.5", 23 | "react": "^0.14.7", 24 | "react-dom": "^0.14.7", 25 | "react-redux": "^4.4.0", 26 | "react-router": "^1.0.0", 27 | "react-tap-event-plugin": "^0.2.2", 28 | "redux": "^3.3.1", 29 | "redux-simple-router": "0.0.10", 30 | "redux-thunk": "^1.0.0" 31 | }, 32 | "devDependencies": { 33 | "babel": "^6.5.2", 34 | "babel-core": "^6.6.5", 35 | "babel-loader": "^6.2.4", 36 | "babel-polyfill": "^6.6.1", 37 | "babel-preset-es2015": "^6.6.0", 38 | "babel-preset-react": "^6.5.0", 39 | "babel-preset-stage-0": "^6.5.0", 40 | "webpack": "^1.12.14", 41 | "webpack-dev-server": "^1.14.1" 42 | }, 43 | "jshintConfig": { 44 | "esversion": 6 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Chapter03/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "project", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "dev": "webpack-dev-server", 6 | "start": "npm run webpack; node server", 7 | "webpack": "webpack --config ./webpack.config.js" 8 | }, 9 | "dependencies": { 10 | "body-parser": "^1.15.0", 11 | "cors": "^2.7.1", 12 | "crypto": "0.0.3", 13 | "express": "^4.13.4", 14 | "falcor": "^0.1.16", 15 | "falcor-express": "^0.1.2", 16 | "falcor-http-datasource": "^0.1.3", 17 | "falcor-router": "^0.2.12", 18 | "formsy-react": "^0.17.0", 19 | "history": "^1.17.0", 20 | "jsonwebtoken": "^7.0.1", 21 | "material-ui": "^0.14.4", 22 | "mongoose": "4.4.5", 23 | "react": "^0.14.7", 24 | "react-dom": "^0.14.7", 25 | "react-redux": "^4.4.0", 26 | "react-router": "^1.0.0", 27 | "react-tap-event-plugin": "^0.2.2", 28 | "redux": "^3.3.1", 29 | "redux-simple-router": "0.0.10", 30 | "redux-thunk": "^1.0.0" 31 | }, 32 | "devDependencies": { 33 | "babel": "^6.5.2", 34 | "babel-core": "^6.6.5", 35 | "babel-loader": "^6.2.4", 36 | "babel-polyfill": "^6.6.1", 37 | "babel-preset-es2015": "^6.6.0", 38 | "babel-preset-react": "^6.5.0", 39 | "babel-preset-stage-0": "^6.5.0", 40 | "webpack": "^1.12.14", 41 | "webpack-dev-server": "^1.14.1" 42 | }, 43 | "jshintConfig": { 44 | "esversion": 6 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Chapter01/server/routes.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | 3 | mongoose.connect('mongodb://localhost/local'); 4 | 5 | const articleSchema = { 6 | articleTitle:String, 7 | articleContent:String 8 | }; 9 | 10 | const Article = mongoose.model('Article', articleSchema, 'articles'); 11 | 12 | const PublishingAppRoutes = [ 13 | { 14 | route: 'articles.length', 15 | get: () => Article.count({}, (err, count) => count) 16 | .then ((articlesCountInDB) => { 17 | return { 18 | path: ['articles', 'length'], 19 | value: articlesCountInDB 20 | }; 21 | }) 22 | }, 23 | { 24 | route: 'articles[{integers}]["id","articleTitle","articleContent"]', 25 | get: (pathSet) => { 26 | const articlesIndex = pathSet[1]; 27 | 28 | return Article.find({}, (err, articlesDocs) => articlesDocs) 29 | .then ((articlesArrayFromDB) => { 30 | let results = []; 31 | 32 | articlesIndex.forEach((index) => { 33 | const singleArticleObject = articlesArrayFromDB[index].toObject(); 34 | const falcorSingleArticleResult = { 35 | path: ['articles', index], 36 | value: singleArticleObject 37 | }; 38 | 39 | results.push(falcorSingleArticleResult); 40 | }); 41 | 42 | return results; 43 | }); 44 | } 45 | } 46 | ]; 47 | 48 | export default PublishingAppRoutes; -------------------------------------------------------------------------------- /Chapter05/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "project", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "dev": "webpack-dev-server", 6 | "start": "npm run webpack; node server", 7 | "webpack": "webpack --config ./webpack.config.js" 8 | }, 9 | "dependencies": { 10 | "babel": "^6.5.2", 11 | "babel-register": "^6.5.2", 12 | "body-parser": "^1.15.0", 13 | "cors": "^2.7.1", 14 | "crypto": "0.0.3", 15 | "draft-js": "^0.5.0", 16 | "draft-js-export-html": "^0.1.13", 17 | "express": "^4.13.4", 18 | "falcor": "^0.1.16", 19 | "falcor-express": "^0.1.2", 20 | "falcor-http-datasource": "^0.1.3", 21 | "falcor-json-graph": "^1.1.7", 22 | "falcor-router": "0.2.12", 23 | "formsy-react": "^0.17.0", 24 | "history": "^1.17.0", 25 | "immutable": "^3.8.1", 26 | "jsonwebtoken": "^7.0.0", 27 | "material-ui": "0.14.4", 28 | "mongoose": "4.4.5", 29 | "react": "^0.14.7", 30 | "react-dom": "^0.14.7", 31 | "react-redux": "^4.4.0", 32 | "react-router": "^1.0.0", 33 | "react-s3-uploader": "^3.0.3", 34 | "react-tap-event-plugin": "^0.2.2", 35 | "redux": "^3.3.1", 36 | "redux-simple-router": "0.0.10", 37 | "redux-thunk": "^1.0.0", 38 | "webpack": "^1.12.14", 39 | "webpack-dev-server": "^1.14.1" 40 | }, 41 | "devDependencies": { 42 | "babel-core": "^6.6.5", 43 | "babel-loader": "^6.2.4", 44 | "babel-polyfill": "^6.6.1", 45 | "babel-preset-es2015": "^6.6.0", 46 | "babel-preset-react": "^6.5.0", 47 | "babel-preset-stage-0": "^6.5.0" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Chapter04/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "project", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "dev": "webpack-dev-server", 6 | "start": "npm run webpack; node server", 7 | "webpack": "webpack --config ./webpack.config.js" 8 | }, 9 | "dependencies": { 10 | "body-parser": "^1.15.0", 11 | "cors": "^2.7.1", 12 | "crypto": "0.0.3", 13 | "draft-js": "^0.5.0", 14 | "draft-js-export-html": "^0.1.13", 15 | "express": "^4.13.4", 16 | "falcor": "^0.1.16", 17 | "falcor-express": "^0.1.2", 18 | "falcor-http-datasource": "^0.1.3", 19 | "falcor-json-graph": "^1.1.7", 20 | "falcor-router": "^0.3.0", 21 | "formsy-react": "^0.17.0", 22 | "history": "^1.17.0", 23 | "immutable": "^3.8.1", 24 | "jsonwebtoken": "^7.0.1", 25 | "material-ui": "^0.14.4", 26 | "mongoose": "^4.4.5", 27 | "react": "^0.14.7", 28 | "react-dom": "^0.14.7", 29 | "react-redux": "^4.4.0", 30 | "react-router": "^1.0.0", 31 | "react-tap-event-plugin": "^0.2.2", 32 | "redux": "^3.3.1", 33 | "redux-simple-router": "0.0.10", 34 | "redux-thunk": "^1.0.0" 35 | }, 36 | "devDependencies": { 37 | "babel": "^6.5.2", 38 | "babel-core": "^6.6.5", 39 | "babel-loader": "^6.2.4", 40 | "babel-polyfill": "^6.6.1", 41 | "babel-preset-es2015": "^6.6.0", 42 | "babel-preset-react": "^6.5.0", 43 | "babel-preset-stage-0": "^6.5.0", 44 | "webpack": "^1.12.14", 45 | "webpack-dev-server": "^1.14.1" 46 | }, 47 | "jshintConfig": { 48 | "esversion": 6 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Chapter04/src/components/ArticleCard.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Card, 4 | CardHeader, 5 | CardMedia, 6 | CardTitle, 7 | CardText 8 | } from 'material-ui/lib/card'; 9 | import { Paper } from 'material-ui'; 10 | 11 | class ArticleCard extends React.Component { 12 | constructor(props) { 13 | super(props); 14 | } 15 | 16 | render() { 17 | let title = this.props.title || 'no title provided'; 18 | let content = this.props.content || 'no content provided'; 19 | 20 | let paperStyle = { 21 | padding: 10, 22 | width: '100%', 23 | height: 300 24 | }; 25 | 26 | let leftDivStyle = { 27 | width: '30%', 28 | float: 'left' 29 | } 30 | 31 | let rightDivStyle = { 32 | width: '60%', 33 | float: 'left', 34 | padding: '10px 10px 10px 10px' 35 | } 36 | 37 | return ( 38 | 39 | 44 |
45 | 46 | }> 48 | 49 | 50 | 51 |
52 |
53 |
54 |
55 | ); 56 | } 57 | }; 58 | export default ArticleCard; -------------------------------------------------------------------------------- /Chapter05/src/components/ArticleCard.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Card, 4 | CardHeader, 5 | CardMedia, 6 | CardTitle, 7 | CardText 8 | } from 'material-ui/lib/card'; 9 | import { Paper } from 'material-ui'; 10 | 11 | class ArticleCard extends React.Component { 12 | constructor(props) { 13 | super(props); 14 | } 15 | 16 | render() { 17 | let title = this.props.title || 'no title provided'; 18 | let content = this.props.content || 'no content provided'; 19 | 20 | let paperStyle = { 21 | padding: 10, 22 | width: '100%', 23 | height: 300 24 | }; 25 | 26 | let leftDivStyle = { 27 | width: '30%', 28 | float: 'left' 29 | } 30 | 31 | let rightDivStyle = { 32 | width: '60%', 33 | float: 'left', 34 | padding: '10px 10px 10px 10px' 35 | } 36 | 37 | return ( 38 | 39 | 44 |
45 | 46 | }> 48 | 49 | 50 | 51 |
52 |
53 |
54 |
55 | ); 56 | } 57 | }; 58 | export default ArticleCard; -------------------------------------------------------------------------------- /Chapter02/src/views/RegisterView.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import falcorModel from '../falcorModel.js'; 3 | import { connect } from 'react-redux'; 4 | import { bindActionCreators } from 'redux'; 5 | import { Snackbar } from 'material-ui'; 6 | import { RegisterForm } from '../components/RegisterForm.js'; 7 | 8 | const mapStateToProps = (state) => ({ 9 | ...state 10 | }); 11 | 12 | // You can add your reducers here 13 | const mapDispatchToProps = (dispatch) => ({ 14 | }); 15 | 16 | class RegisterView extends React.Component { 17 | constructor(props) { 18 | super(props); 19 | this.register = this.register.bind(this); 20 | this.state = { 21 | error: null 22 | }; 23 | } 24 | 25 | async register (newUserModel) { 26 | await falcorModel 27 | .call(['register'],[newUserModel]) 28 | .then((result) => result); 29 | 30 | const newUserId = await falcorModel.getValue(['register', 'newUserId']); 31 | 32 | if (newUserId === 'INVALID') { 33 | const errorRes = await falcorModel.getValue('register.error'); 34 | 35 | this.setState({error: errorRes}); 36 | return; 37 | } 38 | 39 | this.props.history.pushState(null, '/login'); 40 | } 41 | 42 | render () { 43 | return ( 44 |
45 |

Register

46 |
47 | 49 |
50 |
51 | ); 52 | } 53 | } 54 | 55 | export default connect(mapStateToProps, mapDispatchToProps)(RegisterView); -------------------------------------------------------------------------------- /Chapter06/server/configMongoose.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | var Schema = mongoose.Schema; 3 | 4 | var userSchema = { 5 | "username" : { type: String, index: {unique: true, dropDups: true }}, 6 | "password" : String, 7 | "firstName" : String, 8 | "lastName" : String, 9 | "email" : { type: String, index: {unique: true, dropDups: true }}, 10 | "role" : { type: String, default: 'editor' }, 11 | "verified" : Boolean, 12 | "imageUrl" : String 13 | } 14 | 15 | var User = mongoose.model('User', userSchema, 'pubUsers'); 16 | 17 | 18 | const conf = { 19 | hostname: process.env.MONGO_HOSTNAME || 'localhost', 20 | port: process.env.MONGO_PORT || 27017, 21 | env: process.env.MONGO_ENV || 'local', 22 | }; 23 | 24 | mongoose.connect(`mongodb://${conf.hostname}:${conf.port}/${conf.env}`); 25 | 26 | var defaultDraftJSobject = { 27 | "blocks" : [], 28 | "entityMap" : {} 29 | } 30 | 31 | var articleSchema = new Schema({ 32 | articleTitle: { type: String, required: true, default: 'default articletitle' }, 33 | articleSubTitle: { type: String, required: true, default: 'defaulsubtitle' }, 34 | articleContent: { type: String, required: true, default: 'defaultcontent' }, 35 | articleContentJSON: { type: Object, required: true, default:defaultDraftJSobject }, 36 | articlePicUrl: { type: String, required: true, default: '/static/placeholder.png' } 37 | }, 38 | { 39 | minimize: false 40 | } 41 | ); 42 | 43 | var Article = mongoose.model('Article', articleSchema, 'articles'); 44 | 45 | export default { 46 | Article, 47 | User 48 | } -------------------------------------------------------------------------------- /Chapter06/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "project", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "dev": "webpack-dev-server", 6 | "start": "npm run webpack; node server", 7 | "webpack": "webpack --config ./webpack.config.js" 8 | }, 9 | "dependencies": { 10 | "babel": "^6.5.2", 11 | "babel-register": "^6.5.2", 12 | "body-parser": "^1.15.0", 13 | "cors": "^2.7.1", 14 | "crypto": "0.0.3", 15 | "draft-js": "^0.5.0", 16 | "draft-js-export-html": "^0.1.13", 17 | "express": "^4.13.4", 18 | "falcor": "^0.1.16", 19 | "falcor-express": "^0.1.2", 20 | "falcor-http-datasource": "^0.1.3", 21 | "falcor-json-graph": "^1.1.7", 22 | "falcor-router": "0.2.12", 23 | "formsy-react": "^0.17.0", 24 | "history": "^1.17.0", 25 | "immutable": "^3.8.1", 26 | "jsonwebtoken": "^7.0.0", 27 | "material-ui": "0.14.4", 28 | "mongoose": "4.4.5", 29 | "node-env-file": "^0.1.8", 30 | "react": "^0.14.7", 31 | "react-dom": "^0.14.7", 32 | "react-redux": "^4.4.0", 33 | "react-router": "^1.0.0", 34 | "react-s3-uploader": "^3.0.3", 35 | "react-tap-event-plugin": "^0.2.2", 36 | "redux": "^3.3.1", 37 | "redux-simple-router": "0.0.10", 38 | "redux-thunk": "^1.0.0", 39 | "webpack": "^1.12.14", 40 | "webpack-dev-server": "^1.14.1" 41 | }, 42 | "devDependencies": { 43 | "babel-core": "^6.6.5", 44 | "babel-loader": "^6.2.4", 45 | "babel-polyfill": "^6.6.1", 46 | "babel-preset-es2015": "^6.6.0", 47 | "babel-preset-react": "^6.5.0", 48 | "babel-preset-stage-0": "^6.5.0" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Chapter06/src/components/ArticleCard.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Card, 4 | CardHeader, 5 | CardMedia, 6 | CardTitle, 7 | CardText 8 | } from 'material-ui/lib/card'; 9 | import { Paper } from 'material-ui'; 10 | 11 | class ArticleCard extends React.Component { 12 | constructor(props) { 13 | super(props); 14 | } 15 | 16 | render() { 17 | let title = this.props.title || 'no title provided'; 18 | let subTitle = this.props.subTitle || ''; 19 | let content = this.props.content || 'no content provided'; 20 | let articlePicUrl = this.props.articlePicUrl || '/static/placeholder.png'; 21 | let paperStyle = { 22 | padding: 10,width: '100%', 23 | height: 300 24 | }; 25 | 26 | let leftDivStyle = { 27 | width: '30%', 28 | float: 'left' 29 | } 30 | 31 | let rightDivStyle = { 32 | width: '60%', 33 | float: 'left', 34 | padding: '10px 10px 10px 10px' 35 | } 36 | 37 | return ( 38 | 39 | 44 |
45 | 46 | }> 48 | 49 | 50 | 51 |
52 |
53 |
54 |
55 | 56 | ); 57 | } 58 | }; 59 | 60 | export default ArticleCard; -------------------------------------------------------------------------------- /Chapter02/src/layouts/PublishingApp.js: -------------------------------------------------------------------------------- 1 | import falcorModel from '../falcorModel.js'; 2 | import React from 'react'; 3 | import {connect} from 'react-redux'; 4 | import {bindActionCreators} from 'redux'; 5 | import articleActions from '../actions/article.js'; 6 | 7 | const mapStateToProps = (state) => ({ 8 | ...state 9 | }); 10 | 11 | const mapDispatchToProps = (dispatch) => ({ 12 | articleActions: bindActionCreators(articleActions, dispatch) 13 | }) 14 | 15 | class PublishingApp extends React.Component { 16 | componentWillMount() { 17 | this._fetch(); 18 | } 19 | 20 | async _fetch() { 21 | const articlesLength = await falcorModel. 22 | getValue('articles.length') 23 | .then((length) => length); 24 | 25 | const articles = await falcorModel. 26 | get(['articles', 27 | {from: 0, to: articlesLength - 1}, 28 | ['id','articleTitle', 'articleContent']]) 29 | .then((articlesResponse) => articlesResponse.json.articles); 30 | 31 | this.props.articleActions.articlesList(articles); 32 | } 33 | 34 | render () { 35 | let articlesJSX = []; 36 | for (let articleKey in this.props.article) { 37 | const articleDetails = this.props.article[articleKey]; 38 | 39 | const currentArticleJSX = ( 40 |
41 |

{articleDetails.articleTitle}

42 |

{articleDetails.articleContent}

43 |
); 44 | 45 | articlesJSX.push(currentArticleJSX); 46 | } 47 | 48 | return ( 49 |
50 |

Our publishing app

51 | {articlesJSX} 52 |
53 | ); 54 | } 55 | } 56 | 57 | export default connect(mapStateToProps, mapDispatchToProps)(PublishingApp); -------------------------------------------------------------------------------- /Chapter04/src/views/RegisterView.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import React from 'react'; 4 | import falcorModel from '../falcorModel.js'; 5 | import { connect } from 'react-redux'; 6 | import { bindActionCreators } from 'redux'; 7 | import { Snackbar } from 'material-ui'; 8 | import { RegisterForm } from '../components/RegisterForm.js'; 9 | 10 | const mapStateToProps = (state) => ({ 11 | ...state 12 | }); 13 | 14 | const mapDispatchToProps = (dispatch) => ({ 15 | }); 16 | 17 | class RegisterView extends React.Component { 18 | constructor(props) { 19 | super(props); 20 | this.state = { 21 | error: null}; 22 | this.register = this.register.bind(this); 23 | } 24 | 25 | async register (newUserModel) { 26 | console.info("newUserModel", newUserModel); 27 | let registerResult = await falcorModel 28 | .call( 29 | ['register'], 30 | [newUserModel] 31 | ). 32 | then((result) => { 33 | return result; 34 | }); 35 | 36 | let newUserId = await falcorModel.getValue(['register', 'newUserId']); 37 | if(newUserId === "INVALID") { 38 | let errorRes = await falcorModel.getValue('register.error'); 39 | this.setState({error: errorRes}); 40 | return; 41 | } 42 | 43 | if(newUserId) { 44 | this.props.history.pushState(null, '/login'); 45 | return; 46 | }else { 47 | alert("Fatal registration error, please contact an admin"); 48 | } 49 | } 50 | 51 | render () { 52 | return ( 53 |
54 |
55 | 57 |
58 |
59 | ); 60 | } 61 | } 62 | 63 | export default connect(mapStateToProps, mapDispatchToProps)(RegisterView); -------------------------------------------------------------------------------- /Chapter05/src/views/RegisterView.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import React from 'react'; 4 | import falcorModel from '../falcorModel.js'; 5 | import { connect } from 'react-redux'; 6 | import { bindActionCreators } from 'redux'; 7 | import { Snackbar } from 'material-ui'; 8 | import { RegisterForm } from '../components/RegisterForm.js'; 9 | 10 | const mapStateToProps = (state) => ({ 11 | ...state 12 | }); 13 | 14 | const mapDispatchToProps = (dispatch) => ({ 15 | }); 16 | 17 | class RegisterView extends React.Component { 18 | constructor(props) { 19 | super(props); 20 | this.state = { 21 | error: null}; 22 | this.register = this.register.bind(this); 23 | } 24 | 25 | async register (newUserModel) { 26 | console.info("newUserModel", newUserModel); 27 | let registerResult = await falcorModel 28 | .call( 29 | ['register'], 30 | [newUserModel] 31 | ). 32 | then((result) => { 33 | return result; 34 | }); 35 | 36 | let newUserId = await falcorModel.getValue(['register', 'newUserId']); 37 | if(newUserId === "INVALID") { 38 | let errorRes = await falcorModel.getValue('register.error'); 39 | this.setState({error: errorRes}); 40 | return; 41 | } 42 | 43 | if(newUserId) { 44 | this.props.history.pushState(null, '/login'); 45 | return; 46 | }else { 47 | alert("Fatal registration error, please contact an admin"); 48 | } 49 | } 50 | 51 | render () { 52 | return ( 53 |
54 |
55 | 57 |
58 |
59 | ); 60 | } 61 | } 62 | 63 | export default connect(mapStateToProps, mapDispatchToProps)(RegisterView); -------------------------------------------------------------------------------- /Chapter06/src/views/RegisterView.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import React from 'react'; 4 | import falcorModel from '../falcorModel.js'; 5 | import { connect } from 'react-redux'; 6 | import { bindActionCreators } from 'redux'; 7 | import { Snackbar } from 'material-ui'; 8 | import { RegisterForm } from '../components/RegisterForm.js'; 9 | 10 | const mapStateToProps = (state) => ({ 11 | ...state 12 | }); 13 | 14 | const mapDispatchToProps = (dispatch) => ({ 15 | }); 16 | 17 | class RegisterView extends React.Component { 18 | constructor(props) { 19 | super(props); 20 | this.state = { 21 | error: null}; 22 | this.register = this.register.bind(this); 23 | } 24 | 25 | async register (newUserModel) { 26 | console.info("newUserModel", newUserModel); 27 | let registerResult = await falcorModel 28 | .call( 29 | ['register'], 30 | [newUserModel] 31 | ). 32 | then((result) => { 33 | return result; 34 | }); 35 | 36 | let newUserId = await falcorModel.getValue(['register', 'newUserId']); 37 | if(newUserId === "INVALID") { 38 | let errorRes = await falcorModel.getValue('register.error'); 39 | this.setState({error: errorRes}); 40 | return; 41 | } 42 | 43 | if(newUserId) { 44 | this.props.history.pushState(null, '/login'); 45 | return; 46 | }else { 47 | alert("Fatal registration error, please contact an admin"); 48 | } 49 | } 50 | 51 | render () { 52 | return ( 53 |
54 |
55 | 57 |
58 |
59 | ); 60 | } 61 | } 62 | 63 | export default connect(mapStateToProps, mapDispatchToProps)(RegisterView); -------------------------------------------------------------------------------- /Chapter03/src/views/RegisterView.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import React from 'react'; 4 | import falcorModel from '../falcorModel.js'; 5 | import { connect } from 'react-redux'; 6 | import { bindActionCreators } from 'redux'; 7 | import { Snackbar } from 'material-ui'; 8 | import { RegisterForm } from '../components/RegisterForm.js'; 9 | 10 | const mapStateToProps = (state) => ({ 11 | ...state 12 | }); 13 | 14 | const mapDispatchToProps = (dispatch) => ({ 15 | }); 16 | 17 | class RegisterView extends React.Component { 18 | constructor(props) { 19 | super(props); 20 | this.state = { 21 | error: null}; 22 | this.register = this.register.bind(this); 23 | } 24 | 25 | async register (newUserModel) { 26 | console.info("newUserModel", newUserModel); 27 | let registerResult = await falcorModel 28 | .call( 29 | ['register'], 30 | [newUserModel] 31 | ). 32 | then((result) => { 33 | return result; 34 | }); 35 | 36 | let newUserId = await falcorModel.getValue(['register', 'newUserId']); 37 | if(newUserId === "INVALID") { 38 | let errorRes = await falcorModel.getValue('register.error'); 39 | this.setState({error: errorRes}); 40 | return; 41 | } 42 | 43 | if(newUserId) { 44 | this.props.history.pushState(null, '/login'); 45 | return; 46 | }else { 47 | alert("Fatal registration error, please contact an admin"); 48 | } 49 | } 50 | 51 | render () { 52 | return ( 53 |
54 |

Register

55 |
56 | 58 |
59 |
60 | ); 61 | } 62 | } 63 | 64 | export default connect(mapStateToProps, mapDispatchToProps)(RegisterView); -------------------------------------------------------------------------------- /Chapter01/src/layouts/PublishingApp.js: -------------------------------------------------------------------------------- 1 | import falcorModel from '../falcorModel.js'; 2 | import React from 'react'; 3 | import { connect } from 'react-redux'; 4 | import { bindActionCreators } from 'redux'; 5 | import articleActions from '../actions/article.js'; 6 | 7 | const mapStateToProps = (state) => ({ 8 | ...state 9 | }); 10 | 11 | const mapDispatchToProps = (dispatch) => ({ 12 | articleActions: bindActionCreators(articleActions, dispatch) 13 | }) 14 | 15 | 16 | class PublishingApp extends React.Component { 17 | constructor(props) { 18 | super(props); 19 | } 20 | 21 | componentWillMount() { 22 | this._fetch(); 23 | } 24 | 25 | async _fetch() { 26 | const articlesLength = await falcorModel. 27 | getValue('articles.length') 28 | .then((length) => length); 29 | 30 | const articles = await falcorModel. 31 | get(['articles', 32 | {from: 0, to: articlesLength-1}, 33 | ['id','articleTitle', 'articleContent']]) 34 | .then((articlesResponse) => articlesResponse.json.articles); 35 | 36 | this.props.articleActions.articlesList(articles); 37 | } 38 | 39 | // below here are next methods o the PublishingApp 40 | render () { 41 | let articlesJSX = []; 42 | 43 | for (let articleKey in this.props) { 44 | const articleDetails = this.props[articleKey]; 45 | const currentArticleJSX = ( 46 |
47 |

{articleDetails.articleTitle}

48 |

{articleDetails.articleContent}

49 |
); 50 | 51 | articlesJSX.push(currentArticleJSX); 52 | } 53 | 54 | return ( 55 |
56 |

Our publishing app

57 | {articlesJSX} 58 |
59 | ); 60 | } 61 | } 62 | 63 | export default connect(mapStateToProps, mapDispatchToProps)(PublishingApp); -------------------------------------------------------------------------------- /Chapter03/src/layouts/PublishingApp.js: -------------------------------------------------------------------------------- 1 | import falcorModel from '../falcorModel.js'; 2 | import React from 'react'; 3 | import { connect } from 'react-redux'; 4 | import { bindActionCreators } from 'redux'; 5 | import articleActions from '../actions/article.js'; 6 | 7 | const mapStateToProps = (state) => ({ 8 | ...state 9 | }); 10 | 11 | const mapDispatchToProps = (dispatch) => ({ 12 | articleActions: bindActionCreators(articleActions, dispatch) 13 | }) 14 | 15 | 16 | class PublishingApp extends React.Component { 17 | constructor(props) { 18 | super(props); 19 | } 20 | 21 | componentWillMount() { 22 | if(typeof window !== 'undefined') { 23 | this._fetch(); // we are server side rendering, no fetching 24 | } 25 | } 26 | 27 | async _fetch() { 28 | const articlesLength = await falcorModel. 29 | getValue('articles.length') 30 | .then((length) => length); 31 | 32 | const articles = await falcorModel. 33 | get(['articles', 34 | {from: 0, to: articlesLength-1}, 35 | ['id','articleTitle', 'articleContent']]) 36 | .then((articlesResponse) => articlesResponse.json.articles); 37 | 38 | this.props.articleActions.articlesList(articles); 39 | } 40 | 41 | // below here are next methods o the PublishingApp 42 | render () { 43 | let articlesJSX = []; 44 | for(let articleKey in this.props.article) { 45 | let articleDetails = this.props.article[articleKey]; 46 | let currentArticleJSX = ( 47 |
48 |

{articleDetails.articleTitle}

49 |

{articleDetails.articleContent}

50 |
); 51 | articlesJSX.push(currentArticleJSX); 52 | } 53 | 54 | return ( 55 |
56 |

Our publishing app

57 | {articlesJSX} 58 |
59 | ); 60 | } 61 | } 62 | 63 | export default connect(mapStateToProps, mapDispatchToProps)(PublishingApp); -------------------------------------------------------------------------------- /Chapter04/src/layouts/PublishingApp.js: -------------------------------------------------------------------------------- 1 | import falcorModel from '../falcorModel.js'; 2 | import React from 'react'; 3 | import { connect } from 'react-redux'; 4 | import { bindActionCreators } from 'redux'; 5 | import articleActions from '../actions/article.js'; 6 | import ArticleCard from '../components/ArticleCard'; 7 | 8 | const mapStateToProps = (state) => ({ 9 | ...state 10 | }); 11 | 12 | const mapDispatchToProps = (dispatch) => ({ 13 | articleActions: bindActionCreators(articleActions, dispatch) 14 | }) 15 | 16 | 17 | class PublishingApp extends React.Component { 18 | constructor(props) { 19 | super(props); 20 | } 21 | 22 | componentWillMount() { 23 | if(typeof window !== 'undefined') { 24 | this._fetch(); // we are server side rendering, no fetching 25 | } 26 | } 27 | 28 | async _fetch() { 29 | const articlesLength = await falcorModel. 30 | getValue('articles.length') 31 | .then((length) => length); 32 | 33 | const articles = await falcorModel. 34 | get(['articles', 35 | {from: 0, to: articlesLength-1}, 36 | ['_id','articleTitle', 'articleContent']]) 37 | .then((articlesResponse) => articlesResponse.json.articles); 38 | 39 | this.props.articleActions.articlesList(articles); 40 | } 41 | 42 | // below here are next methods o the PublishingApp 43 | render () { 44 | let articlesJSX = []; 45 | this.props.article.forEach((articleDetails, articleKey) => { 46 | let currentArticleJSX = ( 47 |
48 | 51 |
52 | ); 53 | 54 | articlesJSX.push(currentArticleJSX); 55 | }); 56 | 57 | return ( 58 |
59 | {articlesJSX} 60 |
61 | ); 62 | } 63 | } 64 | 65 | export default connect(mapStateToProps, mapDispatchToProps)(PublishingApp); -------------------------------------------------------------------------------- /Chapter05/src/layouts/PublishingApp.js: -------------------------------------------------------------------------------- 1 | import falcorModel from '../falcorModel.js'; 2 | import React from 'react'; 3 | import { connect } from 'react-redux'; 4 | import { bindActionCreators } from 'redux'; 5 | import articleActions from '../actions/article.js'; 6 | import ArticleCard from '../components/ArticleCard'; 7 | 8 | const mapStateToProps = (state) => ({ 9 | ...state 10 | }); 11 | 12 | const mapDispatchToProps = (dispatch) => ({ 13 | articleActions: bindActionCreators(articleActions, dispatch) 14 | }) 15 | 16 | 17 | class PublishingApp extends React.Component { 18 | constructor(props) { 19 | super(props); 20 | } 21 | 22 | componentWillMount() { 23 | if(typeof window !== 'undefined') { 24 | this._fetch(); // we are server side rendering, no fetching 25 | } 26 | } 27 | 28 | async _fetch() { 29 | const articlesLength = await falcorModel. 30 | getValue('articles.length') 31 | .then((length) => length); 32 | 33 | const articles = await falcorModel. 34 | get(['articles', 35 | {from: 0, to: articlesLength-1}, 36 | ['_id','articleTitle', 'articleContent', 'articleContentJSON']]) 37 | .then((articlesResponse) => articlesResponse.json.articles); 38 | 39 | this.props.articleActions.articlesList(articles); 40 | } 41 | 42 | // below here are next methods o the PublishingApp 43 | render () { 44 | let articlesJSX = []; 45 | this.props.article.forEach((articleDetails, articleKey) => { 46 | let currentArticleJSX = ( 47 |
48 | 51 |
52 | ); 53 | 54 | articlesJSX.push(currentArticleJSX); 55 | }); 56 | 57 | return ( 58 |
59 | {articlesJSX} 60 |
61 | ); 62 | } 63 | } 64 | 65 | export default connect(mapStateToProps, mapDispatchToProps)(PublishingApp); -------------------------------------------------------------------------------- /Chapter02/src/views/LoginView.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Falcor from 'falcor'; 3 | import falcorModel from '../falcorModel.js'; 4 | import {connect} from 'react-redux'; 5 | import {bindActionCreators} from 'redux'; 6 | import {LoginForm} from '../components/LoginForm.js'; 7 | import {Snackbar} from 'material-ui'; 8 | 9 | 10 | const mapStateToProps = (state) => ({ 11 | ...state 12 | }); 13 | 14 | // You can add your reducers here 15 | const mapDispatchToProps = (dispatch) => ({}); 16 | 17 | class LoginView extends React.Component { 18 | constructor(props) { 19 | super(props); 20 | this.login = this.login.bind(this); 21 | this.state = { 22 | error: null 23 | }; 24 | } 25 | 26 | async login(credentials) { 27 | await falcorModel 28 | .call(['login'],[credentials]) 29 | .then((result) => result); 30 | 31 | const tokenRes = await falcorModel.getValue('login.token'); 32 | 33 | if (tokenRes === 'INVALID') { 34 | const errorRes = await falcorModel.getValue('login.error'); 35 | 36 | this.setState({error: errorRes}); 37 | return; 38 | } 39 | 40 | if (tokenRes) { 41 | const username = await falcorModel.getValue('login.username'); 42 | const role = await falcorModel.getValue('login.role'); 43 | 44 | localStorage.setItem('token', tokenRes); 45 | localStorage.setItem('username', username); 46 | localStorage.setItem('role', role); 47 | 48 | this.props.history.pushState(null, '/dashboard'); 49 | } 50 | } 51 | 52 | render () { 53 | return ( 54 |
55 |

Login view

56 |
57 | 59 |
60 | null} /> 65 |
66 | ); 67 | } 68 | } 69 | 70 | export default connect(mapStateToProps, mapDispatchToProps)(LoginView); -------------------------------------------------------------------------------- /Chapter04/src/views/DashboardView.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import React from 'react'; 4 | import Falcor from 'falcor'; 5 | import falcorModel from '../falcorModel.js'; 6 | import { connect } from 'react-redux'; 7 | import { bindActionCreators } from 'redux'; 8 | import { LoginForm } from '../components/LoginForm.js'; 9 | import { Link } from 'react-router'; 10 | import List from 'material-ui/lib/lists/list'; 11 | import ListItem from 'material-ui/lib/lists/list-item'; 12 | import Avatar from 'material-ui/lib/avatar'; 13 | import ActionInfo from 'material-ui/lib/svg-icons/action/info'; 14 | import FileFolder from 'material-ui/lib/svg-icons/file/folder'; 15 | import RaisedButton from 'material-ui/lib/raised-button'; 16 | import Divider from 'material-ui/lib/divider'; 17 | 18 | const mapStateToProps = (state) => ({ 19 | ...state 20 | }); 21 | 22 | const mapDispatchToProps = (dispatch) => ({ 23 | }); 24 | 25 | class DashboardView extends React.Component { 26 | constructor(props) { 27 | super(props); 28 | } 29 | 30 | render () { 31 | let articlesJSX = []; 32 | this.props.article.forEach((articleDetails, articleKey) => { 33 | let currentArticleJSX = ( 34 | 35 | } 37 | primaryText={articleDetails.articleTitle} 38 | secondaryText={articleDetails.articleContent} 39 | /> 40 | 41 | ); 42 | 43 | articlesJSX.push(currentArticleJSX); 44 | }); 45 | 46 | return ( 47 |
48 | 49 | 53 | 54 | 55 | {articlesJSX} 56 | 57 |
58 | ); 59 | } 60 | } 61 | 62 | export default connect(mapStateToProps, mapDispatchToProps)(DashboardView); -------------------------------------------------------------------------------- /Chapter05/src/views/DashboardView.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import React from 'react'; 4 | import Falcor from 'falcor'; 5 | import falcorModel from '../falcorModel.js'; 6 | import { connect } from 'react-redux'; 7 | import { bindActionCreators } from 'redux'; 8 | import { LoginForm } from '../components/LoginForm.js'; 9 | import { Link } from 'react-router'; 10 | import List from 'material-ui/lib/lists/list'; 11 | import ListItem from 'material-ui/lib/lists/list-item'; 12 | import Avatar from 'material-ui/lib/avatar'; 13 | import ActionInfo from 'material-ui/lib/svg-icons/action/info'; 14 | import FileFolder from 'material-ui/lib/svg-icons/file/folder'; 15 | import RaisedButton from 'material-ui/lib/raised-button'; 16 | import Divider from 'material-ui/lib/divider'; 17 | 18 | const mapStateToProps = (state) => ({ 19 | ...state 20 | }); 21 | 22 | const mapDispatchToProps = (dispatch) => ({ 23 | }); 24 | 25 | class DashboardView extends React.Component { 26 | constructor(props) { 27 | super(props); 28 | } 29 | 30 | render () { 31 | let articlesJSX = []; 32 | this.props.article.forEach((articleDetails, articleKey) => { 33 | let currentArticleJSX = ( 34 | 35 | } 37 | primaryText={articleDetails.articleTitle} 38 | secondaryText={articleDetails.articleContent} 39 | /> 40 | 41 | ); 42 | 43 | articlesJSX.push(currentArticleJSX); 44 | }); 45 | 46 | return ( 47 |
48 | 49 | 53 | 54 | 55 | {articlesJSX} 56 | 57 |
58 | ); 59 | } 60 | } 61 | 62 | export default connect(mapStateToProps, mapDispatchToProps)(DashboardView); -------------------------------------------------------------------------------- /Chapter04/src/components/articles/wysiwyg/WYSIWYGbuttons.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | class StyleButton extends React.Component { 4 | constructor() { 5 | super(); 6 | this.onToggle = (e) => { 7 | e.preventDefault(); 8 | this.props.onToggle(this.props.style); 9 | }; 10 | } 11 | 12 | render() { 13 | let className = 'RichEditor-styleButton'; 14 | if (this.props.active) { 15 | className += ' RichEditor-activeButton'; 16 | } 17 | return ( 18 | 19 | {this.props.label} 20 | 21 | ); 22 | } 23 | } 24 | 25 | const BLOCK_TYPES = [ 26 | {label: 'H1', style: 'header-one'}, 27 | {label: 'H2', style: 'header-two'}, 28 | {label: 'Blockquote', style: 'blockquote'}, 29 | {label: 'UL', style: 'unordered-list-item'}, 30 | {label: 'OL', style: 'ordered-list-item'} 31 | ]; 32 | 33 | 34 | export const BlockStyleControls = (props) => { 35 | const {editorState} = props; 36 | const selection = editorState.getSelection(); 37 | const blockType = editorState 38 | .getCurrentContent() 39 | .getBlockForKey(selection.getStartKey()) 40 | .getType(); 41 | return ( 42 |
43 | {BLOCK_TYPES.map((type) => 44 | 51 | )} 52 |
53 | ); 54 | }; 55 | 56 | var INLINE_STYLES = [ 57 | {label: 'Bold', style: 'BOLD'}, 58 | {label: 'Italic', style: 'ITALIC'}, 59 | {label: 'Underline', style: 'UNDERLINE'} 60 | ]; 61 | 62 | export const InlineStyleControls = (props) => { 63 | var currentStyle = props.editorState.getCurrentInlineStyle(); 64 | return ( 65 |
66 | {INLINE_STYLES.map(type => 67 | 74 | )} 75 |
76 | ); 77 | }; 78 | 79 | 80 | -------------------------------------------------------------------------------- /Chapter05/src/components/articles/wysiwyg/WYSIWYGbuttons.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | class StyleButton extends React.Component { 4 | constructor() { 5 | super(); 6 | this.onToggle = (e) => { 7 | e.preventDefault(); 8 | this.props.onToggle(this.props.style); 9 | }; 10 | } 11 | 12 | render() { 13 | let className = 'RichEditor-styleButton'; 14 | if (this.props.active) { 15 | className += ' RichEditor-activeButton'; 16 | } 17 | return ( 18 | 19 | {this.props.label} 20 | 21 | ); 22 | } 23 | } 24 | 25 | const BLOCK_TYPES = [ 26 | {label: 'H1', style: 'header-one'}, 27 | {label: 'H2', style: 'header-two'}, 28 | {label: 'Blockquote', style: 'blockquote'}, 29 | {label: 'UL', style: 'unordered-list-item'}, 30 | {label: 'OL', style: 'ordered-list-item'} 31 | ]; 32 | 33 | 34 | export const BlockStyleControls = (props) => { 35 | const {editorState} = props; 36 | const selection = editorState.getSelection(); 37 | const blockType = editorState 38 | .getCurrentContent() 39 | .getBlockForKey(selection.getStartKey()) 40 | .getType(); 41 | return ( 42 |
43 | {BLOCK_TYPES.map((type) => 44 | 51 | )} 52 |
53 | ); 54 | }; 55 | 56 | var INLINE_STYLES = [ 57 | {label: 'Bold', style: 'BOLD'}, 58 | {label: 'Italic', style: 'ITALIC'}, 59 | {label: 'Underline', style: 'UNDERLINE'} 60 | ]; 61 | 62 | export const InlineStyleControls = (props) => { 63 | var currentStyle = props.editorState.getCurrentInlineStyle(); 64 | return ( 65 |
66 | {INLINE_STYLES.map(type => 67 | 74 | )} 75 |
76 | ); 77 | }; 78 | 79 | 80 | -------------------------------------------------------------------------------- /Chapter06/src/components/articles/wysiwyg/WYSIWYGbuttons.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | class StyleButton extends React.Component { 4 | constructor() { 5 | super(); 6 | this.onToggle = (e) => { 7 | e.preventDefault(); 8 | this.props.onToggle(this.props.style); 9 | }; 10 | } 11 | 12 | render() { 13 | let className = 'RichEditor-styleButton'; 14 | if (this.props.active) { 15 | className += ' RichEditor-activeButton'; 16 | } 17 | return ( 18 | 19 | {this.props.label} 20 | 21 | ); 22 | } 23 | } 24 | 25 | const BLOCK_TYPES = [ 26 | {label: 'H1', style: 'header-one'}, 27 | {label: 'H2', style: 'header-two'}, 28 | {label: 'Blockquote', style: 'blockquote'}, 29 | {label: 'UL', style: 'unordered-list-item'}, 30 | {label: 'OL', style: 'ordered-list-item'} 31 | ]; 32 | 33 | 34 | export const BlockStyleControls = (props) => { 35 | const {editorState} = props; 36 | const selection = editorState.getSelection(); 37 | const blockType = editorState 38 | .getCurrentContent() 39 | .getBlockForKey(selection.getStartKey()) 40 | .getType(); 41 | return ( 42 |
43 | {BLOCK_TYPES.map((type) => 44 | 51 | )} 52 |
53 | ); 54 | }; 55 | 56 | var INLINE_STYLES = [ 57 | {label: 'Bold', style: 'BOLD'}, 58 | {label: 'Italic', style: 'ITALIC'}, 59 | {label: 'Underline', style: 'UNDERLINE'} 60 | ]; 61 | 62 | export const InlineStyleControls = (props) => { 63 | var currentStyle = props.editorState.getCurrentInlineStyle(); 64 | return ( 65 |
66 | {INLINE_STYLES.map(type => 67 | 74 | )} 75 |
76 | ); 77 | }; 78 | 79 | 80 | -------------------------------------------------------------------------------- /Chapter06/src/views/DashboardView.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import React from 'react'; 4 | import Falcor from 'falcor'; 5 | import falcorModel from '../falcorModel.js'; 6 | import { connect } from 'react-redux'; 7 | import { bindActionCreators } from 'redux'; 8 | import { LoginForm } from '../components/LoginForm.js'; 9 | import { Link } from 'react-router'; 10 | import List from 'material-ui/lib/lists/list'; 11 | import ListItem from 'material-ui/lib/lists/list-item'; 12 | import Avatar from 'material-ui/lib/avatar'; 13 | import ActionInfo from 'material-ui/lib/svg-icons/action/info'; 14 | import FileFolder from 'material-ui/lib/svg-icons/file/folder'; 15 | import RaisedButton from 'material-ui/lib/raised-button'; 16 | import Divider from 'material-ui/lib/divider'; 17 | 18 | const mapStateToProps = (state) => ({ 19 | ...state 20 | }); 21 | 22 | const mapDispatchToProps = (dispatch) => ({ 23 | }); 24 | 25 | class DashboardView extends React.Component { 26 | constructor(props) { 27 | super(props); 28 | } 29 | 30 | render () { 31 | let articlesJSX = []; 32 | this.props.article.forEach((articleDetails, articleKey) => { 33 | let articlePicUrl = articleDetails.articlePicUrl || '/static/placeholder.png'; 34 | let articleContentPlanText = articleDetails.articleContent.replace(/<\/?[^>]+(>|$)/g, ""); 35 | let currentArticleJSX = ( 36 | 37 | } 39 | primaryText={articleDetails.articleTitle} 40 | secondaryText={articleContentPlanText}/> 41 | 42 | ); 43 | 44 | articlesJSX.push(currentArticleJSX); 45 | }); 46 | 47 | return ( 48 |
49 | 50 | 54 | 55 | 56 | {articlesJSX} 57 | 58 |
59 | ); 60 | } 61 | } 62 | 63 | export default connect(mapStateToProps, mapDispatchToProps)(DashboardView); -------------------------------------------------------------------------------- /Chapter06/src/layouts/PublishingApp.js: -------------------------------------------------------------------------------- 1 | import falcorModel from '../falcorModel.js'; 2 | import React from 'react'; 3 | import { connect } from 'react-redux'; 4 | import { bindActionCreators } from 'redux'; 5 | import articleActions from '../actions/article.js'; 6 | import ArticleCard from '../components/ArticleCard'; 7 | 8 | const mapStateToProps = (state) => ({ 9 | ...state 10 | }); 11 | 12 | const mapDispatchToProps = (dispatch) => ({ 13 | articleActions: bindActionCreators(articleActions, dispatch) 14 | }) 15 | 16 | 17 | class PublishingApp extends React.Component { 18 | constructor(props) { 19 | super(props); 20 | } 21 | 22 | componentWillMount() { 23 | if(typeof window !== 'undefined') { 24 | this._fetch(); // we are server side rendering, no fetching 25 | } 26 | } 27 | 28 | async _fetch() { 29 | let articlesLength = await falcorModel 30 | .getValue("articles.length") 31 | .then(function(length) { 32 | return length; 33 | }); 34 | let articles = await falcorModel 35 | .get(['articles', {from: 0, to: articlesLength-1}, ['_id', 'articleTitle', 36 | 'articleSubTitle','articleContent', 'articleContentJSON', 'articlePicUrl']]). 37 | then((articlesResponse) => { 38 | return articlesResponse.json.articles; 39 | }).catch(e => {console.debug(e); 40 | return 500; 41 | }); 42 | 43 | if(articles === 500) { 44 | return; 45 | } 46 | 47 | this.props.articleActions.articlesList(articles); 48 | } 49 | 50 | // below here are next methods o the PublishingApp 51 | render () { 52 | let articlesJSX = []; 53 | this.props.article.forEach((articleDetails, articleKey) => { 54 | let currentArticleJSX = ( 55 |
56 | 61 |
62 | ); 63 | 64 | articlesJSX.push(currentArticleJSX); 65 | }); 66 | 67 | return ( 68 |
69 | {articlesJSX} 70 |
71 | ); 72 | } 73 | } 74 | 75 | export default connect(mapStateToProps, mapDispatchToProps)(PublishingApp); -------------------------------------------------------------------------------- /Chapter04/src/views/LoginView.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | 4 | import React from 'react'; 5 | import Falcor from 'falcor'; 6 | import falcorModel from '../falcorModel.js'; 7 | import { connect } from 'react-redux'; 8 | import { bindActionCreators } from 'redux'; 9 | import { LoginForm } from '../components/LoginForm.js'; 10 | import { Snackbar } from 'material-ui'; 11 | 12 | 13 | const mapStateToProps = (state) => ({ 14 | ...state 15 | }); 16 | 17 | const mapDispatchToProps = (dispatch) => ({ 18 | }); 19 | 20 | class LoginView extends React.Component { 21 | constructor(props) { 22 | super(props); 23 | this.state = { 24 | error: null 25 | }; 26 | this.login = this.login.bind(this); 27 | } 28 | 29 | async login(credentials) { 30 | console.info("credentials", credentials); 31 | 32 | let loginResult = await falcorModel 33 | .call( 34 | ['login'], 35 | [credentials] 36 | ). 37 | then((result) => { 38 | return loginResult; 39 | }); 40 | 41 | let tokenRes = await falcorModel.getValue('login.token'); 42 | console.info("tokenRes", tokenRes); 43 | 44 | if(tokenRes === "INVALID") { 45 | // login failed, get error msg 46 | let errorRes = await falcorModel.getValue('login.error'); 47 | this.setState({error: errorRes}); 48 | return; 49 | } 50 | 51 | if(tokenRes) { 52 | let username = await falcorModel.getValue('login.username'); 53 | let role = await falcorModel.getValue('login.role'); 54 | 55 | localStorage.setItem("token", tokenRes); 56 | localStorage.setItem("username", username); 57 | localStorage.setItem("role", role); 58 | 59 | this.props.history.pushState(null, '/dashboard'); 60 | return; 61 | } else { 62 | alert("Fatal login error, please contact an admin"); 63 | } 64 | 65 | return; 66 | } 67 | 68 | render () { 69 | return ( 70 |
71 |
72 | 74 |
75 | console.info('you can add custom action here')} /> 80 |
81 | ); 82 | } 83 | } 84 | 85 | export default connect(mapStateToProps, mapDispatchToProps)(LoginView); -------------------------------------------------------------------------------- /Chapter05/src/views/LoginView.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | 4 | import React from 'react'; 5 | import Falcor from 'falcor'; 6 | import falcorModel from '../falcorModel.js'; 7 | import { connect } from 'react-redux'; 8 | import { bindActionCreators } from 'redux'; 9 | import { LoginForm } from '../components/LoginForm.js'; 10 | import { Snackbar } from 'material-ui'; 11 | 12 | 13 | const mapStateToProps = (state) => ({ 14 | ...state 15 | }); 16 | 17 | const mapDispatchToProps = (dispatch) => ({ 18 | }); 19 | 20 | class LoginView extends React.Component { 21 | constructor(props) { 22 | super(props); 23 | this.state = { 24 | error: null 25 | }; 26 | this.login = this.login.bind(this); 27 | } 28 | 29 | async login(credentials) { 30 | console.info("credentials", credentials); 31 | 32 | let loginResult = await falcorModel 33 | .call( 34 | ['login'], 35 | [credentials] 36 | ). 37 | then((result) => { 38 | return loginResult; 39 | }); 40 | 41 | let tokenRes = await falcorModel.getValue('login.token'); 42 | console.info("tokenRes", tokenRes); 43 | 44 | if(tokenRes === "INVALID") { 45 | // login failed, get error msg 46 | let errorRes = await falcorModel.getValue('login.error'); 47 | this.setState({error: errorRes}); 48 | return; 49 | } 50 | 51 | if(tokenRes) { 52 | let username = await falcorModel.getValue('login.username'); 53 | let role = await falcorModel.getValue('login.role'); 54 | 55 | localStorage.setItem("token", tokenRes); 56 | localStorage.setItem("username", username); 57 | localStorage.setItem("role", role); 58 | 59 | this.props.history.pushState(null, '/dashboard'); 60 | return; 61 | } else { 62 | alert("Fatal login error, please contact an admin"); 63 | } 64 | 65 | return; 66 | } 67 | 68 | render () { 69 | return ( 70 |
71 |
72 | 74 |
75 | console.info('you can add custom action here')} /> 80 |
81 | ); 82 | } 83 | } 84 | 85 | export default connect(mapStateToProps, mapDispatchToProps)(LoginView); -------------------------------------------------------------------------------- /Chapter06/src/views/LoginView.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | 4 | import React from 'react'; 5 | import Falcor from 'falcor'; 6 | import falcorModel from '../falcorModel.js'; 7 | import { connect } from 'react-redux'; 8 | import { bindActionCreators } from 'redux'; 9 | import { LoginForm } from '../components/LoginForm.js'; 10 | import { Snackbar } from 'material-ui'; 11 | 12 | 13 | const mapStateToProps = (state) => ({ 14 | ...state 15 | }); 16 | 17 | const mapDispatchToProps = (dispatch) => ({ 18 | }); 19 | 20 | class LoginView extends React.Component { 21 | constructor(props) { 22 | super(props); 23 | this.state = { 24 | error: null 25 | }; 26 | this.login = this.login.bind(this); 27 | } 28 | 29 | async login(credentials) { 30 | console.info("credentials", credentials); 31 | 32 | let loginResult = await falcorModel 33 | .call( 34 | ['login'], 35 | [credentials] 36 | ). 37 | then((result) => { 38 | return loginResult; 39 | }); 40 | 41 | let tokenRes = await falcorModel.getValue('login.token'); 42 | console.info("tokenRes", tokenRes); 43 | 44 | if(tokenRes === "INVALID") { 45 | // login failed, get error msg 46 | let errorRes = await falcorModel.getValue('login.error'); 47 | this.setState({error: errorRes}); 48 | return; 49 | } 50 | 51 | if(tokenRes) { 52 | let username = await falcorModel.getValue('login.username'); 53 | let role = await falcorModel.getValue('login.role'); 54 | 55 | localStorage.setItem("token", tokenRes); 56 | localStorage.setItem("username", username); 57 | localStorage.setItem("role", role); 58 | 59 | this.props.history.pushState(null, '/dashboard'); 60 | return; 61 | } else { 62 | alert("Fatal login error, please contact an admin"); 63 | } 64 | 65 | return; 66 | } 67 | 68 | render () { 69 | return ( 70 |
71 |
72 | 74 |
75 | console.info('you can add custom action here')} /> 80 |
81 | ); 82 | } 83 | } 84 | 85 | export default connect(mapStateToProps, mapDispatchToProps)(LoginView); -------------------------------------------------------------------------------- /Chapter03/src/views/LoginView.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | 4 | import React from 'react'; 5 | import Falcor from 'falcor'; 6 | import falcorModel from '../falcorModel.js'; 7 | import { connect } from 'react-redux'; 8 | import { bindActionCreators } from 'redux'; 9 | import { LoginForm } from '../components/LoginForm.js'; 10 | import { Snackbar } from 'material-ui'; 11 | 12 | 13 | const mapStateToProps = (state) => ({ 14 | ...state 15 | }); 16 | 17 | const mapDispatchToProps = (dispatch) => ({ 18 | }); 19 | 20 | class LoginView extends React.Component { 21 | constructor(props) { 22 | super(props); 23 | this.state = { 24 | error: null 25 | }; 26 | this.login = this.login.bind(this); 27 | } 28 | 29 | async login(credentials) { 30 | console.info("credentials", credentials); 31 | 32 | let loginResult = await falcorModel 33 | .call( 34 | ['login'], 35 | [credentials] 36 | ). 37 | then((result) => { 38 | return loginResult; 39 | }); 40 | 41 | let tokenRes = await falcorModel.getValue('login.token'); 42 | console.info("tokenRes", tokenRes); 43 | 44 | if(tokenRes === "INVALID") { 45 | // login failed, get error msg 46 | let errorRes = await falcorModel.getValue('login.error'); 47 | this.setState({error: errorRes}); 48 | return; 49 | } 50 | 51 | if(tokenRes) { 52 | let username = await falcorModel.getValue('login.username'); 53 | let role = await falcorModel.getValue('login.role'); 54 | 55 | localStorage.setItem("token", tokenRes); 56 | localStorage.setItem("username", username); 57 | localStorage.setItem("role", role); 58 | 59 | this.props.history.pushState(null, '/dashboard'); 60 | return; 61 | } else { 62 | alert("Fatal login error, please contact an admin"); 63 | } 64 | 65 | return; 66 | } 67 | 68 | render () { 69 | return ( 70 |
71 |

Login view

72 |
73 | 75 |
76 | console.info('you can add custom action here')} /> 81 |
82 | ); 83 | } 84 | } 85 | 86 | export default connect(mapStateToProps, mapDispatchToProps)(LoginView); -------------------------------------------------------------------------------- /Chapter06/src/components/articles/ImgUploader.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import React from 'react'; 4 | import ReactS3Uploader from 'react-s3-uploader'; 5 | import { Paper } from 'material-ui'; 6 | 7 | class ImgUploader extends React.Component { 8 | 9 | constructor(props) { 10 | super(props); 11 | this.uploadFinished = this.uploadFinished.bind(this); 12 | this.state = { 13 | uploadDetails: null, 14 | uploadProgress: null, 15 | uploadError: null, 16 | articlePicUrl: props.articlePicUrl 17 | }; 18 | } 19 | 20 | uploadFinished(uploadDetails) { 21 | let articlePicUrl = '/s3/img/'+uploadDetails.filename; 22 | this.setState({ 23 | uploadProgress: null, 24 | uploadDetails: uploadDetails, 25 | articlePicUrl: articlePicUrl 26 | }); 27 | this.props.updateImgUrl(articlePicUrl); 28 | } 29 | 30 | render () { 31 | let imgUploadProgressJSX; 32 | let uploadProgress = this.state.uploadProgress; 33 | if(uploadProgress) { 34 | imgUploadProgressJSX = ( 35 |
36 | {uploadProgress.uploadStatusText} 37 | ({uploadProgress.progressInPercent}%) 38 |
39 | ); 40 | } else if(this.state.articlePicUrl) { 41 | let articlePicStyles = { 42 | maxWidth: 200, 43 | maxHeight: 200, 44 | margin: 'auto' 45 | }; 46 | imgUploadProgressJSX = ; 48 | } 49 | 50 | let uploaderJSX = ( 51 | { 55 | this.setState({ 56 | uploadProgress: { progressInPercent, uploadStatusText }, 57 | uploadError: null 58 | }); 59 | }} 60 | onError={(errorDetails) => { 61 | this.setState({ 62 | uploadProgress: null, 63 | uploadError: errorDetails 64 | }); 65 | }} 66 | onFinish={(uploadDetails) => { 67 | this.uploadFinished(uploadDetails); 68 | }} /> 69 | ); 70 | return ( 71 | 73 | {imgUploadProgressJSX} 74 | {uploaderJSX} 75 | 76 | ); 77 | } 78 | } 79 | 80 | 81 | ImgUploader.propTypes = { 82 | updateImgUrl: React.PropTypes.func.isRequired 83 | }; 84 | 85 | export default ImgUploader; 86 | 87 | -------------------------------------------------------------------------------- /Chapter04/src/layouts/CoreLayout.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | 4 | import React from 'react'; 5 | import { Link } from 'react-router'; 6 | 7 | import themeDecorator from 'material-ui/lib/styles/theme-decorator'; 8 | import getMuiTheme from 'material-ui/lib/styles/getMuiTheme'; 9 | 10 | import RaisedButton from 'material-ui/lib/raised-button'; 11 | import AppBar from 'material-ui/lib/app-bar'; 12 | import ActionHome from 'material-ui/lib/svg-icons/action/home'; 13 | 14 | import { connect } from 'react-redux'; 15 | import { bindActionCreators } from 'redux'; 16 | import articleActions from '../actions/article.js'; 17 | 18 | 19 | const mapStateToProps = (state) => ({ 20 | ...state 21 | }); 22 | 23 | const mapDispatchToProps = (dispatch) => ({ 24 | articleActions: bindActionCreators(articleActions, dispatch) 25 | }); 26 | 27 | 28 | class CoreLayout extends React.Component { 29 | static propTypes = { 30 | children : React.PropTypes.element 31 | } 32 | constructor(props) { 33 | super(props); 34 | } 35 | 36 | componentWillMount() { 37 | if(typeof window !== 'undefined' && !this.props.article.get) { 38 | this.props.articleActions.articlesList(this.props.article); 39 | } 40 | } 41 | 42 | render () { 43 | const buttonStyle = { 44 | margin: 5 45 | }; 46 | 47 | const homeIconStyle = { 48 | margin: 5, 49 | paddingTop: 5 50 | }; 51 | 52 | let menuLinksJSX; 53 | let userIsLoggedIn = typeof localStorage !== 'undefined' && localStorage.token && 54 | this.props.routes[1].name !== 'logout'; 55 | 56 | if(userIsLoggedIn) { 57 | menuLinksJSX = ( 58 | 59 | 60 | ); 61 | }else{ 62 | menuLinksJSX = ( 63 | 64 | 65 | ); 66 | } 67 | let homePageButtonJSX = ( 68 | } style={homeIconStyle} /> 69 | ); 70 | return ( 71 |
72 | 76 |
77 | {this.props.children} 78 |
79 | ); 80 | } 81 | } 82 | 83 | const muiCoreLayout = themeDecorator(getMuiTheme(null, { userAgent: 'all' })) 84 | (CoreLayout); 85 | 86 | export default connect(mapStateToProps, mapDispatchToProps)(muiCoreLayout); -------------------------------------------------------------------------------- /Chapter04/src/views/articles/AddArticleView.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import React from 'react'; 3 | import { connect } from 'react-redux'; 4 | import WYSIWYGeditor from '../../components/articles/WYSIWYGeditor.js'; 5 | import { stateToHTML } from 'draft-js-export-html'; 6 | import { bindActionCreators } from 'redux'; 7 | import { Link } from 'react-router'; 8 | import articleActions from '../../actions/article.js'; 9 | import RaisedButton from 'material-ui/lib/raised-button'; 10 | 11 | const mapStateToProps = (state) => ({ 12 | ...state 13 | }); 14 | 15 | const mapDispatchToProps = (dispatch) => ({ 16 | articleActions: bindActionCreators(articleActions, dispatch) 17 | }); 18 | 19 | class AddArticleView extends React.Component { 20 | constructor(props) { 21 | super(props); 22 | this._onDraftJSChange = this._onDraftJSChange.bind(this); 23 | this._articleSubmit = this._articleSubmit.bind(this); 24 | 25 | this.state = { 26 | title: 'test', 27 | contentJSON: {}, 28 | htmlContent: '', 29 | newArticleID: null 30 | }; 31 | } 32 | 33 | _articleSubmit() { 34 | let newArticle = { 35 | articleTitle: this.state.title, 36 | articleContent: this.state.htmlContent, 37 | articleContentJSON: this.state.contentJSON 38 | } 39 | let newArticleID = "MOCKEDRandomid"+Math.floor(Math.random() * 10000); 40 | 41 | newArticle['_id'] = newArticleID; 42 | 43 | this.props.articleActions.pushNewArticle(newArticle); 44 | this.setState({ newArticleID: newArticleID}); 45 | } 46 | 47 | _onDraftJSChange(contentJSON, contentState) { 48 | let htmlContent = stateToHTML(contentState); 49 | this.setState({contentJSON, htmlContent}); 50 | } 51 | 52 | render () { 53 | if(this.state.newArticleID) { 54 | return ( 55 |
56 |

Your new article ID is {this.state.newArticleID}

57 | 58 | 63 | 64 |
65 | ); 66 | } 67 | return ( 68 |
69 |

Add Article

70 | 74 | 80 |
81 | ); 82 | } 83 | } 84 | 85 | export default connect(mapStateToProps, mapDispatchToProps)(AddArticleView); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mastering Full-Stack React Web Development 2 | 3 | This is the code repository for [Mastering Full Stack React Web Development](https://www.packtpub.com/web-development/mastering-full-stack-react-web-development?utm_source=github&utm_medium=repository&utm_campaign=9781786461766), published by [Packt](www.packtpub.com). It contains all the supporting project files necessary to work through the book from start to finish. 4 | 5 | ## About the Book 6 | Full-stack web development is being redefined by the impact of ReactJS. If MEAN demonstrated just how effective combining JavaScript frameworks and tools could be for the modern web developer, by replacing Angular with React, developers have an easier way to build isomorphic web applications where code can run on both the client and server. 7 | 8 | This book will get you up to speed with one of the latest strategies to meet the demands of today’s dynamic and data-intensive web. Combining detailed insights and guidance with practical and actionable information that will ensure you can build a complete isomorphic web app, it’s an essential resource for the forward-thinking developer. 9 | 10 | You’ll learn how to create a reliable and powerful back-end platform with Node.js and Express, as well as exploring how to use MongoDB as the primary database. You’ll see how its flexibility is a core part of any full-stack developer’s workflow, as well as learning how to use Mongoose alongside it to make data storage safer and more reliable. 11 | 12 | ## Instructions and Navigations 13 | All of the code is organized into folders. Each folder starts with a number followed by the application name. For example, Chapter02. 14 | 15 | 16 | 17 | The code will look like the following: 18 | ``` 19 | [ 20 | { 21 | articleId: '987654', 22 | articleTitle: 'Lorem ipsum - article one', 23 | articleContent: 'Here goes the content of the article' 24 | }, 25 | { 26 | articleId: '123456', 27 | articleTitle: 'Lorem ipsum - article two', 28 | articleContent: 'Sky is the limit, the content goes here.' 29 | } 30 | ] 31 | ``` 32 | 33 | ## Related Products 34 | * [Meteor: Full-Stack Web Application Development](https://www.packtpub.com/web-development/meteor-full-stack-web-application-development?utm_source=github&utm_medium=repository&utm_campaign=9781787287754) 35 | 36 | * [Learning Full Stack React [Video]](https://www.packtpub.com/web-development/learning-full-stack-react-video?utm_source=github&utm_medium=repository&utm_campaign=9781787121348) 37 | 38 | * [Mastering MEAN Web Development: Expert Full Stack JavaScript [Video]](https://www.packtpub.com/web-development/mastering-mean-web-development-expert-full-stack-javascript-video?utm_source=github&utm_medium=repository&utm_campaign=9781785882159) 39 | ### Suggestions and Feedback 40 | [Click here](https://docs.google.com/forms/d/e/1FAIpQLSe5qwunkGf6PUvzPirPDtuy1Du5Rlzew23UBp2S-P3wB-GcwQ/viewform) if you have any feedback or suggestions. 41 | -------------------------------------------------------------------------------- /Chapter05/src/views/articles/AddArticleView.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import React from 'react'; 3 | import { connect } from 'react-redux'; 4 | import WYSIWYGeditor from '../../components/articles/WYSIWYGeditor.js'; 5 | import { stateToHTML } from 'draft-js-export-html'; 6 | import { bindActionCreators } from 'redux'; 7 | import { Link } from 'react-router'; 8 | import articleActions from '../../actions/article.js'; 9 | import RaisedButton from 'material-ui/lib/raised-button'; 10 | import falcorModel from '../../falcorModel.js'; 11 | 12 | const mapStateToProps = (state) => ({ 13 | ...state 14 | }); 15 | 16 | const mapDispatchToProps = (dispatch) => ({ 17 | articleActions: bindActionCreators(articleActions, dispatch) 18 | }); 19 | 20 | class AddArticleView extends React.Component { 21 | constructor(props) { 22 | super(props); 23 | this._onDraftJSChange = this._onDraftJSChange.bind(this); 24 | this._articleSubmit = this._articleSubmit.bind(this); 25 | 26 | this.state = { 27 | title: 'test', 28 | contentJSON: {}, 29 | htmlContent: '', 30 | newArticleID: null 31 | }; 32 | } 33 | 34 | async _articleSubmit() { 35 | let newArticle = { 36 | articleTitle: this.state.title, 37 | articleContent: this.state.htmlContent, 38 | articleContentJSON: this.state.contentJSON 39 | } 40 | 41 | let newArticleID = await falcorModel 42 | .call( 43 | 'articles.add', 44 | [newArticle] 45 | ). 46 | then((result) => { 47 | return falcorModel.getValue( 48 | ['articles', 'newArticleID'] 49 | ).then((articleID) => { 50 | return articleID; 51 | }); 52 | }); 53 | 54 | newArticle['_id'] = newArticleID; 55 | this.props.articleActions.pushNewArticle(newArticle); 56 | this.setState({ newArticleID: newArticleID}); 57 | } 58 | 59 | _onDraftJSChange(contentJSON, contentState) { 60 | let htmlContent = stateToHTML(contentState); 61 | this.setState({contentJSON, htmlContent}); 62 | } 63 | 64 | render () { 65 | if(this.state.newArticleID) { 66 | return ( 67 |
68 |

Your new article ID is {this.state.newArticleID}

69 | 70 | 75 | 76 |
77 | ); 78 | } 79 | return ( 80 |
81 |

Add Article

82 | 86 | 92 |
93 | ); 94 | } 95 | } 96 | 97 | export default connect(mapStateToProps, mapDispatchToProps)(AddArticleView); -------------------------------------------------------------------------------- /Chapter03/server/routesSession.js: -------------------------------------------------------------------------------- 1 | import configMongoose from './configMongoose'; 2 | import jwt from 'jsonwebtoken'; 3 | import crypto from 'crypto'; 4 | import jwtSecret from './configSecret'; 5 | let User = configMongoose.User; 6 | 7 | export default [ 8 | { 9 | route: ['login'] , 10 | call: (callPath, args) => 11 | { 12 | let { username, password } = args[0]; 13 | let saltedPassword = password+"pubApp"; // pubApp is our salt string 14 | let saltedPassHash = crypto.createHash('sha256').update(saltedPassword).digest('hex'); 15 | let userStatementQuery = { 16 | $and: [ 17 | { 'username': username }, 18 | { 'password': saltedPassHash } 19 | ] 20 | } 21 | return User.find(userStatementQuery, function(err, user) { 22 | if (err) throw err; 23 | }).then((result) => { 24 | if(result.length) { 25 | let role = result[0].role; 26 | let userDetailsToHash = username+role; 27 | let token = jwt.sign(userDetailsToHash, jwtSecret.secret); 28 | return [ 29 | { 30 | path: ['login', 'token'], 31 | value: token 32 | }, 33 | { 34 | path: ['login', 'username'], 35 | value: username 36 | }, 37 | { 38 | path: ['login', 'role'], 39 | value: role 40 | }, 41 | {path: ['login', 'error'], 42 | value: false 43 | } 44 | ]; 45 | } else { 46 | // INVALID LOGIN 47 | return [ 48 | { 49 | path: ['login', 'token'], 50 | value: "INVALID" 51 | }, 52 | { 53 | path: ['login', 'error'], 54 | value: "NO USER FOUND, incorrect login information" 55 | } 56 | ]; 57 | } 58 | return result; 59 | }); 60 | } 61 | }, 62 | { 63 | route: ['register'], 64 | call: (callPath, args) => 65 | { 66 | let newUserObj = args[0]; 67 | newUserObj.password = newUserObj.password+"pubApp"; 68 | newUserObj.password = 69 | crypto.createHash('sha256').update(newUserObj.password).digest('hex'); 70 | let newUser = new User(newUserObj); 71 | return newUser.save((err, data) => { if (err) return err; }) 72 | .then ((newRes) => { 73 | /* 74 | got new obj data, now let's get count: 75 | */ 76 | let newUserDetail = newRes.toObject(); 77 | if(newUserDetail._id) { 78 | let newUserId = newUserDetail._id.toString(); 79 | return [ 80 | { 81 | path: ['register', 'newUserId'], 82 | value: newUserId 83 | }, 84 | { 85 | path: ['register', 'error'], 86 | value: false 87 | } 88 | ]; 89 | }else { 90 | // registration failed 91 | return [ 92 | { 93 | path: ['register', 'newUserId'], 94 | value: 'INVALID' 95 | }, 96 | { 97 | path: ['register', 'error'], 98 | value: 'Registration failed - no id has been created' 99 | } 100 | ]; 101 | } 102 | return; 103 | }).catch((reason) => console.error(reason)); 104 | } 105 | } 106 | 107 | ]; -------------------------------------------------------------------------------- /Chapter04/server/routesSession.js: -------------------------------------------------------------------------------- 1 | import configMongoose from './configMongoose'; 2 | import jwt from 'jsonwebtoken'; 3 | import crypto from 'crypto'; 4 | import jwtSecret from './configSecret'; 5 | let User = configMongoose.User; 6 | 7 | export default [ 8 | { 9 | route: ['login'] , 10 | call: (callPath, args) => 11 | { 12 | let { username, password } = args[0]; 13 | let saltedPassword = password+"pubApp"; // pubApp is our salt string 14 | let saltedPassHash = crypto.createHash('sha256').update(saltedPassword).digest('hex'); 15 | let userStatementQuery = { 16 | $and: [ 17 | { 'username': username }, 18 | { 'password': saltedPassHash } 19 | ] 20 | } 21 | return User.find(userStatementQuery, function(err, user) { 22 | if (err) throw err; 23 | }).then((result) => { 24 | if(result.length) { 25 | let role = result[0].role; 26 | let userDetailsToHash = username+role; 27 | let token = jwt.sign(userDetailsToHash, jwtSecret.secret); 28 | return [ 29 | { 30 | path: ['login', 'token'], 31 | value: token 32 | }, 33 | { 34 | path: ['login', 'username'], 35 | value: username 36 | }, 37 | { 38 | path: ['login', 'role'], 39 | value: role 40 | }, 41 | {path: ['login', 'error'], 42 | value: false 43 | } 44 | ]; 45 | } else { 46 | // INVALID LOGIN 47 | return [ 48 | { 49 | path: ['login', 'token'], 50 | value: "INVALID" 51 | }, 52 | { 53 | path: ['login', 'error'], 54 | value: "NO USER FOUND, incorrect login information" 55 | } 56 | ]; 57 | } 58 | return result; 59 | }); 60 | } 61 | }, 62 | { 63 | route: ['register'], 64 | call: (callPath, args) => 65 | { 66 | let newUserObj = args[0]; 67 | newUserObj.password = newUserObj.password+"pubApp"; 68 | newUserObj.password = 69 | crypto.createHash('sha256').update(newUserObj.password).digest('hex'); 70 | let newUser = new User(newUserObj); 71 | return newUser.save((err, data) => { if (err) return err; }) 72 | .then ((newRes) => { 73 | /* 74 | got new obj data, now let's get count: 75 | */ 76 | let newUserDetail = newRes.toObject(); 77 | if(newUserDetail._id) { 78 | let newUserId = newUserDetail._id.toString(); 79 | return [ 80 | { 81 | path: ['register', 'newUserId'], 82 | value: newUserId 83 | }, 84 | { 85 | path: ['register', 'error'], 86 | value: false 87 | } 88 | ]; 89 | }else { 90 | // registration failed 91 | return [ 92 | { 93 | path: ['register', 'newUserId'], 94 | value: 'INVALID' 95 | }, 96 | { 97 | path: ['register', 'error'], 98 | value: 'Registration failed - no id has been created' 99 | } 100 | ]; 101 | } 102 | return; 103 | }).catch((reason) => console.error(reason)); 104 | } 105 | } 106 | 107 | ]; -------------------------------------------------------------------------------- /Chapter05/server/routesSession.js: -------------------------------------------------------------------------------- 1 | import configMongoose from './configMongoose'; 2 | import jwt from 'jsonwebtoken'; 3 | import crypto from 'crypto'; 4 | import jwtSecret from './configSecret'; 5 | let User = configMongoose.User; 6 | 7 | export default [ 8 | { 9 | route: ['login'] , 10 | call: (callPath, args) => 11 | { 12 | let { username, password } = args[0]; 13 | let saltedPassword = password+"pubApp"; // pubApp is our salt string 14 | let saltedPassHash = crypto.createHash('sha256').update(saltedPassword).digest('hex'); 15 | let userStatementQuery = { 16 | $and: [ 17 | { 'username': username }, 18 | { 'password': saltedPassHash } 19 | ] 20 | } 21 | return User.find(userStatementQuery, function(err, user) { 22 | if (err) throw err; 23 | }).then((result) => { 24 | if(result.length) { 25 | let role = result[0].role; 26 | let userDetailsToHash = username+role; 27 | let token = jwt.sign(userDetailsToHash, jwtSecret.secret); 28 | return [ 29 | { 30 | path: ['login', 'token'], 31 | value: token 32 | }, 33 | { 34 | path: ['login', 'username'], 35 | value: username 36 | }, 37 | { 38 | path: ['login', 'role'], 39 | value: role 40 | }, 41 | {path: ['login', 'error'], 42 | value: false 43 | } 44 | ]; 45 | } else { 46 | // INVALID LOGIN 47 | return [ 48 | { 49 | path: ['login', 'token'], 50 | value: "INVALID" 51 | }, 52 | { 53 | path: ['login', 'error'], 54 | value: "NO USER FOUND, incorrect login information" 55 | } 56 | ]; 57 | } 58 | return result; 59 | }); 60 | } 61 | }, 62 | { 63 | route: ['register'], 64 | call: (callPath, args) => 65 | { 66 | let newUserObj = args[0]; 67 | newUserObj.password = newUserObj.password+"pubApp"; 68 | newUserObj.password = 69 | crypto.createHash('sha256').update(newUserObj.password).digest('hex'); 70 | let newUser = new User(newUserObj); 71 | return newUser.save((err, data) => { if (err) return err; }) 72 | .then ((newRes) => { 73 | /* 74 | got new obj data, now let's get count: 75 | */ 76 | let newUserDetail = newRes.toObject(); 77 | if(newUserDetail._id) { 78 | let newUserId = newUserDetail._id.toString(); 79 | return [ 80 | { 81 | path: ['register', 'newUserId'], 82 | value: newUserId 83 | }, 84 | { 85 | path: ['register', 'error'], 86 | value: false 87 | } 88 | ]; 89 | }else { 90 | // registration failed 91 | return [ 92 | { 93 | path: ['register', 'newUserId'], 94 | value: 'INVALID' 95 | }, 96 | { 97 | path: ['register', 'error'], 98 | value: 'Registration failed - no id has been created' 99 | } 100 | ]; 101 | } 102 | return; 103 | }).catch((reason) => console.error(reason)); 104 | } 105 | } 106 | 107 | ]; -------------------------------------------------------------------------------- /Chapter06/server/routesSession.js: -------------------------------------------------------------------------------- 1 | import configMongoose from './configMongoose'; 2 | import jwt from 'jsonwebtoken'; 3 | import crypto from 'crypto'; 4 | import jwtSecret from './configSecret'; 5 | let User = configMongoose.User; 6 | 7 | export default [ 8 | { 9 | route: ['login'] , 10 | call: (callPath, args) => 11 | { 12 | let { username, password } = args[0]; 13 | let saltedPassword = password+"pubApp"; // pubApp is our salt string 14 | let saltedPassHash = crypto.createHash('sha256').update(saltedPassword).digest('hex'); 15 | let userStatementQuery = { 16 | $and: [ 17 | { 'username': username }, 18 | { 'password': saltedPassHash } 19 | ] 20 | } 21 | return User.find(userStatementQuery, function(err, user) { 22 | if (err) throw err; 23 | }).then((result) => { 24 | if(result.length) { 25 | let role = result[0].role; 26 | let userDetailsToHash = username+role; 27 | let token = jwt.sign(userDetailsToHash, jwtSecret.secret); 28 | return [ 29 | { 30 | path: ['login', 'token'], 31 | value: token 32 | }, 33 | { 34 | path: ['login', 'username'], 35 | value: username 36 | }, 37 | { 38 | path: ['login', 'role'], 39 | value: role 40 | }, 41 | {path: ['login', 'error'], 42 | value: false 43 | } 44 | ]; 45 | } else { 46 | // INVALID LOGIN 47 | return [ 48 | { 49 | path: ['login', 'token'], 50 | value: "INVALID" 51 | }, 52 | { 53 | path: ['login', 'error'], 54 | value: "NO USER FOUND, incorrect login information" 55 | } 56 | ]; 57 | } 58 | return result; 59 | }); 60 | } 61 | }, 62 | { 63 | route: ['register'], 64 | call: (callPath, args) => 65 | { 66 | let newUserObj = args[0]; 67 | newUserObj.password = newUserObj.password+"pubApp"; 68 | newUserObj.password = 69 | crypto.createHash('sha256').update(newUserObj.password).digest('hex'); 70 | let newUser = new User(newUserObj); 71 | return newUser.save((err, data) => { if (err) return err; }) 72 | .then ((newRes) => { 73 | /* 74 | got new obj data, now let's get count: 75 | */ 76 | let newUserDetail = newRes.toObject(); 77 | if(newUserDetail._id) { 78 | let newUserId = newUserDetail._id.toString(); 79 | return [ 80 | { 81 | path: ['register', 'newUserId'], 82 | value: newUserId 83 | }, 84 | { 85 | path: ['register', 'error'], 86 | value: false 87 | } 88 | ]; 89 | }else { 90 | // registration failed 91 | return [ 92 | { 93 | path: ['register', 'newUserId'], 94 | value: 'INVALID' 95 | }, 96 | { 97 | path: ['register', 'error'], 98 | value: 'Registration failed - no id has been created' 99 | } 100 | ]; 101 | } 102 | return; 103 | }).catch((reason) => console.error(reason)); 104 | } 105 | } 106 | 107 | ]; -------------------------------------------------------------------------------- /Chapter04/src/components/articles/WYSIWYGeditor.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { BlockStyleControls, InlineStyleControls } from './wysiwyg/WYSIWYGbuttons'; 3 | import { 4 | Editor, 5 | EditorState, 6 | ContentState, 7 | RichUtils, 8 | convertToRaw, 9 | convertFromRaw 10 | } from 'draft-js'; 11 | 12 | export default class WYSIWYGeditor extends React.Component { 13 | constructor(props) { 14 | super(props); 15 | 16 | let initialEditorFromProps; 17 | 18 | if(typeof props.initialValue === 'undefined' || typeof props.initialValue !== 'object') { 19 | initialEditorFromProps = 20 | EditorState.createWithContent(ContentState.createFromText('')); 21 | } else { 22 | let isInvalidObject = typeof props.initialValue.entityMap === 'undefined' || typeof props.initialValue.blocks === 'undefined'; 23 | if(isInvalidObject) { 24 | alert('Invalid article-edit error provided, exit'); 25 | return; 26 | } 27 | let draftBlocks = convertFromRaw(props.initialValue); 28 | let contentToConsume = ContentState.createFromBlockArray(draftBlocks); 29 | initialEditorFromProps = EditorState.createWithContent(contentToConsume); 30 | } 31 | 32 | this.state = { 33 | editorState: initialEditorFromProps 34 | }; 35 | 36 | this.focus = () => this.refs['WYSIWYGeditor'].focus(); 37 | this.onChange = (editorState) => { 38 | var contentState = editorState.getCurrentContent(); 39 | 40 | let contentJSON = convertToRaw(contentState); 41 | props.onChangeTextJSON(contentJSON, contentState); 42 | this.setState({editorState}) 43 | }; 44 | 45 | this.handleKeyCommand = (command) => this._handleKeyCommand(command); 46 | this.toggleInlineStyle = (style) => this._toggleInlineStyle(style); 47 | this.toggleBlockType = (type) => this._toggleBlockType(type); 48 | } 49 | 50 | _handleKeyCommand(command) { 51 | const {editorState} = this.state; 52 | const newState = RichUtils.handleKeyCommand(editorState, command); 53 | if (newState) { 54 | this.onChange(newState); 55 | return true; 56 | } 57 | return false; 58 | } 59 | 60 | _toggleBlockType(blockType) { 61 | this.onChange( 62 | RichUtils.toggleBlockType( 63 | this.state.editorState, 64 | blockType 65 | ) 66 | ); 67 | } 68 | 69 | _toggleInlineStyle(inlineStyle) { 70 | this.onChange( 71 | RichUtils.toggleInlineStyle( 72 | this.state.editorState, 73 | inlineStyle 74 | ) 75 | ); 76 | } 77 | 78 | render() { 79 | const { editorState } = this.state; 80 | let className = 'RichEditor-editor'; 81 | var contentState = editorState.getCurrentContent(); 82 | 83 | return ( 84 |
85 |

{this.props.title}

86 |
87 | 90 | 91 | 94 | 95 |
96 | 101 |
102 |
103 |
104 | ); 105 | } 106 | } -------------------------------------------------------------------------------- /Chapter05/src/components/articles/WYSIWYGeditor.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { BlockStyleControls, InlineStyleControls } from './wysiwyg/WYSIWYGbuttons'; 3 | import { 4 | Editor, 5 | EditorState, 6 | ContentState, 7 | RichUtils, 8 | convertToRaw, 9 | convertFromRaw 10 | } from 'draft-js'; 11 | 12 | export default class WYSIWYGeditor extends React.Component { 13 | constructor(props) { 14 | super(props); 15 | 16 | let initialEditorFromProps; 17 | 18 | if(typeof props.initialValue === 'undefined' || typeof props.initialValue !== 'object') { 19 | initialEditorFromProps = 20 | EditorState.createWithContent(ContentState.createFromText('')); 21 | } else { 22 | let isInvalidObject = typeof props.initialValue.entityMap === 'undefined' || typeof props.initialValue.blocks === 'undefined'; 23 | if(isInvalidObject) { 24 | alert('Invalid article-edit error provided, exit'); 25 | return; 26 | } 27 | let draftBlocks = convertFromRaw(props.initialValue); 28 | let contentToConsume = ContentState.createFromBlockArray(draftBlocks); 29 | initialEditorFromProps = EditorState.createWithContent(contentToConsume); 30 | } 31 | 32 | this.state = { 33 | editorState: initialEditorFromProps 34 | }; 35 | 36 | this.focus = () => this.refs['WYSIWYGeditor'].focus(); 37 | this.onChange = (editorState) => { 38 | var contentState = editorState.getCurrentContent(); 39 | 40 | let contentJSON = convertToRaw(contentState); 41 | props.onChangeTextJSON(contentJSON, contentState); 42 | this.setState({editorState}) 43 | }; 44 | 45 | this.handleKeyCommand = (command) => this._handleKeyCommand(command); 46 | this.toggleInlineStyle = (style) => this._toggleInlineStyle(style); 47 | this.toggleBlockType = (type) => this._toggleBlockType(type); 48 | } 49 | 50 | _handleKeyCommand(command) { 51 | const {editorState} = this.state; 52 | const newState = RichUtils.handleKeyCommand(editorState, command); 53 | if (newState) { 54 | this.onChange(newState); 55 | return true; 56 | } 57 | return false; 58 | } 59 | 60 | _toggleBlockType(blockType) { 61 | this.onChange( 62 | RichUtils.toggleBlockType( 63 | this.state.editorState, 64 | blockType 65 | ) 66 | ); 67 | } 68 | 69 | _toggleInlineStyle(inlineStyle) { 70 | this.onChange( 71 | RichUtils.toggleInlineStyle( 72 | this.state.editorState, 73 | inlineStyle 74 | ) 75 | ); 76 | } 77 | 78 | render() { 79 | const { editorState } = this.state; 80 | let className = 'RichEditor-editor'; 81 | var contentState = editorState.getCurrentContent(); 82 | 83 | return ( 84 |
85 |

{this.props.title}

86 |
87 | 90 | 91 | 94 | 95 |
96 | 101 |
102 |
103 |
104 | ); 105 | } 106 | } -------------------------------------------------------------------------------- /Chapter06/src/components/articles/WYSIWYGeditor.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { BlockStyleControls, InlineStyleControls } from './wysiwyg/WYSIWYGbuttons'; 3 | import { 4 | Editor, 5 | EditorState, 6 | ContentState, 7 | RichUtils, 8 | convertToRaw, 9 | convertFromRaw 10 | } from 'draft-js'; 11 | 12 | export default class WYSIWYGeditor extends React.Component { 13 | constructor(props) { 14 | super(props); 15 | 16 | let initialEditorFromProps; 17 | 18 | if(typeof props.initialValue === 'undefined' || typeof props.initialValue !== 'object') { 19 | initialEditorFromProps = 20 | EditorState.createWithContent(ContentState.createFromText('')); 21 | } else { 22 | let isInvalidObject = typeof props.initialValue.entityMap === 'undefined' || typeof props.initialValue.blocks === 'undefined'; 23 | if(isInvalidObject) { 24 | alert('Invalid article-edit error provided, exit'); 25 | return; 26 | } 27 | let draftBlocks = convertFromRaw(props.initialValue); 28 | let contentToConsume = ContentState.createFromBlockArray(draftBlocks); 29 | initialEditorFromProps = EditorState.createWithContent(contentToConsume); 30 | } 31 | 32 | this.state = { 33 | editorState: initialEditorFromProps 34 | }; 35 | 36 | this.focus = () => this.refs['WYSIWYGeditor'].focus(); 37 | this.onChange = (editorState) => { 38 | var contentState = editorState.getCurrentContent(); 39 | 40 | let contentJSON = convertToRaw(contentState); 41 | props.onChangeTextJSON(contentJSON, contentState); 42 | this.setState({editorState}) 43 | }; 44 | 45 | this.handleKeyCommand = (command) => this._handleKeyCommand(command); 46 | this.toggleInlineStyle = (style) => this._toggleInlineStyle(style); 47 | this.toggleBlockType = (type) => this._toggleBlockType(type); 48 | } 49 | 50 | _handleKeyCommand(command) { 51 | const {editorState} = this.state; 52 | const newState = RichUtils.handleKeyCommand(editorState, command); 53 | if (newState) { 54 | this.onChange(newState); 55 | return true; 56 | } 57 | return false; 58 | } 59 | 60 | _toggleBlockType(blockType) { 61 | this.onChange( 62 | RichUtils.toggleBlockType( 63 | this.state.editorState, 64 | blockType 65 | ) 66 | ); 67 | } 68 | 69 | _toggleInlineStyle(inlineStyle) { 70 | this.onChange( 71 | RichUtils.toggleInlineStyle( 72 | this.state.editorState, 73 | inlineStyle 74 | ) 75 | ); 76 | } 77 | 78 | render() { 79 | const { editorState } = this.state; 80 | let className = 'RichEditor-editor'; 81 | var contentState = editorState.getCurrentContent(); 82 | 83 | return ( 84 |
85 |

{this.props.title}

86 |
87 | 90 | 91 | 94 | 95 |
96 | 101 |
102 |
103 |
104 | ); 105 | } 106 | } -------------------------------------------------------------------------------- /Chapter02/server/routesSession.js: -------------------------------------------------------------------------------- 1 | import configMongoose from './configMongoose'; 2 | import jwt from 'jsonwebtoken'; 3 | import crypto from 'crypto'; 4 | import jwtSecret from './configSecret'; 5 | 6 | const User = configMongoose.User; 7 | 8 | export default [ 9 | { 10 | route: ['login'] , 11 | call: (callPath, args) => 12 | { 13 | const {username, password} = args[0]; 14 | const saltedPassword = password + 'pubApp'; // pubApp is our salt string 15 | const saltedPassHash = crypto.createHash('sha256').update(saltedPassword).digest('hex'); 16 | const userStatementQuery = { 17 | $and: [ 18 | { 'username': username }, 19 | { 'password': saltedPassHash } 20 | ] 21 | } 22 | 23 | return User.find(userStatementQuery, (err, user) => { 24 | if (err) throw err; 25 | }).then((result) => { 26 | if (result.length) { 27 | const role = result[0].role; 28 | const userDetailsToHash = username+role; 29 | const token = jwt.sign(userDetailsToHash, jwtSecret.secret); 30 | 31 | return [ 32 | { 33 | path: ['login', 'token'], 34 | value: token 35 | }, 36 | { 37 | path: ['login', 'username'], 38 | value: username 39 | }, 40 | { 41 | path: ['login', 'role'], 42 | value: role 43 | }, 44 | { 45 | path: ['login', 'error'], 46 | value: false 47 | } 48 | ]; 49 | } else { 50 | return [ 51 | { 52 | path: ['login', 'token'], 53 | value: 'INVALID' 54 | }, 55 | { 56 | path: ['login', 'error'], 57 | value: 'NO USER FOUND, incorrect login information' 58 | } 59 | ]; 60 | } 61 | 62 | return result; 63 | }); 64 | } 65 | }, 66 | { 67 | route: ['register'], 68 | call: (callPath, args) => { 69 | const newUserObj = args[0]; 70 | 71 | newUserObj.password = newUserObj.password + 'pubApp'; 72 | newUserObj.password = crypto 73 | .createHash('sha256') 74 | .update(newUserObj.password) 75 | .digest('hex'); 76 | 77 | const newUser = new User(newUserObj); 78 | 79 | return newUser.save((err, data) => { 80 | if (err) { 81 | throw err; 82 | } 83 | }) 84 | .then((newRes) => { 85 | const newUserDetail = newRes.toObject(); 86 | 87 | if (newUserDetail._id) { 88 | const newUserId = newUserDetail._id.toString(); 89 | 90 | return [ 91 | { 92 | path: ['register', 'newUserId'], 93 | value: newUserId 94 | }, 95 | { 96 | path: ['register', 'error'], 97 | value: false 98 | } 99 | ]; 100 | 101 | } else { 102 | return [ 103 | { 104 | path: ['register', 'newUserId'], 105 | value: 'INVALID' 106 | }, 107 | { 108 | path: ['register', 'error'], 109 | value: 'Registration failed - no id has been created' 110 | } 111 | ]; 112 | } 113 | }).catch((reason) => console.error(reason)); 114 | } 115 | } 116 | 117 | ]; -------------------------------------------------------------------------------- /Chapter03/server/server.js: -------------------------------------------------------------------------------- 1 | import http from 'http'; 2 | import express from 'express'; 3 | import cors from 'cors'; 4 | import bodyParser from 'body-parser'; 5 | import falcor from 'falcor'; 6 | import falcorExpress from 'falcor-express'; 7 | import falcorRouter from 'falcor-router'; 8 | import routes from './routes.js'; 9 | import React from 'react' 10 | import { createStore } from 'redux' 11 | import { Provider } from 'react-redux' 12 | import { renderToStaticMarkup } from 'react-dom/server' 13 | import ReactRouter from 'react-router'; 14 | import { RoutingContext, match } from 'react-router'; 15 | import * as hist from 'history'; 16 | import rootReducer from '../src/reducers'; 17 | import reactRoutes from '../src/routes'; 18 | import fetchServerSide from './fetchServerSide'; 19 | 20 | const app = express(); 21 | 22 | app.server = http.createServer(app); 23 | // CORS - 3rd party middleware 24 | app.use(cors()); 25 | // This is required by falcor-express middleware to work correctly with falcor-browser 26 | app.use(bodyParser.json({extended: false})); 27 | 28 | app.use(bodyParser.urlencoded({extended: false})); 29 | 30 | app.use('/static', express.static('dist')); 31 | 32 | app.use('/model.json', falcorExpress.dataSourceRoute(function(req, res) { 33 | return new falcorRouter(routes); 34 | })); 35 | 36 | 37 | let handleServerSideRender = (req, res, next) => { 38 | try { 39 | let initMOCKstore = fetchServerSide(); // mocked for now 40 | // Create a new Redux store instance 41 | const store = createStore(rootReducer, initMOCKstore); 42 | const location = hist.createLocation(req.path); 43 | match({ 44 | routes: reactRoutes, 45 | location: location, 46 | }, (err, redirectLocation, renderProps) => { 47 | if (redirectLocation) { 48 | 49 | res.redirect(301, redirectLocation.pathname + redirectLocation.search); 50 | } else if (err) { 51 | 52 | next(err); 53 | // res.send(500, error.message); 54 | } else if (renderProps === null) { 55 | 56 | res.status(404) 57 | .send('Not found'); 58 | } else { 59 | if (typeof renderProps === 'undefined') { 60 | // using handleServerSideRender middleware not required; 61 | // we are not requesting HTML (probably an app.js or other file) 62 | 63 | return; 64 | } 65 | let html = renderToStaticMarkup( 66 | 67 | 68 | 69 | ); 70 | 71 | const initialState = store.getState() 72 | let fullHTML = renderFullPage(html, initialState); 73 | res.send(fullHTML); 74 | } 75 | }); 76 | } catch (err) { 77 | next(err) 78 | } 79 | } 80 | 81 | let renderFullPage = (html, initialState) => 82 | { 83 | return ` 84 | 85 | 86 | 87 | Publishing App Server Side Rendering 88 | 89 | 90 |

Server side publishing app

91 |
${html}
92 | 95 | 96 | 97 | 98 | ` 99 | }; 100 | 101 | app.use(handleServerSideRender); 102 | 103 | 104 | app.server.listen(process.env.PORT || 3000); 105 | console.log(`Started on port ${app.server.address().port}`); 106 | 107 | export default app; 108 | -------------------------------------------------------------------------------- /Chapter05/src/layouts/CoreLayout.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | 4 | import React from 'react'; 5 | import { Link } from 'react-router'; 6 | 7 | import themeDecorator from 'material-ui/lib/styles/theme-decorator'; 8 | import getMuiTheme from 'material-ui/lib/styles/getMuiTheme'; 9 | 10 | import RaisedButton from 'material-ui/lib/raised-button'; 11 | import AppBar from 'material-ui/lib/app-bar'; 12 | import Snackbar from 'material-ui/lib/snackbar'; 13 | import ActionHome from 'material-ui/lib/svg-icons/action/home'; 14 | 15 | import { connect } from 'react-redux'; 16 | import { bindActionCreators } from 'redux'; 17 | import articleActions from '../actions/article.js'; 18 | 19 | 20 | const mapStateToProps = (state) => ({ 21 | ...state 22 | }); 23 | 24 | const mapDispatchToProps = (dispatch) => ({ 25 | articleActions: bindActionCreators(articleActions, dispatch) 26 | }); 27 | 28 | 29 | class CoreLayout extends React.Component { 30 | static propTypes = { 31 | children : React.PropTypes.element 32 | } 33 | constructor(props) { 34 | super(props); 35 | this.state = { 36 | errorValue: null 37 | } 38 | 39 | if(typeof window !== 'undefined') { 40 | errorFuncUtil = this.handleFalcorErrors.bind(this); 41 | } 42 | 43 | } 44 | 45 | handleFalcorErrors(errMsg, errPath) { 46 | let errorValue = `Error: ${errMsg} (path ${JSON.stringify(errPath)})` 47 | this.setState({errorValue}); 48 | } 49 | 50 | componentWillMount() { 51 | if(typeof window !== 'undefined' && !this.props.article.get) { 52 | this.props.articleActions.articlesList(this.props.article); 53 | } 54 | } 55 | 56 | render () { 57 | let errorSnackbarJSX = null; 58 | if(this.state.errorValue) { 59 | errorSnackbarJSX = console.log('You can add custom onClose code') } />; 64 | } 65 | 66 | const buttonStyle = { 67 | margin: 5 68 | }; 69 | const homeIconStyle = { 70 | margin: 5, 71 | paddingTop: 5 72 | }; 73 | 74 | let menuLinksJSX; 75 | let userIsLoggedIn = typeof localStorage !== 'undefined' && localStorage.token && this.props.routes[1].name !== 'logout'; 76 | 77 | if(userIsLoggedIn) { 78 | menuLinksJSX = ( 79 | 80 | 81 | ); 82 | } else { 83 | menuLinksJSX = ( 84 | 85 | 86 | ); 87 | } 88 | 89 | let homePageButtonJSX = ( 90 | } style={homeIconStyle} /> 91 | ); 92 | 93 | return ( 94 |
95 | {errorSnackbarJSX} 96 | 100 |
101 | {this.props.children} 102 |
103 | 104 | ); 105 | } 106 | } 107 | 108 | let errorFuncUtil = (errMsg, errPath) => { 109 | } 110 | 111 | export { errorFuncUtil as errorFunc }; 112 | 113 | const muiCoreLayout = themeDecorator(getMuiTheme(null, { userAgent: 'all' })) 114 | (CoreLayout); 115 | 116 | export default connect(mapStateToProps, mapDispatchToProps)(muiCoreLayout); -------------------------------------------------------------------------------- /Chapter06/src/layouts/CoreLayout.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | 4 | import React from 'react'; 5 | import { Link } from 'react-router'; 6 | 7 | import themeDecorator from 'material-ui/lib/styles/theme-decorator'; 8 | import getMuiTheme from 'material-ui/lib/styles/getMuiTheme'; 9 | 10 | import RaisedButton from 'material-ui/lib/raised-button'; 11 | import AppBar from 'material-ui/lib/app-bar'; 12 | import Snackbar from 'material-ui/lib/snackbar'; 13 | import ActionHome from 'material-ui/lib/svg-icons/action/home'; 14 | 15 | import { connect } from 'react-redux'; 16 | import { bindActionCreators } from 'redux'; 17 | import articleActions from '../actions/article.js'; 18 | 19 | 20 | const mapStateToProps = (state) => ({ 21 | ...state 22 | }); 23 | 24 | const mapDispatchToProps = (dispatch) => ({ 25 | articleActions: bindActionCreators(articleActions, dispatch) 26 | }); 27 | 28 | 29 | class CoreLayout extends React.Component { 30 | static propTypes = { 31 | children : React.PropTypes.element 32 | } 33 | constructor(props) { 34 | super(props); 35 | this.state = { 36 | errorValue: null 37 | } 38 | 39 | if(typeof window !== 'undefined') { 40 | errorFuncUtil = this.handleFalcorErrors.bind(this); 41 | } 42 | 43 | } 44 | 45 | handleFalcorErrors(errMsg, errPath) { 46 | let errorValue = `Error: ${errMsg} (path ${JSON.stringify(errPath)})` 47 | this.setState({errorValue}); 48 | } 49 | 50 | componentWillMount() { 51 | if(typeof window !== 'undefined' && !this.props.article.get) { 52 | this.props.articleActions.articlesList(this.props.article); 53 | } 54 | } 55 | 56 | render () { 57 | let errorSnackbarJSX = null; 58 | if(this.state.errorValue) { 59 | errorSnackbarJSX = console.log('You can add custom onClose code') } />; 64 | } 65 | 66 | const buttonStyle = { 67 | margin: 5 68 | }; 69 | const homeIconStyle = { 70 | margin: 5, 71 | paddingTop: 5 72 | }; 73 | 74 | let menuLinksJSX; 75 | let userIsLoggedIn = typeof localStorage !== 'undefined' && localStorage.token && this.props.routes[1].name !== 'logout'; 76 | 77 | if(userIsLoggedIn) { 78 | menuLinksJSX = ( 79 | 80 | 81 | ); 82 | } else { 83 | menuLinksJSX = ( 84 | 85 | 86 | ); 87 | } 88 | 89 | let homePageButtonJSX = ( 90 | } style={homeIconStyle} /> 91 | ); 92 | 93 | return ( 94 |
95 | {errorSnackbarJSX} 96 | 100 |
101 | {this.props.children} 102 |
103 | 104 | ); 105 | } 106 | } 107 | 108 | let errorFuncUtil = (errMsg, errPath) => { 109 | } 110 | 111 | export { errorFuncUtil as errorFunc }; 112 | 113 | const muiCoreLayout = themeDecorator(getMuiTheme(null, { userAgent: 'all' })) 114 | (CoreLayout); 115 | 116 | export default connect(mapStateToProps, mapDispatchToProps)(muiCoreLayout); -------------------------------------------------------------------------------- /Chapter04/server/server.js: -------------------------------------------------------------------------------- 1 | import http from 'http'; 2 | import express from 'express'; 3 | import cors from 'cors'; 4 | import bodyParser from 'body-parser'; 5 | import falcor from 'falcor'; 6 | import falcorExpress from 'falcor-express'; 7 | import falcorRouter from 'falcor-router'; 8 | import routes from './routes.js'; 9 | import React from 'react' 10 | import { createStore } from 'redux' 11 | import { Provider } from 'react-redux' 12 | import { renderToStaticMarkup } from 'react-dom/server' 13 | import ReactRouter from 'react-router'; 14 | import { RoutingContext, match } from 'react-router'; 15 | import * as hist from 'history'; 16 | import rootReducer from '../src/reducers'; 17 | import reactRoutes from '../src/routes'; 18 | import fetchServerSide from './fetchServerSide'; 19 | 20 | const app = express(); 21 | 22 | app.server = http.createServer(app); 23 | // CORS - 3rd party middleware 24 | app.use(cors()); 25 | // This is required by falcor-express middleware to work correctly with falcor-browser 26 | app.use(bodyParser.json({extended: false})); 27 | 28 | app.use(bodyParser.urlencoded({extended: false})); 29 | 30 | app.use('/static', express.static('dist')); 31 | 32 | app.use('/model.json', falcorExpress.dataSourceRoute(function(req, res) { 33 | return new falcorRouter(routes); 34 | })); 35 | 36 | 37 | let handleServerSideRender = async (req, res, next) => { 38 | try { 39 | let articlesArray = await fetchServerSide(); 40 | let initMOCKstore = { 41 | article: articlesArray 42 | } 43 | // Create a new Redux store instance 44 | const store = createStore(rootReducer, initMOCKstore); 45 | const location = hist.createLocation(req.path); 46 | match({ 47 | routes: reactRoutes, 48 | location: location, 49 | }, (err, redirectLocation, renderProps) => { 50 | if (redirectLocation) { 51 | 52 | res.redirect(301, redirectLocation.pathname + redirectLocation.search); 53 | } else if (err) { 54 | 55 | next(err); 56 | // res.send(500, error.message); 57 | } else if (renderProps === null) { 58 | 59 | res.status(404) 60 | .send('Not found'); 61 | } else { 62 | if (typeof renderProps === 'undefined') { 63 | // using handleServerSideRender middleware not required; 64 | // we are not requesting HTML (probably an app.js or other file) 65 | 66 | return; 67 | } 68 | let html = renderToStaticMarkup( 69 | 70 | 71 | 72 | ); 73 | 74 | const initialState = store.getState() 75 | let fullHTML = renderFullPage(html, initialState); 76 | res.send(fullHTML); 77 | } 78 | }); 79 | } catch (err) { 80 | next(err); 81 | } 82 | } 83 | 84 | let renderFullPage = (html, initialState) => 85 | { 86 | return ` 87 | 88 | 89 | 90 | Publishing App Server Side Rendering 91 | 92 | 93 | 94 |
${html}
95 | 98 | 99 | 100 | 101 | ` 102 | }; 103 | 104 | app.use(handleServerSideRender); 105 | 106 | 107 | app.server.listen(process.env.PORT || 3000); 108 | console.log(`Started on port ${app.server.address().port}`); 109 | 110 | export default app; 111 | -------------------------------------------------------------------------------- /Chapter05/server/server.js: -------------------------------------------------------------------------------- 1 | import http from 'http'; 2 | import express from 'express'; 3 | import cors from 'cors'; 4 | import bodyParser from 'body-parser'; 5 | import falcor from 'falcor'; 6 | import falcorExpress from 'falcor-express'; 7 | import FalcorRouter from 'falcor-router'; 8 | import routes from './routes.js'; 9 | import React from 'react' 10 | import { createStore } from 'redux' 11 | import { Provider } from 'react-redux' 12 | import { renderToStaticMarkup } from 'react-dom/server' 13 | import ReactRouter from 'react-router'; 14 | import { RoutingContext, match } from 'react-router'; 15 | import * as hist from 'history'; 16 | import rootReducer from '../src/reducers'; 17 | import reactRoutes from '../src/routes'; 18 | import fetchServerSide from './fetchServerSide'; 19 | 20 | const app = express(); 21 | 22 | app.server = http.createServer(app); 23 | // CORS - 3rd party middleware 24 | app.use(cors()); 25 | // This is required by falcor-express middleware to work correctly with falcor-browser 26 | app.use(bodyParser.json({extended: false})); 27 | 28 | app.use(bodyParser.urlencoded({extended: false})); 29 | 30 | app.use('/static', express.static('dist')); 31 | 32 | app.use('/model.json', falcorExpress.dataSourceRoute(function(req, res) { 33 | return new FalcorRouter( 34 | [] 35 | .concat(routes(req, res)) 36 | ); 37 | })); 38 | 39 | 40 | let handleServerSideRender = async (req, res, next) => { 41 | try { 42 | let articlesArray = await fetchServerSide(); 43 | let initMOCKstore = { 44 | article: articlesArray 45 | } 46 | // Create a new Redux store instance 47 | const store = createStore(rootReducer, initMOCKstore); 48 | const location = hist.createLocation(req.path); 49 | match({ 50 | routes: reactRoutes, 51 | location: location, 52 | }, (err, redirectLocation, renderProps) => { 53 | if (redirectLocation) { 54 | 55 | res.redirect(301, redirectLocation.pathname + redirectLocation.search); 56 | } else if (err) { 57 | 58 | next(err); 59 | // res.send(500, error.message); 60 | } else if (renderProps === null) { 61 | 62 | res.status(404) 63 | .send('Not found'); 64 | } else { 65 | if (typeof renderProps === 'undefined') { 66 | // using handleServerSideRender middleware not required; 67 | // we are not requesting HTML (probably an app.js or other file) 68 | 69 | return; 70 | } 71 | let html = renderToStaticMarkup( 72 | 73 | 74 | 75 | ); 76 | 77 | const initialState = store.getState() 78 | let fullHTML = renderFullPage(html, initialState); 79 | res.send(fullHTML); 80 | } 81 | }); 82 | } catch (err) { 83 | next(err); 84 | } 85 | } 86 | 87 | let renderFullPage = (html, initialState) => 88 | { 89 | return ` 90 | 91 | 92 | 93 | Publishing App Server Side Rendering 94 | 95 | 96 | 97 |
${html}
98 | 101 | 102 | 103 | 104 | ` 105 | }; 106 | 107 | app.use(handleServerSideRender); 108 | 109 | 110 | app.server.listen(process.env.PORT || 3000); 111 | console.log(`Started on port ${app.server.address().port}`); 112 | 113 | export default app; 114 | -------------------------------------------------------------------------------- /Chapter06/server/server.js: -------------------------------------------------------------------------------- 1 | import http from 'http'; 2 | import express from 'express'; 3 | import cors from 'cors'; 4 | import bodyParser from 'body-parser'; 5 | import falcor from 'falcor'; 6 | import falcorExpress from 'falcor-express'; 7 | import FalcorRouter from 'falcor-router'; 8 | import routes from './routes.js'; 9 | import React from 'react' 10 | import { createStore } from 'redux' 11 | import { Provider } from 'react-redux' 12 | import { renderToStaticMarkup } from 'react-dom/server' 13 | import ReactRouter from 'react-router'; 14 | import { RoutingContext, match } from 'react-router'; 15 | import * as hist from 'history'; 16 | import rootReducer from '../src/reducers'; 17 | import reactRoutes from '../src/routes'; 18 | import fetchServerSide from './fetchServerSide'; 19 | import s3router from 'react-s3-uploader/s3router'; 20 | 21 | const app = express(); 22 | 23 | app.server = http.createServer(app); 24 | // CORS - 3rd party middleware 25 | app.use(cors()); 26 | // This is required by falcor-express middleware to work correctly with falcor-browser 27 | app.use(bodyParser.json({extended: false})); 28 | 29 | app.use(bodyParser.urlencoded({extended: false})); 30 | 31 | app.use('/s3', s3router({ 32 | bucket: process.env.AWS_BUCKET_NAME, 33 | region: process.env.AWS_REGION_NAME, 34 | signatureVersion: 'v4', 35 | headers: {'Access-Control-Allow-Origin': '*'}, 36 | ACL: 'public-read' 37 | })); 38 | 39 | app.use('/static', express.static('dist')); 40 | 41 | app.use('/model.json', falcorExpress.dataSourceRoute(function(req, res) { 42 | return new FalcorRouter( 43 | [] 44 | .concat(routes(req, res)) 45 | ); 46 | })); 47 | 48 | 49 | let handleServerSideRender = async (req, res, next) => { 50 | try { 51 | let articlesArray = await fetchServerSide(); 52 | let initMOCKstore = { 53 | article: articlesArray 54 | } 55 | // Create a new Redux store instance 56 | const store = createStore(rootReducer, initMOCKstore); 57 | const location = hist.createLocation(req.path); 58 | match({ 59 | routes: reactRoutes, 60 | location: location, 61 | }, (err, redirectLocation, renderProps) => { 62 | if (redirectLocation) { 63 | 64 | res.redirect(301, redirectLocation.pathname + redirectLocation.search); 65 | } else if (err) { 66 | 67 | next(err); 68 | // res.send(500, error.message); 69 | } else if (renderProps === null) { 70 | 71 | res.status(404) 72 | .send('Not found'); 73 | } else { 74 | if (typeof renderProps === 'undefined') { 75 | // using handleServerSideRender middleware not required; 76 | // we are not requesting HTML (probably an app.js or other file) 77 | 78 | return; 79 | } 80 | let html = renderToStaticMarkup( 81 | 82 | 83 | 84 | ); 85 | 86 | const initialState = store.getState() 87 | let fullHTML = renderFullPage(html, initialState); 88 | res.send(fullHTML); 89 | } 90 | }); 91 | } catch (err) { 92 | next(err); 93 | } 94 | } 95 | 96 | let renderFullPage = (html, initialState) => 97 | { 98 | return ` 99 | 100 | 101 | 102 | Publishing App Server Side Rendering 103 | 104 | 105 | 106 |
${html}
107 | 110 | 111 | 112 | 113 | ` 114 | }; 115 | 116 | app.use(handleServerSideRender); 117 | 118 | 119 | app.server.listen(process.env.PORT || 3000); 120 | console.log(`Started on port ${app.server.address().port}`); 121 | 122 | export default app; 123 | --------------------------------------------------------------------------------