finishes functionality for cart, checkout, return and renter registration
This commit is contained in:
+15
-3
@@ -1,8 +1,11 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import './App.css';
|
||||
import styled from 'styled-components';
|
||||
import Sidebar from './containers/Sidebar/Sidebar';
|
||||
import Content from './containers/Content/Content';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { fetchBooks } from './actions/books';
|
||||
import { fetchRenters } from './actions/renters';
|
||||
|
||||
const AppContainer = styled.div`
|
||||
background: #13334A;
|
||||
@@ -10,11 +13,20 @@ const AppContainer = styled.div`
|
||||
display: flex;
|
||||
`
|
||||
|
||||
const App: React.FC = () => (
|
||||
const App: React.FC = () => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchBooks());
|
||||
dispatch(fetchRenters());
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<AppContainer>
|
||||
<Sidebar/>
|
||||
<Content />
|
||||
</AppContainer>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
|
||||
@@ -5,15 +5,19 @@ import bookishApi from '../api/bookish';
|
||||
|
||||
import { Dispatch } from 'redux';
|
||||
|
||||
import { fetchRenters } from './renters';
|
||||
|
||||
export function fetchBooks() {
|
||||
return (dispatch: Dispatch) => {
|
||||
dispatch({ type: actionTypes.FETCH_BOOKS.request });
|
||||
|
||||
booksApi.get('/books')
|
||||
.then(res => dispatch({
|
||||
type: actionTypes.FETCH_BOOKS.success,
|
||||
data: res.data
|
||||
}))
|
||||
.then(res => {
|
||||
dispatch({
|
||||
type: actionTypes.FETCH_BOOKS.success,
|
||||
data: res.data
|
||||
})
|
||||
})
|
||||
.catch(err => dispatch({
|
||||
type: actionTypes.FETCH_BOOKS.failure,
|
||||
err: err.message
|
||||
@@ -29,12 +33,11 @@ export function fetchBookCover(title: string, isbn: string) {
|
||||
.then(res => {
|
||||
if (res.data.totalItems === 0 ||
|
||||
res.data.items[0].volumeInfo.imageLinks === undefined) {
|
||||
return dispatch({
|
||||
type: actionTypes.FETCH_COVER_IMAGE.failure,
|
||||
err: "no image"
|
||||
})
|
||||
return dispatch({
|
||||
type: actionTypes.FETCH_COVER_IMAGE.failure,
|
||||
err: "no image"
|
||||
})
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: actionTypes.FETCH_COVER_IMAGE.success,
|
||||
data: { title: title, imageLink: res.data.items[0].volumeInfo.imageLinks.thumbnail }
|
||||
@@ -48,7 +51,7 @@ export function fetchBookCover(title: string, isbn: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export function checkoutBook(title: string, renterId: number) {
|
||||
export function checkoutBook(title: string, renterId: string) {
|
||||
return (dispatch: Dispatch) => {
|
||||
dispatch({ type: actionTypes.CHECKOUT_BOOK.request });
|
||||
|
||||
@@ -56,7 +59,12 @@ export function checkoutBook(title: string, renterId: number) {
|
||||
.then(() => {
|
||||
dispatch({ type: actionTypes.CHECKOUT_BOOK.success });
|
||||
})
|
||||
// After a book is checked out successfully, refetch renters.
|
||||
.then(() => {
|
||||
dispatch<any>(fetchRenters());
|
||||
})
|
||||
.catch(err => {
|
||||
alert(`Couldn't check out ${title}`);
|
||||
dispatch({
|
||||
type: actionTypes.CHECKOUT_BOOK.failure,
|
||||
err: err.message
|
||||
@@ -64,18 +72,25 @@ export function checkoutBook(title: string, renterId: number) {
|
||||
}
|
||||
}
|
||||
|
||||
export function returnBook(title: string, renterId: number) {
|
||||
export function returnBook(title: string, renterId: string) {
|
||||
return (dispatch: Dispatch) => {
|
||||
dispatch({ type: actionTypes.RETURN_BOOK.request });
|
||||
|
||||
bookishApi.post(`/renters/${renterId}/books/return`, { title })
|
||||
.then(() => {
|
||||
dispatch({ type: actionTypes.RETURN_BOOK.success });
|
||||
alert(`${title} was returned`);
|
||||
})
|
||||
// After a book is returned successfully, refetch renters.
|
||||
.then(() => {
|
||||
dispatch<any>(fetchRenters());
|
||||
})
|
||||
.catch(err => {
|
||||
dispatch({
|
||||
type: actionTypes.RETURN_BOOK.failure,
|
||||
err: err.message
|
||||
})})
|
||||
})
|
||||
alert(`Failed to return ${title}`);
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,6 @@ export function fetchRenters() {
|
||||
|
||||
bookishApi.get('/renters')
|
||||
.then(res => {
|
||||
console.log(res)
|
||||
dispatch({
|
||||
type: actionTypes.FETCH_RENTERS.success,
|
||||
data: res.data
|
||||
@@ -31,10 +30,17 @@ export function registerRenter(name: string, address: string, email: string, pho
|
||||
type: actionTypes.REGISTER_RENTER.success,
|
||||
data: res.data
|
||||
}))
|
||||
.catch(err => dispatch({
|
||||
type: actionTypes.REGISTER_RENTER.failure,
|
||||
err: err.message
|
||||
}))
|
||||
// After a renter is registered successfully, refetch renters.
|
||||
.then(() => {
|
||||
dispatch<any>(fetchRenters());
|
||||
})
|
||||
.catch(err => {
|
||||
alert('Could not register renter');
|
||||
dispatch({
|
||||
type: actionTypes.REGISTER_RENTER.failure,
|
||||
err: err.message
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@ const FETCH_BOOKS = generateActionType('FETCH_BOOKS');
|
||||
const FETCH_COVER_IMAGE = generateActionType('FETCH_COVER_IMAGE');
|
||||
const SET_FILTER = 'SET_FILTER';
|
||||
const ADD_TO_CART = 'ADD_TO_CART';
|
||||
const REMOVE_FROM_CART = 'REMOVE_FROM_CART';
|
||||
const CLEAR_CART = 'CLEAR_CART';
|
||||
const CHECKOUT_BOOK = generateActionType('CHECKOUT_BOOK');
|
||||
const RETURN_BOOK = generateActionType('RETURN_BOOK');
|
||||
|
||||
@@ -12,6 +14,8 @@ export const actionTypes = {
|
||||
FETCH_COVER_IMAGE,
|
||||
SET_FILTER,
|
||||
ADD_TO_CART,
|
||||
REMOVE_FROM_CART,
|
||||
CLEAR_CART,
|
||||
CHECKOUT_BOOK,
|
||||
RETURN_BOOK
|
||||
}
|
||||
@@ -2,13 +2,16 @@ import React from 'react';
|
||||
import { useSelector, RootStateOrAny } from 'react-redux';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { actionTypes } from '../../../../constants/books/action_types';
|
||||
|
||||
const BookCoverCardContainer = styled.div`
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
border: 0.1px solid #f0f0f0;
|
||||
box-shadow: 10px 5px 15px rgba(175,175,175,50);
|
||||
height: 135px;
|
||||
max-width: 200px;
|
||||
height: 225px;
|
||||
min-width: 325px;
|
||||
margin: 10px 0;
|
||||
`
|
||||
|
||||
@@ -21,23 +24,27 @@ const EmptyCart = styled.div`
|
||||
`
|
||||
|
||||
const CoverImage = styled.img`
|
||||
height: 100px;
|
||||
height: 200px;
|
||||
object-fit: contain;
|
||||
margin: auto 15px;
|
||||
cursor: pointer;
|
||||
`
|
||||
|
||||
const BookCoverCard: React.FC = () => {
|
||||
const cartItems = useSelector((state: RootStateOrAny) => state.books.cart)
|
||||
const images = useSelector((state: RootStateOrAny) => state.books.images)
|
||||
|
||||
console.log(cartItems)
|
||||
const dispatch = useDispatch();
|
||||
const cartItems = useSelector((state: RootStateOrAny) => state.books.cart);
|
||||
const images = useSelector((state: RootStateOrAny) => state.books.images);
|
||||
|
||||
return (
|
||||
<BookCoverCardContainer>
|
||||
{cartItems.length === 0 && (<EmptyCart>Nothing here yet!</EmptyCart>)}
|
||||
{cartItems.length === 0 && (<EmptyCart>Nothing here yet. Pick out a book!</EmptyCart>)}
|
||||
{cartItems && cartItems.map(cartItem => (
|
||||
<CoverImage
|
||||
src={images[cartItem] || 'https://college.indiana.edu/images/publications/book-cover-placeholder.jpg'} />
|
||||
src={images[cartItem] || 'https://college.indiana.edu/images/publications/book-cover-placeholder.jpg'}
|
||||
onClick={() => {
|
||||
dispatch({ type: actionTypes.REMOVE_FROM_CART, data: cartItem })
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</BookCoverCardContainer>
|
||||
)
|
||||
|
||||
@@ -1,13 +1,53 @@
|
||||
import React from 'react';
|
||||
// import styled from 'styled-components';
|
||||
|
||||
import Panel from '../../UIKit/Panel/Panel';
|
||||
import BookCoverCard from './BookCoverCard/BookCoverCard';
|
||||
import CartDetail from './CartDetail/CartDetail';
|
||||
import Renters from '../Renters/Renters';
|
||||
import { useDispatch, useSelector, RootStateOrAny } from 'react-redux';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
import { checkoutBook } from '../../../actions/books';
|
||||
import { actionTypes } from '../../../constants/books/action_types';
|
||||
|
||||
const Cart: React.FC = () => (
|
||||
<Panel>
|
||||
<BookCoverCard />
|
||||
</Panel>
|
||||
)
|
||||
const Cart: React.FC<RouteComponentProps> = ({ history }) => {
|
||||
const dispatch = useDispatch();
|
||||
const cart = useSelector((state: RootStateOrAny) => state.books.cart);
|
||||
|
||||
let panelStyle = {};
|
||||
if (cart.length !== 0) {
|
||||
panelStyle = {
|
||||
display: 'flex', justifyContent: 'space-between'
|
||||
}
|
||||
}
|
||||
|
||||
const checkoutHandler = (renterId: string) => {
|
||||
if (cart.length === 0) {
|
||||
alert("Add items to your cart before checking out");
|
||||
return
|
||||
}
|
||||
|
||||
// Checkout each cart item.
|
||||
cart.map(cartItem => {
|
||||
dispatch(checkoutBook(cartItem, renterId))
|
||||
})
|
||||
|
||||
// After each item has been checked out clear the cart.
|
||||
dispatch({ type: actionTypes.CLEAR_CART });
|
||||
|
||||
// Redirect to the homepage.
|
||||
history.push('/');
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Panel style={panelStyle}>
|
||||
<BookCoverCard />
|
||||
{cart.length !== 0 && <CartDetail />}
|
||||
</Panel>
|
||||
<Renters
|
||||
checkout={checkoutHandler}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Cart;
|
||||
@@ -0,0 +1,63 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { useSelector, RootStateOrAny } from 'react-redux';
|
||||
|
||||
const CartDetailContainer = styled.div`
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 0.1px solid #f0f0f0;
|
||||
box-shadow: 10px 5px 15px rgba(175,175,175,50);
|
||||
height: 225px;
|
||||
min-width: 300px;
|
||||
width: 100%;
|
||||
margin: 10px 0 0 20px;
|
||||
font-family: 'Helvetica';
|
||||
font-size: 1.5rem;
|
||||
font-weight: 100;
|
||||
color: #13334A;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const BreakdownContainer = styled.div`
|
||||
background-color: #fff;
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
font-size: 1.25rem;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
`
|
||||
|
||||
const BreakdownP = styled.p`
|
||||
margin: 0;
|
||||
margin-left: 20px;
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
`
|
||||
|
||||
const CartDetail: React.FC = () => {
|
||||
const cart = useSelector((state: RootStateOrAny) => state.books.cart);
|
||||
// 12096e5 is a magic number for 14 days.
|
||||
const dueDate = new Date(Date.now() + 12096e5);
|
||||
|
||||
return (
|
||||
<CartDetailContainer>
|
||||
Cart
|
||||
<BreakdownContainer>
|
||||
<p/>
|
||||
<BreakdownP>Books in cart:</BreakdownP>
|
||||
<ul>
|
||||
{cart.length !== 0 && cart.map(cartItem => (
|
||||
<li>{cartItem}</li>
|
||||
))}
|
||||
</ul>
|
||||
<BreakdownP>Due date:</BreakdownP>
|
||||
<BreakdownP>{dueDate.toDateString()}</BreakdownP>
|
||||
</BreakdownContainer>
|
||||
</CartDetailContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default CartDetail;
|
||||
@@ -6,6 +6,7 @@ import Header from './Header/Header';
|
||||
import Library from './Library/Library';
|
||||
import Cart from './Cart/Cart';
|
||||
import Renters from './Renters/Renters';
|
||||
import Register from './Register/Register';
|
||||
|
||||
const ContentContainer = styled.div`
|
||||
width: 100%;
|
||||
@@ -18,9 +19,9 @@ const Content: React.FC = () => (
|
||||
<Route path="/" exact component={Library} />
|
||||
<Route path="/library" component={Library} />
|
||||
<Route path="/renters" component={Renters} />
|
||||
<Route path="/register" component={Register} />
|
||||
<Route path="/return" component={Renters} />
|
||||
<Route path="/checkout" component={Cart} />
|
||||
<Route path="/checkout" component={Renters} />
|
||||
</ContentContainer>
|
||||
)
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ const ButtonBay: React.FC = () => (
|
||||
<ButtonBayContainer>
|
||||
<Button
|
||||
text="Register new renter"
|
||||
destination="/renters"
|
||||
destination="/register"
|
||||
/>
|
||||
<Button
|
||||
text="Checkout"
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { actionTypes } from '../../../../constants/books/action_types';
|
||||
|
||||
@@ -37,16 +39,21 @@ const StyledIcon = styled(FontAwesomeIcon)`
|
||||
|
||||
const Search: React.FC = () => {
|
||||
const dispatch = useDispatch();
|
||||
const location = useLocation();
|
||||
|
||||
const displaySearch = location.pathname === '/library' || location.pathname === '/';
|
||||
|
||||
return (
|
||||
<SearchContainer>
|
||||
<StyledInput
|
||||
onChange={(e: React.FormEvent<HTMLInputElement>) => {
|
||||
dispatch({ type: actionTypes.SET_FILTER, data: e.currentTarget.value });
|
||||
}}
|
||||
/>
|
||||
<StyledIcon icon={faSearch} />
|
||||
</SearchContainer>
|
||||
<>
|
||||
{displaySearch && <SearchContainer>
|
||||
<StyledInput
|
||||
onChange={(e: React.FormEvent<HTMLInputElement>) => {
|
||||
dispatch({ type: actionTypes.SET_FILTER, data: e.currentTarget.value });
|
||||
}}
|
||||
/>
|
||||
<StyledIcon icon={faSearch} />
|
||||
</SearchContainer>}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -63,8 +63,8 @@ type Props = {
|
||||
}
|
||||
|
||||
const Book: React.FC<Props> = ({ title, author, isbn }) => {
|
||||
const imageLink = useSelector((state: RootStateOrAny) => state.books.images[title])
|
||||
const cart = useSelector((state: RootStateOrAny) => state.books.cart)
|
||||
const imageLink = useSelector((state: RootStateOrAny) => state.books.images[title]);
|
||||
const cart = useSelector((state: RootStateOrAny) => state.books.cart);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -75,12 +75,10 @@ const Book: React.FC<Props> = ({ title, author, isbn }) => {
|
||||
|
||||
const handleAddToCart = () => {
|
||||
if (cart.length > 1) {
|
||||
// TODO: Add a toast notification here when cart is full.
|
||||
alert("You can only check out 2 books at a time!");
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Add a toast notification here when item added to cart.
|
||||
dispatch({ type: actionTypes.ADD_TO_CART, data: title });
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ const Library: React.FC = () => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchBooks())
|
||||
dispatch(fetchBooks());
|
||||
}, []);
|
||||
|
||||
const displayBooks = books.books.filter((book: BookType) => book.title.toLowerCase().indexOf(filter) !== -1);
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
import React, { useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { registerRenter } from '../../../actions/renters';
|
||||
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
|
||||
import Panel from '../../UIKit/Panel/Panel';
|
||||
|
||||
const RegistrationContainer = styled.div`
|
||||
font-family: 'Helvetica';
|
||||
width: 80%;
|
||||
max-width: 600px;
|
||||
margin: auto;
|
||||
`
|
||||
|
||||
const RegistrationForm = styled.form`
|
||||
width: 100%;
|
||||
`
|
||||
|
||||
const RegistrationRow = styled.div`
|
||||
width: 100%;
|
||||
display: block;
|
||||
`
|
||||
|
||||
const RegistrationColumn = styled.div`
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
`
|
||||
|
||||
const RegistrationLabel = styled.label`
|
||||
width: 30%;
|
||||
max-width: 100px;
|
||||
float: left;
|
||||
color: #13334A;
|
||||
height: 38px;
|
||||
line-height: 38px;
|
||||
text-align: right;
|
||||
`
|
||||
|
||||
const RegistrationInput = styled.input`
|
||||
width: 60%;
|
||||
float: right;
|
||||
height: 38px;
|
||||
border: 1px solid #ccc;
|
||||
color: #13334A;
|
||||
font-size: 1.25rem;
|
||||
outline: none;
|
||||
box-shadow: 10px 5px 15px rgba(175,175,175,50)
|
||||
`
|
||||
const RegistrationButton = styled.button`
|
||||
display: block;
|
||||
height: 38px;
|
||||
width: 200px;
|
||||
margin: 15px auto 0 auto;
|
||||
cursor: pointer;
|
||||
`
|
||||
|
||||
const Register: React.FC<RouteComponentProps> = ({ history }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const [name, setName] = useState('');
|
||||
const [address, setAddress] = useState('');
|
||||
const [email, setEmail] = useState('');
|
||||
const [phone, setPhone] = useState('');
|
||||
|
||||
return (
|
||||
<Panel>
|
||||
<RegistrationContainer>
|
||||
<RegistrationForm>
|
||||
<RegistrationRow>
|
||||
<RegistrationColumn>
|
||||
<RegistrationLabel>
|
||||
Name:
|
||||
</RegistrationLabel>
|
||||
<RegistrationInput
|
||||
onChange={e => {
|
||||
setName(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</RegistrationColumn>
|
||||
<RegistrationColumn>
|
||||
<RegistrationLabel>
|
||||
Addresss:
|
||||
</RegistrationLabel>
|
||||
<RegistrationInput
|
||||
onChange={e => {
|
||||
setAddress(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</RegistrationColumn>
|
||||
<RegistrationColumn>
|
||||
<RegistrationLabel>
|
||||
Email:
|
||||
</RegistrationLabel>
|
||||
<RegistrationInput
|
||||
onChange={e => {
|
||||
setEmail(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</RegistrationColumn>
|
||||
<RegistrationColumn>
|
||||
<RegistrationLabel>
|
||||
Phone:
|
||||
</RegistrationLabel>
|
||||
<RegistrationInput
|
||||
onChange={e => {
|
||||
setPhone(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</RegistrationColumn>
|
||||
</RegistrationRow>
|
||||
</RegistrationForm>
|
||||
</RegistrationContainer>
|
||||
<RegistrationButton
|
||||
onClick={() => {
|
||||
dispatch(registerRenter(name, address, email, phone));
|
||||
history.push('/renters');
|
||||
}}
|
||||
>Submit</RegistrationButton>
|
||||
</Panel>
|
||||
);
|
||||
};
|
||||
|
||||
export default Register;
|
||||
@@ -1,5 +1,9 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { BookType } from '../../../../../types/book';
|
||||
|
||||
import { useDispatch, useSelector, RootStateOrAny } from 'react-redux';
|
||||
import { fetchBookCover, returnBook } from '../../../../../actions/books';
|
||||
|
||||
const BookReturnContainer = styled.div`
|
||||
display: inline-flex;
|
||||
@@ -11,16 +15,43 @@ const CoverImage = styled.img`
|
||||
height: 100px;
|
||||
object-fit: contain;
|
||||
margin: auto 15px;
|
||||
cursor: pointer;
|
||||
`
|
||||
|
||||
type Props = {
|
||||
imageLink: string;
|
||||
renterId: string;
|
||||
book: BookType;
|
||||
}
|
||||
|
||||
const BookReturn: React.FC<Props> = ({ imageLink }) => (
|
||||
<BookReturnContainer>
|
||||
<CoverImage src={imageLink} />
|
||||
</BookReturnContainer>
|
||||
);
|
||||
const BookReturn: React.FC<Props> = ({ renterId, book }) => {
|
||||
let title = '';
|
||||
let isbn = '';
|
||||
if (book !== undefined) {
|
||||
title = book.title;
|
||||
if (book.isbn) {
|
||||
isbn = book.isbn;
|
||||
}
|
||||
}
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const images = useSelector((state: RootStateOrAny) => state.books.images);
|
||||
|
||||
useEffect(() => {
|
||||
if (images[title] === undefined) {
|
||||
dispatch(fetchBookCover(title, isbn));
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<BookReturnContainer>
|
||||
<CoverImage
|
||||
src={images[title] || 'https://college.indiana.edu/images/publications/book-cover-placeholder.jpg'}
|
||||
onClick={() => {
|
||||
dispatch(returnBook(title, renterId));
|
||||
}}
|
||||
/>
|
||||
</BookReturnContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export default BookReturn;
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import styled from'styled-components';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faShoppingBasket } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
const CheckoutButtonContainer = styled.div`
|
||||
height: 100%;
|
||||
width: 200px;
|
||||
float: right;
|
||||
text-align: center;
|
||||
line-height: 135px;
|
||||
cursor: pointer;
|
||||
font-family: 'Helvetica';
|
||||
font-size: 24px;
|
||||
font-weight: 100;
|
||||
color: #777;
|
||||
`
|
||||
|
||||
type Props = {
|
||||
clickHandler: Function;
|
||||
}
|
||||
|
||||
const CheckoutButton: React.FC<Props> = ({ clickHandler }) => (
|
||||
<CheckoutButtonContainer
|
||||
onClick={() => clickHandler()}
|
||||
>
|
||||
Checkout <FontAwesomeIcon icon={faShoppingBasket} />
|
||||
</CheckoutButtonContainer>
|
||||
);
|
||||
|
||||
export default CheckoutButton;
|
||||
@@ -1,7 +1,10 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { useSelector, RootStateOrAny } from 'react-redux';
|
||||
|
||||
import BookReturn from './BookReturn/BookReturn';
|
||||
import CheckoutButton from './CheckoutButton/CheckoutButton';
|
||||
|
||||
const RenterCardContainer = styled.div`
|
||||
background-color: #fff;
|
||||
@@ -55,41 +58,62 @@ const RenterReturnContainer = styled.div`
|
||||
`
|
||||
|
||||
type Props = {
|
||||
id: string;
|
||||
name: string;
|
||||
phoneNumber: string;
|
||||
email: string;
|
||||
rentals: [string];
|
||||
displayRentals: boolean;
|
||||
displayCheckout: boolean;
|
||||
handleCheckoutClicked: Function;
|
||||
}
|
||||
|
||||
const books = [
|
||||
{title: "To Kill a Mockingbird", author: "Harper Lee", imageLink: "http://www.prepressure.com/images/book-cover-to-kill-a-mocking-bird.jpg"},
|
||||
{title: "To Kill a Mockingbird", author: "Harper Lee", imageLink: "http://www.prepressure.com/images/book-cover-to-kill-a-mocking-bird.jpg"}
|
||||
]
|
||||
const RenterCard: React.FC<Props> = ({
|
||||
id,
|
||||
name,
|
||||
phoneNumber,
|
||||
email,
|
||||
rentals,
|
||||
displayRentals,
|
||||
displayCheckout,
|
||||
handleCheckoutClicked
|
||||
}) => {
|
||||
const books = useSelector((state: RootStateOrAny) => state.books.books);
|
||||
|
||||
const RenterCard: React.FC<Props> = ({ name, phoneNumber, email }) => (
|
||||
<RenterCardContainer>
|
||||
<RenterImageContainer>
|
||||
<RenterImagePlaceholder
|
||||
src="https://avatars3.githubusercontent.com/u/47925772?s=400&v=4" />
|
||||
</RenterImageContainer>
|
||||
<RenterInfoContainer>
|
||||
<RenterName>
|
||||
{name}
|
||||
</RenterName>
|
||||
<RenterPhone>
|
||||
{phoneNumber}
|
||||
</RenterPhone>
|
||||
<RenterEmail>
|
||||
{email}
|
||||
</RenterEmail>
|
||||
</RenterInfoContainer>
|
||||
<RenterReturnContainer>
|
||||
{books.map(book => (
|
||||
<BookReturn
|
||||
imageLink={book.imageLink}
|
||||
/>
|
||||
))}
|
||||
</RenterReturnContainer>
|
||||
</RenterCardContainer>
|
||||
)
|
||||
return (
|
||||
<RenterCardContainer>
|
||||
<RenterImageContainer>
|
||||
<RenterImagePlaceholder
|
||||
src="https://avatars3.githubusercontent.com/u/47925772?s=400&v=4" />
|
||||
</RenterImageContainer>
|
||||
<RenterInfoContainer>
|
||||
<RenterName>
|
||||
{name}
|
||||
</RenterName>
|
||||
<RenterPhone>
|
||||
{phoneNumber}
|
||||
</RenterPhone>
|
||||
<RenterEmail>
|
||||
{email}
|
||||
</RenterEmail>
|
||||
</RenterInfoContainer>
|
||||
<RenterReturnContainer>
|
||||
{books && displayRentals && rentals && rentals.map(rental => {
|
||||
return (
|
||||
<BookReturn
|
||||
key={id}
|
||||
renterId={id}
|
||||
book={books.find(book => book.title === rental)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{displayCheckout &&
|
||||
<CheckoutButton
|
||||
clickHandler={() => handleCheckoutClicked(id)}
|
||||
/>}
|
||||
</RenterReturnContainer>
|
||||
</RenterCardContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default RenterCard;
|
||||
@@ -7,9 +7,19 @@ import RenterCard from './RenterCard/RenterCard';
|
||||
|
||||
import { RenterType } from '../../../types/renter';
|
||||
|
||||
const Renters: React.FC = () => {
|
||||
const renters = useSelector((state: RootStateOrAny) => state.renters);
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
type Props = {
|
||||
checkout: Function;
|
||||
}
|
||||
|
||||
const Renters: React.FC<Props> = ({ checkout }) => {
|
||||
const renters = useSelector((state: RootStateOrAny) => state.renters.renters);
|
||||
const dispatch = useDispatch();
|
||||
const location = useLocation();
|
||||
|
||||
const displayRentals = location.pathname === '/return';
|
||||
const displayCheckout = location.pathname === '/checkout';
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchRenters())
|
||||
@@ -22,11 +32,17 @@ const Renters: React.FC = () => {
|
||||
flexWrap: 'wrap',
|
||||
justifyContent: 'center'
|
||||
}}>
|
||||
{renters && renters.renters.map((renter: RenterType) => (
|
||||
{renters && renters.map((renter: RenterType) => (
|
||||
<RenterCard
|
||||
name={renter.name || ''}
|
||||
phoneNumber={renter.phoneNumber || ''}
|
||||
email={renter.email || ''}
|
||||
key={renter.id}
|
||||
id={renter.id}
|
||||
name={renter.name}
|
||||
phoneNumber={renter.phoneNumber}
|
||||
email={renter.email}
|
||||
rentals={renter.rentals}
|
||||
displayRentals={displayRentals}
|
||||
displayCheckout={displayCheckout}
|
||||
handleCheckoutClicked={checkout}
|
||||
/>
|
||||
))}
|
||||
</Panel>
|
||||
|
||||
@@ -6,7 +6,7 @@ import Menu from './Menu/Menu'
|
||||
|
||||
const SidebarContainer = styled.div`
|
||||
background: #011126;
|
||||
box-shadow: 0px 0px 5px rgba(0,0,0,50);
|
||||
box-shadow: 0px 0px 12px rgba(0,0,0,100);
|
||||
padding-top: 2.5%;
|
||||
min-width: 275px;
|
||||
flex: 1;
|
||||
|
||||
@@ -73,11 +73,20 @@ export function books(state = initialState, action: Action) {
|
||||
|
||||
// ADD_TO_CART action
|
||||
case actionTypes.ADD_TO_CART:
|
||||
alert(`Added ${action.data} to the cart`);
|
||||
return {
|
||||
...state,
|
||||
cart: state.cart.concat(action.data)
|
||||
}
|
||||
|
||||
// REMOVE_FROM_CART action
|
||||
case actionTypes.REMOVE_FROM_CART:
|
||||
alert(`Removed ${action.data} from the cart`);
|
||||
return {
|
||||
...state,
|
||||
cart: state.cart.filter(item => item !== action.data)
|
||||
}
|
||||
|
||||
// CHECKOUT_BOOK action
|
||||
case actionTypes.CHECKOUT_BOOK.request:
|
||||
return {
|
||||
@@ -115,6 +124,13 @@ export function books(state = initialState, action: Action) {
|
||||
loading: false,
|
||||
returnError: action.err
|
||||
}
|
||||
|
||||
// CLEAR_CART action
|
||||
case actionTypes.CLEAR_CART:
|
||||
return {
|
||||
...state,
|
||||
cart: [],
|
||||
}
|
||||
|
||||
default:
|
||||
return state;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
export type RenterType = {
|
||||
id: string;
|
||||
name: string;
|
||||
address: string;
|
||||
email: string;
|
||||
phoneNumber: string;
|
||||
rentals: [string];
|
||||
}
|
||||
Reference in New Issue
Block a user