diff --git a/src/components/menu/menu.js b/src/components/menu/menu.js index 4ee1ded..bead021 100644 --- a/src/components/menu/menu.js +++ b/src/components/menu/menu.js @@ -1,42 +1,49 @@ -import { Component } from 'react'; +import { connect } from 'react-redux'; +import { useEffect } from 'react'; import PropTypes from 'prop-types'; - import Product from '../product'; import Basket from '../basket'; - +import Loader from '../loader'; +import { loadProducts } from '../../redux/actions'; +import { + productsLoadingSelector, + productsLoadedSelector, +} from '../../redux/selectors'; import styles from './menu.module.css'; -class Menu extends Component { - static propTypes = { - menu: PropTypes.arrayOf(PropTypes.string.isRequired).isRequired, - }; +const Menu = ({ menu, restId, loadProducts, loading, loaded }) => { + useEffect(() => { + if (!loading && !loaded) loadProducts(restId); + }, [restId, loadProducts, loading, loaded]); - state = { error: null }; + if (loading) return ; + if (!loaded) return 'No data :('; - componentDidCatch(error) { - this.setState({ error }); - } + return ( +
+
+ {menu.map((id) => ( + + ))} +
+
+ +
+
+ ); +}; - render() { - const { menu } = this.props; +Menu.propTypes = { + menu: PropTypes.arrayOf(PropTypes.string.isRequired).isRequired, +}; - if (this.state.error) { - return

Меню этого ресторана сейчас недоступно :(

; - } +const mapStateToProps = (state, props) => ({ + loading: productsLoadingSelector(state, props), + loaded: productsLoadedSelector(state, props), +}); - return ( -
-
- {menu.map((id) => ( - - ))} -
-
- -
-
- ); - } -} +const mapDispatchToProps = { + loadProducts, +}; -export default Menu; +export default connect(mapStateToProps, mapDispatchToProps)(Menu); diff --git a/src/components/restaurant/restaurant.js b/src/components/restaurant/restaurant.js index 51fe01e..5fcc6fc 100644 --- a/src/components/restaurant/restaurant.js +++ b/src/components/restaurant/restaurant.js @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useState, useEffect, useMemo } from 'react'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import Menu from '../menu'; @@ -6,29 +6,51 @@ import Reviews from '../reviews'; import Banner from '../banner'; import Rate from '../rate'; import Tabs from '../tabs'; +import Loader from '../loader'; import { averageRatingSelector, restaurantSelector, + reviewsLoadedSelector, + reviewsLoadingSelector, } from '../../redux/selectors'; +import { loadReviews } from '../../redux/actions'; -const Restaurant = ({ restaurant, averageRating }) => { +const Restaurant = ({ + restaurant, + averageRating, + loadReviews, + reviwesLoading, + reviewsLoaded, +}) => { const { id, name, menu, reviews } = restaurant; - const [activeTab, setActiveTab] = useState('menu'); - const tabs = [ { id: 'menu', label: 'Menu' }, { id: 'reviews', label: 'Reviews' }, ]; + useEffect(() => { + if (!reviwesLoading && !reviewsLoaded) loadReviews(id); + }, [id, loadReviews, reviwesLoading, reviewsLoaded]); + + const rateComponent = useMemo(() => { + if (reviwesLoading) return ; + if (!reviewsLoaded) return ; + return ; + }, [reviwesLoading, reviewsLoaded, averageRating]); + + const reviewsComponent = useMemo(() => { + if (reviwesLoading) return ; + if (!reviewsLoaded) return ; + return ; + }, [reviwesLoading, reviewsLoaded, id, reviews]); + return (
- - - + {rateComponent} - {activeTab === 'menu' && } - {activeTab === 'reviews' && } + {activeTab === 'menu' && } + {activeTab === 'reviews' && reviewsComponent}
); }; @@ -46,6 +68,11 @@ Restaurant.propTypes = { const mapStateToProps = (state, props) => ({ restaurant: restaurantSelector(state, props), averageRating: averageRatingSelector(state, props), + reviwesLoading: reviewsLoadingSelector(state, props), + reviewsLoaded: reviewsLoadedSelector(state, props), }); -export default connect(mapStateToProps)(Restaurant); +const mapDispatchToProps = { + loadReviews, +}; +export default connect(mapStateToProps, mapDispatchToProps)(Restaurant); diff --git a/src/components/reviews/reviews.js b/src/components/reviews/reviews.js index 8dc5628..7467073 100644 --- a/src/components/reviews/reviews.js +++ b/src/components/reviews/reviews.js @@ -4,13 +4,21 @@ import PropTypes from 'prop-types'; import Review from './review'; import ReviewForm from './review-form'; import styles from './reviews.module.css'; +import Loader from '../loader'; +import { + usersLoadingSelector, + usersLoadedSelector, +} from '../../redux/selectors'; -import { loadReviews } from '../../redux/actions'; +import { loadUsers } from '../../redux/actions'; -const Reviews = ({ reviews, restId, loadReviews }) => { +const Reviews = ({ reviews, restId, loadUsers, usersLoading, usersLoaded }) => { useEffect(() => { - loadReviews(restId); - }, [restId, loadReviews]); + if (!usersLoading && !usersLoaded) loadUsers(); + }, [loadUsers, usersLoading, usersLoaded]); + + if (usersLoading) return ; + if (!usersLoaded) return 'No data :('; return (
@@ -27,8 +35,13 @@ Reviews.propTypes = { reviews: PropTypes.arrayOf(PropTypes.string.isRequired).isRequired, }; +const mapStateToProps = (state, props) => ({ + usersLoading: usersLoadingSelector(state), + usersLoaded: usersLoadedSelector(state), +}); + const mapDispatchToProps = { - loadReviews, + loadUsers, }; -export default connect(null, mapDispatchToProps)(Reviews); +export default connect(mapStateToProps, mapDispatchToProps)(Reviews); diff --git a/src/fixtures.js b/src/fixtures.js deleted file mode 100644 index ed35fc6..0000000 --- a/src/fixtures.js +++ /dev/null @@ -1,334 +0,0 @@ -export const restaurants = [ - { - id: 'a757a0e9-03c1-4a2a-b384-8ac21dbe2fb2', - name: 'Dishoom', - menu: [ - { - id: 'd75f762a-eadd-49be-8918-ed0daa8dd024', - name: 'Chicken tikka masala', - price: 12, - ingredients: ['chicken', 'rice'], - }, - { - id: 'c3cb8f92-a2ed-4716-92a1-b6ea813e9049', - name: 'Naan', - price: 3, - ingredients: ['bread'], - }, - { - id: 'bd129641-c0eb-432b-84b6-8b81d2930358', - name: 'Samosa', - price: 8, - ingredients: ['chicken', 'bread'], - }, - ], - reviews: [ - { - id: '5909796d-5030-4e36-adec-68b8f9ec2d96', - user: 'Antony', - text: 'Not bad', - rating: 5, - }, - { - id: '429dea85-11dd-4054-a31e-c60c92e17255', - user: 'Sam', - text: 'No burgers', - rating: 3, - }, - ], - }, - { - id: 'bb8afbec-2fec-491f-93e9-7f13950dd80b', - name: 'Homeslice', - menu: [ - { - id: '25402233-0095-49ea-9939-1e67ed89ffb9', - name: 'Margarita', - price: 9, - ingredients: ['bread', 'cheese', 'tomatoes'], - }, - { - id: '90902233-0095-49ea-9939-1e67ed89ffb9', - name: 'Chef pizza', - price: 10, - ingredients: ['bread', 'cheese', 'tomatoes', 'chicken'], - }, - ], - reviews: [ - { - id: '53b642d7-5e86-4717-a466-0640a1dee076', - user: 'Diana', - text: 'Perfect Margarita', - rating: 5, - }, - { - id: 'c27ab88e-375c-4e98-aa94-8a180150a797', - user: 'Sam', - text: 'No burgers again. But Chef Pizza is the best one', - rating: 4, - }, - { - id: 'abc0c5e1-cd57-4f0a-99d9-00e6b4533b3a', - user: 'Lolly', - text: 'Good for lunch', - rating: 5, - }, - ], - }, - { - id: '982bfbce-c5e0-41a0-9f99-d5c20ecee49d', - name: 'Fabrique', - menu: [ - { - id: '08c9ffa0-d003-4310-9e15-20978743296e', - name: 'Cinnamon buns', - price: 5, - ingredients: ['bread'], - }, - { - id: '64a4967c-2080-4a99-9074-4655a4569a95', - name: 'Semlor', - price: 2, - ingredients: ['bread', 'cream'], - }, - { - id: '4bc8528e-26d1-46c3-a522-8e18d10c8c84', - name: 'Saffron bun', - price: 4, - ingredients: ['bread'], - }, - ], - reviews: [ - { - id: '53b642d7-5e86-4717-a466-0640a1dee076', - user: 'Agata', - text: 'Best bakery', - rating: 5, - }, - ], - }, - { - id: 'd9241927-09e1-44f3-8986-a76346869037', - name: 'Flat Iron', - menu: [ - { - id: '6c02c2ce-b868-4191-b4a7-8686429f4bac', - name: 'Flat Iron Steak', - price: 10, - ingredients: ['beef'], - }, - { - id: '99bb6fbb-e53b-4b7e-b9c2-23b63b77385d', - name: 'Flat Iron Burger', - price: 10, - ingredients: ['bread', 'beef'], - }, - ], - reviews: [ - { - id: '5db6247b-ab1c-49db-be1f-8dd27fd38b81', - user: 'Sam', - text: 'Finally! This place is amazing place for breakfast, lunch, dinner and supper', - rating: 5, - }, - { - id: '381b0c31-6360-43ff-80d1-581a116159d8', - user: 'Rebeca', - text: 'Meat here is extremely delicious', - rating: 5, - }, - ], - }, -]; - -export const normalizedRestaurants = [ - { - id: 'a757a0e9-03c1-4a2a-b384-8ac21dbe2fb2', - name: 'Dishoom', - menu: [ - 'd75f762a-eadd-49be-8918-ed0daa8dd024', - 'c3cb8f92-a2ed-4716-92a1-b6ea813e9049', - 'bd129641-c0eb-432b-84b6-8b81d2930358', - ], - reviews: [ - '5909796d-5030-4e36-adec-68b8f9ec2d96', - '429dea85-11dd-4054-a31e-c60c92e17255', - ], - }, - { - id: 'bb8afbec-2fec-491f-93e9-7f13950dd80b', - name: 'Homeslice', - menu: [ - '25402233-0095-49ea-9939-1e67ed89ffb9', - '90902233-0095-49ea-9939-1e67ed89ffb9', - ], - reviews: [ - '53b642d7-5e86-4717-a466-0640a1dee076', - 'c27ab88e-375c-4e98-aa94-8a180150a797', - 'abc0c5e1-cd57-4f0a-99d9-00e6b4533b3a', - ], - }, - { - id: '982bfbce-c5e0-41a0-9f99-d5c20ecee49d', - name: 'Fabrique', - menu: [ - '08c9ffa0-d003-4310-9e15-20978743296e', - '64a4967c-2080-4a99-9074-4655a4569a95', - '4bc8528e-26d1-46c3-a522-8e18d10c8c84', - ], - reviews: ['13b642d7-5e86-4717-a466-0640a1dee076'], - }, - { - id: 'd9241927-09e1-44f3-8986-a76346869037', - name: 'Flat Iron', - menu: [ - '6c02c2ce-b868-4191-b4a7-8686429f4bac', - '99bb6fbb-e53b-4b7e-b9c2-23b63b77385d', - ], - reviews: [ - '5db6247b-ab1c-49db-be1f-8dd27fd38b81', - '381b0c31-6360-43ff-80d1-581a116159d8', - ], - }, -]; - -export const normalizedProducts = [ - { - id: 'd75f762a-eadd-49be-8918-ed0daa8dd024', - name: 'Chicken tikka masala', - price: 12, - ingredients: ['chicken', 'rice'], - }, - { - id: 'c3cb8f92-a2ed-4716-92a1-b6ea813e9049', - name: 'Naan', - price: 3, - ingredients: ['bread'], - }, - { - id: 'bd129641-c0eb-432b-84b6-8b81d2930358', - name: 'Samosa', - price: 8, - ingredients: ['chicken', 'bread'], - }, - { - id: '25402233-0095-49ea-9939-1e67ed89ffb9', - name: 'Margarita', - price: 9, - ingredients: ['bread', 'cheese', 'tomatoes'], - }, - { - id: '90902233-0095-49ea-9939-1e67ed89ffb9', - name: 'Chef pizza', - price: 10, - ingredients: ['bread', 'cheese', 'tomatoes', 'chicken'], - }, - { - id: '08c9ffa0-d003-4310-9e15-20978743296e', - name: 'Cinnamon buns', - price: 5, - ingredients: ['bread'], - }, - { - id: '64a4967c-2080-4a99-9074-4655a4569a95', - name: 'Semlor', - price: 2, - ingredients: ['bread', 'cream'], - }, - { - id: '4bc8528e-26d1-46c3-a522-8e18d10c8c84', - name: 'Saffron bun', - price: 4, - ingredients: ['bread'], - }, - { - id: '6c02c2ce-b868-4191-b4a7-8686429f4bac', - name: 'Flat Iron Steak', - price: 10, - ingredients: ['beef'], - }, - { - id: '99bb6fbb-e53b-4b7e-b9c2-23b63b77385d', - name: 'Flat Iron Burger', - price: 10, - ingredients: ['bread', 'beef'], - }, -]; - -export const normalizedReviews = [ - { - id: '5909796d-5030-4e36-adec-68b8f9ec2d96', - userId: 'a304959a-76c0-4b34-954a-b38dbf310360', - text: 'Not bad', - rating: 5, - }, - { - id: '429dea85-11dd-4054-a31e-c60c92e17255', - userId: 'dfb982e9-b432-4b7d-aec6-7f6ff2e6af54', - text: 'No burgers', - rating: 3, - }, - { - id: '53b642d7-5e86-4717-a466-0640a1dee076', - userId: '20bed9b5-9c7b-4771-8221-75b74ed1904a', - text: 'Perfect Margarita', - rating: 5, - }, - { - id: 'c27ab88e-375c-4e98-aa94-8a180150a797', - userId: 'dfb982e9-b432-4b7d-aec6-7f6ff2e6af54', - text: 'No burgers again. But Chef Pizza is the best one', - rating: 4, - }, - { - id: 'abc0c5e1-cd57-4f0a-99d9-00e6b4533b3a', - userId: 'c3d4abd4-c3ef-46e1-8719-eb17db1d6e99', - text: 'Good for lunch', - rating: 5, - }, - { - id: '13b642d7-5e86-4717-a466-0640a1dee076', - userId: '52a63cc0-5a6f-41f3-9774-0161ea4c9b0c', - text: 'Best bakery', - rating: 5, - }, - { - id: '5db6247b-ab1c-49db-be1f-8dd27fd38b81', - userId: 'dfb982e9-b432-4b7d-aec6-7f6ff2e6af54', - text: 'Finally! This place is amazing place for breakfast, lunch, dinner and supper', - rating: 5, - }, - { - id: '381b0c31-6360-43ff-80d1-581a116159d8', - userId: '1547335a-ea18-4547-a73d-32bd6e9f651c', - text: 'Meat here is extremely delicious', - rating: 5, - }, -]; - -export const normalizedUsers = [ - { - id: 'a304959a-76c0-4b34-954a-b38dbf310360', - name: 'Antony', - }, - { - id: '20bed9b5-9c7b-4771-8221-75b74ed1904a', - name: 'Diana', - }, - { - id: 'c3d4abd4-c3ef-46e1-8719-eb17db1d6e99', - name: 'Lolly', - }, - { - id: '52a63cc0-5a6f-41f3-9774-0161ea4c9b0c', - name: 'Agata', - }, - { - id: '1547335a-ea18-4547-a73d-32bd6e9f651c', - name: 'Rebeca', - }, - { - id: 'dfb982e9-b432-4b7d-aec6-7f6ff2e6af54', - name: 'Sam', - }, -]; diff --git a/src/redux/actions.js b/src/redux/actions.js index ae2a5c2..3187788 100644 --- a/src/redux/actions.js +++ b/src/redux/actions.js @@ -4,11 +4,13 @@ import { REMOVE, ADD_REVIEW, LOAD_RESTAURANTS, + LOAD_PRODUCTS, CHANGE_RESTAURANT, REQUEST, SUCCESS, FAILURE, LOAD_REVIEWS, + LOAD_USERS, } from './constants'; export const increment = (id) => ({ type: INCREMENT, id }); @@ -44,3 +46,20 @@ export const loadReviews = (restId) => async (dispatch) => { dispatch({ type: LOAD_REVIEWS + FAILURE, restId, error }); } }; + +export const loadProducts = (restId) => ({ + type: LOAD_PRODUCTS, + CallAPI: `/api/products?id=${restId}`, + restId, +}); + +export const loadUsers = () => async (dispatch) => { + dispatch({ type: LOAD_USERS + REQUEST }); + + try { + const data = await fetch('/api/users').then((res) => res.json()); + dispatch({ type: LOAD_USERS + SUCCESS, data }); + } catch (error) { + dispatch({ type: LOAD_USERS + FAILURE, error }); + } +}; diff --git a/src/redux/constants.js b/src/redux/constants.js index ba58347..15285a9 100644 --- a/src/redux/constants.js +++ b/src/redux/constants.js @@ -6,6 +6,8 @@ export const CHANGE_RESTAURANT = 'CHANGE_RESTAURANT'; export const LOAD_RESTAURANTS = 'LOAD_RESTAURANTS'; export const LOAD_REVIEWS = 'LOAD_REVIEWS'; +export const LOAD_PRODUCTS = 'LOAD_PRODUCTS'; +export const LOAD_USERS = 'LOAD_USERS'; export const REQUEST = '_REQUEST'; export const SUCCESS = '_SUCCESS'; diff --git a/src/redux/reducer/dataLoadingStatus.js b/src/redux/reducer/dataLoadingStatus.js new file mode 100644 index 0000000..0538561 --- /dev/null +++ b/src/redux/reducer/dataLoadingStatus.js @@ -0,0 +1,111 @@ +import produce from 'immer'; +import { + FAILURE, + LOAD_PRODUCTS, + LOAD_RESTAURANTS, + LOAD_REVIEWS, + LOAD_USERS, + REQUEST, + SUCCESS, +} from '../constants'; + +const defaultState = { + users: { + loading: false, + loaded: false, + error: null, + }, +}; + +export default produce((draft = defaultState, action) => { + const { type, data, restId, error } = action; + switch (type) { + case LOAD_RESTAURANTS + SUCCESS: + draft.restaurants = { + ...data.reduce( + (acc, { id }) => ({ + ...acc, + [id]: { + products: { + loading: false, + loaded: false, + error: null, + }, + reviews: { + loading: false, + loaded: false, + error: null, + }, + }, + }), + {} + ), + }; + break; + case LOAD_PRODUCTS + REQUEST: + draft.restaurants[restId].products = { + loading: true, + loaded: false, + error: null, + }; + break; + case LOAD_PRODUCTS + SUCCESS: + draft.restaurants[restId].products = { + loading: false, + loaded: true, + error: null, + }; + break; + case LOAD_PRODUCTS + FAILURE: + draft.restaurants[restId].products = { + loading: false, + loaded: false, + error, + }; + break; + case LOAD_REVIEWS + REQUEST: + draft.restaurants[restId].reviews = { + loading: true, + loaded: false, + error: null, + }; + break; + case LOAD_REVIEWS + SUCCESS: + draft.restaurants[restId].reviews = { + loading: false, + loaded: true, + error: null, + }; + break; + case LOAD_REVIEWS + FAILURE: + draft.restaurants[restId].reviews = { + loading: false, + loaded: false, + error, + }; + break; + case LOAD_USERS + REQUEST: + draft.users = { + loading: true, + loaded: false, + error: null, + }; + break; + case LOAD_USERS + SUCCESS: + draft.users = { + loading: false, + loaded: true, + error: null, + }; + break; + case LOAD_USERS + FAILURE: + draft.users = { + loading: false, + loaded: false, + error, + }; + break; + default: + return draft; + } +}); diff --git a/src/redux/reducer/index.js b/src/redux/reducer/index.js index 3b5c632..72d8562 100644 --- a/src/redux/reducer/index.js +++ b/src/redux/reducer/index.js @@ -4,6 +4,7 @@ import restaurants from './restaurants'; import products from './products'; import reviews from './reviews'; import users from './users'; +import dataLoadingStatus from './dataLoadingStatus'; export default combineReducers({ order, @@ -11,4 +12,5 @@ export default combineReducers({ products, reviews, users, + dataLoadingStatus, }); diff --git a/src/redux/reducer/order.js b/src/redux/reducer/order.js index e20530c..c07e3bd 100644 --- a/src/redux/reducer/order.js +++ b/src/redux/reducer/order.js @@ -1,16 +1,20 @@ +import produce from 'immer'; + import { DECREMENT, INCREMENT, REMOVE } from '../constants'; -// { [productId]: amount } -export default function (state = {}, action) { +export default produce((draft = {}, action) => { const { type, id } = action; switch (type) { case INCREMENT: - return { ...state, [id]: (state[id] || 0) + 1 }; + draft[id] = (draft[id] || 0) + 1; + break; case DECREMENT: - return { ...state, [id]: state[id] > 0 ? (state[id] || 0) - 1 : 0 }; + draft[id] = draft[id] > 0 ? (draft[id] || 0) - 1 : 0; + break; case REMOVE: - return { ...state, [id]: 0 }; + draft[id] = 0; + break; default: - return state; + return draft; } -} +}); diff --git a/src/redux/reducer/products.js b/src/redux/reducer/products.js index 55bc808..53a242c 100644 --- a/src/redux/reducer/products.js +++ b/src/redux/reducer/products.js @@ -1,10 +1,16 @@ -import { normalizedProducts } from '../../fixtures'; +import { LOAD_PRODUCTS, SUCCESS } from '../constants'; import { arrToMap } from '../utils'; -export default (state = arrToMap(normalizedProducts), action) => { - const { type } = action; +export default (state = {}, action) => { + const { type, data } = action; switch (type) { + case LOAD_PRODUCTS + SUCCESS: + return { + ...state, + ...arrToMap(data), + }; + default: return state; } diff --git a/src/redux/reducer/restaurants.js b/src/redux/reducer/restaurants.js index 6241a68..604381a 100644 --- a/src/redux/reducer/restaurants.js +++ b/src/redux/reducer/restaurants.js @@ -3,7 +3,9 @@ import { ADD_REVIEW, CHANGE_RESTAURANT, FAILURE, + LOAD_PRODUCTS, LOAD_RESTAURANTS, + LOAD_REVIEWS, REQUEST, SUCCESS, } from '../constants'; @@ -38,6 +40,15 @@ export default (state = initialState, action) => { loaded: false, error, }; + + case LOAD_PRODUCTS + SUCCESS: + return produce(state, (draft) => { + draft.entities[restId].menu = data.map((product) => product.id); + }); + case LOAD_REVIEWS + SUCCESS: + return produce(state, (draft) => { + draft.entities[restId].reviews = data.map((review) => review.id); + }); case CHANGE_RESTAURANT: return { ...state, activeId }; case ADD_REVIEW: diff --git a/src/redux/reducer/reviews.js b/src/redux/reducer/reviews.js index e79ab91..33553ea 100644 --- a/src/redux/reducer/reviews.js +++ b/src/redux/reducer/reviews.js @@ -1,17 +1,21 @@ -import { ADD_REVIEW } from '../constants'; -import { normalizedReviews } from '../../fixtures'; +import produce from 'immer'; +import { ADD_REVIEW, LOAD_REVIEWS, SUCCESS } from '../constants'; import { arrToMap } from '../utils'; -export default (state = arrToMap(normalizedReviews), action) => { - const { type, review, reviewId, userId } = action; +export default (state = {}, action) => { + const { type, review, reviewId, userId, data } = action; switch (type) { - case ADD_REVIEW: - const { text, rating } = review; + case LOAD_REVIEWS + SUCCESS: return { ...state, - [reviewId]: { id: reviewId, userId, text, rating }, + ...arrToMap(data), }; + case ADD_REVIEW: + const { text, rating } = review; + return produce(state, (draft) => { + draft[reviewId] = { id: reviewId, userId, text, rating }; + }); default: return state; } diff --git a/src/redux/reducer/users.js b/src/redux/reducer/users.js index 1143dff..bc4dd1f 100644 --- a/src/redux/reducer/users.js +++ b/src/redux/reducer/users.js @@ -1,18 +1,20 @@ -import produce from 'immer'; -import { ADD_REVIEW } from '../constants'; -import { normalizedUsers } from '../../fixtures'; +import { ADD_REVIEW, LOAD_USERS, SUCCESS } from '../constants'; import { arrToMap } from '../utils'; -export default produce((draft = arrToMap(normalizedUsers), action) => { - const { type, review, userId } = action; +//export default produce((draft = {}, action) => { +export default (state = {}, action) => { + const { type, review, userId, data } = action; switch (type) { + case LOAD_USERS + SUCCESS: + return { ...state, ...arrToMap(data) }; case ADD_REVIEW: const { name } = review; - draft[userId] = { id: userId, name }; - break; - + return { + ...state, + [userId]: { id: userId, name }, + }; default: - return draft; + return state; } -}); +}; diff --git a/src/redux/selectors.js b/src/redux/selectors.js index 547712d..ba83cb8 100644 --- a/src/redux/selectors.js +++ b/src/redux/selectors.js @@ -5,11 +5,25 @@ const productsSelector = (state) => state.products; const orderSelector = (state) => state.order; const reviewsSelector = (state) => state.reviews; const usersSelector = (state) => state.users; +const dataLoadingStatusSelector = (state) => state.dataLoadingStatus; export const activeIdRestaurantSelector = (state) => state.restaurants.activeId; export const restaurantsLoadingSelector = (state) => state.restaurants.loading; export const restaurantsLoadedSelector = (state) => state.restaurants.loaded; +export const productsLoadingSelector = (state, { restId }) => + dataLoadingStatusSelector(state).restaurants[restId].products.loading; +export const productsLoadedSelector = (state, { restId }) => + dataLoadingStatusSelector(state).restaurants[restId].products.loaded; +export const reviewsLoadingSelector = (state, { id }) => + dataLoadingStatusSelector(state).restaurants[id].reviews.loading; +export const reviewsLoadedSelector = (state, { id }) => + dataLoadingStatusSelector(state).restaurants[id].reviews.loaded; +export const usersLoadingSelector = (state) => + dataLoadingStatusSelector(state).users.loading; +export const usersLoadedSelector = (state) => + dataLoadingStatusSelector(state).users.loaded; + export const restaurantsListSelector = createSelector( restaurantsSelector, Object.values @@ -53,7 +67,9 @@ export const averageRatingSelector = createSelector( reviewsSelector, restaurantSelector, (reviews, restaurant) => { - const ratings = restaurant.reviews.map((id) => reviews[id].rating); + const ratings = restaurant.reviews.map((id) => + !!reviews[id] ? reviews[id].rating : 0 + ); return Math.round( ratings.reduce((acc, rating) => acc + rating) / ratings.length );