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

Commit 320178f

Browse files
arnostpleskotdannytce
authored andcommitted
Week 5 (#10)
* Pagination + 404 * Link to detail * Get Products hook * Romoved products store * Product detail woth hooks * Fetch products in cart * Padding removed from paggination * Remove @flow * Naive exception handling * Inline exports * Routes params changed to query, changed imports of actions * Reordered imports * Extract routes into constants * Fix for resolve conditions passed into useApi hook
1 parent 8781930 commit 320178f

File tree

23 files changed

+291
-202
lines changed

23 files changed

+291
-202
lines changed

.eslintrc.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ module.exports = {
55
'prettier',
66
'prettier/react',
77
],
8+
plugins: ['react-hooks'],
89
root: true,
910
env: {
1011
browser: true,
@@ -28,5 +29,7 @@ module.exports = {
2829
'no-shadow': [2, { allow: ['name'] }],
2930
// let's enforce this approach a bit
3031
'import/no-default-export': 1,
32+
'react-hooks/rules-of-hooks': 'error',
33+
'react-hooks/exhaustive-deps': 'warn',
3134
},
3235
}

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,12 @@
3636
],
3737
"dependencies": {
3838
"formik": "^1.5.2",
39+
"qs": "^6.7.0",
40+
"ramda": "^0.26.1",
3941
"react": "^16.8.4",
4042
"react-dom": "^16.8.4",
4143
"react-redux": "^6.0.1",
44+
"react-router": "^5.0.0",
4245
"react-router-dom": "^5.0.0",
4346
"react-scripts": "2.1.8",
4447
"redux": "^4.0.1",
@@ -51,6 +54,7 @@
5154
"@strv/eslint-config-react": "^1.0.1",
5255
"@strv/stylelint-config-styled-components": "^1.0.0",
5356
"eslint-config-prettier": "^4.1.0",
57+
"eslint-plugin-react-hooks": "^1.6.0",
5458
"husky": "^1.3.1",
5559
"lint-staged": "^8.1.5",
5660
"prettier": "^1.16.4",

src/App.js

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import React, { Component } from 'react'
2-
import { Switch, Route } from 'react-router-dom'
1+
import React from 'react'
2+
import { Switch, Route, Redirect } from 'react-router-dom'
33
import { Provider } from 'react-redux'
44

55
import GlobalStyles from './globalStyles'
@@ -9,32 +9,36 @@ import { Cart } from './pages/Cart'
99
import { SignUp } from './pages/SignUp'
1010
import { LogIn } from './pages/LogIn'
1111
import { Account } from './pages/Account'
12+
import { NotFound } from './pages/NotFound'
1213
import { PrivateRoute } from './components/PrivateRoute'
1314
import { getCustomer } from './utils/customer'
1415
import { configureStore } from './store'
16+
import * as routes from './routes'
1517

1618
const store = configureStore({
1719
customer: getCustomer(),
1820
})
1921

20-
class App extends Component {
21-
render() {
22-
return (
23-
<Provider store={store}>
24-
<React.Fragment>
25-
<GlobalStyles />
26-
<Switch>
27-
<Route path="/" exact component={ProductList} />
28-
<Route path="/cart" component={Cart} />
29-
<Route path="/signup" component={SignUp} />
30-
<Route path="/login" component={LogIn} />
31-
<PrivateRoute path="/account" component={Account} />
32-
<Route path="/:productId" component={ProductDetail} />
33-
</Switch>
34-
</React.Fragment>
35-
</Provider>
36-
)
37-
}
38-
}
22+
const App = () => (
23+
<Provider store={store}>
24+
<React.Fragment>
25+
<GlobalStyles />
26+
<Switch>
27+
<Route
28+
path={routes.HOMEPAGE}
29+
exact
30+
render={() => <Redirect to={routes.PRODUCT_LIST} />}
31+
/>
32+
<Route path={routes.PRODUCT_LIST} exact component={ProductList} />
33+
<Route path={routes.PRODUCT_DETAIL} component={ProductDetail} />
34+
<Route path={routes.CART} component={Cart} />
35+
<Route path={routes.SIGN_UP} component={SignUp} />
36+
<Route path={routes.LOGIN} component={LogIn} />
37+
<PrivateRoute path={routes.ACCOUNT} component={Account} />
38+
<Route component={NotFound} />
39+
</Switch>
40+
</React.Fragment>
41+
</Provider>
42+
)
3943

40-
export default App
44+
export { App }

src/api/products/get-products.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
1+
import qs from 'qs'
12
import { api } from '../api-client'
23
import { formatProduct } from './utils/format-product'
34

4-
export const getProducts = async () => {
5-
const { data, included } = await api('/api/skus?include=prices')
5+
export const getProducts = async urlQuery => {
6+
const { data, meta, included } = await api(
7+
`/api/skus?${qs.stringify({ include: 'prices', ...urlQuery })}`
8+
)
69

7-
return data.map(product => formatProduct(product, included))
10+
return {
11+
data: data.map(product => formatProduct(product, included)),
12+
meta,
13+
}
814
}

src/api/use-api.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { useState, useEffect } from 'react'
2+
3+
const useApi = (fn, resolveCondition = []) => {
4+
const [data, setData] = useState(null)
5+
const [isLoading, setLoading] = useState(false)
6+
7+
if (!Array.isArray(resolveCondition)) {
8+
// eslint-disable-next-line no-console
9+
console.error('Passed resolve condition for useEffect hook is not an Array')
10+
}
11+
12+
const request = (...args) => {
13+
setLoading(true)
14+
fn(...args)
15+
.then(returnedData => setData(returnedData))
16+
// eslint-disable-next-line no-console
17+
.catch(console.error)
18+
.finally(() => setLoading(false))
19+
}
20+
21+
useEffect(request, resolveCondition)
22+
23+
return {
24+
request,
25+
data,
26+
isLoading,
27+
}
28+
}
29+
30+
export { useApi }

src/components/Layout/index.js

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { withRouter } from 'react-router-dom'
33
import { connect } from 'react-redux'
44

55
import * as customerActions from '../../store/customer/actions'
6+
import * as routes from '../../routes'
7+
68
import { removeToken } from '../../utils/token'
79
import { removeCustomer } from '../../utils/customer'
810
import { Wrapper, Header, HeaderSection, HeaderLink } from './styled'
@@ -12,7 +14,7 @@ class Layout extends Component {
1214
this.props.logout()
1315
removeToken()
1416
removeCustomer()
15-
this.props.history.push('/')
17+
this.props.history.push(routes.HOMEPAGE)
1618
}
1719

1820
render() {
@@ -22,21 +24,21 @@ class Layout extends Component {
2224
<Fragment>
2325
<Header>
2426
<HeaderSection>
25-
<HeaderLink to="/">All Products</HeaderLink>
27+
<HeaderLink to={routes.PRODUCT_LIST}>All Products</HeaderLink>
2628
</HeaderSection>
2729
<HeaderSection>
28-
<HeaderLink to="/cart">My Cart</HeaderLink>|
30+
<HeaderLink to={routes.CART}>My Cart</HeaderLink>|
2931
{isAuthenticated ? (
3032
<>
31-
<HeaderLink to="/account">My Account</HeaderLink>|
33+
<HeaderLink to={routes.ACCOUNT}>My Account</HeaderLink>|
3234
<HeaderLink as="button" onClick={this.handleLogout}>
3335
Logout
3436
</HeaderLink>
3537
</>
3638
) : (
3739
<>
38-
<HeaderLink to="/login">Log In</HeaderLink> |
39-
<HeaderLink to="/signup">Sign Up</HeaderLink>
40+
<HeaderLink to={routes.LOGIN}>Log In</HeaderLink> |
41+
<HeaderLink to={routes.SIGN_UP}>Sign Up</HeaderLink>
4042
</>
4143
)}
4244
</HeaderSection>

src/components/Pagination/index.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import * as React from 'react'
2+
import range from 'ramda/src/range'
3+
import map from 'ramda/src/map'
4+
import { Link } from 'react-router-dom'
5+
6+
import * as routes from '../../routes'
7+
8+
import { List, ListItem } from './styled'
9+
10+
const renderPaginationItem = number => (
11+
<ListItem key={number}>
12+
<Link to={`${routes.PRODUCT_LIST}?page=${number}`}>{number}</Link>
13+
</ListItem>
14+
)
15+
16+
const Pagination = ({ pages }) => (
17+
<List>{map(renderPaginationItem, range(1, pages + 1))}</List>
18+
)
19+
20+
export { Pagination }

src/components/Pagination/styled.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import styled from 'styled-components'
2+
3+
export const List = styled.ul`
4+
align-items: center;
5+
display: flex;
6+
flex-direction: row;
7+
justify-content: center;
8+
list-style: none;
9+
padding: 0;
10+
text-align: center;
11+
width: 100%;
12+
`
13+
14+
export const ListItem = styled.li`
15+
margin: 5px;
16+
`

src/components/PrivateRoute/index.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import React from 'react'
22
import { Route, Redirect } from 'react-router-dom'
33
import { connect } from 'react-redux'
44

5+
import * as routes from '../../routes'
6+
57
const PrivateRouteComponent = ({
68
isAuthenticated,
79
component: Component,
@@ -17,7 +19,7 @@ const PrivateRouteComponent = ({
1719
return (
1820
<Redirect
1921
to={{
20-
pathname: '/login',
22+
pathname: routes.LOGIN,
2123
state: {
2224
from: routeProps.location.pathname,
2325
},

src/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import * as serviceWorker from './serviceWorker'
22

3-
import App from './App'
43
import React from 'react'
54
import ReactDOM from 'react-dom'
65
import { BrowserRouter as Router } from 'react-router-dom'
76

7+
import { App } from './App'
8+
89
const render = () => {
910
ReactDOM.render(
1011
<Router>

src/pages/Cart/CartItem.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import * as React from 'react'
2+
3+
import { getProductById } from '../../api/products/get-product'
4+
import { useApi } from '../../api/use-api'
5+
6+
import Loader from '../../components/Loader'
7+
import Button from '../../components/Button'
8+
9+
const CartItem = ({ productId, quantity, removeProduct }) => {
10+
const { data: product, isLoading } = useApi(
11+
() => getProductById(productId),
12+
productId
13+
)
14+
15+
return (
16+
<li key={productId}>
17+
{isLoading && <Loader small />}
18+
<p>
19+
{product ? product.name : productId} - {quantity}
20+
</p>
21+
<Button type="button" onClick={() => removeProduct(productId)}>
22+
Remove
23+
</Button>
24+
</li>
25+
)
26+
}
27+
28+
export { CartItem }

src/pages/Cart/index.js

Lines changed: 23 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,43 @@
1-
import React, { Component } from 'react'
1+
import React from 'react'
22
import { connect } from 'react-redux'
33

4-
import Button from '../../components/Button'
54
import Layout from '../../components/Layout'
65
import { H1 } from '../../components/Typography'
7-
import { removeProduct } from '../../store/cart/actions'
6+
import * as cartActions from '../../store/cart/actions'
7+
import { CartItem } from './CartItem'
88

9-
class CartView extends Component {
10-
render() {
11-
return (
12-
<Layout>
13-
<H1>Your cart</H1>
14-
<ul>
15-
{this.props.items.map(item => (
16-
<li key={item.product.id}>
17-
<p>
18-
{item.product.name} - {item.quantity}
19-
</p>
20-
<Button
21-
type="button"
22-
onClick={() => this.props.removeProduct(item.product.id)}
23-
>
24-
Remove
25-
</Button>
26-
</li>
27-
))}
28-
</ul>
29-
</Layout>
30-
)
31-
}
9+
const CartView = ({ items, removeProduct }) => {
10+
return (
11+
<Layout>
12+
<H1>Your cart</H1>
13+
<ul>
14+
{items.map(item => (
15+
<CartItem
16+
key={item.product.id}
17+
productId={item.product.id}
18+
quantity={item.quantity}
19+
removeProduct={removeProduct}
20+
/>
21+
))}
22+
</ul>
23+
</Layout>
24+
)
3225
}
3326

3427
const mapStateToProps = state => ({
3528
items: Object.keys(state.cart).map(productId => ({
3629
quantity: state.cart[productId],
37-
product: state.products.find(p => p.id === productId),
30+
product: { id: productId },
3831
})),
3932
})
4033

41-
const actionCreators = {
42-
removeProduct,
34+
const mapDispatchToProps = {
35+
removeProduct: cartActions.removeProduct,
4336
}
4437

4538
const Cart = connect(
4639
mapStateToProps,
47-
actionCreators
40+
mapDispatchToProps
4841
)(CartView)
4942

5043
export { Cart }

src/pages/LogIn/index.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import { Form, GlobalFormError } from '../../components/Form'
88
import { Input } from '../../components/Input'
99
import Button from '../../components/Button'
1010
import * as customerActions from '../../store/customer/actions'
11+
import * as routes from '../../routes'
12+
1113
import { getCustomerToken } from '../../api/customers/get-customer-token'
1214
import { getCustomer } from '../../api/customers/get-customer'
1315
import { schema } from './schema'
@@ -31,7 +33,7 @@ class LogInPage extends Component {
3133
})
3234
const customer = await getCustomer(ownerId)
3335
this.props.login(customer)
34-
this.props.history.push('/account')
36+
this.props.history.push(routes.ACCOUNT)
3537
} catch (error) {
3638
this.setState({
3739
globalError: error.message,

src/pages/NotFound/index.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import * as React from 'react'
2+
3+
const NotFound = () => <div>{"Sorry, page doesn't exist"}</div>
4+
5+
export { NotFound }

0 commit comments

Comments
 (0)