From 5128de3c60fcd81acd406e57698095cb6d7912f9 Mon Sep 17 00:00:00 2001 From: Kristina Volosiuk Date: Wed, 9 Dec 2020 09:25:19 +0300 Subject: [PATCH 1/3] [homework-7]: add redirects to restaurant page --- .idea/.gitignore | 5 +++ .idea/codeStyles/Project.xml | 59 ++++++++++++++++++++++++++++ .idea/codeStyles/codeStyleConfig.xml | 5 +++ .idea/misc.xml | 6 +++ .idea/modules.xml | 8 ++++ .idea/react-2020-11-13.iml | 12 ++++++ .idea/vcs.xml | 6 +++ src/components/app/app.js | 4 +- src/pages/restaurants-page.js | 19 +++++---- src/redux/selectors.js | 5 +++ 10 files changed, 119 insertions(+), 10 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/codeStyles/Project.xml create mode 100644 .idea/codeStyles/codeStyleConfig.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/react-2020-11-13.iml create mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..b58b603 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..fe178a4 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..79ee123 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..24eb271 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..00be607 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/react-2020-11-13.iml b/.idea/react-2020-11-13.iml new file mode 100644 index 0000000..24643cc --- /dev/null +++ b/.idea/react-2020-11-13.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/components/app/app.js b/src/components/app/app.js index 7a97d93..108d2ac 100644 --- a/src/components/app/app.js +++ b/src/components/app/app.js @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { Route, Switch } from 'react-router-dom'; +import { Redirect, Route, Switch } from 'react-router-dom'; import Header from '../header'; import Basket from '../basket'; import RestaurantsPage from '../../pages/restaurants-page'; @@ -15,7 +15,7 @@ const App = () => {

Error Page

} /> - '404 - not found'} /> + diff --git a/src/pages/restaurants-page.js b/src/pages/restaurants-page.js index bf7c38c..5ab3de8 100644 --- a/src/pages/restaurants-page.js +++ b/src/pages/restaurants-page.js @@ -1,5 +1,5 @@ import React, { useEffect } from 'react'; -import { Route } from 'react-router-dom'; +import { Redirect, Route } from 'react-router-dom'; import { connect } from 'react-redux'; import { createStructuredSelector } from 'reselect'; import Restaurants from '../components/restaurants'; @@ -8,11 +8,18 @@ import { restaurantsListSelector, restaurantsLoadedSelector, restaurantsLoadingSelector, + firstRestaurantIdSelector, } from '../redux/selectors'; import { loadRestaurants } from '../redux/actions'; -function RestaurantsPage({ loadRestaurants, loading, loaded, match }) { +function RestaurantsPage({ + loadRestaurants, + loading, + loaded, + match, + firstRestId, +}) { useEffect(() => { if (!loading && !loaded) loadRestaurants(); }, []); // eslint-disable-line @@ -20,12 +27,7 @@ function RestaurantsPage({ loadRestaurants, loading, loaded, match }) { if (loading || !loaded) return ; if (match.isExact) { - return ( - <> - -

Select restaurant

- - ); + return ; } return ; @@ -33,6 +35,7 @@ function RestaurantsPage({ loadRestaurants, loading, loaded, match }) { export default connect( createStructuredSelector({ + firstRestId: firstRestaurantIdSelector, restaurants: restaurantsListSelector, loading: restaurantsLoadingSelector, loaded: restaurantsLoadedSelector, diff --git a/src/redux/selectors.js b/src/redux/selectors.js index 990b2ce..572b90f 100644 --- a/src/redux/selectors.js +++ b/src/redux/selectors.js @@ -29,6 +29,11 @@ export const restaurantsListSelector = createSelector( Object.values ); +export const firstRestaurantIdSelector = createSelector( + restaurantsListSelector, + (restaurants) => restaurants[0]?.id +); + const restaurantsIdsByProductsSelector = createSelector( restaurantsListSelector, (restaurants) => From 919b61b4f8c83e912800bc1df48f73331ff98d15 Mon Sep 17 00:00:00 2001 From: Kristina Volosiuk Date: Wed, 9 Dec 2020 21:27:10 +0300 Subject: [PATCH 2/3] [homework-7]: add order checkout --- src/components/app/app.js | 4 ++- src/components/basket/basket.js | 39 ++++++++++++++++++---- src/pages/error-page.js | 19 +++++++++++ src/redux/actions.js | 26 +++++++++++++++ src/redux/constants.js | 1 + src/redux/reducer/order.js | 59 ++++++++++++++++++++++++--------- src/redux/selectors.js | 22 +++++++++++- 7 files changed, 147 insertions(+), 23 deletions(-) create mode 100644 src/pages/error-page.js diff --git a/src/components/app/app.js b/src/components/app/app.js index 108d2ac..b9edd7a 100644 --- a/src/components/app/app.js +++ b/src/components/app/app.js @@ -3,6 +3,7 @@ import { Redirect, Route, Switch } from 'react-router-dom'; import Header from '../header'; import Basket from '../basket'; import RestaurantsPage from '../../pages/restaurants-page'; +import ErrorPage from '../../pages/error-page'; import { UserProvider } from '../../contexts/user-context'; const App = () => { @@ -14,7 +15,8 @@ const App = () => { -

Error Page

} /> + +

Thanks for order!

} />
diff --git a/src/components/basket/basket.js b/src/components/basket/basket.js index 0285b6f..4932b95 100644 --- a/src/components/basket/basket.js +++ b/src/components/basket/basket.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useCallback } from 'react'; import { connect } from 'react-redux'; import { Link } from 'react-router-dom'; import { createStructuredSelector } from 'reselect'; @@ -10,11 +10,31 @@ import './basket.css'; import BasketRow from './basket-row'; import BasketItem from './basket-item'; import Button from '../button'; -import { orderProductsSelector, totalSelector } from '../../redux/selectors'; +import { checkout } from '../../redux/actions'; +import { + checkoutOrderProductsSelector, + orderProductsSelector, + currentRouteSelector, + orderSavingSelector, + totalSelector, +} from '../../redux/selectors'; import { UserConsumer } from '../../contexts/user-context'; +import Loader from '../loader'; -function Basket({ title = 'Basket', total, orderProducts }) { - // const { name } = useContext(userContext); +function Basket({ + title = 'Basket', + total, + orderProducts, + checkoutProducts, + route, + checkout, + saving, +}) { + const onClickHandler = useCallback(() => { + if (route === '/checkout') { + checkout(checkoutProducts); + } + }, [checkoutProducts, route, checkout]); if (!total) { return ( @@ -24,6 +44,10 @@ function Basket({ title = 'Basket', total, orderProducts }) { ); } + if (saving) { + return ; + } + return (
{/*

{`${name}'s ${title}`}

*/} @@ -51,7 +75,7 @@ function Basket({ title = 'Basket', total, orderProducts }) { - @@ -61,7 +85,10 @@ function Basket({ title = 'Basket', total, orderProducts }) { const mapStateToProps = createStructuredSelector({ total: totalSelector, + saving: orderSavingSelector, + route: currentRouteSelector, orderProducts: orderProductsSelector, + checkoutProducts: checkoutOrderProductsSelector, }); -export default connect(mapStateToProps)(Basket); +export default connect(mapStateToProps, { checkout })(Basket); diff --git a/src/pages/error-page.js b/src/pages/error-page.js new file mode 100644 index 0000000..3115d45 --- /dev/null +++ b/src/pages/error-page.js @@ -0,0 +1,19 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { createStructuredSelector } from 'reselect'; +import { currentErrorRouteSelector } from '../redux/selectors'; + +function ErrorPage({ error }) { + return ( + <> +

Error Page

+

{error}

+ + ); +} + +export default connect( + createStructuredSelector({ + error: currentErrorRouteSelector, + }) +)(ErrorPage); diff --git a/src/redux/actions.js b/src/redux/actions.js index 355e95e..d374715 100644 --- a/src/redux/actions.js +++ b/src/redux/actions.js @@ -4,6 +4,7 @@ import { DECREMENT, REMOVE, ADD_REVIEW, + CHECKOUT, LOAD_RESTAURANTS, LOAD_REVIEWS, LOAD_PRODUCTS, @@ -67,3 +68,28 @@ export const loadUsers = () => async (dispatch, getState) => { dispatch({ type: LOAD_USERS, CallAPI: '/api/users' }); }; + +export const checkout = (checkoutProducts) => async (dispatch, getState) => { + dispatch({ type: CHECKOUT + REQUEST }); + try { + const response = await fetch('/api/order', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(checkoutProducts), + }).then(async (res) => { + if (!res.ok) { + const text = await res.json(); + + throw new Error(text); + } else { + return res.json(); + } + }); + + dispatch({ type: CHECKOUT + SUCCESS, response }); + dispatch(replace('/success')); + } catch (error) { + dispatch({ type: CHECKOUT + FAILURE, error: error?.message }); + dispatch(replace('/error', { error: error?.message })); + } +}; diff --git a/src/redux/constants.js b/src/redux/constants.js index 27ba575..1c71d98 100644 --- a/src/redux/constants.js +++ b/src/redux/constants.js @@ -2,6 +2,7 @@ export const INCREMENT = 'INCREMENT'; export const DECREMENT = 'DECREMENT'; export const REMOVE = 'REMOVE'; export const ADD_REVIEW = 'ADD_REVIEW'; +export const CHECKOUT = 'CHECKOUT'; export const LOAD_RESTAURANTS = 'LOAD_RESTAURANTS'; export const LOAD_PRODUCTS = 'LOAD_PRODUCTS'; diff --git a/src/redux/reducer/order.js b/src/redux/reducer/order.js index 2394928..3048033 100644 --- a/src/redux/reducer/order.js +++ b/src/redux/reducer/order.js @@ -1,24 +1,53 @@ -import { DECREMENT, INCREMENT, REMOVE } from '../constants'; +import produce from 'immer'; +import { + DECREMENT, + INCREMENT, + REMOVE, + CHECKOUT, + REQUEST, + SUCCESS, + FAILURE, +} from '../constants'; + +const initialState = { + saving: false, + error: null, + entities: {}, +}; + +const reducer = produce((draft = initialState, action) => { + const { type, payload, error } = action; -// { [productId]: amount } -const reducer = (state = {}, action) => { - const { type, payload } = action; switch (type) { case INCREMENT: - return { ...state, [payload.id]: (state[payload.id] || 0) + 1 }; + draft.entities[payload.id] = (draft.entities[payload.id] || 0) + 1; + break; case DECREMENT: - return { - ...state, - [payload.id]: Math.max((state[payload.id] || 0) - 1, 0), - }; + draft.entities[payload.id] = Math.max( + (draft.entities[payload.id] || 0) - 1, + 0 + ); + break; case REMOVE: - return { - ...state, - [payload.id]: 0, - }; + draft.entities[payload.id] = 0; + break; + case CHECKOUT + REQUEST: + draft.error = null; + draft.saving = true; + break; + case CHECKOUT + SUCCESS: { + draft.saving = false; + draft.entities = {}; + break; + } + case CHECKOUT + FAILURE: { + draft.saving = false; + draft.error = error; + break; + } default: - return state; + return draft; } -}; +}); export default reducer; diff --git a/src/redux/selectors.js b/src/redux/selectors.js index 572b90f..d6f8ba0 100644 --- a/src/redux/selectors.js +++ b/src/redux/selectors.js @@ -6,7 +6,8 @@ const productsSelector = (state) => state.products.entities; const reviewsSelector = (state) => state.reviews.entities; const usersSelector = (state) => state.users.entities; -const orderSelector = (state) => state.order; +const orderSelector = (state) => state.order.entities; +const routeSelector = (state) => state.router.location; export const restaurantsLoadingSelector = (state) => state.restaurants.loading; export const restaurantsLoadedSelector = (state) => state.restaurants.loaded; @@ -24,6 +25,9 @@ export const reviewsLoadedSelector = (state, props) => export const usersLoadingSelector = (state) => state.users.loading; export const usersLoadedSelector = (state) => state.users.loaded; +export const orderSavingSelector = (state) => state.order.saving; +export const orderErrorSelector = (state) => state.order.error; + export const restaurantsListSelector = createSelector( restaurantsSelector, Object.values @@ -64,6 +68,12 @@ export const orderProductsSelector = createSelector( } ); +export const checkoutOrderProductsSelector = createSelector( + orderProductsSelector, + (orderProducts) => + orderProducts.map(({ product, amount }) => ({ id: product.id, amount })) +); + export const totalSelector = createSelector( orderProductsSelector, (orderProducts) => @@ -93,3 +103,13 @@ export const averageRatingSelector = createSelector( ); } ); + +export const currentRouteSelector = createSelector( + routeSelector, + (location) => location.pathname +); + +export const currentErrorRouteSelector = createSelector( + routeSelector, + (location) => location?.state?.error +); From d14dd8a66177aef0706b13d233d743aee0b8bc99 Mon Sep 17 00:00:00 2001 From: Kristina Volosiuk Date: Fri, 11 Dec 2020 13:05:11 +0300 Subject: [PATCH 3/3] [homework-7]: add currency from context (not finished) --- .gitignore | 1 + .idea/.gitignore | 5 - .idea/codeStyles/Project.xml | 59 ------- .idea/codeStyles/codeStyleConfig.xml | 5 - .idea/misc.xml | 6 - .idea/modules.xml | 8 - .idea/react-2020-11-13.iml | 12 -- .idea/vcs.xml | 6 - package.json | 1 + src/components/app/app.js | 8 +- .../basket/basket-item/basket-item.js | 7 +- .../basket/basket-row/basket-row.js | 7 +- src/components/basket/basket.js | 4 +- .../header/currency-select/currency-select.js | 33 ++++ .../header/currency-select/index.js | 1 + src/components/header/header.js | 18 +-- src/components/header/header.module.css | 15 +- src/constants/currencies.js | 8 + src/contexts/currency-context.js | 8 + yarn.lock | 147 +++++++++++++++++- 20 files changed, 231 insertions(+), 128 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/codeStyles/Project.xml delete mode 100644 .idea/codeStyles/codeStyleConfig.xml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/react-2020-11-13.iml delete mode 100644 .idea/vcs.xml create mode 100644 src/components/header/currency-select/currency-select.js create mode 100644 src/components/header/currency-select/index.js create mode 100644 src/constants/currencies.js create mode 100644 src/contexts/currency-context.js diff --git a/.gitignore b/.gitignore index 04c5395..7366867 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ node_modules .env.development.local .env.test.local .env.production.local +.idea npm-debug.log* yarn-debug.log* diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index b58b603..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml deleted file mode 100644 index fe178a4..0000000 --- a/.idea/codeStyles/Project.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml deleted file mode 100644 index 79ee123..0000000 --- a/.idea/codeStyles/codeStyleConfig.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 24eb271..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 00be607..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/react-2020-11-13.iml b/.idea/react-2020-11-13.iml deleted file mode 100644 index 24643cc..0000000 --- a/.idea/react-2020-11-13.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/package.json b/package.json index f0ccd02..91abc6d 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "prop-types": "^15.7.2", "react": "^17.0.1", "react-dom": "^17.0.1", + "react-dropdown-select": "^4.7.1", "react-redux": "^7.2.2", "react-router-dom": "^5.2.0", "react-scripts": "4.0.0", diff --git a/src/components/app/app.js b/src/components/app/app.js index b9edd7a..0becf47 100644 --- a/src/components/app/app.js +++ b/src/components/app/app.js @@ -4,13 +4,13 @@ import Header from '../header'; import Basket from '../basket'; import RestaurantsPage from '../../pages/restaurants-page'; import ErrorPage from '../../pages/error-page'; -import { UserProvider } from '../../contexts/user-context'; +import { CurrencyProvider } from '../../contexts/currency-context'; const App = () => { - const [name, setName] = useState('Igor'); + const [currency, setCurrency] = useState('USD'); return (
- +
@@ -19,7 +19,7 @@ const App = () => {

Thanks for order!

} />
- +
); }; diff --git a/src/components/basket/basket-item/basket-item.js b/src/components/basket/basket-item/basket-item.js index 66b097a..f2679a1 100644 --- a/src/components/basket/basket-item/basket-item.js +++ b/src/components/basket/basket-item/basket-item.js @@ -5,6 +5,7 @@ import cn from 'classnames'; import { increment, decrement, remove } from '../../../redux/actions'; import Button from '../../button'; import styles from './basket-item.module.css'; +import { CurrencyConsumer } from '../../../contexts/currency-context'; function BasketItem({ product, @@ -38,7 +39,11 @@ function BasketItem({ small />
-

{subtotal} $

+

+ + {({ currency }) => `${subtotal} ${currency}`} + +