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/restaurant/restaurant.js b/src/components/restaurant/restaurant.js index 364e73c..590aa74 100644 --- a/src/components/restaurant/restaurant.js +++ b/src/components/restaurant/restaurant.js @@ -1,4 +1,5 @@ import { useMemo, useState } from 'react'; +import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import Menu from '../menu'; import Reviews from '../reviews'; @@ -6,15 +7,15 @@ import Banner from '../banner'; import Rate from '../rate'; import Tabs from '../tabs'; -const Restaurant = ({ restaurant }) => { +const Restaurant = ({restaurant, allReviews}) => { const { id, name, menu, reviews } = restaurant; const [activeTab, setActiveTab] = useState('menu'); const averageRating = useMemo(() => { - const total = reviews.reduce((acc, { rating }) => acc + rating, 0); + const total = reviews.reduce((acc, review) => acc + (allReviews[review].rating || 0), 0); return Math.round(total / reviews.length); - }, [reviews]); + }, [reviews, allReviews]); const tabs = [ { id: 'menu', label: 'Menu' }, @@ -28,7 +29,7 @@ const Restaurant = ({ restaurant }) => { {activeTab === 'menu' && } - {activeTab === 'reviews' && } + {activeTab === 'reviews' && } ); }; @@ -46,4 +47,8 @@ Restaurant.propTypes = { }).isRequired, }; -export default Restaurant; +const mapStateToProps = (state) => ({ + allReviews: state.reviews, +}); + +export default connect(mapStateToProps)(Restaurant); diff --git a/src/components/restaurants/restaurants.js b/src/components/restaurants/restaurants.js index 2037758..55e1294 100644 --- a/src/components/restaurants/restaurants.js +++ b/src/components/restaurants/restaurants.js @@ -5,17 +5,17 @@ import Restaurant from '../restaurant'; import Tabs from '../tabs'; function Restaurants({ restaurants }) { - const [activeId, setActiveId] = useState(restaurants[0].id); + const [firstKey] = Object.keys(restaurants); + const [activeId, setActiveId] = useState(restaurants[firstKey].id); const tabs = useMemo( - () => restaurants.map(({ id, name }) => ({ id, label: name })), + () => Object.keys(restaurants).map((key) => { + return {id: restaurants[key].id, label: restaurants[key].name}; + }), [restaurants] ); - const activeRestaurant = useMemo( - () => restaurants.find((restaurant) => restaurant.id === activeId), - [activeId, restaurants] - ); + const activeRestaurant = restaurants[activeId] return (
diff --git a/src/components/reviews/review-form/review-form.js b/src/components/reviews/review-form/review-form.js index 1267e77..56fff6a 100644 --- a/src/components/reviews/review-form/review-form.js +++ b/src/components/reviews/review-form/review-form.js @@ -5,6 +5,7 @@ import Rate from '../../rate'; import Button from '../../button'; import styles from './review-form.module.css'; +import {addReview} from "../../../redux/actions"; const INITIAL_VALUES = { name: '', text: '', rating: 3 }; @@ -51,6 +52,6 @@ const ReviewForm = ({ onSubmit }) => { ); }; -export default connect(null, () => ({ - onSubmit: (values) => console.log(values), // TODO +export default connect(null, (dispatch) => ({ + onSubmit: (values) => {dispatch(addReview(values))}, // TODO }))(ReviewForm); diff --git a/src/components/reviews/review/review.js b/src/components/reviews/review/review.js index fe89ecb..0b0427f 100644 --- a/src/components/reviews/review/review.js +++ b/src/components/reviews/review/review.js @@ -1,34 +1,46 @@ import PropTypes from 'prop-types'; - +import { connect } from 'react-redux'; import Rate from '../../rate'; import styles from './review.module.css'; -const Review = ({ user, text, rating }) => ( -
-
-
-

- {user} -

-

- {text} -

-
-
- +function Review({review, users}) { + const user = users[review?.userId] ? users[review?.userId] : null; + return ( +
+
+
+

+ { user && user.name ? user.name : "Anonymous"} +

+

+ {review && (review.text)} +

+
+
+ {review && review.rating && ()} +
-
-); + ); +}; Review.propTypes = { - user: PropTypes.string, - text: PropTypes.string, - rating: PropTypes.number.isRequired, + review: PropTypes.shape({ + user: PropTypes.string, + text: PropTypes.string, + rating: PropTypes.number.isRequired, + }).isRequired, + user: PropTypes.shape({ + id: PropTypes.string.isRequired, + name: PropTypes.string.isRequired + }).isRequired }; -Review.defaultProps = { - user: 'Anonymous', +const mapStateToProps = (state, props) => { + return { + review: state.reviews[props.reviewId], + users: state.users + } }; -export default Review; +export default connect(mapStateToProps)(Review); diff --git a/src/components/reviews/reviews.js b/src/components/reviews/reviews.js index 66f6cf1..4cbf3b1 100644 --- a/src/components/reviews/reviews.js +++ b/src/components/reviews/reviews.js @@ -1,15 +1,18 @@ import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; import Review from './review'; import ReviewForm from './review-form'; import styles from './reviews.module.css'; +import {restaurantsSelector} from "../../redux/selectors"; -const Reviews = ({ reviews }) => { +const Reviews = ({ currentRestaurant, restaurants }) => { return (
- {reviews.map((review) => ( - + + {restaurants[currentRestaurant].reviews.map((id) => ( + ))} - +
); }; @@ -22,4 +25,8 @@ Reviews.propTypes = { ).isRequired, }; -export default Reviews; +const mapStateToProps = (state, props) => ({ + restaurants: restaurantsSelector(state) +}); + +export default connect(mapStateToProps)(Reviews); diff --git a/src/redux/actions.js b/src/redux/actions.js index ea78f99..f21d349 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, ADD_REVIEW } 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 = (params) => ({ type: ADD_REVIEW, payload: params}); diff --git a/src/redux/constants.js b/src/redux/constants.js index 9cfa25d..aaac693 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 ADD_REVIEW = 'ADD_REVIEW'; diff --git a/src/redux/middleware/uuidGenerator.js b/src/redux/middleware/uuidGenerator.js new file mode 100644 index 0000000..12d491b --- /dev/null +++ b/src/redux/middleware/uuidGenerator.js @@ -0,0 +1,10 @@ +import { v4 as uuidv4 } from 'uuid'; +import { ADD_REVIEW } from "../constants"; + +export default (store) => (next) => (action) => { + if (action.type === ADD_REVIEW) { + action.payload.id = uuidv4(); + action.payload.uuid = uuidv4(); + } + next(action); +}; diff --git a/src/redux/reducer/index.js b/src/redux/reducer/index.js index f86f67d..9b2572b 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..f6006d0 100644 --- a/src/redux/reducer/restaurants.js +++ b/src/redux/reducer/restaurants.js @@ -1,4 +1,9 @@ -import { normalizedRestaurants as defaultRestaurants } from '../../fixtures'; +import {normalizedRestaurants} from '../../fixtures'; + +const defaultRestaurants = normalizedRestaurants.reduce( + (acc, restaurant) => ({...acc, [restaurant.id]: restaurant}), + {} +); export default (restaurants = defaultRestaurants, action) => { const { type } = action; diff --git a/src/redux/reducer/reviews.js b/src/redux/reducer/reviews.js index 494b5cd..804cb17 100644 --- a/src/redux/reducer/reviews.js +++ b/src/redux/reducer/reviews.js @@ -1,9 +1,23 @@ -import { normalizedReviews as defaultReviews } from '../../fixtures'; +import {normalizedReviews} from '../../fixtures'; +import {ADD_REVIEW} from "../constants"; + +const defaultReviews = normalizedReviews.reduce( + (acc, review) => ({...acc, [review.id]: review}), + {} +); export default (reviews = defaultReviews, action) => { - const { type } = action; + const { type, payload} = action; switch (type) { + case ADD_REVIEW: + return { ...reviews, [payload.id]: { + name: payload.name, + rating: payload.rating, + text: payload.text, + userId: payload.uuid + } + }; default: return reviews; } diff --git a/src/redux/reducer/users.js b/src/redux/reducer/users.js new file mode 100644 index 0000000..b60de09 --- /dev/null +++ b/src/redux/reducer/users.js @@ -0,0 +1,15 @@ +import { normalizedUsers } from '../../fixtures'; + +const defaultUsers = normalizedUsers.reduce( + (acc, user) => ({ ...acc, [user.id]: user }), + {} +); + +export default (products = defaultUsers, action) => { + const { type } = action; + + switch (type) { + default: + return products; + } +}; diff --git a/src/redux/selectors.js b/src/redux/selectors.js index 887af3f..bbb2f33 100644 --- a/src/redux/selectors.js +++ b/src/redux/selectors.js @@ -3,6 +3,9 @@ import { createSelector } from 'reselect'; // const restaurantsSelector = (state) => state.restaurants; const productsSelector = (state) => state.products; const orderSelector = (state) => state.order; +const reviewsSelector = (state) => state.reviews; +export const usersSelector = (state) => state.users; +export const restaurantsSelector = (state) => state.restaurants; export const orderProductsSelector = createSelector( orderSelector, @@ -23,3 +26,16 @@ export const totalSelector = createSelector( (orderProducts) => orderProducts.reduce((acc, { subtotal }) => acc + subtotal, 0) ); + +export const getReviewsSelector = createSelector( + [reviewsSelector], + (reviewsSelector) => + Object.keys(reviewsSelector).map((reviewId) => reviewsSelector[reviewId]) + +); + +export const getUsersSelector = createSelector( + [usersSelector], + (usersSelector) => + Object.keys(usersSelector).map((userId) => usersSelector[userId]) +); diff --git a/src/redux/store.js b/src/redux/store.js index 7ac3e5a..423fb43 100644 --- a/src/redux/store.js +++ b/src/redux/store.js @@ -2,10 +2,13 @@ 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'; +const middlewares = [logger, uuidGenerator]; + export default createStore( reducer, - composeWithDevTools(applyMiddleware(logger)) + applyMiddleware(...middlewares), );