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
);