diff --git a/package.json b/package.json
index ba1b4a1..21dae21 100644
--- a/package.json
+++ b/package.json
@@ -12,7 +12,8 @@
"react-scripts": "4.0.3",
"redux": "^4.1.1",
"redux-devtools-extension": "^2.13.9",
- "reselect": "^4.0.0"
+ "reselect": "^4.0.0",
+ "uuid": "^8.3.2"
},
"scripts": {
"start": "react-scripts start",
diff --git a/src/components/menu/menu.js b/src/components/menu/menu.js
index aebcf81..9acea29 100644
--- a/src/components/menu/menu.js
+++ b/src/components/menu/menu.js
@@ -8,11 +8,7 @@ import styles from './menu.module.css';
class Menu extends Component {
static propTypes = {
- menu: PropTypes.arrayOf(
- PropTypes.shape({
- id: PropTypes.string.isRequired,
- }).isRequired
- ).isRequired,
+ menu: PropTypes.arrayOf(PropTypes.string).isRequired,
};
state = { error: null };
diff --git a/src/components/product/product.js b/src/components/product/product.js
index 71ce160..2d7d60f 100644
--- a/src/components/product/product.js
+++ b/src/components/product/product.js
@@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
import styles from './product.module.css';
import Button from '../button';
import { decrement, increment } from '../../redux/actions';
+import { orderSelectorById, productSelectorById } from '../../redux/selectors';
function Product({ product, amount, decrement, increment, fetchData }) {
useEffect(() => {
@@ -56,8 +57,8 @@ Product.propTypes = {
};
const mapStateToProps = (state, props) => ({
- amount: state.order[props.id] || 0,
- product: state.products[props.id],
+ amount: orderSelectorById(state, props),
+ product: productSelectorById(state, props),
});
// const mapDispatchToProps = {
diff --git a/src/components/restaurant/restaurant.js b/src/components/restaurant/restaurant.js
index 364e73c..f9e329a 100644
--- a/src/components/restaurant/restaurant.js
+++ b/src/components/restaurant/restaurant.js
@@ -1,21 +1,21 @@
-import { useMemo, useState } from 'react';
+import { useState } from 'react';
import PropTypes from 'prop-types';
+import { connect } from 'react-redux';
import Menu from '../menu';
import Reviews from '../reviews';
import Banner from '../banner';
import Rate from '../rate';
import Tabs from '../tabs';
+import {
+ restaurantSelectorById,
+ averageRatingSelector,
+} from '../../redux/selectors';
-const Restaurant = ({ restaurant }) => {
+const Restaurant = ({ restaurantId, restaurant, averageRating }) => {
const { id, name, menu, reviews } = restaurant;
const [activeTab, setActiveTab] = useState('menu');
- const averageRating = useMemo(() => {
- const total = reviews.reduce((acc, { rating }) => acc + rating, 0);
- return Math.round(total / reviews.length);
- }, [reviews]);
-
const tabs = [
{ id: 'menu', label: 'Menu' },
{ id: 'reviews', label: 'Reviews' },
@@ -28,22 +28,27 @@ const Restaurant = ({ restaurant }) => {
{activeTab === 'menu' &&
@@ -31,4 +32,9 @@ Review.defaultProps = {
user: 'Anonymous',
};
-export default Review;
+const mapStateToProps = (state, props) => {
+ const { user, text, rating } = reviewSelectorById(state, props);
+ return { user, text, rating };
+};
+
+export default connect(mapStateToProps)(Review);
diff --git a/src/components/reviews/reviews.js b/src/components/reviews/reviews.js
index 66f6cf1..b95520b 100644
--- a/src/components/reviews/reviews.js
+++ b/src/components/reviews/reviews.js
@@ -3,23 +3,19 @@ import Review from './review';
import ReviewForm from './review-form';
import styles from './reviews.module.css';
-const Reviews = ({ reviews }) => {
+const Reviews = ({ restaurantId, reviews }) => {
return (
- {reviews.map((review) => (
-
+ {reviews.map((id) => (
+
))}
-
+
);
};
Reviews.propTypes = {
- reviews: PropTypes.arrayOf(
- PropTypes.shape({
- id: PropTypes.string.isRequired,
- }).isRequired
- ).isRequired,
+ reviews: PropTypes.arrayOf(PropTypes.string).isRequired,
};
export default Reviews;
diff --git a/src/redux/actions.js b/src/redux/actions.js
index ea78f99..3d12ae7 100644
--- a/src/redux/actions.js
+++ b/src/redux/actions.js
@@ -1,5 +1,6 @@
-import { DECREMENT, INCREMENT, REMOVE } from './constants';
+import { DECREMENT, INCREMENT, REMOVE, ADDREVIEW } from './constants';
export const increment = (id) => ({ type: INCREMENT, id });
export const decrement = (id) => ({ type: DECREMENT, id });
export const remove = (id) => ({ type: REMOVE, id });
+export const addreview = (review) => ({ type: ADDREVIEW, review });
diff --git a/src/redux/constants.js b/src/redux/constants.js
index 9cfa25d..b811296 100644
--- a/src/redux/constants.js
+++ b/src/redux/constants.js
@@ -1,3 +1,4 @@
export const INCREMENT = 'INCREMENT';
export const DECREMENT = 'DECREMENT';
export const REMOVE = 'REMOVE';
+export const ADDREVIEW = 'ADDREVIEW';
diff --git a/src/redux/middleware/uuidGenerator.js b/src/redux/middleware/uuidGenerator.js
new file mode 100644
index 0000000..5392266
--- /dev/null
+++ b/src/redux/middleware/uuidGenerator.js
@@ -0,0 +1,7 @@
+import { v4 as uuidv4 } from 'uuid';
+
+export default (store) => (next) => (action) => {
+ action.review.id = uuidv4();
+ action.review.userId = uuidv4();
+ next(action);
+};
diff --git a/src/redux/reducer/index.js b/src/redux/reducer/index.js
index f86f67d..3b5c632 100644
--- a/src/redux/reducer/index.js
+++ b/src/redux/reducer/index.js
@@ -3,10 +3,12 @@ import order from './order';
import restaurants from './restaurants';
import products from './products';
import reviews from './reviews';
+import users from './users';
export default combineReducers({
order,
restaurants,
products,
reviews,
+ users,
});
diff --git a/src/redux/reducer/restaurants.js b/src/redux/reducer/restaurants.js
index e7f30c6..17b8f93 100644
--- a/src/redux/reducer/restaurants.js
+++ b/src/redux/reducer/restaurants.js
@@ -1,9 +1,25 @@
-import { normalizedRestaurants as defaultRestaurants } from '../../fixtures';
+import { normalizedRestaurants } from '../../fixtures';
+import { ADDREVIEW } from '../constants';
+
+const defaultRestaurants = normalizedRestaurants.reduce(
+ (acc, restaurant) => ({ ...acc, [restaurant.id]: restaurant }),
+ {}
+);
export default (restaurants = defaultRestaurants, action) => {
- const { type } = action;
+ const { type, review } = action;
switch (type) {
+ case ADDREVIEW:
+ const { restaurantId, id: reviewId } = review;
+ const restaurant = restaurants[restaurantId];
+ return {
+ ...restaurants,
+ [restaurantId]: {
+ ...restaurant,
+ reviews: [...restaurant.reviews, reviewId],
+ },
+ };
default:
return restaurants;
}
diff --git a/src/redux/reducer/reviews.js b/src/redux/reducer/reviews.js
index 494b5cd..4ca0d70 100644
--- a/src/redux/reducer/reviews.js
+++ b/src/redux/reducer/reviews.js
@@ -1,9 +1,18 @@
-import { normalizedReviews as defaultReviews } from '../../fixtures';
+import { normalizedReviews } from '../../fixtures';
+import { ADDREVIEW } from '../constants';
+
+const defaultReviews = normalizedReviews.reduce(
+ (acc, rewiew) => ({ ...acc, [rewiew.id]: rewiew }),
+ {}
+);
export default (reviews = defaultReviews, action) => {
- const { type } = action;
+ const { type, review } = action;
switch (type) {
+ case ADDREVIEW:
+ const { id, userId, text, rating } = review;
+ return { ...reviews, [id]: { id, userId, text, rating } };
default:
return reviews;
}
diff --git a/src/redux/reducer/users.js b/src/redux/reducer/users.js
new file mode 100644
index 0000000..e72a059
--- /dev/null
+++ b/src/redux/reducer/users.js
@@ -0,0 +1,20 @@
+import { normalizedUsers } from '../../fixtures';
+import { ADDREVIEW } from '../constants';
+
+const defaultUsers = normalizedUsers.reduce(
+ (acc, user) => ({ ...acc, [user.id]: user }),
+ {}
+);
+
+export default (users = defaultUsers, action) => {
+ const { type, review } = action;
+
+ switch (type) {
+ case ADDREVIEW:
+ const { userId, name } = review;
+ return { ...users, [userId]: { userId, name } };
+
+ default:
+ return users;
+ }
+};
diff --git a/src/redux/selectors.js b/src/redux/selectors.js
index 887af3f..aa4110d 100644
--- a/src/redux/selectors.js
+++ b/src/redux/selectors.js
@@ -1,9 +1,29 @@
import { createSelector } from 'reselect';
-// const restaurantsSelector = (state) => state.restaurants;
+const restaurantsSelector = (state) => state.restaurants;
const productsSelector = (state) => state.products;
const orderSelector = (state) => state.order;
+export const restaurantSelectorById = (state, props) =>
+ state.restaurants[props.restaurantId];
+
+const reviewsSelectorByRestaurantId = (state, props) => {
+ return state.restaurants[props.restaurantId].reviews.map(
+ (reviewId) => state.reviews[reviewId]
+ );
+};
+
+export const orderSelectorById = (state, props) => state.order[props.id] || 0;
+export const productSelectorById = (state, props) => state.products[props.id];
+export const reviewSelectorById = (state, props) => {
+ const review = state.reviews[props.id];
+ return {
+ text: review.text,
+ rating: review.rating,
+ user: state.users[review.userId].name || 'Anonymous',
+ };
+};
+
export const orderProductsSelector = createSelector(
orderSelector,
productsSelector,
@@ -23,3 +43,20 @@ export const totalSelector = createSelector(
(orderProducts) =>
orderProducts.reduce((acc, { subtotal }) => acc + subtotal, 0)
);
+
+export const tabsSelector = createSelector(
+ [restaurantsSelector],
+ (restaurants) =>
+ Object.entries(restaurants).map(([id, restaurant]) => ({
+ id,
+ label: restaurant.name,
+ }))
+);
+
+export const averageRatingSelector = createSelector(
+ [reviewsSelectorByRestaurantId],
+ (reviews) => {
+ const total = reviews.reduce((acc, { rating }) => acc + rating, 0);
+ return Math.round(total / reviews.length);
+ }
+);
diff --git a/src/redux/store.js b/src/redux/store.js
index 7ac3e5a..61c72d4 100644
--- a/src/redux/store.js
+++ b/src/redux/store.js
@@ -2,10 +2,11 @@ import { applyMiddleware, createStore } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import logger from './middleware/logger';
+import uuidGenerator from './middleware/uuidGenerator';
import reducer from './reducer';
export default createStore(
reducer,
- composeWithDevTools(applyMiddleware(logger))
+ composeWithDevTools(applyMiddleware(logger, uuidGenerator))
);