From 0db6a9483671fc6924d50f42cdad5f84b4090485 Mon Sep 17 00:00:00 2001 From: Carlos Santana Date: Tue, 27 Jun 2017 00:15:23 -0700 Subject: [PATCH 1/2] V12 - Adding Library Container --- package.json | 1 + src/actions/actionTypes.js | 3 + src/config/index.js | 7 ++ src/constants/api.js | 6 ++ src/containers/Library/actions.js | 19 +++++ src/containers/Library/api.js | 17 ++++ src/containers/Library/index.js | 133 ++++++++++++++++++++++++++++++ src/containers/Library/reducer.js | 30 +++++++ src/data/books.json | 34 ++++++++ src/data/menu.js | 4 + src/data/post.json | 16 ---- src/index.js | 1 + src/lib/utils/api.js | 59 +++++++++++++ src/lib/utils/frontend.js | 9 ++ src/lib/utils/is.js | 15 ++++ src/reducers/index.js | 6 +- src/routes.js | 3 + src/server/api/blog.js | 13 ++- src/server/api/library.js | 28 +++++++ src/server/index.js | 2 + yarn.lock | 30 +------ 21 files changed, 388 insertions(+), 48 deletions(-) create mode 100644 src/actions/actionTypes.js create mode 100644 src/config/index.js create mode 100644 src/constants/api.js create mode 100644 src/containers/Library/actions.js create mode 100644 src/containers/Library/api.js create mode 100644 src/containers/Library/index.js create mode 100644 src/containers/Library/reducer.js create mode 100644 src/data/books.json delete mode 100644 src/data/post.json create mode 100644 src/lib/utils/api.js create mode 100644 src/lib/utils/frontend.js create mode 100644 src/lib/utils/is.js create mode 100644 src/server/api/library.js diff --git a/package.json b/package.json index a5860af..07469ce 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "isomorphic-fetch": "^2.2.1", "path": "^0.12.7", "prop-types": "^15.5.8", + "query-string": "^4.3.4", "react": "^15.5.4", "react-dom": "^15.5.4", "react-redux": "^5.0.5", diff --git a/src/actions/actionTypes.js b/src/actions/actionTypes.js new file mode 100644 index 0000000..1ceea4b --- /dev/null +++ b/src/actions/actionTypes.js @@ -0,0 +1,3 @@ +// Library actions +export const LIBRARY_LIST_BOOKS = 'LIBRARY_LIST_BOOKS'; +export const LIBRARY_SHOW_SINGLE_BOOK = 'LIBRARY_SHOW_SINGLE_BOOK'; diff --git a/src/config/index.js b/src/config/index.js new file mode 100644 index 0000000..58b832f --- /dev/null +++ b/src/config/index.js @@ -0,0 +1,7 @@ +export default { + serverPort: 3000, + baseUrl: 'http://localhost:3000', + api: { + url: '/api/' + } +} diff --git a/src/constants/api.js b/src/constants/api.js new file mode 100644 index 0000000..7380d02 --- /dev/null +++ b/src/constants/api.js @@ -0,0 +1,6 @@ +export const API = Object.freeze({ + LIBRARY: { + BOOK: 'library/book', + BOOKS: 'library/books' + } +}); diff --git a/src/containers/Library/actions.js b/src/containers/Library/actions.js new file mode 100644 index 0000000..938629f --- /dev/null +++ b/src/containers/Library/actions.js @@ -0,0 +1,19 @@ +// Actions Types +import * as types from '../../actions/actionTypes'; + +// Api +import libraryApi from './api'; + +export function loadBooks() { + return { + type: types.LIBRARY_LIST_BOOKS, + payload: libraryApi.getAllBooks() + }; +} + +export function loadSingleBook(query) { + return { + type: types.LIBRARY_SHOW_SINGLE_BOOK, + payload: libraryApi.getSingleBook(query) + }; +} diff --git a/src/containers/Library/api.js b/src/containers/Library/api.js new file mode 100644 index 0000000..5bc62c2 --- /dev/null +++ b/src/containers/Library/api.js @@ -0,0 +1,17 @@ +// Constants +import { API } from '../../constants/api'; + +// Utils +import { apiFetch } from '../../lib/utils/api'; + +class LibraryApi { + static getAllBooks(query) { + return apiFetch(API.LIBRARY.BOOKS, {}, query); + } + + static getSingleBook(query) { + return apiFetch(API.LIBRARY.BOOK, {}, query); + } +} + +export default LibraryApi; diff --git a/src/containers/Library/index.js b/src/containers/Library/index.js new file mode 100644 index 0000000..70844bf --- /dev/null +++ b/src/containers/Library/index.js @@ -0,0 +1,133 @@ +// Dependencies +import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import { Link } from 'react-router-dom'; + +// Actions +import * as actions from '../../containers/Library/actions'; + +// Utils +import { isFirstRender } from '../../lib/utils/frontend'; + +class Library extends Component { + static propTypes = { + loadBooks: PropTypes.func.isRequired, + books: PropTypes.array.isRequired, + book: PropTypes.array + }; + + constructor(props) { + super(props); + + this.state = { + displaySingleBook: false + }; + } + + componentWillMount() { + const { + match: { + params: { + id = 0 + } + } + } = this.props; + + if (id > 0) { + this.setState({ + displaySingleBook: true + }); + + this.props.loadSingleBook({ id }); + } else { + this.setState({ + displaySingleBook: false + }); + + this.props.loadBooks(); + } + } + + componentWillReceiveProps(nextProps) { + const { + match: { + params: { + id = 0 + } + } + } = nextProps; + + if (nextProps.match.params !== this.props.match.params) { + if (id > 0) { + this.setState({ + displaySingleBook: true + }); + + this.props.loadSingleBook({ id }); + } else { + this.setState({ + displaySingleBook: false + }); + } + } + } + + renderSingleBook(book) { + return ( +
+

