Skip to content

Commit

Permalink
list and create books
Browse files Browse the repository at this point in the history
  • Loading branch information
Remchi committed Oct 14, 2017
1 parent 01945a8 commit 9fe3521
Show file tree
Hide file tree
Showing 11 changed files with 104 additions and 16 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"axios": "^0.16.2",
"gravatar-url": "^2.0.0",
"jwt-decode": "^2.2.0",
"normalizr": "^3.2.4",
"prop-types": "^15.5.10",
"react": "^15.6.1",
"react-dom": "^15.6.1",
Expand Down
25 changes: 25 additions & 0 deletions src/actions/books.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { normalize } from "normalizr";
import { BOOKS_FETCHED, BOOK_CREATED } from "../types";
import api from "../api";
import { bookSchema } from "../schemas";

// data.entities.books
const booksFetched = data => ({
type: BOOKS_FETCHED,
data
});

const bookCreated = data => ({
type: BOOK_CREATED,
data
});

export const fetchBooks = () => dispatch =>
api.books
.fetchAll()
.then(books => dispatch(booksFetched(normalize(books, [bookSchema]))));

export const createBook = data => dispatch =>
api.books
.create(data)
.then(book => dispatch(bookCreated(normalize(book, bookSchema))));
5 changes: 5 additions & 0 deletions src/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,10 @@ export default {
axios.post("/api/auth/reset_password_request", { email }),
validateToken: token => axios.post("/api/auth/validate_token", { token }),
resetPassword: data => axios.post("/api/auth/reset_password", { data })
},
books: {
fetchAll: () => axios.get("/api/books").then(res => res.data.books),
create: book =>
axios.post("/api/books", { book }).then(res => res.data.book)
}
};
9 changes: 5 additions & 4 deletions src/components/forms/BookForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ class BookForm extends React.Component {
return (
<Segment>
<Form onSubmit={this.onSubmit} loading={loading}>
<Grid columns={2} fluid stackable>
<Grid columns={2} stackable>
<Grid.Row>
<Grid.Column>
<Form.Field error={!!errors.title}>
Expand Down Expand Up @@ -115,10 +115,11 @@ class BookForm extends React.Component {
<Form.Field error={!!errors.pages}>
<label htmlFor="pages">Pages</label>
<input
type="number"
disabled={data.pages === undefined}
type="text"
id="pages"
name="pages"
value={data.pages}
value={data.pages !== undefined ? data.pages : "Loading..."}
onChange={this.onChangeNumber}
/>
{errors.pages && <InlineError text={errors.pages} />}
Expand Down Expand Up @@ -152,7 +153,7 @@ BookForm.propTypes = {
title: PropTypes.string.isRequired,
authors: PropTypes.string.isRequired,
covers: PropTypes.arrayOf(PropTypes.string.isRequired).isRequired,
pages: PropTypes.number.isRequired
pages: PropTypes.number
}).isRequired
};

Expand Down
12 changes: 10 additions & 2 deletions src/components/navigation/TopNavigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,18 @@ import { connect } from "react-redux";
import { Link } from "react-router-dom";
import gravatarUrl from "gravatar-url";
import * as actions from "../../actions/auth";
import { allBooksSelector } from "../../reducers/books";

const TopNavigation = ({ user, logout }) => (
const TopNavigation = ({ user, logout, hasBooks }) => (
<Menu secondary pointing>
<Menu.Item as={Link} to="/dashboard">
Dashboard
</Menu.Item>
{hasBooks && (
<Menu.Item as={Link} to="/books/new">
Add New Book
</Menu.Item>
)}

<Menu.Menu position="right">
<Dropdown trigger={<Image avatar src={gravatarUrl(user.email)} />}>
Expand All @@ -26,12 +32,14 @@ TopNavigation.propTypes = {
user: PropTypes.shape({
email: PropTypes.string.isRequired
}).isRequired,
hasBooks: PropTypes.bool.isRequired,
logout: PropTypes.func.isRequired
};

function mapStateToProps(state) {
return {
user: state.user
user: state.user,
hasBooks: allBooksSelector(state).length > 0
};
}

Expand Down
25 changes: 18 additions & 7 deletions src/components/pages/DashboardPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,28 @@ import { connect } from "react-redux";
import ConfirmEmailMessage from "../messages/ConfirmEmailMessage";
import { allBooksSelector } from "../../reducers/books";
import AddBookCtA from "../ctas/AddBookCtA";
import { fetchBooks } from "../../actions/books";

const DashboardPage = ({ isConfirmed, books }) => (
<div>
{!isConfirmed && <ConfirmEmailMessage />}
class DashboardPage extends React.Component {
componentDidMount = () => this.onInit(this.props);

{books.length === 0 && <AddBookCtA />}
</div>
);
onInit = props => props.fetchBooks();

render() {
const { isConfirmed, books } = this.props;
return (
<div>
{!isConfirmed && <ConfirmEmailMessage />}

{books.length === 0 ? <AddBookCtA /> : <p>You have books!</p>}
</div>
);
}
}

DashboardPage.propTypes = {
isConfirmed: PropTypes.bool.isRequired,
fetchBooks: PropTypes.func.isRequired,
books: PropTypes.arrayOf(
PropTypes.shape({
title: PropTypes.string.isRequired
Expand All @@ -29,4 +40,4 @@ function mapStateToProps(state) {
};
}

export default connect(mapStateToProps)(DashboardPage);
export default connect(mapStateToProps, { fetchBooks })(DashboardPage);
26 changes: 23 additions & 3 deletions src/components/pages/NewBookPage.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
import React from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { Segment } from "semantic-ui-react";
import axios from "axios";
import SearchBookForm from "../forms/SearchBookForm";
import BookForm from "../forms/BookForm";
import { createBook } from "../../actions/books";

class NewBookPage extends React.Component {
state = {
book: null
};

onBookSelect = book => this.setState({ book });
onBookSelect = book => {
this.setState({ book });
axios
.get(`/api/books/fetchPages?goodreadsId=${book.goodreadsId}`)
.then(res => res.data.pages)
.then(pages => this.setState({ book: { ...book, pages } }));
};

addBook = () => console.log("hi");
addBook = book =>
this.props
.createBook(book)
.then(() => this.props.history.push("/dashboard"));

render() {
return (
Expand All @@ -26,4 +39,11 @@ class NewBookPage extends React.Component {
}
}

export default NewBookPage;
NewBookPage.propTypes = {
createBook: PropTypes.func.isRequired,
history: PropTypes.shape({
push: PropTypes.func.isRequired
}).isRequired
};

export default connect(null, { createBook })(NewBookPage);
4 changes: 4 additions & 0 deletions src/reducers/books.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { createSelector } from "reselect";
import { BOOKS_FETCHED, BOOK_CREATED } from "../types";

export default function books(state = {}, action = {}) {
switch (action.type) {
case BOOKS_FETCHED:
case BOOK_CREATED:
return { ...state, ...action.data.entities.books };
default:
return state;
}
Expand Down
7 changes: 7 additions & 0 deletions src/schemas.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { schema } from "normalizr";

export const bookSchema = new schema.Entity(
"books",
{},
{ idAttribute: "_id" }
);
2 changes: 2 additions & 0 deletions src/types.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export const USER_LOGGED_IN = "USER_LOGGED_IN";
export const USER_LOGGED_OUT = "USER_LOGGED_OUT";
export const BOOKS_FETCHED = "BOOKS_FETCHED";
export const BOOK_CREATED = "BOOK_CREATED";
4 changes: 4 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4410,6 +4410,10 @@ normalize-url@^1.4.0:
query-string "^4.1.0"
sort-keys "^1.0.0"

normalizr@^3.2.4:
version "3.2.4"
resolved "https://registry.yarnpkg.com/normalizr/-/normalizr-3.2.4.tgz#16aafc540ca99dc1060ceaa1933556322eac4429"

npm-run-path@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
Expand Down

0 comments on commit 9fe3521

Please sign in to comment.