From e00e59e1bd407349daa07178948e18b26a200afd Mon Sep 17 00:00:00 2001 From: Wei Gao Date: Fri, 25 Oct 2019 22:43:19 -0400 Subject: [PATCH 01/12] Complete project setup --- .gitignore | 3 +++ client/dist/index.html | 7 +++++++ client/src/index.jsx | 14 ++++++++++++++ package.json | 29 +++++++++++++++++++++++++++++ server/index.js | 14 ++++++++++++++ webpack.config.js | 23 +++++++++++++++++++++++ 6 files changed, 90 insertions(+) create mode 100644 .gitignore create mode 100644 client/dist/index.html create mode 100644 client/src/index.jsx create mode 100644 package.json create mode 100644 server/index.js create mode 100644 webpack.config.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eced731 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/package-lock.json +/client/dist/bundle.js +/node_modules/ diff --git a/client/dist/index.html b/client/dist/index.html new file mode 100644 index 0000000..ab8bd87 --- /dev/null +++ b/client/dist/index.html @@ -0,0 +1,7 @@ + + + +
+ + + \ No newline at end of file diff --git a/client/src/index.jsx b/client/src/index.jsx new file mode 100644 index 0000000..acb0eb3 --- /dev/null +++ b/client/src/index.jsx @@ -0,0 +1,14 @@ +// CLIENT + +import React, { Component } from 'react'; +import ReactDOM from 'react-dom'; +import axios from 'axios'; + +class App extends Component { + + render() { + return (
HELLO WORLD
) + } +} + +ReactDOM.render(, document.getElementById('root')) \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..b18cb03 --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "fullstack-developer-challenge", + "version": "1.0.0", + "description": "Bellese technologies coding take home challenge", + "main": "index.js", + "scripts": { + "build": "webpack --config webpack.config.js", + "react-dev": "webpack -d --watch", + "server-dev": "nodemon server/index.js" + }, + "author": "WG", + "homepage": "https://github.com/weigao10/fullstack-developer-challenge", + "dependencies": { + "axios": "^0.18.0", + "body-parser": "^1.18.2", + "express": "^4.16.3", + "nodemon": "^1.19.4", + "react": "^16.3.1", + "react-dom": "^16.3.1" + }, + "devDependencies": { + "babel-core": "^6.26.0", + "babel-loader": "^7.1.4", + "babel-preset-es2015": "^6.24.1", + "babel-preset-react": "^6.24.1", + "webpack": "^2.7.0", + "webpack-cli": "^3.3.9" + } +} diff --git a/server/index.js b/server/index.js new file mode 100644 index 0000000..e722fcc --- /dev/null +++ b/server/index.js @@ -0,0 +1,14 @@ +// SERVER + +const express = require('express') +const parser = require('body-parser') +const app = express() + +let port = 3000 + +app.use(express.static(__dirname + '/../client/dist')); +app.use(parser.json()); + +app.listen(port, () => { + console.log('listening on port ' +port) +}) \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..b2942e2 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,23 @@ +const path = require('path'); +const SRC_DIR = path.join(__dirname, '/client/src'); +const DIST_DIR = path.join(__dirname, '/client/dist'); + +module.exports = { + entry: `${SRC_DIR}/index.jsx`, + output: { + filename: 'bundle.js', + path: DIST_DIR + }, + module : { + loaders : [ + { + test : /\.jsx?/, + include : SRC_DIR, + loader : 'babel-loader', + query: { + presets: ['react', 'es2015'] + } + } + ] + } +}; \ No newline at end of file From bdfc60f49f991fddfc87727a9a3e12c53eb90512 Mon Sep 17 00:00:00 2001 From: Wei Gao Date: Thu, 31 Oct 2019 10:36:46 -0400 Subject: [PATCH 02/12] Add Portal child component --- .gitignore | 1 + client/dist/index.html | 5 + client/dist/styles.css | 155 +++++++++++++++++++++++++ client/src/components/books.jsx | 34 ++++++ client/src/components/modal.jsx | 31 +++++ client/src/components/selectedBook.jsx | 12 ++ client/src/index.jsx | 65 ++++++++++- package.json | 1 + server/index.js | 23 +++- 9 files changed, 323 insertions(+), 4 deletions(-) create mode 100644 client/dist/styles.css create mode 100644 client/src/components/books.jsx create mode 100644 client/src/components/modal.jsx create mode 100644 client/src/components/selectedBook.jsx diff --git a/.gitignore b/.gitignore index eced731..3be4e05 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /package-lock.json /client/dist/bundle.js /node_modules/ +.env \ No newline at end of file diff --git a/client/dist/index.html b/client/dist/index.html index ab8bd87..e309e01 100644 --- a/client/dist/index.html +++ b/client/dist/index.html @@ -1,7 +1,12 @@ + + OkayReads + +
+ \ No newline at end of file diff --git a/client/dist/styles.css b/client/dist/styles.css new file mode 100644 index 0000000..c3d8ba3 --- /dev/null +++ b/client/dist/styles.css @@ -0,0 +1,155 @@ +html, body, #app { + margin: 0; + padding: 0; + font-family: "Roboto", sans-serif; + font-weight: 400; + height: 100%; + width: 100%; +} + +.app { + height: 100%; + width: 100%; + background-color: rgba(231, 76, 60, 0.25); + display: flex; + flex-direction: column; + overflow: hidden; +} + +.navbar { + width: 100%; + text-align: center; + + box-sizing: border-box; + background-color: #34495e; + color: #fff; + box-shadow: 0 10px 28px -7px rgba(0,0,0,0.5); + z-index:2; + padding: 20px; +} + +.navbar > h1 { + margin: 0; + font-weight: 400; +} + +.main { + flex-grow: 1; + display: flex; +} + +.search { + flex-basis: 20%; + min-width: 200px; + + padding: 20px; + background-color: #d35400; + z-index:2; + text-align:center; + font-size: 20px; +} + +.search > button{ + font-size: 20px; + padding: 10px; +} + +.search > select{ + font-size: 20px; + padding: 10px; +} + +.books { + flex-basis:80%; + display: flex; + flex-wrap: wrap; + + margin:0; + padding: 20px; + overflow-y: scroll; + background-color: #2c3e50; +} + +.book_item { + flex-basis: 22%; + box-sizing: border-box; + margin: 1.5%; + + + display: flex; + flex-direction: column; + list-style: none; + + background-color: #fff; + border: 1px solid #eee; + box-shadow: 0 10px 28px -7px rgba(0,0,0,0.1); +} + +.book_item > img { + width: 100%; +} + +.book_description { + display: flex; + flex-grow: 1; + flex-direction: column; + justify-content: space-between; + padding: 10px; +} + +.book_description h2 { + color: #555; + font-weight: bold; + margin-bottom: 20px; +} + +.book_details { + display: flex; + justify-content: space-between; +} + +.book_details span { + color: #555; + font-size: 0.8rem; + font-weight: bold; +} + +.book_year, .book_rating { + display: flex; + flex-direction: column; +} + +.book_year .title, .book_rating .title { + color: #aaa; + margin-bottom: 5px; + font-size: 0.65rem; + font-weight: normal; +} + +.book_rating { + align-items: flex-end; +} + +#modal-root { + position: relative; + z-index: 999; +} + +.app { + height: 10em; + width: 10em; + background: lightblue; + overflow: hidden; +} + +.modal { + background-color: rgba(248, 247, 247, 0.8); + position: fixed; + height: 100%; + width: 100%; + top: 0; + left: 0; + display: flex; + align-items: center; + justify-content: center; +} diff --git a/client/src/components/books.jsx b/client/src/components/books.jsx new file mode 100644 index 0000000..be7a1db --- /dev/null +++ b/client/src/components/books.jsx @@ -0,0 +1,34 @@ + +import React, { Component } from 'react'; + +class Books extends Component { + + render() { + return ( +
    + { + this.props.books.map((book) => { + return
  • +
    + +
    +

    {book.title}

    +
    +
    + + {book.author} +
    +
    + Rank: + {book.rank} +
    +
    +
    +
  • + }) + } +
) + } +} + +export default Books \ No newline at end of file diff --git a/client/src/components/modal.jsx b/client/src/components/modal.jsx new file mode 100644 index 0000000..485fab3 --- /dev/null +++ b/client/src/components/modal.jsx @@ -0,0 +1,31 @@ + +import React, { Component } from 'react'; +import ReactDOM, { createPortal } from 'react-dom'; + +const modalRoot = document.getElementById('modal-root'); + +class Modal extends Component { + + constructor(props) { + super(props) + this.el = document.createElement('div'); + } + + componentDidMount() { + modalRoot.appendChild(this.el); + } + + componentWillUnmount() { + modalRoot.removeChild(this.el); + } + + render() { + return createPortal( + this.props.children, + this.el, + ); + + } +} + +export default Modal \ No newline at end of file diff --git a/client/src/components/selectedBook.jsx b/client/src/components/selectedBook.jsx new file mode 100644 index 0000000..13f9644 --- /dev/null +++ b/client/src/components/selectedBook.jsx @@ -0,0 +1,12 @@ +import React from 'react' + +const SelectedBook = ({currentBook, handleHideModal}) => ( +
+
+ Selected Book: {currentBook.title} +
+ +
+); + +export default SelectedBook \ No newline at end of file diff --git a/client/src/index.jsx b/client/src/index.jsx index acb0eb3..d58fc2f 100644 --- a/client/src/index.jsx +++ b/client/src/index.jsx @@ -1,13 +1,74 @@ // CLIENT -import React, { Component } from 'react'; +import React, { Component, Fragment } from 'react'; import ReactDOM from 'react-dom'; import axios from 'axios'; +import Books from './components/books.jsx' +import Modal from './components/modal.jsx' +import SelectedBook from './components/selectedBook.jsx' + class App extends Component { + constructor(props) { + super(props) + this.state = { + showWishList: false, + books: [], + currentBook: {}, + showErrorPage: false, + showModal: false, + } + this.getTopBooks = this.getTopBooks.bind(this) + this.handleBookClick = this.handleBookClick.bind(this) + this.handleHideModal = this.handleHideModal.bind(this) + // this.saveToWishList = this.saveToWishList.bind(this) + // this.deleteFromWishList = this.deleteFromWishList.bind(this) + } + + componentDidMount() { + this.getTopBooks() + } + + getTopBooks(){ + axios.get('/books/top') + .then((response) => { + this.setState({ books: response.data.results.books}) + }) + .catch((err) => { + console.log('An error occurred: ', err) + + }) + } + + handleBookClick(e) { + let rank = e.currentTarget.dataset.id + let book = this.state.books[rank-1] + this.setState({showModal: true, currentBook: book}); + } + + handleHideModal() { + this.setState({showModal: false}) + } render() { - return (
HELLO WORLD
) + let modal = this.state.showModal ? + + + + : + + return ( + + + {modal} + + ) } } diff --git a/package.json b/package.json index b18cb03..b2d079d 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "dependencies": { "axios": "^0.18.0", "body-parser": "^1.18.2", + "dotenv": "^8.2.0", "express": "^4.16.3", "nodemon": "^1.19.4", "react": "^16.3.1", diff --git a/server/index.js b/server/index.js index e722fcc..7d9a6ac 100644 --- a/server/index.js +++ b/server/index.js @@ -2,13 +2,32 @@ const express = require('express') const parser = require('body-parser') +const axios = require('axios'); const app = express() -let port = 3000 +if (process.env.NODE_ENV !== 'production') { + require('dotenv').config(); +} + +const port = process.env.PORT; +const apiKey = process.env.API_KEY app.use(express.static(__dirname + '/../client/dist')); app.use(parser.json()); +app.get('/books/top', (req, res) => { + let url = `https://api.nytimes.com/svc/books/v3/lists/current/hardcover-fiction.json?api-key=${apiKey}` + axios.get(url) + .then(response => { + res.end(JSON.stringify(response.data)) + }) + .catch(err => { + res.end(err) + }) +}) + + + app.listen(port, () => { - console.log('listening on port ' +port) + console.log(`listening on port ${port}`) }) \ No newline at end of file From 96f83e1c6509a97ff86fa9d5882d9d1793f651cb Mon Sep 17 00:00:00 2001 From: Wei Gao Date: Thu, 31 Oct 2019 19:22:10 -0400 Subject: [PATCH 03/12] WIP Add description to selectedBook.jsx --- client/dist/styles.css | 6 +++++ client/src/components/selectedBook.jsx | 31 +++++++++++++++++++++----- client/src/index.jsx | 25 ++++++++++++--------- 3 files changed, 46 insertions(+), 16 deletions(-) diff --git a/client/dist/styles.css b/client/dist/styles.css index c3d8ba3..837573a 100644 --- a/client/dist/styles.css +++ b/client/dist/styles.css @@ -106,6 +106,7 @@ html, body, #app { .book_details { display: flex; justify-content: space-between; + } .book_details span { @@ -153,3 +154,8 @@ html, body, #app { align-items: center; justify-content: center; } + +.preview { + background-color: white; + padding: 10px; +} \ No newline at end of file diff --git a/client/src/components/selectedBook.jsx b/client/src/components/selectedBook.jsx index 13f9644..d8d8498 100644 --- a/client/src/components/selectedBook.jsx +++ b/client/src/components/selectedBook.jsx @@ -1,11 +1,32 @@ -import React from 'react' +import React, {Fragment} from 'react' const SelectedBook = ({currentBook, handleHideModal}) => (
-
- Selected Book: {currentBook.title} -
- +
+
+

{currentBook.title}

+
+ {/* div 1 */} +
+ +
+ {/* div 2 */} +
+
+ {currentBook.author} +
+
+ Rank: + {currentBook.rank} +
+ + {currentBook.description}
+ Get the book: {currentBook.amazon_product_url}
+
+
+
+ +
); diff --git a/client/src/index.jsx b/client/src/index.jsx index d58fc2f..c46fdb9 100644 --- a/client/src/index.jsx +++ b/client/src/index.jsx @@ -43,6 +43,7 @@ class App extends Component { handleBookClick(e) { let rank = e.currentTarget.dataset.id let book = this.state.books[rank-1] + console.log(book) this.setState({showModal: true, currentBook: book}); } @@ -53,21 +54,23 @@ class App extends Component { render() { let modal = this.state.showModal ? - +
+
+ +
+
: return ( - - - {modal} - + + + {modal} + + ) } } From 1a3cd621198884e0951b2ef6e7b294b49da62978 Mon Sep 17 00:00:00 2001 From: Wei Gao Date: Sat, 2 Nov 2019 15:40:11 -0400 Subject: [PATCH 04/12] Add wishlist functionality --- client/dist/styles.css | 15 ++++++++- client/src/components/books.jsx | 8 +++++ client/src/components/selectedBook.jsx | 9 ++++-- client/src/index.jsx | 43 +++++++++++++++++++------- 4 files changed, 61 insertions(+), 14 deletions(-) diff --git a/client/dist/styles.css b/client/dist/styles.css index 837573a..9c6bc29 100644 --- a/client/dist/styles.css +++ b/client/dist/styles.css @@ -106,7 +106,7 @@ html, body, #app { .book_details { display: flex; justify-content: space-between; - + } .book_details span { @@ -158,4 +158,17 @@ html, body, #app { .preview { background-color: white; padding: 10px; +} + +.right-align { + float: right; + padding: 5px; +} + +.header { + padding: 10px; +} + +#wishlist-notice{ + visibility: hidden; } \ No newline at end of file diff --git a/client/src/components/books.jsx b/client/src/components/books.jsx index be7a1db..2f6631c 100644 --- a/client/src/components/books.jsx +++ b/client/src/components/books.jsx @@ -4,6 +4,14 @@ import React, { Component } from 'react'; class Books extends Component { render() { + + if (!this.props.books) { + return ( +
Wishlist empty!
+ ) + } + + return (
    { diff --git a/client/src/components/selectedBook.jsx b/client/src/components/selectedBook.jsx index d8d8498..2f2d8ec 100644 --- a/client/src/components/selectedBook.jsx +++ b/client/src/components/selectedBook.jsx @@ -1,6 +1,6 @@ import React, {Fragment} from 'react' -const SelectedBook = ({currentBook, handleHideModal}) => ( +const SelectedBook = ({currentBook, handleHideModal, showWishlist, saveToWishlist, deleteFromWishlist}) => (
    @@ -21,7 +21,12 @@ const SelectedBook = ({currentBook, handleHideModal}) => (
    {currentBook.description}
    - Get the book: {currentBook.amazon_product_url}
    +
    + + {showWishlist ? + : + } +
    diff --git a/client/src/index.jsx b/client/src/index.jsx index c46fdb9..6bbbabf 100644 --- a/client/src/index.jsx +++ b/client/src/index.jsx @@ -12,7 +12,8 @@ class App extends Component { constructor(props) { super(props) this.state = { - showWishList: false, + showWishlist: false, + wishlist: [], books: [], currentBook: {}, showErrorPage: false, @@ -21,8 +22,8 @@ class App extends Component { this.getTopBooks = this.getTopBooks.bind(this) this.handleBookClick = this.handleBookClick.bind(this) this.handleHideModal = this.handleHideModal.bind(this) - // this.saveToWishList = this.saveToWishList.bind(this) - // this.deleteFromWishList = this.deleteFromWishList.bind(this) + this.saveToWishlist = this.saveToWishlist.bind(this) + this.deleteFromWishlist = this.deleteFromWishlist.bind(this) } componentDidMount() { @@ -36,14 +37,12 @@ class App extends Component { }) .catch((err) => { console.log('An error occurred: ', err) - }) } handleBookClick(e) { let rank = e.currentTarget.dataset.id let book = this.state.books[rank-1] - console.log(book) this.setState({showModal: true, currentBook: book}); } @@ -51,21 +50,43 @@ class App extends Component { this.setState({showModal: false}) } + saveToWishlist() { + this.setState({ + wishlist : [this.state.currentBook, ...this.state.wishlist] + }) + } + + deleteFromWishlist(e) { + // let idx = e.currentTarget.dataset.id - 1 + // console.log(idx) + // let newWishlist = this.state.wishlist.splice(idx, 1) + // console.log(newWishlist) + // this.setState({wishlist: newWishlist}) + } + render() { let modal = this.state.showModal ? -
    -
    - -
    -
    +
    : + let navButton = this.state.showWishlist ? "See NYT best sellers" : "See your wishlist"; return ( +
    +

    Okay Reads

    +
    A barely okay clone of Good Reads. Click on a book to learn more about that NYT bestseller. + +
    +
    {modal} From 78e6a92e04852f3b16a462868be7a0b773d438b8 Mon Sep 17 00:00:00 2001 From: Wei Gao Date: Sat, 2 Nov 2019 18:04:36 -0400 Subject: [PATCH 05/12] Prepare to add AWS RDS DB --- client/src/components/selectedBook.jsx | 12 +++++++++--- client/src/index.jsx | 18 +++++++++++++++--- database/index.js | 0 package.json | 3 +++ server/index.js | 19 +++++++++++++++++++ 5 files changed, 46 insertions(+), 6 deletions(-) create mode 100644 database/index.js diff --git a/client/src/components/selectedBook.jsx b/client/src/components/selectedBook.jsx index 2f2d8ec..75670de 100644 --- a/client/src/components/selectedBook.jsx +++ b/client/src/components/selectedBook.jsx @@ -1,6 +1,6 @@ import React, {Fragment} from 'react' -const SelectedBook = ({currentBook, handleHideModal, showWishlist, saveToWishlist, deleteFromWishlist}) => ( +const SelectedBook = ({currentBook, handleHideModal, showWishlist, showMsg, saveToWishlist, deleteFromWishlist}) => (
    @@ -23,9 +23,10 @@ const SelectedBook = ({currentBook, handleHideModal, showWishlist, saveToWishlis {currentBook.description}
    - {showWishlist ? - : + {showWishlist ? + : } +
    {showMsg ? "Success!" : ""}
    @@ -35,4 +36,9 @@ const SelectedBook = ({currentBook, handleHideModal, showWishlist, saveToWishlis
    ); +const handleOnClick = function(method) { + method() + +} + export default SelectedBook \ No newline at end of file diff --git a/client/src/index.jsx b/client/src/index.jsx index 6bbbabf..944f087 100644 --- a/client/src/index.jsx +++ b/client/src/index.jsx @@ -16,14 +16,16 @@ class App extends Component { wishlist: [], books: [], currentBook: {}, - showErrorPage: false, + showError: false, showModal: false, + showMsg: false, } this.getTopBooks = this.getTopBooks.bind(this) this.handleBookClick = this.handleBookClick.bind(this) this.handleHideModal = this.handleHideModal.bind(this) this.saveToWishlist = this.saveToWishlist.bind(this) this.deleteFromWishlist = this.deleteFromWishlist.bind(this) + this.hideBannerAfterDelay = this.hideBannerAfterDelay.bind(this) } componentDidMount() { @@ -52,8 +54,9 @@ class App extends Component { saveToWishlist() { this.setState({ - wishlist : [this.state.currentBook, ...this.state.wishlist] - }) + wishlist : [this.state.currentBook, ...this.state.wishlist], + showMsg : true, + }, this.hideBannerAfterDelay) } deleteFromWishlist(e) { @@ -64,11 +67,20 @@ class App extends Component { // this.setState({wishlist: newWishlist}) } + hideBannerAfterDelay(delay = 2000) { + let self = this; + setTimeout(() => { + self.setState({ showMsg: false }) + }, delay) + } + + render() { let modal = this.state.showModal ? Date: Sat, 2 Nov 2019 19:15:27 -0400 Subject: [PATCH 06/12] Add connection to AWS RDS DB --- server/index.js | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/server/index.js b/server/index.js index 455b5c8..c058b49 100644 --- a/server/index.js +++ b/server/index.js @@ -6,28 +6,27 @@ const mysql = require('mysql'); const axios = require('axios'); const app = express() +if (process.env.NODE_ENV !== 'production') { + require('dotenv').config(); +} + const connection = mysql.createConnection({ - host : process.env.RDS_HOSTNAME, - user : process.env.RDS_USERNAME, - password : process.env.RDS_PASSWORD, - port : process.env.RDS_PORT + host: process.env.RDS_HOSTNAME, + user: process.env.RDS_USERNAME, + password: process.env.RDS_PASSWORD, + port: process.env.RDS_PORT, }); -connection.connect(function(err) { +connection.connect(function (err) { if (err) { console.error('Database connection failed: ' + err.stack); return; } - console.log('Connected to database.'); }); connection.end(); -if (process.env.NODE_ENV !== 'production') { - require('dotenv').config(); -} - const port = process.env.PORT; const apiKey = process.env.API_KEY From cfae041a45fa88481730a735e44196dd00828f5e Mon Sep 17 00:00:00 2001 From: Wei Gao Date: Sat, 2 Nov 2019 20:11:45 -0400 Subject: [PATCH 07/12] Add delete from wishlist functionality --- client/src/components/books.jsx | 4 ++-- client/src/index.jsx | 17 ++++++++--------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/client/src/components/books.jsx b/client/src/components/books.jsx index 2f6631c..025584c 100644 --- a/client/src/components/books.jsx +++ b/client/src/components/books.jsx @@ -15,8 +15,8 @@ class Books extends Component { return (
      { - this.props.books.map((book) => { - return
    • + this.props.books.map((book, idx) => { + return
    • diff --git a/client/src/index.jsx b/client/src/index.jsx index 944f087..f395729 100644 --- a/client/src/index.jsx +++ b/client/src/index.jsx @@ -35,7 +35,7 @@ class App extends Component { getTopBooks(){ axios.get('/books/top') .then((response) => { - this.setState({ books: response.data.results.books}) + this.setState({ books: response.data.results.books, wishlist: response.data.results.books}) }) .catch((err) => { console.log('An error occurred: ', err) @@ -43,9 +43,10 @@ class App extends Component { } handleBookClick(e) { - let rank = e.currentTarget.dataset.id + let rank = e.currentTarget.dataset.rank + let idx = e.currentTarget.dataset.idx let book = this.state.books[rank-1] - this.setState({showModal: true, currentBook: book}); + this.setState({showModal: true, currentBook: book, currentIdx: idx}); } handleHideModal() { @@ -59,12 +60,10 @@ class App extends Component { }, this.hideBannerAfterDelay) } - deleteFromWishlist(e) { - // let idx = e.currentTarget.dataset.id - 1 - // console.log(idx) - // let newWishlist = this.state.wishlist.splice(idx, 1) - // console.log(newWishlist) - // this.setState({wishlist: newWishlist}) + deleteFromWishlist() { + let { wishlist, currentIdx } = this.state + wishlist.splice(currentIdx, 1) + this.setState({wishlist: wishlist}) } hideBannerAfterDelay(delay = 2000) { From 3eb14b40549447507845642ce29d57cb6734c3dd Mon Sep 17 00:00:00 2001 From: Wei Gao Date: Sun, 3 Nov 2019 16:31:07 -0500 Subject: [PATCH 08/12] Add delete from wishlist functionality --- client/src/components/books.jsx | 44 +++++++++++++------------- client/src/index.jsx | 55 +++++++++++++++++---------------- database/index.js | 31 +++++++++++++++++++ database/schema.sql | 0 4 files changed, 81 insertions(+), 49 deletions(-) create mode 100644 database/schema.sql diff --git a/client/src/components/books.jsx b/client/src/components/books.jsx index 2f6631c..46348d5 100644 --- a/client/src/components/books.jsx +++ b/client/src/components/books.jsx @@ -4,7 +4,7 @@ import React, { Component } from 'react'; class Books extends Component { render() { - + if (!this.props.books) { return (
      Wishlist empty!
      @@ -14,28 +14,28 @@ class Books extends Component { return (
        - { - this.props.books.map((book) => { - return
      • -
        - -
        -

        {book.title}

        -
        -
        - - {book.author} -
        -
        - Rank: - {book.rank} + { + this.props.books.map((book, idx) => { + return
      • +
        + +
        +

        {book.title}

        +
        +
        + + {book.author} +
        +
        + Rank: + {book.rank} +
        +
        -
      • -
        -
      • - }) - } -
      ) +
    • + }) + } +
    ) } } diff --git a/client/src/index.jsx b/client/src/index.jsx index 944f087..d4649da 100644 --- a/client/src/index.jsx +++ b/client/src/index.jsx @@ -16,6 +16,7 @@ class App extends Component { wishlist: [], books: [], currentBook: {}, + currentIdx: 0, showError: false, showModal: false, showMsg: false, @@ -32,10 +33,10 @@ class App extends Component { this.getTopBooks() } - getTopBooks(){ + getTopBooks() { axios.get('/books/top') .then((response) => { - this.setState({ books: response.data.results.books}) + this.setState({ books: response.data.results.books }) }) .catch((err) => { console.log('An error occurred: ', err) @@ -43,28 +44,28 @@ class App extends Component { } handleBookClick(e) { - let rank = e.currentTarget.dataset.id - let book = this.state.books[rank-1] - this.setState({showModal: true, currentBook: book}); + let rank = e.currentTarget.dataset.rank + let idx = e.currentTarget.dataset.idx + let book = this.state.books[rank - 1] + this.setState({ showModal: true, currentBook: book, currentIdx: idx }) } handleHideModal() { - this.setState({showModal: false}) + this.setState({ showModal: false }) } saveToWishlist() { this.setState({ - wishlist : [this.state.currentBook, ...this.state.wishlist], - showMsg : true, + wishlist: [this.state.currentBook, ...this.state.wishlist], + showMsg: true, }, this.hideBannerAfterDelay) } - deleteFromWishlist(e) { - // let idx = e.currentTarget.dataset.id - 1 - // console.log(idx) - // let newWishlist = this.state.wishlist.splice(idx, 1) - // console.log(newWishlist) - // this.setState({wishlist: newWishlist}) + deleteFromWishlist() { + let { wishlist, currentIdx } = this.state + let newWishlist = wishlist.slice() + newWishlist.splice(currentIdx, 1) + this.setState({ wishlist: newWishlist }) } hideBannerAfterDelay(delay = 2000) { @@ -76,15 +77,15 @@ class App extends Component { render() { - let modal = this.state.showModal ? + let modal = this.state.showModal ? - + : @@ -94,12 +95,12 @@ class App extends Component {

    Okay Reads

    A barely okay clone of Good Reads. Click on a book to learn more about that NYT bestseller. - -
    + +
    - {modal}
    diff --git a/database/index.js b/database/index.js index e69de29..58ca67b 100644 --- a/database/index.js +++ b/database/index.js @@ -0,0 +1,31 @@ +// DATABASE + +const mysql = require('mysql'); + +if (process.env.NODE_ENV !== 'production') { + require('dotenv').config(); +} + +const connection = mysql.createConnection({ + host: process.env.RDS_HOSTNAME, + user: process.env.RDS_USERNAME, + password: process.env.RDS_PASSWORD, + port: process.env.RDS_PORT, +}); + +const getWishlistBooks = (cb) => { + let query = `SELECT * FROM wishlist`; + connection.query(query, (err, results) => { + callback(err, results); + }) +} + +const addToWishlist = ({ title, rank, author, description }, cb) => { + var query = `INSERT INTO wishlist (title, rank, author, description) VALUE (?, ?, ?, ?);`; + connection.query(query, [title, rank, author, description], (err, results) => { + err ? console.error(err) : callback(err, results); + }); +} + +exports.connection = connection; +exports.getWishlistBooks = getWishlistBooks; diff --git a/database/schema.sql b/database/schema.sql new file mode 100644 index 0000000..e69de29 From 5f7fac1317516b7180f82ec759430ddfe335b54f Mon Sep 17 00:00:00 2001 From: Wei Gao Date: Sun, 3 Nov 2019 20:04:27 -0500 Subject: [PATCH 09/12] Completes MVP --- client/src/components/selectedBook.jsx | 2 +- client/src/index.jsx | 33 +++++++++++++++----------- database/index.js | 7 +++--- server/index.js | 8 ++----- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/client/src/components/selectedBook.jsx b/client/src/components/selectedBook.jsx index 368351f..468f7f2 100644 --- a/client/src/components/selectedBook.jsx +++ b/client/src/components/selectedBook.jsx @@ -26,7 +26,7 @@ const SelectedBook = ({currentBook, handleHideModal, showWishlist, showMsg, addT {showWishlist ? : } -
    {showMsg ? "Success!" : ""}
    +
    {showMsg}
    diff --git a/client/src/index.jsx b/client/src/index.jsx index 3bd8efa..6f6dab5 100644 --- a/client/src/index.jsx +++ b/client/src/index.jsx @@ -8,6 +8,8 @@ import Books from './components/books.jsx' import Modal from './components/modal.jsx' import SelectedBook from './components/selectedBook.jsx' +const errorMessage = "Oh no! An error occurred. Please try again later." + class App extends Component { constructor(props) { super(props) @@ -36,20 +38,14 @@ class App extends Component { getTopBooks() { axios.get('/books/top') - .then(({data}) => { - this.setState({ books: data.results.books }) - }) - .catch((err) => { - console.log('An error occurred: ', err) - }) + .then(({data}) => this.setState({ books: data.results.books })) + .catch((err) => console.log('An error occurred: ', err)) } getWishlistBooks() { axios.get('/books/wishlist') - .then(({data}) => { - this.setState({ wishlist: data }) - }) - .catch((err) => console.log("An error occured:", err)) + .then(({data}) => this.setState({ wishlist: data })) + .catch((err) => console.err(err)) } handleBookClick(e) { @@ -64,8 +60,15 @@ class App extends Component { addToWishlist() { axios.post('/books/wishlist', {currentBook: this.state.currentBook}) - .then((data) => { - this.setState({showMsg: true}, () => { + .then((response) => { + let message = "Good choice! Successfully added to your wishlist :)" + + if(response && response.data) { + if(response.data === 'ER_DUP_ENTRY') message = "This title is already in your wishlist!" + else message = errorMessage + } + + this.setState({showMsg: message}, () => { this.hideBannerAfterDelay() this.getWishlistBooks() }) @@ -76,8 +79,10 @@ class App extends Component { deleteFromWishlist() { let {currentBook} = this.state axios.delete('/books/wishlist', {data: { currentBook, }}) - .then((data) => { - this.setState({showMsg: true}, () => { + .then((response) => { + let message = "Successfully deleted from your wishlist."; + if (response) message = errorMessage + this.setState({showMsg: message}, () => { this.hideBannerAfterDelay() this.getWishlistBooks() }) diff --git a/database/index.js b/database/index.js index 19acc46..09853b9 100644 --- a/database/index.js +++ b/database/index.js @@ -33,10 +33,9 @@ const getWishlistBooks = (cb) => { const addToWishlist = ({ title, book_image, amazon_product_url, author, rank, description, primary_isbn10 }, cb) => { var query = `INSERT INTO wishlist (title, book_image, amazon_product_url, author, rank, description, primary_isbn10) VALUE (?, ?, ?, ?, ?, ?, ?);`; - connection.query(query, [title, book_image, amazon_product_url, author, rank, description, primary_isbn10], (err, results) => { - console.log(err) - //err.code === 'ER_DUP_ENTRY' - cb(err); + connection.query(query, [title, book_image, amazon_product_url, author, rank, description, primary_isbn10], (err) => { + if(err) cb(err.code) + else cb(null) }); } diff --git a/server/index.js b/server/index.js index 8cf8dc2..cf9811f 100644 --- a/server/index.js +++ b/server/index.js @@ -20,12 +20,8 @@ app.use(parser.json()); app.get('/books/top', (req, res) => { let url = `https://api.nytimes.com/svc/books/v3/lists/current/hardcover-fiction.json?api-key=${apiKey}` axios.get(url) - .then(response => { - res.end(JSON.stringify(response.data)) - }) - .catch(err => { - res.end(err) - }) + .then(response => res.end(JSON.stringify(response.data))) + .catch(err => res.end(err)) }) app.get('/books/wishlist', (req, res) => { From b27b0685621521310915fbb9def4bf0b0bcc43b2 Mon Sep 17 00:00:00 2001 From: Wei Gao Date: Mon, 4 Nov 2019 18:00:52 -0500 Subject: [PATCH 10/12] Set up file structure for AWS EB deployment --- .gitignore | 10 +++++++++- server/index.js => app.js | 6 +++--- client/src/index.jsx | 2 +- package.json | 6 ++---- 4 files changed, 15 insertions(+), 9 deletions(-) rename server/index.js => app.js (87%) diff --git a/.gitignore b/.gitignore index 3be4e05..525e668 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,12 @@ /package-lock.json /client/dist/bundle.js +/client/.DS_Store /node_modules/ -.env \ No newline at end of file +Archive.zip +.env +.DS_Store + +# Elastic Beanstalk Files +.elasticbeanstalk/* +!.elasticbeanstalk/*.cfg.yml +!.elasticbeanstalk/*.global.yml diff --git a/server/index.js b/app.js similarity index 87% rename from server/index.js rename to app.js index cf9811f..b5a7d68 100644 --- a/server/index.js +++ b/app.js @@ -5,16 +5,16 @@ const parser = require('body-parser') const axios = require('axios'); const app = express() -const db = require('../database/index.js') +const db = require('./database/index.js') if (process.env.NODE_ENV !== 'production') { require('dotenv').config(); } -const port = process.env.PORT; +const port = process.env.PORT || 8000; const apiKey = process.env.API_KEY -app.use(express.static(__dirname + '/../client/dist')); +app.use(express.static(__dirname + '/client/dist')); app.use(parser.json()); app.get('/books/top', (req, res) => { diff --git a/client/src/index.jsx b/client/src/index.jsx index 6f6dab5..0ca486b 100644 --- a/client/src/index.jsx +++ b/client/src/index.jsx @@ -81,7 +81,7 @@ class App extends Component { axios.delete('/books/wishlist', {data: { currentBook, }}) .then((response) => { let message = "Successfully deleted from your wishlist."; - if (response) message = errorMessage + if (response.data) message = errorMessage this.setState({showMsg: message}, () => { this.hideBannerAfterDelay() this.getWishlistBooks() diff --git a/package.json b/package.json index 83b7b9f..aa2cbdc 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,8 @@ "description": "Bellese technologies coding take home challenge", "main": "index.js", "scripts": { - "build": "webpack --config webpack.config.js", - "react-dev": "webpack -d --watch", - "server-dev": "nodemon server/index.js" + "start": "node app.js", + "build": "webpack --config webpack.config.js" }, "author": "WG", "homepage": "https://github.com/weigao10/fullstack-developer-challenge", @@ -18,7 +17,6 @@ "ejs": "^2.7.1", "express": "^4.16.3", "mysql": "^2.17.1", - "nodemon": "^1.19.4", "react": "^16.3.1", "react-dom": "^16.3.1" }, From 6566ace509835e2723ce79ee3f0fb86d6fff712a Mon Sep 17 00:00:00 2001 From: Wei Gao Date: Mon, 4 Nov 2019 21:43:00 -0500 Subject: [PATCH 11/12] Add button styling --- client/dist/styles.css | 92 ++++++++++++-------------- client/src/components/selectedBook.jsx | 58 ++++++++-------- client/src/index.jsx | 24 +++---- package.json | 7 +- 4 files changed, 88 insertions(+), 93 deletions(-) diff --git a/client/dist/styles.css b/client/dist/styles.css index 9c6bc29..9bff2d7 100644 --- a/client/dist/styles.css +++ b/client/dist/styles.css @@ -7,7 +7,7 @@ html, body, #app { width: 100%; } -.app { +#app { height: 100%; width: 100%; background-color: rgba(231, 76, 60, 0.25); @@ -16,55 +16,17 @@ html, body, #app { overflow: hidden; } -.navbar { - width: 100%; - text-align: center; - - box-sizing: border-box; - background-color: #34495e; - color: #fff; - box-shadow: 0 10px 28px -7px rgba(0,0,0,0.5); - z-index:2; - padding: 20px; -} - -.navbar > h1 { - margin: 0; - font-weight: 400; -} - .main { flex-grow: 1; display: flex; } -.search { - flex-basis: 20%; - min-width: 200px; - - padding: 20px; - background-color: #d35400; - z-index:2; - text-align:center; - font-size: 20px; -} - -.search > button{ - font-size: 20px; - padding: 10px; -} - -.search > select{ - font-size: 20px; - padding: 10px; -} - .books { flex-basis:80%; display: flex; flex-wrap: wrap; - - margin:0; + min-height: 20rem; + margin: 0; padding: 20px; overflow-y: scroll; background-color: #2c3e50; @@ -74,18 +36,15 @@ html, body, #app { flex-basis: 22%; box-sizing: border-box; margin: 1.5%; - - display: flex; flex-direction: column; list-style: none; - background-color: #fff; border: 1px solid #eee; box-shadow: 0 10px 28px -7px rgba(0,0,0,0.1); } -.book_item > img { +.book_item > div > img { width: 100%; } @@ -120,7 +79,7 @@ html, body, #app { flex-direction: column; } -.book_year .title, .book_rating .title { +.book_year, .book_rating{ color: #aaa; margin-bottom: 5px; font-size: 0.65rem; @@ -131,11 +90,6 @@ html, body, #app { align-items: flex-end; } -#modal-root { - position: relative; - z-index: 999; -} - .app { height: 10em; width: 10em; @@ -143,6 +97,15 @@ html, body, #app { overflow: hidden; } +#modal-root { + position: relative; + z-index: 999; +} + +.modal-right { + padding: 2rem; +} + .modal { background-color: rgba(248, 247, 247, 0.8); position: fixed; @@ -155,9 +118,14 @@ html, body, #app { justify-content: center; } +.modal-left{ + padding-bottom: 1rem; +} + .preview { background-color: white; padding: 10px; + max-width: 50%; } .right-align { @@ -169,6 +137,28 @@ html, body, #app { padding: 10px; } +button{ + border: 0; + background: #21a8ca; + border-radius: 5px; + padding: 0.5rem 1rem; + font-size: 0.8rem; + line-height: 1; + color: white; + cursor: pointer; +} + +.close-btn { + width: 100%; + border: 0; + background: #21a8ca; + border-radius: 5px; + padding: 0.5rem 1rem; + font-size: 0.8rem; + line-height: 1; + color: white; +} + #wishlist-notice{ visibility: hidden; } \ No newline at end of file diff --git a/client/src/components/selectedBook.jsx b/client/src/components/selectedBook.jsx index 468f7f2..cd2a03a 100644 --- a/client/src/components/selectedBook.jsx +++ b/client/src/components/selectedBook.jsx @@ -1,42 +1,44 @@ -import React, {Fragment} from 'react' +import React, { Fragment } from 'react' -const SelectedBook = ({currentBook, handleHideModal, showWishlist, showMsg, addToWishlist, deleteFromWishlist}) => ( +const SelectedBook = ({ currentBook, handleHideModal, showWishlist, showMsg, addToWishlist, deleteFromWishlist }) => (
    -
    -

    {currentBook.title}

    -
    - {/* div 1 */} -
    - -
    - {/* div 2 */} -
    -
    - {currentBook.author} +
    +

    {currentBook.title}

    +
    + {/* div 1 */} +
    + +
    + {/* div 2 */} +
    +
    + Author: {currentBook.author}
    -
    - Rank: - {currentBook.rank} +
    + Rank: {currentBook.rank} +

    +
    + {currentBook.description} +

    +
    +

    + {showWishlist ? + : + }

    +
    {showMsg}
    - - {currentBook.description}
    -
    - - {showWishlist ? - : - } -
    {showMsg}
    +
    +
    +
    -
    -
    - +
    ); -const handleOnClick = function(method) { +const handleOnClick = function (method) { method() } diff --git a/client/src/index.jsx b/client/src/index.jsx index 0ca486b..f3b0ee5 100644 --- a/client/src/index.jsx +++ b/client/src/index.jsx @@ -38,14 +38,14 @@ class App extends Component { getTopBooks() { axios.get('/books/top') - .then(({data}) => this.setState({ books: data.results.books })) + .then(({ data }) => this.setState({ books: data.results.books })) .catch((err) => console.log('An error occurred: ', err)) } getWishlistBooks() { axios.get('/books/wishlist') - .then(({data}) => this.setState({ wishlist: data })) - .catch((err) => console.err(err)) + .then(({ data }) => this.setState({ wishlist: data })) + .catch((err) => console.err('An error occurred:', err)) } handleBookClick(e) { @@ -59,30 +59,30 @@ class App extends Component { } addToWishlist() { - axios.post('/books/wishlist', {currentBook: this.state.currentBook}) + axios.post('/books/wishlist', { currentBook: this.state.currentBook }) .then((response) => { let message = "Good choice! Successfully added to your wishlist :)" - - if(response && response.data) { - if(response.data === 'ER_DUP_ENTRY') message = "This title is already in your wishlist!" + + if (response && response.data) { + if (response.data === 'ER_DUP_ENTRY') message = "This title is already in your wishlist!" else message = errorMessage } - this.setState({showMsg: message}, () => { + this.setState({ showMsg: message }, () => { this.hideBannerAfterDelay() this.getWishlistBooks() }) }) - .catch((err) => console.log('err', err)) + .catch((err) => console.err('An error occurred:', err)) } deleteFromWishlist() { - let {currentBook} = this.state - axios.delete('/books/wishlist', {data: { currentBook, }}) + let { currentBook } = this.state + axios.delete('/books/wishlist', { data: { currentBook, } }) .then((response) => { let message = "Successfully deleted from your wishlist."; if (response.data) message = errorMessage - this.setState({showMsg: message}, () => { + this.setState({ showMsg: message }, () => { this.hideBannerAfterDelay() this.getWishlistBooks() }) diff --git a/package.json b/package.json index aa2cbdc..3c0db0c 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,9 @@ "main": "index.js", "scripts": { "start": "node app.js", - "build": "webpack --config webpack.config.js" + "build": "webpack --config webpack.config.js", + "react-dev": "webpack -d --watch", + "server-dev": "nodemon app.js" }, "author": "WG", "homepage": "https://github.com/weigao10/fullstack-developer-challenge", @@ -17,6 +19,7 @@ "ejs": "^2.7.1", "express": "^4.16.3", "mysql": "^2.17.1", + "nodemon": "^1.19.4", "react": "^16.3.1", "react-dom": "^16.3.1" }, @@ -28,4 +31,4 @@ "webpack": "^2.7.0", "webpack-cli": "^3.3.9" } -} +} \ No newline at end of file From 2c4519631f8ee7439edd3793430b007973dc2834 Mon Sep 17 00:00:00 2001 From: Wei Gao Date: Mon, 4 Nov 2019 22:16:35 -0500 Subject: [PATCH 12/12] Add x to exit out of modal --- client/dist/styles.css | 13 +++++++++++-- client/src/components/books.jsx | 4 +++- client/src/components/selectedBook.jsx | 2 +- client/src/index.jsx | 2 +- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/client/dist/styles.css b/client/dist/styles.css index 9bff2d7..00e24e3 100644 --- a/client/dist/styles.css +++ b/client/dist/styles.css @@ -21,6 +21,10 @@ html, body, #app { display: flex; } +.wishlist-empty{ + color: white; +} + .books { flex-basis:80%; display: flex; @@ -42,6 +46,7 @@ html, body, #app { background-color: #fff; border: 1px solid #eee; box-shadow: 0 10px 28px -7px rgba(0,0,0,0.1); + cursor: pointer; } .book_item > div > img { @@ -65,7 +70,6 @@ html, body, #app { .book_details { display: flex; justify-content: space-between; - } .book_details span { @@ -122,10 +126,15 @@ html, body, #app { padding-bottom: 1rem; } +#modal-x{ + float: right; + cursor: pointer; +} + .preview { background-color: white; padding: 10px; - max-width: 50%; + max-width: 70%; } .right-align { diff --git a/client/src/components/books.jsx b/client/src/components/books.jsx index 273b671..8bb6ff7 100644 --- a/client/src/components/books.jsx +++ b/client/src/components/books.jsx @@ -7,7 +7,9 @@ class Books extends Component { if (!this.props.books) { return ( -
    Wishlist empty!
    +
    + Oh no! You haven't added any books to your wishlist. Check out the NYT bestsellers and start adding now! +
    ) } diff --git a/client/src/components/selectedBook.jsx b/client/src/components/selectedBook.jsx index cd2a03a..7592879 100644 --- a/client/src/components/selectedBook.jsx +++ b/client/src/components/selectedBook.jsx @@ -4,7 +4,7 @@ const SelectedBook = ({ currentBook, handleHideModal, showWishlist, showMsg, add
    -

    {currentBook.title}

    +

    {currentBook.title} X

    {/* div 1 */}
    diff --git a/client/src/index.jsx b/client/src/index.jsx index f3b0ee5..f07bcb4 100644 --- a/client/src/index.jsx +++ b/client/src/index.jsx @@ -110,7 +110,7 @@ class App extends Component { : - let navButton = this.state.showWishlist ? "See NYT best sellers" : "See your wishlist"; + let navButton = this.state.showWishlist ? "See NYT bestsellers" : "See your wishlist"; return (