{book.title}

+

Autor: {book.author}

+

+

Go back

+
+ ); + } + + renderBooksList(books) { + return ( +
+

Library

+ +
+ ); + } + + render() { + const { + books, + book + } = this.props; + + if (isFirstRender(books) && book.length === 0) { + return null; + } + + let show = this.renderBooksList(books); + + if (this.state.displaySingleBook && book.length > 0) { + show = this.renderSingleBook(book[0]); + } + + return ( +
+ {show} +
+ ); + } +} + +export default connect(state => ({ + books: state.library.books, + book: state.library.book +}), actions)(Library); diff --git a/src/containers/Library/reducer.js b/src/containers/Library/reducer.js new file mode 100644 index 0000000..cdb7ad4 --- /dev/null +++ b/src/containers/Library/reducer.js @@ -0,0 +1,30 @@ +// Utils +import { getNewState } from '../../lib/utils/frontend'; + +const initialState = { + books: [], + book: [] +}; + +export default function libraryReducer(state = initialState, action) { + switch (action.type) { + case 'LIBRARY_LIST_BOOKS_SUCCESS': { + const { payload: { response = [] } } = action; + + return getNewState(state, { + books: response + }); + } + + case 'LIBRARY_SHOW_SINGLE_BOOK_SUCCESS': { + const { payload: { response = [] } } = action; + + return getNewState(state, { + book: response + }); + } + + default: + return state; + } +} diff --git a/src/data/books.json b/src/data/books.json new file mode 100644 index 0000000..9c84958 --- /dev/null +++ b/src/data/books.json @@ -0,0 +1,34 @@ +{ + "response": [ + { + "id": 1, + "title": "El SeƱor de los Anillos", + "author": "J. R. R. Tolkien", + "image": "https://imagessl7.casadellibro.com/a/l/t0/87/9788445000687.jpg" + }, + { + "id": 2, + "title": "Padre Rico Padre Pobre", + "author": "Robert Kiyosaki", + "image": "https://imagessl2.casadellibro.com/a/l/t0/02/9788466317702.jpg" + }, + { + "id": 3, + "title": "El Tao de Warren Buffett", + "author": "Mary Buffett", + "image": "https://imagessl6.casadellibro.com/a/l/t0/56/9788493562656.jpg" + }, + { + "id": 4, + "title": "Burlar al Diablo. Secretos desde la Cripta", + "author": "Napoleon Hill", + "image": "https://images-na.ssl-images-amazon.com/images/I/41DcYrw4upL._SX311_BO1,204,203,200_.jpg" + }, + { + "id": 5, + "title": "El Alquimista", + "author": "Paulo Coelho", + "image": "http://libros-gratis.com/wp-content/uploads/2015/12/el-alquimista.jpg" + } + ] +} diff --git a/src/data/menu.js b/src/data/menu.js index 33c2e95..915f9d4 100644 --- a/src/data/menu.js +++ b/src/data/menu.js @@ -10,5 +10,9 @@ export default [ { title: 'Contact Us', url: '/contact' + }, + { + title: 'Library', + url: '/library' } ]; diff --git a/src/data/post.json b/src/data/post.json deleted file mode 100644 index 4187a5f..0000000 --- a/src/data/post.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "response": [ - { - "id": 1, - "title": "Test 1", - "slug": "test-1", - "content": "

