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/
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/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/components/restaurants/restaurants.js b/src/components/restaurants/restaurants.js
index 1e20ee0..a716900 100644
--- a/src/components/restaurants/restaurants.js
+++ b/src/components/restaurants/restaurants.js
@@ -1,26 +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 }) => {
- const tabs = restaurants.map((restaurant) => ({
+const Restaurants = ({ restaurants, setActiveRestaurant }) => {
+ useEffect(() => {
+ setActiveRestaurant({ id: Object.keys(restaurants)[0] });
+ }, [restaurants, setActiveRestaurant]);
+
+ const tabs = Object.values(restaurants).map((restaurant) => ({
title: restaurant.name,
+ id: restaurant.id,
content: ,
}));
- return ;
+ return ;
};
Restaurants.propTypes = {
- restaurants: PropTypes.arrayOf(
- PropTypes.shape({
- id: PropTypes.string.isRequired,
- }).isRequired
- ).isRequired,
+ 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 654c421..8e907e1 100644
--- a/src/components/reviews/review-form/review-form.js
+++ b/src/components/reviews/review-form/review-form.js
@@ -5,15 +5,16 @@ 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 };
-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();
};
@@ -51,6 +52,12 @@ const ReviewForm = ({ onSubmit }) => {
);
};
-export default connect(null, () => ({
- onSubmit: (values) => console.log(values), // TODO
-}))(ReviewForm);
+const mapStateToProps = (state) => ({
+ activeRestaurant: state.activeRestaurant,
+});
+
+const mapDispatchToProps = (dispatch) => ({
+ onSubmit: (values) => dispatch(addReview(values)),
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(ReviewForm);
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/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 1403ee9..0a8161b 100644
--- a/src/redux/actions.js
+++ b/src/redux/actions.js
@@ -1,5 +1,21 @@
-import { INCREMENT, DECREMENT, REMOVE } from './constants';
+import {
+ INCREMENT,
+ DECREMENT,
+ REMOVE,
+ ADD_REVIEW,
+ SET_ACTIVE,
+} 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 } });
+
+/*restaurant*/
+export const setActive = (restaurantId) => ({
+ type: SET_ACTIVE,
+ payload: restaurantId,
+});
diff --git a/src/redux/constants.js b/src/redux/constants.js
index 9cfa25d..b97658d 100644
--- a/src/redux/constants.js
+++ b/src/redux/constants.js
@@ -1,3 +1,10 @@
+/*product*/
export const INCREMENT = 'INCREMENT';
export const DECREMENT = 'DECREMENT';
export const REMOVE = 'REMOVE';
+
+/*form*/
+export const ADD_REVIEW = 'ADD_REVIEW';
+
+/*restaurant*/
+export const SET_ACTIVE = 'SET_ACTIVE';
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/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 af5bd9c..a68ed37 100644
--- a/src/redux/reducer/index.js
+++ b/src/redux/reducer/index.js
@@ -4,12 +4,16 @@ import order from './order';
import restaurants from './restaurants';
import products from './products';
import reviews from './reviews';
+import users from './users';
+import activeRestaurant from './activeRestaurant';
const reducer = combineReducers({
order,
restaurants,
products,
+ users,
reviews,
+ activeRestaurant,
});
export default reducer;
diff --git a/src/redux/reducer/restaurants.js b/src/redux/reducer/restaurants.js
index 74ec69a..8fd67dd 100644
--- a/src/redux/reducer/restaurants.js
+++ b/src/redux/reducer/restaurants.js
@@ -1,9 +1,20 @@
-import { normalizedRestaurants as defaultRestaurants } from '../../fixtures';
+import { normalizedRestaurants } from '../../fixtures';
+import { ADD_REVIEW } from '../constants';
+
+const defaultRestaurants = normalizedRestaurants.reduce(
+ (acc, restaurant) => ({ ...acc, [restaurant.id]: restaurant }),
+ {}
+);
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 0d14a13..3f1382d 100644
--- a/src/redux/reducer/reviews.js
+++ b/src/redux/reducer/reviews.js
@@ -1,9 +1,21 @@
-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 }),
+ {}
+);
const reducer = (reviews = defaultReviews, action) => {
const { type } = action;
switch (type) {
+ case ADD_REVIEW:
+ const { id, userId, text, rate } = action.payload;
+ return {
+ ...reviews,
+ [action.payload.id]: { id, userId, text, rating: rate },
+ };
default:
return reviews;
}
diff --git a/src/redux/reducer/users.js b/src/redux/reducer/users.js
new file mode 100644
index 0000000..8b3031c
--- /dev/null
+++ b/src/redux/reducer/users.js
@@ -0,0 +1,21 @@
+import { normalizedUsers } from '../../fixtures';
+import { ADD_REVIEW } from '../constants';
+
+const defaultUsers = normalizedUsers.reduce(
+ (acc, user) => ({ ...acc, [user.id]: user.name }),
+ {}
+);
+
+const reducer = (users = defaultUsers, action) => {
+ const { type } = action;
+
+ switch (type) {
+ case ADD_REVIEW:
+ const { userId, name } = action.payload;
+ return { ...users, [userId]: name };
+ default:
+ return users;
+ }
+};
+
+export default reducer;
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==