Skip to content
This repository has been archived by the owner on Aug 5, 2022. It is now read-only.

Week5 homework #11

Merged
merged 10 commits into from
Apr 24, 2019
3 changes: 2 additions & 1 deletion src/components/Form/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import styled from 'styled-components'
import { Form as FormikForm } from 'formik'

import theme from '../../common/theme'

export const Form = styled.form`
export const Form = styled(FormikForm)`
display: flex;
flex-direction: column;
margin: 1.4rem auto;
Expand Down
66 changes: 31 additions & 35 deletions src/components/Layout/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { Component, Fragment } from 'react'
import React, { Fragment } from 'react'
import { withRouter } from 'react-router-dom'
import { connect } from 'react-redux'

Expand All @@ -9,44 +9,40 @@ import { removeToken } from '../../utils/token'
import { removeCustomer } from '../../utils/customer'
import { Wrapper, Header, HeaderSection, HeaderLink } from './styled'

class Layout extends Component {
handleLogout = () => {
this.props.logout()
const Layout = ({ logout, isAuthenticated, history, children }) => {
const handleLogout = () => {
logout()
removeToken()
removeCustomer()
this.props.history.push(routes.HOMEPAGE)
history.push(routes.HOMEPAGE)
}

render() {
const { isAuthenticated } = this.props

return (
<Fragment>
<Header>
<HeaderSection>
<HeaderLink to={routes.PRODUCT_LIST}>All Products</HeaderLink>
</HeaderSection>
<HeaderSection>
<HeaderLink to={routes.CART}>My Cart</HeaderLink>|
{isAuthenticated ? (
<>
<HeaderLink to={routes.ACCOUNT}>My Account</HeaderLink>|
<HeaderLink as="button" onClick={this.handleLogout}>
Logout
</HeaderLink>
</>
) : (
<>
<HeaderLink to={routes.LOGIN}>Log In</HeaderLink> |
<HeaderLink to={routes.SIGN_UP}>Sign Up</HeaderLink>
</>
)}
</HeaderSection>
</Header>
<Wrapper>{this.props.children}</Wrapper>
</Fragment>
)
}
return (
<Fragment>
<Header>
<HeaderSection>
<HeaderLink to={routes.PRODUCT_LIST}>All Products</HeaderLink>
</HeaderSection>
<HeaderSection>
<HeaderLink to={routes.CART}>My Cart</HeaderLink>|
{isAuthenticated ? (
<>
<HeaderLink to={routes.ACCOUNT}>My Account</HeaderLink>|
<HeaderLink as="button" onClick={handleLogout}>
Logout
</HeaderLink>
</>
) : (
<>
<HeaderLink to={routes.LOGIN}>Log In</HeaderLink> |
<HeaderLink to={routes.SIGN_UP}>Sign Up</HeaderLink>
</>
)}
</HeaderSection>
</Header>
<Wrapper>{children}</Wrapper>
</Fragment>
)
}

const mapStateToProps = state => ({
Expand Down
24 changes: 24 additions & 0 deletions src/components/Pagination/SizeSelect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react'
import { PAGE_SIZE_OPTIONS } from '../../constants'

const SizeSelect = ({ onChange, value }) => {
const handleChange = event => {
const newValue = event.target.value

if (newValue !== value) {
onChange(newValue)
}
}

return (
<select onChange={handleChange} onBlur={handleChange} value={value}>
{PAGE_SIZE_OPTIONS.map(number => (
<option value={number} key={number}>
{number}
</option>
))}
</select>
)
}

export { SizeSelect }
14 changes: 10 additions & 4 deletions src/components/Pagination/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,22 @@ import { Link } from 'react-router-dom'

import * as routes from '../../routes'

import { SizeSelect } from './SizeSelect'
import { List, ListItem } from './styled'

const renderPaginationItem = number => (
const renderPaginationItem = size => number => (
<ListItem key={number}>
<Link to={`${routes.PRODUCT_LIST}?page=${number}`}>{number}</Link>
<Link to={`${routes.PRODUCT_LIST}?page=${number}&size=${size}`}>
{number}
</Link>
</ListItem>
)

const Pagination = ({ pages }) => (
<List>{map(renderPaginationItem, range(1, pages + 1))}</List>
const Pagination = ({ pages, size, onSizeChange }) => (
<>
<List>{map(renderPaginationItem(size), range(1, pages + 1))}</List>
<SizeSelect onChange={onSizeChange} value={size} />
</>
)

export { Pagination }
3 changes: 2 additions & 1 deletion src/components/PrivateRoute/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react'
import { Route, Redirect } from 'react-router-dom'
import { connect } from 'react-redux'
import isEmpty from 'ramda/src/isEmpty'

import * as routes from '../../routes'

Expand Down Expand Up @@ -32,7 +33,7 @@ const PrivateRouteComponent = ({
}

const mapStateToProps = state => ({
isAuthenticated: Object.keys(state.customer).length !== 0,
isAuthenticated: !isEmpty(state.customer),
})

export const PrivateRoute = connect(mapStateToProps)(PrivateRouteComponent)
3 changes: 3 additions & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const PAGE_SIZE_OPTIONS = [10, 25, 50, 100]
export const PAGE_SIZE_DEFAULT = PAGE_SIZE_OPTIONS[2]
export const PAGE_DEFAULT = 1
27 changes: 22 additions & 5 deletions src/pages/Cart/CartItem.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,39 @@
import * as React from 'react'
import flip from 'ramda/src/flip'
import propOr from 'ramda/src/propOr'

import { getProductById } from '../../api/products/get-product'
import { useApi } from '../../api/use-api'

import Loader from '../../components/Loader'
import Button from '../../components/Button'

// Helper function
// propOr takes 2 arguments: (fallback, propertyName)
// flip changes order of arguments => arguments for flip(propOr) are (propertyName, fallback)
// and we can provide propertyName because all functions in ramda are curried
const getNameFallback = flip(propOr)('name')
dannytce marked this conversation as resolved.
Show resolved Hide resolved

// this can be also written as
// const getNameFallback = (fallback, product) => propOr(fallback, 'name')(product)
// or
// const getNameFallback = (fallback, product) => product && product.name ? product.name : fallback
// but it is less variable

const CartItem = ({ productId, quantity, removeProduct }) => {
const { data: product, isLoading } = useApi(
() => getProductById(productId),
productId
)
const { data: product, isLoading } = useApi(() => getProductById(productId), [
productId,
])

// Here we provide default fallback - productId passed as prop
const getName = getNameFallback(productId)

return (
<li key={productId}>
{isLoading && <Loader small />}
<p>
{product ? product.name : productId} - {quantity}
{/* and finally here we try to get name from downloaded product */}
{getName(product)} - {quantity}
</p>
<Button type="button" onClick={() => removeProduct(productId)}>
Remove
Expand Down
6 changes: 2 additions & 4 deletions src/pages/Cart/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { connect } from 'react-redux'
import Layout from '../../components/Layout'
import { H1 } from '../../components/Typography'
import * as cartActions from '../../store/cart/actions'
import * as cartSelectors from '../../store/cart/selectors'
import { CartItem } from './CartItem'

const CartView = ({ items, removeProduct }) => {
Expand All @@ -25,10 +26,7 @@ const CartView = ({ items, removeProduct }) => {
}

const mapStateToProps = state => ({
items: Object.keys(state.cart).map(productId => ({
quantity: state.cart[productId],
product: { id: productId },
})),
items: cartSelectors.getCartItems(state),
})

const mapDispatchToProps = {
Expand Down
76 changes: 34 additions & 42 deletions src/pages/LogIn/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { Component } from 'react'
import React, { useState } from 'react'
import { Formik } from 'formik'
import { connect } from 'react-redux'

Expand All @@ -14,61 +14,53 @@ import { getCustomerToken } from '../../api/customers/get-customer-token'
import { getCustomer } from '../../api/customers/get-customer'
import { schema } from './schema'

class LogInPage extends Component {
state = {
globalError: '',
}
const initialValues = {
email: '',
password: '',
}

initialValues = {
email: '',
password: '',
}
const LogInPage = ({ login, history }) => {
const [globalError, setGlobalError] = useState('')

handleSubmit = async ({ email, password }, { setSubmitting }) => {
const handleSubmit = async ({ email, password }, { setSubmitting }) => {
try {
setSubmitting(true)
const { ownerId } = await getCustomerToken({
username: email,
password,
})
const customer = await getCustomer(ownerId)
this.props.login(customer)
this.props.history.push(routes.ACCOUNT)
login(customer)
history.push(routes.ACCOUNT)
} catch (error) {
this.setState({
globalError: error.message,
})
setGlobalError(error.message)
}
setSubmitting(false)
}

render() {
const { globalError } = this.state

return (
<Layout>
<H1 textAlign="center">Log In</H1>
<Formik
initialValues={this.initialValues}
validationSchema={schema}
onSubmit={this.handleSubmit}
>
{({ handleSubmit, isSubmitting }) => (
<Form onSubmit={handleSubmit}>
{Boolean(globalError) && (
<GlobalFormError>{globalError}</GlobalFormError>
)}
<Input name="email" type="email" label="Email address" />
<Input name="password" type="password" label="Password" />
<Button disabled={isSubmitting}>
{isSubmitting ? 'Logging In...' : 'Log In'}
</Button>
</Form>
)}
</Formik>
</Layout>
)
}
return (
<Layout>
<H1 textAlign="center">Log In</H1>
<Formik
initialValues={initialValues}
validationSchema={schema}
onSubmit={handleSubmit}
>
{({ isSubmitting }) => (
<Form>
{Boolean(globalError) && (
<GlobalFormError>{globalError}</GlobalFormError>
)}
<Input name="email" type="email" label="Email address" />
<Input name="password" type="password" label="Password" />
<Button disabled={isSubmitting}>
{isSubmitting ? 'Logging In...' : 'Log In'}
</Button>
</Form>
)}
</Formik>
</Layout>
)
}

const mapDispatchToProps = {
Expand Down
2 changes: 1 addition & 1 deletion src/pages/ProductList/Product/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ const Product = ({ node, onAddToCart }) => (
</Wrapper>
)

export default Product
export { Product }
27 changes: 22 additions & 5 deletions src/pages/ProductList/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import React from 'react'
import { connect } from 'react-redux'
import qs from 'qs'
import compose from 'ramda/src/compose'
import prop from 'ramda/src/prop'
import tail from 'ramda/src/tail'

import { getProducts } from '../../api/products/get-products'
import { useApi } from '../../api/use-api'
Expand All @@ -11,18 +14,30 @@ import { H1 } from '../../components/Typography'
import { Pagination } from '../../components/Pagination'

import * as cartActions from '../../store/cart/actions'
import Product from './Product'
import { Product } from './Product'
import { ProductsWrap } from './styled'
import { PAGE_DEFAULT, PAGE_SIZE_DEFAULT } from '../../constants'

const Products = ({ match, location, addProduct }) => {
const { page } = qs.parse(location.search.substr(1))
const getUrlParams = compose(
qs.parse,
tail,
prop('search')
)

const Products = ({ match, location, addProduct, history }) => {
const { page = PAGE_DEFAULT, size = PAGE_SIZE_DEFAULT } = getUrlParams(
location
)

const { data: res, isLoading } = useApi(
() => getProducts({ page: { number: page } }),
[page]
() => getProducts({ page: { number: page, size } }),
[page, size]
)

const handleAddToCart = productId => addProduct(productId)
const handleSizeChange = newSize => {
history.push(`/products?page=${page}&size=${newSize}`)
}

return (
<Layout>
Expand All @@ -33,6 +48,8 @@ const Products = ({ match, location, addProduct }) => {
<Pagination
pages={res.meta.page_count}
activePage={match.params.page}
size={size}
onSizeChange={handleSizeChange}
/>
<ProductsWrap>
{res.data.map(product => (
Expand Down
Loading