Test 1 - Content

", - "author": "Codejobs", - "day": "16", - "month": "06", - "year": "2017", - "language": "es", - "state": "Active" - } - ] -} diff --git a/src/index.js b/src/index.js index c70a541..e06579d 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,5 @@ // Dependencies +import 'babel-polyfill'; import React from 'react'; import { render } from 'react-dom'; import { BrowserRouter as Router } from 'react-router-dom'; diff --git a/src/lib/utils/api.js b/src/lib/utils/api.js new file mode 100644 index 0000000..02bf96e --- /dev/null +++ b/src/lib/utils/api.js @@ -0,0 +1,59 @@ +// Dependencies +import queryString from 'query-string'; + +// Config +import config from '../../config'; + +export function apiEndpoint(endpoint, qs) { + let query = ''; + + if (qs) { + query = `?${qs}`; + } + console.log(`${config.api.url}${endpoint}${query}`); + return `${config.api.url}${endpoint}${query}`; +} + +export function apiFetch(endpoint, options = {}, query = false) { + let qs; + + if (query) { + qs = queryString.stringify(query); + } + + const getPromise = async () => { + try { + const fetchOptions = apiOptions(options); + const fetchEndpoint = apiEndpoint(endpoint, qs); + const response = await fetch(fetchEndpoint, fetchOptions); + + return response.json(); + } catch (e) { + throw e; + } + }; + + return getPromise(); +} + +export function apiOptions(options = {}) { + const { + method = 'GET', + headers = { + 'Content-Type': 'application/json' + }, + body = false + } = options; + + const newOptions = { + method, + headers, + credentials: 'include' + }; + + if (body) { + newOptions.body = body; + } + + return newOptions; +} diff --git a/src/lib/utils/frontend.js b/src/lib/utils/frontend.js new file mode 100644 index 0000000..b10483f --- /dev/null +++ b/src/lib/utils/frontend.js @@ -0,0 +1,9 @@ +import { isDefined } from './is'; + +export function getNewState(state, newState) { + return Object.assign({}, state, newState); +} + +export function isFirstRender(items) { + return items && items.length === 0 || !isDefined(items); +} diff --git a/src/lib/utils/is.js b/src/lib/utils/is.js new file mode 100644 index 0000000..bde4006 --- /dev/null +++ b/src/lib/utils/is.js @@ -0,0 +1,15 @@ +export function isArray(variable) { + return variable instanceof Array; +} + +export function isDefined(variable) { + return typeof variable !== 'undefined' && variable !== null; +} + +export function isFunction(variable) { + return typeof variable === 'function'; +} + +export function isObject(variable) { + return isDefined(variable) && typeof variable === 'object'; +} diff --git a/src/reducers/index.js b/src/reducers/index.js index bf7efbf..3888552 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -1,11 +1,15 @@ // Dependencies import { combineReducers } from 'redux'; +// App Reducers +import library from '../containers/Library/reducer'; + // Shared Reducers import device from './deviceReducer'; const rootReducer = combineReducers({ - device + device, + library }); export default rootReducer; diff --git a/src/routes.js b/src/routes.js index 1b9b933..22380dc 100644 --- a/src/routes.js +++ b/src/routes.js @@ -10,12 +10,15 @@ import Page404 from './components/Page404'; // Container import Home from './containers/Home'; +import Library from './containers/Library'; const AppRoutes = () => + + diff --git a/src/server/api/blog.js b/src/server/api/blog.js index d716ff5..185b62e 100644 --- a/src/server/api/blog.js +++ b/src/server/api/blog.js @@ -3,7 +3,6 @@ import express from 'express'; // Data import posts from '../../data/posts.json'; -import post from '../../data/post.json'; // Express Router const Router = express.Router(); @@ -13,7 +12,17 @@ Router.get('/posts', (req, res, next) => { }); Router.get('/post', (req, res, next) => { - res.json(post); + const { + query: { + slug = '' + } + } = req; + + const selectedPost = posts.response.filter(book => book.slug === slug); + + res.json({ + response: selectedPost + }); }); export default Router; diff --git a/src/server/api/library.js b/src/server/api/library.js new file mode 100644 index 0000000..01828f6 --- /dev/null +++ b/src/server/api/library.js @@ -0,0 +1,28 @@ +// Dependencies +import express from 'express'; + +// Data +import books from '../../data/books.json'; + +// Express Router +const Router = express.Router(); + +Router.get('/books', (req, res, next) => { + res.json(books); +}); + +Router.get('/book', (req, res, next) => { + const { + query: { + id = 0 + } + } = req; + + const selectedBook = books.response.filter(book => book.id === Number(id)); + + res.json({ + response: selectedBook + }); +}); + +export default Router; diff --git a/src/server/index.js b/src/server/index.js index f25df20..274c332 100644 --- a/src/server/index.js +++ b/src/server/index.js @@ -12,6 +12,7 @@ import webpackConfig from '../../webpack.config.babel'; // API import blogApi from './api/blog'; +import libraryApi from './api/library'; // Helpers import * as hbsHelper from '../lib/handlebars'; @@ -58,6 +59,7 @@ app.use((req, res, next) => { // API dispatch app.use('/api/blog', blogApi); +app.use('/api/library', libraryApi); // Sending all the traffic to React app.get('*', (req, res) => { diff --git a/yarn.lock b/yarn.lock index ce0c053..666ab45 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1948,10 +1948,6 @@ hoek@2.x.x: version "2.16.3" resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" -hoist-non-react-statics@1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-1.0.5.tgz#0e36d2c130c8511f267a0d4ceb45ec7d7e4f0c70" - hoist-non-react-statics@^1.0.3, hoist-non-react-statics@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz#aa448cf0986d55cc40773b17174b7dd066cb7cfb" @@ -2058,12 +2054,6 @@ interpret@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.3.tgz#cbc35c62eeee73f19ab7b10a801511401afc0f90" -invariant@2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.0.tgz#c8d7e847366a49cc18b622f058a689d481e895f2" - dependencies: - loose-envify "^1.0.0" - invariant@^2.0.0, invariant@^2.1.0, invariant@^2.2.0, invariant@^2.2.1, invariant@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360" @@ -2209,10 +2199,6 @@ js-base64@^2.1.9: version "2.1.9" resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.1.9.tgz#f0e80ae039a4bd654b5f281fc93f04a914a7fcce" -js-tokens@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-1.0.3.tgz#14e56eb68c8f1a92c43d59f5014ec29dc20f2ae1" - js-tokens@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.1.tgz#08e9f132484a2c45a30907e9dc4d5567b7f114d7" @@ -2342,12 +2328,6 @@ longest@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" -loose-envify@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.1.0.tgz#527582d62cff4e04da3f9976c7110d3392ec7e0c" - dependencies: - js-tokens "^1.0.1" - loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" @@ -3102,7 +3082,7 @@ qs@6.4.0, qs@~6.4.0: version "6.4.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" -query-string@^4.1.0: +query-string@^4.1.0, query-string@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" dependencies: @@ -3286,14 +3266,6 @@ redux-promise-middleware@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/redux-promise-middleware/-/redux-promise-middleware-4.3.0.tgz#38997574b6f150ab8ff1aa72ca5563fbc82c7cef" -redux-state@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/redux-state/-/redux-state-1.1.0.tgz#04ed8304f902282d34cf9317eb29bb830f1088f7" - dependencies: - hoist-non-react-statics "1.0.5" - invariant "2.2.0" - loose-envify "1.1.0" - redux@^3.7.0: version "3.7.0" resolved "https://registry.yarnpkg.com/redux/-/redux-3.7.0.tgz#07a623cafd92eee8abe309d13d16538f6707926f" From f9111e4b24b138cfd1418aa08c0587e6d9abc27a Mon Sep 17 00:00:00 2001 From: Carlos Santana Date: Tue, 27 Jun 2017 00:20:09 -0700 Subject: [PATCH 2/2] Adding config --- src/config/index.js | 5 +++++ src/lib/utils/api.js | 2 +- src/server/index.js | 18 +++++++++--------- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/config/index.js b/src/config/index.js index 58b832f..999d405 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -3,5 +3,10 @@ export default { baseUrl: 'http://localhost:3000', api: { url: '/api/' + }, + views: { + engine: '.hbs', + extension: '.hbs', + path: './views' } } diff --git a/src/lib/utils/api.js b/src/lib/utils/api.js index 02bf96e..becffee 100644 --- a/src/lib/utils/api.js +++ b/src/lib/utils/api.js @@ -10,7 +10,7 @@ export function apiEndpoint(endpoint, qs) { if (qs) { query = `?${qs}`; } - console.log(`${config.api.url}${endpoint}${query}`); + return `${config.api.url}${endpoint}${query}`; } diff --git a/src/server/index.js b/src/server/index.js index 274c332..83836f7 100644 --- a/src/server/index.js +++ b/src/server/index.js @@ -7,6 +7,9 @@ import webpackHotMiddleware from 'webpack-hot-middleware'; import open from 'open'; import exphbs from 'express-handlebars'; +// Config +import config from '../config'; + // Webpack Configuration import webpackConfig from '../../webpack.config.babel'; @@ -20,9 +23,6 @@ import * as hbsHelper from '../lib/handlebars'; // Utils import { isMobile } from '../lib/utils/device'; -// Server Port -const port = 3000; - // Environment const isDevelopment = process.env.NODE_ENV !== 'production'; @@ -33,14 +33,14 @@ const app = express(); app.use(express.static(path.join(__dirname, '../public'))); // Handlebars setup -app.engine('.hbs', exphbs({ - extname: '.hbs', +app.engine(config.views.engine, exphbs({ + extname: config.views.extension, helpers: hbsHelper })); // View Engine Setup -app.set('views', path.join(__dirname, './views')); -app.set('view engine', '.hbs'); +app.set('views', path.join(__dirname, config.views.path)); +app.set('view engine', config.views.engine); // Webpack Compiler const webpackCompiler = webpack(webpackConfig); @@ -69,8 +69,8 @@ app.get('*', (req, res) => { }); // Listen port 3000 -app.listen(port, err => { +app.listen(config.serverPort, err => { if (!err) { - open(`http://localhost:${port}`); + open(`${config.baseUrl}`); } });