From 8ab3c44524d0e5d83f9a04b8a8c75c76724146d4 Mon Sep 17 00:00:00 2001 From: antonnekrasov Date: Fri, 20 Nov 2020 20:13:18 +0300 Subject: [PATCH 1/5] gitignore update --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 4d29575..eecc05b 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,5 @@ npm-debug.log* yarn-debug.log* yarn-error.log* + +.idea/ From 523acc1f17fd5406e246d404f6ceabd60e06aed2 Mon Sep 17 00:00:00 2001 From: antonnekrasov Date: Mon, 30 Nov 2020 16:52:07 +0300 Subject: [PATCH 2/5] [ht4] fixed proptypes --- src/components/menu/menu.js | 6 +----- src/components/restaurant/restaurant.js | 20 ++++++++++++-------- src/redux/reducer/reviews.js | 7 ++++++- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/components/menu/menu.js b/src/components/menu/menu.js index 7935465..ff13c50 100644 --- a/src/components/menu/menu.js +++ b/src/components/menu/menu.js @@ -7,11 +7,7 @@ import styles from './menu.module.css'; class Menu extends React.Component { static propTypes = { - menu: PropTypes.arrayOf( - PropTypes.shape({ - id: PropTypes.string.isRequired, - }).isRequired - ).isRequired, + menu: PropTypes.arrayOf(PropTypes.string.isRequired).isRequired, }; state = { error: null }; diff --git a/src/components/restaurant/restaurant.js b/src/components/restaurant/restaurant.js index 3c0d586..5647218 100644 --- a/src/components/restaurant/restaurant.js +++ b/src/components/restaurant/restaurant.js @@ -1,4 +1,5 @@ import React, { useMemo } from 'react'; +import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import Menu from '../menu'; import Reviews from '../reviews'; @@ -6,8 +7,8 @@ import Banner from '../banner'; import Rate from '../rate'; import Tabs from '../tabs'; -const Restaurant = ({ restaurant }) => { - const { name, menu, reviews } = restaurant; +const Restaurant = ({ restaurant, reviews }) => { + const { name, menu } = restaurant; const averageRating = useMemo(() => { const total = reviews.reduce((acc, { rating }) => acc + rating, 0); @@ -33,12 +34,15 @@ Restaurant.propTypes = { restaurant: PropTypes.shape({ name: PropTypes.string, menu: PropTypes.array, - reviews: PropTypes.arrayOf( - PropTypes.shape({ - rating: PropTypes.number.isRequired, - }).isRequired - ).isRequired, + reviews: PropTypes.arrayOf(PropTypes.string.isRequired).isRequired, }).isRequired, }; -export default Restaurant; +const mapStateToProps = (state, ownProps) => { + const { reviews } = ownProps.restaurant; + return { + reviews: reviews.map((id) => state.reviews[id]), + }; +}; + +export default connect(mapStateToProps)(Restaurant); diff --git a/src/redux/reducer/reviews.js b/src/redux/reducer/reviews.js index 0d14a13..c3ce65a 100644 --- a/src/redux/reducer/reviews.js +++ b/src/redux/reducer/reviews.js @@ -1,4 +1,9 @@ -import { normalizedReviews as defaultReviews } from '../../fixtures'; +import { normalizedReviews } from '../../fixtures'; + +const defaultReviews = normalizedReviews.reduce( + (acc, review) => ({ ...acc, [review.id]: review }), + {} +); const reducer = (reviews = defaultReviews, action) => { const { type } = action; From cafc682d855f7695fec944e2620ce3ba9ec619b9 Mon Sep 17 00:00:00 2001 From: antonnekrasov Date: Mon, 30 Nov 2020 17:09:00 +0300 Subject: [PATCH 3/5] [ht4] fixed reviews --- src/components/restaurants/restaurants.js | 8 ++------ src/components/reviews/review/review.js | 8 +++++++- src/redux/reducer/index.js | 2 ++ src/redux/reducer/restaurants.js | 7 ++++++- src/redux/reducer/users.js | 17 +++++++++++++++++ 5 files changed, 34 insertions(+), 8 deletions(-) create mode 100644 src/redux/reducer/users.js diff --git a/src/components/restaurants/restaurants.js b/src/components/restaurants/restaurants.js index 1e20ee0..5830a35 100644 --- a/src/components/restaurants/restaurants.js +++ b/src/components/restaurants/restaurants.js @@ -5,7 +5,7 @@ import Restaurant from '../restaurant'; import Tabs from '../tabs'; const Restaurants = ({ restaurants }) => { - const tabs = restaurants.map((restaurant) => ({ + const tabs = Object.values(restaurants).map((restaurant) => ({ title: restaurant.name, content: , })); @@ -14,11 +14,7 @@ const Restaurants = ({ restaurants }) => { }; Restaurants.propTypes = { - restaurants: PropTypes.arrayOf( - PropTypes.shape({ - id: PropTypes.string.isRequired, - }).isRequired - ).isRequired, + restaurants: PropTypes.object.isRequired, }; export default connect((state) => ({ diff --git a/src/components/reviews/review/review.js b/src/components/reviews/review/review.js index b35f517..cae9bb7 100644 --- a/src/components/reviews/review/review.js +++ b/src/components/reviews/review/review.js @@ -1,4 +1,5 @@ import React from 'react'; +import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import Rate from '../../rate'; @@ -32,4 +33,9 @@ Review.defaultProps = { user: 'Anonymous', }; -export default Review; +const mapStateToProps = (state, ownProps) => { + const user = state.users[ownProps.userId]; + return { user }; +}; + +export default connect(mapStateToProps)(Review); diff --git a/src/redux/reducer/index.js b/src/redux/reducer/index.js index af5bd9c..c064aae 100644 --- a/src/redux/reducer/index.js +++ b/src/redux/reducer/index.js @@ -4,12 +4,14 @@ import order from './order'; import restaurants from './restaurants'; import products from './products'; import reviews from './reviews'; +import users from './users'; const reducer = combineReducers({ order, restaurants, products, reviews, + users, }); export default reducer; diff --git a/src/redux/reducer/restaurants.js b/src/redux/reducer/restaurants.js index 74ec69a..eb39984 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 }), + {} +); const reducer = (restaurants = defaultRestaurants, action) => { const { type } = action; diff --git a/src/redux/reducer/users.js b/src/redux/reducer/users.js new file mode 100644 index 0000000..f17e25c --- /dev/null +++ b/src/redux/reducer/users.js @@ -0,0 +1,17 @@ +import { normalizedUsers } from '../../fixtures'; + +const defaultRestaurants = normalizedUsers.reduce( + (acc, user) => ({ ...acc, [user.id]: user.name }), + {} +); + +const reducer = (restaurants = defaultRestaurants, action) => { + const { type } = action; + + switch (type) { + default: + return restaurants; + } +}; + +export default reducer; From 6cf13c1145be9380da6ac55206f3d3327eda30c4 Mon Sep 17 00:00:00 2001 From: antonnekrasov Date: Mon, 30 Nov 2020 18:53:20 +0300 Subject: [PATCH 4/5] [ht4] add reviews (no restaurant) --- package.json | 1 + src/components/reviews/review-form/review-form.js | 5 +++-- src/redux/actions.js | 6 +++++- src/redux/constants.js | 4 ++++ src/redux/middleware/uuid-generator.js | 12 ++++++++++++ src/redux/reducer/index.js | 2 +- src/redux/reducer/reviews.js | 3 +++ src/redux/reducer/users.js | 10 +++++++--- src/redux/store.js | 3 ++- yarn.lock | 2 +- 10 files changed, 39 insertions(+), 9 deletions(-) create mode 100644 src/redux/middleware/uuid-generator.js diff --git a/package.json b/package.json index 41f66a5..975dc84 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "redux": "^4.0.5", "redux-devtools-extension": "^2.13.8", "reselect": "^4.0.0", + "uuid": "^8.3.1", "web-vitals": "^0.2.4" }, "scripts": { diff --git a/src/components/reviews/review-form/review-form.js b/src/components/reviews/review-form/review-form.js index 654c421..2eea622 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 styles from './review-form.module.css'; import { connect } from 'react-redux'; import Button from '../../button'; +import { addReview } from '../../../redux/actions'; const INITIAL_VALUES = { name: '', text: '', rate: 5 }; @@ -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)), }))(ReviewForm); diff --git a/src/redux/actions.js b/src/redux/actions.js index 1403ee9..64a470c 100644 --- a/src/redux/actions.js +++ b/src/redux/actions.js @@ -1,5 +1,9 @@ -import { INCREMENT, DECREMENT, REMOVE } from './constants'; +import { INCREMENT, DECREMENT, REMOVE, ADD_REVIEW } from './constants'; +/*products*/ export const increment = (id) => ({ type: INCREMENT, payload: { id } }); export const decrement = (id) => ({ type: DECREMENT, payload: { id } }); export const remove = (id) => ({ type: REMOVE, payload: { id } }); + +/*form*/ +export const addReview = (data) => ({ type: ADD_REVIEW, payload: { ...data } }); diff --git a/src/redux/constants.js b/src/redux/constants.js index 9cfa25d..ae01888 100644 --- a/src/redux/constants.js +++ b/src/redux/constants.js @@ -1,3 +1,7 @@ +/*product*/ export const INCREMENT = 'INCREMENT'; export const DECREMENT = 'DECREMENT'; export const REMOVE = 'REMOVE'; + +/*form*/ +export const ADD_REVIEW = 'ADD_REVIEW'; diff --git a/src/redux/middleware/uuid-generator.js b/src/redux/middleware/uuid-generator.js new file mode 100644 index 0000000..d1847d7 --- /dev/null +++ b/src/redux/middleware/uuid-generator.js @@ -0,0 +1,12 @@ +import { v1 as uuidv4 } from 'uuid'; +import { ADD_REVIEW } from '../constants'; + +const uuidGenerator = () => (next) => (action) => { + if (action.type === ADD_REVIEW) { + action.payload.userId = uuidv4(); + action.payload.id = uuidv4(); + } + next(action); +}; + +export default uuidGenerator; diff --git a/src/redux/reducer/index.js b/src/redux/reducer/index.js index c064aae..fb5e991 100644 --- a/src/redux/reducer/index.js +++ b/src/redux/reducer/index.js @@ -10,8 +10,8 @@ const reducer = combineReducers({ order, restaurants, products, - reviews, users, + reviews, }); export default reducer; diff --git a/src/redux/reducer/reviews.js b/src/redux/reducer/reviews.js index c3ce65a..8f266fc 100644 --- a/src/redux/reducer/reviews.js +++ b/src/redux/reducer/reviews.js @@ -1,4 +1,5 @@ import { normalizedReviews } from '../../fixtures'; +import { ADD_REVIEW } from '../constants'; const defaultReviews = normalizedReviews.reduce( (acc, review) => ({ ...acc, [review.id]: review }), @@ -9,6 +10,8 @@ const reducer = (reviews = defaultReviews, action) => { const { type } = action; switch (type) { + case ADD_REVIEW: + return { ...reviews, [action.payload.id]: { ...action.payload } }; default: return reviews; } diff --git a/src/redux/reducer/users.js b/src/redux/reducer/users.js index f17e25c..8b3031c 100644 --- a/src/redux/reducer/users.js +++ b/src/redux/reducer/users.js @@ -1,16 +1,20 @@ import { normalizedUsers } from '../../fixtures'; +import { ADD_REVIEW } from '../constants'; -const defaultRestaurants = normalizedUsers.reduce( +const defaultUsers = normalizedUsers.reduce( (acc, user) => ({ ...acc, [user.id]: user.name }), {} ); -const reducer = (restaurants = defaultRestaurants, action) => { +const reducer = (users = defaultUsers, action) => { const { type } = action; switch (type) { + case ADD_REVIEW: + const { userId, name } = action.payload; + return { ...users, [userId]: name }; default: - return restaurants; + return users; } }; diff --git a/src/redux/store.js b/src/redux/store.js index 7988b58..32e36f9 100644 --- a/src/redux/store.js +++ b/src/redux/store.js @@ -1,12 +1,13 @@ import { applyMiddleware, createStore } from 'redux'; import { composeWithDevTools } from 'redux-devtools-extension'; import logger from './middleware/logger'; +import uuidGenerator from './middleware/uuid-generator'; import reducer from './reducer'; const store = createStore( reducer, - composeWithDevTools(applyMiddleware(logger)) + composeWithDevTools(applyMiddleware(logger, uuidGenerator)) ); export default store; diff --git a/yarn.lock b/yarn.lock index af7cd76..7ed90e0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11256,7 +11256,7 @@ uuid@^3.3.2, uuid@^3.4.0: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== -uuid@^8.3.0: +uuid@^8.3.0, uuid@^8.3.1: version "8.3.1" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.1.tgz#2ba2e6ca000da60fce5a196954ab241131e05a31" integrity sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg== From 904c7ceb086f479255d81d85f181207d3842f000 Mon Sep 17 00:00:00 2001 From: antonnekrasov Date: Mon, 30 Nov 2020 22:05:54 +0300 Subject: [PATCH 5/5] [ht4] mechamism for adding reviews --- src/components/restaurants/restaurants.js | 24 +++++++++++---- .../reviews/review-form/review-form.js | 14 ++++++--- src/components/tabs/tabs.js | 29 ++++++++++++------- src/redux/actions.js | 14 ++++++++- src/redux/constants.js | 3 ++ src/redux/reducer/activeRestaurant.js | 14 +++++++++ src/redux/reducer/index.js | 2 ++ src/redux/reducer/restaurants.js | 6 ++++ src/redux/reducer/reviews.js | 6 +++- 9 files changed, 90 insertions(+), 22 deletions(-) create mode 100644 src/redux/reducer/activeRestaurant.js diff --git a/src/components/restaurants/restaurants.js b/src/components/restaurants/restaurants.js index 5830a35..a716900 100644 --- a/src/components/restaurants/restaurants.js +++ b/src/components/restaurants/restaurants.js @@ -1,22 +1,36 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import Restaurant from '../restaurant'; import Tabs from '../tabs'; +import { setActive } from '../../redux/actions'; + +const Restaurants = ({ restaurants, setActiveRestaurant }) => { + useEffect(() => { + setActiveRestaurant({ id: Object.keys(restaurants)[0] }); + }, [restaurants, setActiveRestaurant]); -const Restaurants = ({ restaurants }) => { const tabs = Object.values(restaurants).map((restaurant) => ({ title: restaurant.name, + id: restaurant.id, content: , })); - return ; + return ; }; Restaurants.propTypes = { restaurants: PropTypes.object.isRequired, }; -export default connect((state) => ({ +const mapStateToProps = (state) => ({ restaurants: state.restaurants, -}))(Restaurants); +}); + +const mapDispatchToProps = (dispatch) => { + return { + setActiveRestaurant: (entity) => dispatch(setActive(entity.id)), + }; +}; + +export default connect(mapStateToProps, mapDispatchToProps)(Restaurants); diff --git a/src/components/reviews/review-form/review-form.js b/src/components/reviews/review-form/review-form.js index 2eea622..8e907e1 100644 --- a/src/components/reviews/review-form/review-form.js +++ b/src/components/reviews/review-form/review-form.js @@ -9,12 +9,12 @@ import { addReview } from '../../../redux/actions'; const INITIAL_VALUES = { name: '', text: '', rate: 5 }; -const ReviewForm = ({ onSubmit }) => { +const ReviewForm = ({ onSubmit, activeRestaurant }) => { const { values, handlers, reset } = useForm(INITIAL_VALUES); const handleSubmit = (ev) => { ev.preventDefault(); - onSubmit(values); + onSubmit({ ...values, activeRestaurant }); reset(); }; @@ -52,6 +52,12 @@ const ReviewForm = ({ onSubmit }) => { ); }; -export default connect(null, (dispatch) => ({ +const mapStateToProps = (state) => ({ + activeRestaurant: state.activeRestaurant, +}); + +const mapDispatchToProps = (dispatch) => ({ onSubmit: (values) => dispatch(addReview(values)), -}))(ReviewForm); +}); + +export default connect(mapStateToProps, mapDispatchToProps)(ReviewForm); diff --git a/src/components/tabs/tabs.js b/src/components/tabs/tabs.js index b96b039..15e1714 100644 --- a/src/components/tabs/tabs.js +++ b/src/components/tabs/tabs.js @@ -4,23 +4,30 @@ import cn from 'classnames'; import styles from './tabs.module.css'; -const Tabs = ({ tabs }) => { +const Tabs = ({ tabs, onSetActiveCallback }) => { const [activeTab, setActiveTab] = useState(0); - const { content } = tabs[activeTab]; return ( <>
- {tabs.map(({ title }, index) => ( - setActiveTab(index)} - > - {title} - - ))} + {tabs.map((entity, index) => { + const { title } = entity; + return ( + { + setActiveTab(index); + onSetActiveCallback && onSetActiveCallback(entity); + }} + > + {title} + + ); + })}
{content} diff --git a/src/redux/actions.js b/src/redux/actions.js index 64a470c..0a8161b 100644 --- a/src/redux/actions.js +++ b/src/redux/actions.js @@ -1,4 +1,10 @@ -import { INCREMENT, DECREMENT, REMOVE, ADD_REVIEW } from './constants'; +import { + INCREMENT, + DECREMENT, + REMOVE, + ADD_REVIEW, + SET_ACTIVE, +} from './constants'; /*products*/ export const increment = (id) => ({ type: INCREMENT, payload: { id } }); @@ -7,3 +13,9 @@ export const remove = (id) => ({ type: REMOVE, payload: { id } }); /*form*/ export const addReview = (data) => ({ type: ADD_REVIEW, payload: { ...data } }); + +/*restaurant*/ +export const setActive = (restaurantId) => ({ + type: SET_ACTIVE, + payload: restaurantId, +}); diff --git a/src/redux/constants.js b/src/redux/constants.js index ae01888..b97658d 100644 --- a/src/redux/constants.js +++ b/src/redux/constants.js @@ -5,3 +5,6 @@ export const REMOVE = 'REMOVE'; /*form*/ export const ADD_REVIEW = 'ADD_REVIEW'; + +/*restaurant*/ +export const SET_ACTIVE = 'SET_ACTIVE'; diff --git a/src/redux/reducer/activeRestaurant.js b/src/redux/reducer/activeRestaurant.js new file mode 100644 index 0000000..346d9d5 --- /dev/null +++ b/src/redux/reducer/activeRestaurant.js @@ -0,0 +1,14 @@ +import { SET_ACTIVE } from '../constants'; + +const reducer = (activeRestaurant = '', action) => { + const { type } = action; + + switch (type) { + case SET_ACTIVE: + return action.payload; + default: + return activeRestaurant; + } +}; + +export default reducer; diff --git a/src/redux/reducer/index.js b/src/redux/reducer/index.js index fb5e991..a68ed37 100644 --- a/src/redux/reducer/index.js +++ b/src/redux/reducer/index.js @@ -5,6 +5,7 @@ import restaurants from './restaurants'; import products from './products'; import reviews from './reviews'; import users from './users'; +import activeRestaurant from './activeRestaurant'; const reducer = combineReducers({ order, @@ -12,6 +13,7 @@ const reducer = combineReducers({ products, users, reviews, + activeRestaurant, }); export default reducer; diff --git a/src/redux/reducer/restaurants.js b/src/redux/reducer/restaurants.js index eb39984..8fd67dd 100644 --- a/src/redux/reducer/restaurants.js +++ b/src/redux/reducer/restaurants.js @@ -1,4 +1,5 @@ import { normalizedRestaurants } from '../../fixtures'; +import { ADD_REVIEW } from '../constants'; const defaultRestaurants = normalizedRestaurants.reduce( (acc, restaurant) => ({ ...acc, [restaurant.id]: restaurant }), @@ -9,6 +10,11 @@ const reducer = (restaurants = defaultRestaurants, action) => { const { type } = action; switch (type) { + case ADD_REVIEW: + const { id, activeRestaurant } = action.payload; + const restaurant = restaurants[activeRestaurant]; + restaurant.reviews.push(id); + return { ...restaurants }; default: return restaurants; } diff --git a/src/redux/reducer/reviews.js b/src/redux/reducer/reviews.js index 8f266fc..3f1382d 100644 --- a/src/redux/reducer/reviews.js +++ b/src/redux/reducer/reviews.js @@ -11,7 +11,11 @@ const reducer = (reviews = defaultReviews, action) => { switch (type) { case ADD_REVIEW: - return { ...reviews, [action.payload.id]: { ...action.payload } }; + const { id, userId, text, rate } = action.payload; + return { + ...reviews, + [action.payload.id]: { id, userId, text, rating: rate }, + }; default: return reviews; }