From 953ea004c81c055568456fccfa79886957dd20fa Mon Sep 17 00:00:00 2001 From: Benny Ogidan Date: Wed, 29 Nov 2017 22:14:33 +0100 Subject: [PATCH] feature(search): Implement search feature app - Implemented search function for app utilising search books api - Implemented search component and strategy on reducer [Delivers #153149549] --- client/src/app/actions/actiontype.js | 6 +- client/src/app/actions/api.js | 3 + client/src/app/actions/fetchbooks.js | 7 +- client/src/app/actions/searchbooks.js | 29 ++++++++ .../container/booklist/DisplayAllBooks.jsx | 4 + .../app/components/presentation/Dashboard.jsx | 35 ++++++--- .../presentation/common/book/DisplayBook.jsx | 1 - .../common/book/DisplayBookModal.jsx | 2 +- .../presentation/common/book/SearchBooks.jsx | 74 +++++++++++++++++++ .../dashboardMessages/MessageforNoBooks.jsx | 2 +- .../MessageforNoBooksHistory.jsx | 6 +- .../MessageforNoOverdueBooks.jsx | 4 +- client/src/app/css/style.scss | 14 ++++ client/src/app/reducers/bookReducers.js | 19 ++++- package.json | 13 ++-- 15 files changed, 184 insertions(+), 35 deletions(-) create mode 100644 client/src/app/actions/searchbooks.js create mode 100644 client/src/app/components/presentation/common/book/SearchBooks.jsx diff --git a/client/src/app/actions/actiontype.js b/client/src/app/actions/actiontype.js index 8086955..7abf9d8 100644 --- a/client/src/app/actions/actiontype.js +++ b/client/src/app/actions/actiontype.js @@ -5,8 +5,6 @@ export const USER_LOG_IN_FAILURE = 'USER_LOG_IN_FAILURE'; export const USER_LOGGED_OUT = 'USER_LOGGED_OUT'; -export const FETCHING_BOOKS = 'FETCHING_BOOKS'; - export const SIGN_UP_USER_FAILURE = 'SIGN_UP_USER_FAILURE'; export const SIGNUP_USER_SUCCESS = 'SIGNUP_USER_SUCCESS'; @@ -27,6 +25,10 @@ export const UPLOAD_TO_CLOUD_IMAGE_SUCCESS = 'UPLOAD_TO_CLOUD_IMAGE_SUCCESS'; export const UPLOAD_TO_CLOUD_IMAGE_FAILURE = 'UPLOAD_TO_CLOUD_IMAGE_FAILURE'; +export const SEARCH_BOOKS_SUCCESS = 'SEARCH_BOOKS_SUCCESS'; + +export const SEARCH_BOOKS_FAILURE = 'SEARCH_BOOKS_FAILURE'; + export const CLOUDINARY_UPLOAD_PRESET = 'yn0wpv0n'; export const CLOUDINARY_UPLOAD_URL = 'https://api.cloudinary.com/v1_1/digpnxufx/upload'; diff --git a/client/src/app/actions/api.js b/client/src/app/actions/api.js index b056f19..4568921 100644 --- a/client/src/app/actions/api.js +++ b/client/src/app/actions/api.js @@ -25,6 +25,9 @@ export default { .then(res => res.data), loanhistory: (offset, limit) => axios .get(`api/v1/users/getloanhistory?offset=${offset}&limit=${limit}`) + .then(res => res.data), + searchBooks: value => axios + .get(`api/v1/books/search?searchTerm=${value}`) .then(res => res.data) } }; diff --git a/client/src/app/actions/fetchbooks.js b/client/src/app/actions/fetchbooks.js index 381473f..bb8caba 100644 --- a/client/src/app/actions/fetchbooks.js +++ b/client/src/app/actions/fetchbooks.js @@ -4,7 +4,7 @@ import { FETCH_ALL_BOOKS, FETCH_BOOKS_REJECTED, FETCH_BOOKS_BY_USER_ID, - FETCHING_BOOKS, + FETCH_ALL_OVERDUE_BOOKS } from './actiontype'; import api from './api'; @@ -13,7 +13,6 @@ export const fetchBooksRejected = error => ({ type: FETCH_BOOKS_REJECTED, error export const fetchRecentBooks = books => ({ type: FETCH_ALL_RECENT_BOOKS, books }); export const fetchBooks = books => ({ type: FETCH_ALL_BOOKS, books }); export const fetchBooksByUserId = books => ({ type: FETCH_BOOKS_BY_USER_ID, books }); -export const fetchingBooks = state => ({ type: FETCHING_BOOKS, state }); export const fetchOverdueBooks = books => ({ type: FETCH_ALL_OVERDUE_BOOKS, books }); /** @@ -28,13 +27,11 @@ export const fetchAllBooks = (offset, limit) => dispatch => api .fetch(offset, limit) .then((response) => { dispatch(fetchBooks(response)); - dispatch(fetchingBooks(false)); return response; }) .catch((error) => { dispatch(showErrorNotification({ error })); dispatch(fetchBooksRejected({ error })); - dispatch(fetchingBooks(false)); }); /** @@ -69,13 +66,11 @@ export const fetchAllRecentBooks = (offset, limit) => dispatch => api .fetchRecentBooks(offset, limit) .then((response) => { dispatch(fetchRecentBooks(response)); - dispatch(fetchingBooks(false)); return response; }) .catch((error) => { dispatch(showErrorNotification({ error })); dispatch(fetchBooksRejected({ error })); - dispatch(fetchingBooks(false)); }); /** diff --git a/client/src/app/actions/searchbooks.js b/client/src/app/actions/searchbooks.js new file mode 100644 index 0000000..81a5543 --- /dev/null +++ b/client/src/app/actions/searchbooks.js @@ -0,0 +1,29 @@ +import { showErrorNotification } from './notifications'; +import { + SEARCH_BOOKS_SUCCESS, + SEARCH_BOOKS_FAILURE +} from './actiontype'; +import api from './api'; + +export const SearchBookSuccess = books => ({ type: SEARCH_BOOKS_SUCCESS, books }); +export const SearchBookFailure = error => ({ type: SEARCH_BOOKS_FAILURE, error }); + + +/** + * async helper function: search Books + * @function SearchBooks + * @param {object} value + * @returns {function} asynchronous action + */ +export const searchAllBooks = value => dispatch => api + .book + .searchBooks(value) + .then((response) => { + dispatch(SearchBookSuccess(response.booksFound)); + return (response); + }) + .catch((error) => { + dispatch(showErrorNotification({ error })); + dispatch(SearchBookFailure({ error })); + return error; + }); diff --git a/client/src/app/components/container/booklist/DisplayAllBooks.jsx b/client/src/app/components/container/booklist/DisplayAllBooks.jsx index 4f329cc..e370e3b 100644 --- a/client/src/app/components/container/booklist/DisplayAllBooks.jsx +++ b/client/src/app/components/container/booklist/DisplayAllBooks.jsx @@ -5,6 +5,7 @@ import { Row, Preloader, Col } from 'react-materialize'; import Book from '../../presentation/common/book/DisplayBook.jsx'; import { fetchAllBooks } from '../../../actions/fetchbooks'; import PaginationWrapper from '../common/Pagination.jsx'; +import SearchBooks from '../../presentation/common/book/SearchBooks.jsx'; /** * @description Component for Display Books on the Landing page for all users @@ -65,6 +66,9 @@ this.props.allBooksList.books.map(book => ( }; return (
+ + + { diff --git a/client/src/app/components/presentation/Dashboard.jsx b/client/src/app/components/presentation/Dashboard.jsx index 980d1bc..f21deb7 100644 --- a/client/src/app/components/presentation/Dashboard.jsx +++ b/client/src/app/components/presentation/Dashboard.jsx @@ -1,18 +1,20 @@ import React from 'react'; -import { Row } from 'react-materialize'; +import { Row, Icon, Col } from 'react-materialize'; import { Tab, Tabs, TabList, TabPanel } from 'react-tabs'; +import PropTypes from 'prop-types'; import SideNav from '../presentation/common/SideNav/index.jsx'; import DisplayAllBorrowedBooks from '../container/booklist/DisplayBorrowedBooks.jsx'; import DisplayAllBooks from '../container/booklist/DisplayAllBooks.jsx'; import LoanHistoryTable from '../container/loanhistory/LoanHistory.jsx'; import DisplayOverdueBooks from '../container/booklist/DisplayOverdueBooks.jsx'; + /** * @description Show User Dashboard * @class DashboardView * @param {object} props */ -const Dashoard = props => +const Dashboard = props => (
@@ -24,12 +26,19 @@ const Dashoard = props => />
- - DASHBOARD - ALL BOOKS - BOOKS OVERDUE - LOAN HISTORY - + + + + DASHBOARD + ALL BOOKS + BOOKS OVERDUE + LOAN HISTORY + + + + {/* search + */} + @@ -51,4 +60,12 @@ const Dashoard = props =>
); -export default Dashoard; +Dashboard.propTypes = { + firstname: PropTypes.string.isRequired, + username: PropTypes.string.isRequired, + email: PropTypes.string.isRequired, + profilePic: PropTypes.string.isRequired, +}; + + +export default Dashboard; diff --git a/client/src/app/components/presentation/common/book/DisplayBook.jsx b/client/src/app/components/presentation/common/book/DisplayBook.jsx index 10e6275..3785ffb 100644 --- a/client/src/app/components/presentation/common/book/DisplayBook.jsx +++ b/client/src/app/components/presentation/common/book/DisplayBook.jsx @@ -1,5 +1,4 @@ import React from 'react'; -import PropTypes from 'prop-types'; import ReactTooltip from 'react-tooltip'; import DisplayBookModal from './DisplayBookModal.jsx'; diff --git a/client/src/app/components/presentation/common/book/DisplayBookModal.jsx b/client/src/app/components/presentation/common/book/DisplayBookModal.jsx index dd9eccb..e649c9c 100644 --- a/client/src/app/components/presentation/common/book/DisplayBookModal.jsx +++ b/client/src/app/components/presentation/common/book/DisplayBookModal.jsx @@ -30,7 +30,7 @@ class DisplayBookModal extends React.Component { } } - handleChange = date => this.setState({ returndate: date }); + handleChange = date => this.setState({ returnDate: date }); handleReturnClick = (event) =>{ this. diff --git a/client/src/app/components/presentation/common/book/SearchBooks.jsx b/client/src/app/components/presentation/common/book/SearchBooks.jsx new file mode 100644 index 0000000..d095583 --- /dev/null +++ b/client/src/app/components/presentation/common/book/SearchBooks.jsx @@ -0,0 +1,74 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import { Col, Icon,Autocomplete } from 'react-materialize'; +import { searchAllBooks } from '../../../../actions/searchbooks'; +import { fetchAllBooks } from '../../../../actions/fetchbooks'; + + +/** + * + * + * @class SearchBooks + * @extends {React.Component} + */ +class SearchBooks extends React.Component { + /** + * Creates an instance of SearchBooks. + * @param {any} props + * + * @memberOf SearchBooks + */ + constructor(props) { + super(props); + + this.state = { + searchTerm:'', + offset: 0, + limit:8, + dataSource: {} + }; + this.onChange = this.onChange.bind(this) + } + + onChange = (event, value ) => { + event.preventDefault(); + this.setState({ + searchTerm: value + }); + if(value.length> 1){ + this.props.searchAllBooks(value) + } + else{ + this.props.fetchAllBooks(this.state.offset, this.state.limit); + } + + }; + + render() { + return ( + +
+ +
+ search + +
+ + + + + +
+ + + ); + } +} + + +export default connect(null, { fetchAllBooks, searchAllBooks })(SearchBooks); diff --git a/client/src/app/components/presentation/messages/dashboardMessages/MessageforNoBooks.jsx b/client/src/app/components/presentation/messages/dashboardMessages/MessageforNoBooks.jsx index 1a76e47..60894ac 100644 --- a/client/src/app/components/presentation/messages/dashboardMessages/MessageforNoBooks.jsx +++ b/client/src/app/components/presentation/messages/dashboardMessages/MessageforNoBooks.jsx @@ -8,7 +8,7 @@ import { Row } from 'react-materialize'; */ const MessageforNoBooks = () => ( -

+

You have not borrowed any books.

diff --git a/client/src/app/components/presentation/messages/dashboardMessages/MessageforNoBooksHistory.jsx b/client/src/app/components/presentation/messages/dashboardMessages/MessageforNoBooksHistory.jsx index fdac74e..d5dacb2 100644 --- a/client/src/app/components/presentation/messages/dashboardMessages/MessageforNoBooksHistory.jsx +++ b/client/src/app/components/presentation/messages/dashboardMessages/MessageforNoBooksHistory.jsx @@ -7,10 +7,10 @@ import { Row } from 'react-materialize'; */ const MessageforNoBooksHistory = () => ( -

+

You have no loan history. Go and search our collection and loan some books -

+
); -export default MessageforNoBooksHistory +export default MessageforNoBooksHistory; diff --git a/client/src/app/components/presentation/messages/dashboardMessages/MessageforNoOverdueBooks.jsx b/client/src/app/components/presentation/messages/dashboardMessages/MessageforNoOverdueBooks.jsx index 8234d4e..dbf6f57 100644 --- a/client/src/app/components/presentation/messages/dashboardMessages/MessageforNoOverdueBooks.jsx +++ b/client/src/app/components/presentation/messages/dashboardMessages/MessageforNoOverdueBooks.jsx @@ -6,8 +6,8 @@ import React from 'react'; * @class MessageforNoOverdueBooks */ const MessageforNoOverdueBooks = () => ( -

+

You have no overdue books at present

); -export default MessageforNoOverdueBooks +export default MessageforNoOverdueBooks; diff --git a/client/src/app/css/style.scss b/client/src/app/css/style.scss index bd9831e..aad3171 100644 --- a/client/src/app/css/style.scss +++ b/client/src/app/css/style.scss @@ -277,6 +277,20 @@ footer { color: $base-white; } +.autocomplete{ + height: 2.4rem !important; +} + +i.medium{ + font-size: 2.5rem ; + padding-top: 14px ; +// margin-left: -6px; +} + +.react-autosuggest__container{ + padding-top: 5.5px; +} + .btn { background-color: black; font-family: $font-stack2; diff --git a/client/src/app/reducers/bookReducers.js b/client/src/app/reducers/bookReducers.js index 3a6d248..0f79a4b 100644 --- a/client/src/app/reducers/bookReducers.js +++ b/client/src/app/reducers/bookReducers.js @@ -9,8 +9,9 @@ import { RETURN_BOOKS_SUCCESS, LOAN_HISTORY_FAILURE, LOAN_HISTORY_SUCCESS, - FETCHING_BOOKS, - FETCH_ALL_OVERDUE_BOOKS + FETCH_ALL_OVERDUE_BOOKS, + SEARCH_BOOKS_SUCCESS, + SEARCH_BOOKS_FAILURE } from '../actions/actiontype'; /** @@ -25,11 +26,21 @@ import { export default function bookReducer(state = { }, action) { switch (action.type) { - case FETCHING_BOOKS: + case SEARCH_BOOKS_SUCCESS: + { return { ...state, - fetchingBooks: action.state + allBooksList: + action.books }; + } + case SEARCH_BOOKS_FAILURE: + { + return { + ...state, + error: action.message + }; + } case FETCH_ALL_OVERDUE_BOOKS: { return { diff --git a/package.json b/package.json index 3233ffa..bb7ee88 100644 --- a/package.json +++ b/package.json @@ -11,10 +11,10 @@ "test": "export NODE_ENV=test && mocha server/dist/test/index.spec.js --timeout 10000 && export NODE_ENV=development", "client:test": "jest client/ --coverage", "coverage": "export NODE_ENV=test && npm run pretest && nyc --reporter=lcov --reporter=text --reporter=lcovonly mocha --compilers js:babel-register server/src/test/*", - "start:migrate": "sequelize db:migrate ", - "undo:migrate": "sequelize db:migrate:undo:all", - "seed:all":"sequelize db:seed:all", - "undo:seed":"sequelize db:seed:undo:all", + "start:migrate": "sequelize db:migrate ", + "undo:migrate": "sequelize db:migrate:undo:all", + "seed:all": "sequelize db:seed:all", + "undo:seed": "sequelize db:seed:undo:all", "start:dev": "babel-node server/dist/bin/www.js", "start:webdev": "webpack -d && webpack-dev-server && --content-base client/src/ --inline --hot", "start-prod": "babel server/src --out-dir server/dist --presets es2015 && npm run heroku-postbuild", @@ -89,14 +89,15 @@ "morgan": "^1.8.2", "muicss": "^0.9.25", "node-cron": "^1.2.1", - "node-sass": "^4.5.3", - "nodemailer": "^4.4.0", + "node-sass": "^4.5.3", + "nodemailer": "^4.4.0", "nodemailer-mailgun-transport": "^1.3.5", "nodemon": "^1.11.0", "pg": "^7.0.2", "pg-hstore": "^2.3.2", "prop-types": "^15.5.10", "react": "^15.6.1", + "react-autosuggest": "^9.3.2", "react-cookie": "^2.0.8", "react-datepicker": "^0.61.0", "react-dom": "^15.6.1",