Skip to content

Commit

Permalink
HW-4
Browse files Browse the repository at this point in the history
  • Loading branch information
vera-l committed Dec 13, 2021
1 parent b06508c commit 7830ddd
Show file tree
Hide file tree
Showing 17 changed files with 168 additions and 51 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"react-scripts": "4.0.3",
"redux": "^4.1.2",
"redux-devtools-extension": "^2.13.9",
"reselect": "^4.1.5"
"reselect": "^4.1.5",
"uuid": "^8.3.2"
},
"scripts": {
"start": "react-scripts start",
Expand Down
6 changes: 1 addition & 5 deletions src/components/menu/menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
Expand Down
5 changes: 3 additions & 2 deletions src/components/product/product.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import PropTypes from 'prop-types';
import styles from './product.module.css';
import Button from '../button';
import { decrement, increment } from '../../redux/actions';
import { amountSelector, productSelector } from '../../redux/selectors';

function Product({ product, amount, decrement, increment, fetchData }) {
useEffect(() => {
Expand Down Expand Up @@ -57,8 +58,8 @@ Product.propTypes = {
};

const mapStateToProps = (state, props) => ({
amount: state.order[props.id] || 0,
product: state.products[props.id],
amount: amountSelector(state, props),
product: productSelector(state, props),
});

// const mapDispatchToProps = {
Expand Down
35 changes: 20 additions & 15 deletions src/components/restaurant/restaurant.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
import { useMemo, useState } from 'react';
import { useState } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import Menu from '../menu';
import Reviews from '../reviews';
import Banner from '../banner';
import Rate from '../rate';
import Tabs from '../tabs';

const Restaurant = ({ restaurant }) => {
import {
averageRatingSelector,
restaurantSelector,
} from '../../redux/selectors';

const Restaurant = ({ 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' },
Expand All @@ -28,7 +29,9 @@ const Restaurant = ({ restaurant }) => {
</Banner>
<Tabs tabs={tabs} activeId={activeTab} onChange={setActiveTab} />
{activeTab === 'menu' && <Menu menu={menu} key={id} />}
{activeTab === 'reviews' && <Reviews reviews={reviews} />}
{activeTab === 'reviews' && (
<Reviews restaurantId={id} reviewIds={reviews} />
)}
</div>
);
};
Expand All @@ -37,13 +40,15 @@ Restaurant.propTypes = {
restaurant: PropTypes.shape({
id: PropTypes.string.isRequired,
name: PropTypes.string,
menu: PropTypes.array,
reviews: PropTypes.arrayOf(
PropTypes.shape({
rating: PropTypes.number.isRequired,
}).isRequired
).isRequired,
menu: PropTypes.arrayOf(PropTypes.string),
reviews: PropTypes.arrayOf(PropTypes.string).isRequired,
averageRating: PropTypes.number,
}).isRequired,
};

export default Restaurant;
const mapStateToProps = (state, props) => ({
restaurant: restaurantSelector(state, props),
averageRating: averageRatingSelector(state, props),
});

export default connect(mapStateToProps)(Restaurant);
14 changes: 6 additions & 8 deletions src/components/restaurants/restaurants.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { useMemo, useState } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';

import Restaurant from '../restaurant';
import Tabs from '../tabs';

import { restaurantsAsArraySelector } from '../../redux/selectors';

function Restaurants({ restaurants }) {
const [activeId, setActiveId] = useState(restaurants[0].id);

Expand All @@ -12,15 +15,10 @@ function Restaurants({ restaurants }) {
[restaurants]
);

const activeRestaurant = useMemo(
() => restaurants.find((restaurant) => restaurant.id === activeId),
[activeId, restaurants]
);

return (
<div>
<Tabs tabs={tabs} onChange={setActiveId} activeId={activeId} />
<Restaurant restaurant={activeRestaurant} />
<Restaurant id={activeId} />
</div>
);
}
Expand All @@ -30,12 +28,12 @@ Restaurants.propTypes = {
PropTypes.shape({
id: PropTypes.string.isRequired,
name: PropTypes.string,
}).isRequired
})
).isRequired,
};

const mapStateToProps = (state) => ({
restaurants: state.restaurants,
restaurants: restaurantsAsArraySelector(state),
});

export default connect(mapStateToProps)(Restaurants);
14 changes: 11 additions & 3 deletions src/components/reviews/review-form/review-form.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { connect } from 'react-redux';
import PropTypes from 'prop-types';

import useForm from '../../../hooks/use-form';
import Rate from '../../rate';
import Button from '../../button';

import { addReview } from '../../../redux/actions';

import styles from './review-form.module.css';

const INITIAL_VALUES = { name: '', text: '', rating: 3 };

const ReviewForm = ({ onSubmit }) => {
const ReviewForm = ({ restaurantId, onSubmit }) => {
const { values, handlers, reset } = useForm(INITIAL_VALUES);

const handleSubmit = (ev) => {
Expand Down Expand Up @@ -51,6 +54,11 @@ const ReviewForm = ({ onSubmit }) => {
);
};

export default connect(null, () => ({
onSubmit: (values) => console.log(values), // TODO
ReviewForm.propTypes = {
restaurantId: PropTypes.string.isRequired,
onSubmit: PropTypes.func.isRequired,
};

export default connect(null, (dispatch, props) => ({
onSubmit: (review) => dispatch(addReview(review, props.restaurantId)),
}))(ReviewForm);
7 changes: 6 additions & 1 deletion src/components/reviews/review/review.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import PropTypes from 'prop-types';
import { connect } from 'react-redux';

import { reviewFullInfoSelector } from '../../../redux/selectors';

import Rate from '../../rate';
import styles from './review.module.css';
Expand Down Expand Up @@ -31,4 +34,6 @@ Review.defaultProps = {
user: 'Anonymous',
};

export default Review;
export default connect((state, props) => reviewFullInfoSelector(state, props))(
Review
);
15 changes: 6 additions & 9 deletions src/components/reviews/reviews.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,20 @@ import Review from './review';
import ReviewForm from './review-form';
import styles from './reviews.module.css';

const Reviews = ({ reviews }) => {
const Reviews = ({ restaurantId, reviewIds }) => {
return (
<div className={styles.reviews}>
{reviews.map((review) => (
<Review key={review.id} {...review} />
{reviewIds.map((reviewId) => (
<Review key={reviewId} id={reviewId} />
))}
<ReviewForm />
<ReviewForm restaurantId={restaurantId} />
</div>
);
};

Reviews.propTypes = {
reviews: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string.isRequired,
}).isRequired
).isRequired,
restaurantId: PropTypes.string,
reviewIds: PropTypes.arrayOf(PropTypes.string).isRequired,
};

export default Reviews;
9 changes: 8 additions & 1 deletion src/redux/actions.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
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 = (review, restaurantId) => ({
type: ADD_REVIEW,
review,
restaurantId,
uuidFor: ['userId', 'reviewId'],
});
1 change: 1 addition & 0 deletions src/redux/constants.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export const INCREMENT = 'INCREMENT';
export const DECREMENT = 'DECREMENT';
export const REMOVE = 'REMOVE';
export const ADD_REVIEW = 'ADD_REVIEW';
10 changes: 10 additions & 0 deletions src/redux/middleware/uuid.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { v4 as uuid } from 'uuid';

export default (store) => (next) => (action) => {
if (!action.uuidFor) return next(action);
const { uuidFor, ...rest } = action;
next({
...rest,
...uuidFor.reduce((acc, key) => ({ ...acc, [key]: uuid() }), {}),
});
};
2 changes: 2 additions & 0 deletions src/redux/reducer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});
20 changes: 18 additions & 2 deletions src/redux/reducer/restaurants.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,25 @@
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 }),
{}
);

export default (restaurants = defaultRestaurants, action) => {
const { type } = action;
const { type, restaurantId, reviewId } = action;

switch (type) {
case ADD_REVIEW:
const restaurant = restaurants[restaurantId];
return {
...restaurants,
[restaurantId]: {
...restaurant,
reviews: [...restaurant.reviews, reviewId],
},
};

default:
return restaurants;
}
Expand Down
17 changes: 15 additions & 2 deletions src/redux/reducer/reviews.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
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, userId, reviewId, review } = action;

switch (type) {
case ADD_REVIEW:
const { text, rating } = review;
return {
...reviews,
[reviewId]: { userId, id: reviewId, text, rating },
};

default:
return reviews;
}
Expand Down
23 changes: 23 additions & 0 deletions src/redux/reducer/users.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { normalizedUsers } from '../../fixtures';
import { ADD_REVIEW } from '../constants';

const defaultUsers = normalizedUsers.reduce(
(acc, user) => ({ ...acc, [user.id]: user }),
{}
);

export default (users = defaultUsers, action) => {
const { type, review, userId } = action;

switch (type) {
case ADD_REVIEW:
const { name } = review;
return {
...users,
[userId]: { id: userId, name },
};

default:
return users;
}
};
35 changes: 34 additions & 1 deletion src/redux/selectors.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { createSelector } from 'reselect';

// const restaurantsSelector = (state) => state.restaurants;
const restaurantsSelector = (state) => state.restaurants;
const productsSelector = (state) => state.products;
const orderSelector = (state) => state.order;
const reviewsSelector = (state) => state.reviews;
const usersSelector = (state) => state.users;

export const orderProductsSelector = createSelector(
[productsSelector, orderSelector],
Expand All @@ -22,3 +24,34 @@ export const totalSelector = createSelector(
(orderProducts) =>
orderProducts.reduce((acc, { subtotal }) => acc + subtotal, 0)
);

export const amountSelector = (state, { id }) => orderSelector(state)[id] || 0;
export const productSelector = (state, { id }) => productsSelector(state)[id];
export const restaurantSelector = (state, { id }) =>
restaurantsSelector(state)[id];

export const restaurantsAsArraySelector = createSelector(
[restaurantsSelector],
(restaurants) => Object.values(restaurants)
);

export const reviewSelector = (state, { id }) => reviewsSelector(state)[id];

export const reviewFullInfoSelector = createSelector(
[reviewSelector, usersSelector],
(review, users) => ({
...review,
user: users[review.userId]?.name,
})
);

export const averageRatingSelector = createSelector(
reviewsSelector,
restaurantSelector,
(reviews, restaurant) => {
const ratings = restaurant.reviews.map((id) => reviews[id].rating);
return Math.round(
ratings.reduce((acc, rating) => acc + rating) / ratings.length
);
}
);
Loading

0 comments on commit 7830ddd

Please sign in to comment.