From 1d6cae188f4156b52498a54deee83204b3eac4f8 Mon Sep 17 00:00:00 2001
From: Alexandra Boyko
Date: Tue, 14 Jan 2020 17:55:30 +0200
Subject: [PATCH 1/4] Restaurants cards + basic header
---
package.json | 7 ++-
public/images/logo.svg | 4 ++
public/images/place.svg | 4 ++
public/images/search.svg | 3 +
public/index.html | 2 +
src/App.js | 10 +++-
src/components/Header/Header.js | 44 ++++++++++++++
src/components/Header/Header.scss | 29 +++++++++
src/components/input/Input.js | 55 +++++++++++++++++
src/components/input/Input.scss | 22 +++++++
.../restaurantCard/RestaurantCard.js | 37 ++++++++++++
.../restaurantCard/RestaurantCard.scss | 30 ++++++++++
.../RestaurantsListPage.js | 59 +++++++++++++++++++
.../RestaurantsListPage.scss | 5 ++
src/index.js | 9 ++-
src/store/actions.js | 16 +++++
src/store/index.js | 28 +++++++++
src/store/selectors.js | 16 +++++
src/store/styles/extends.scss | 5 ++
src/store/styles/index.scss | 30 ++++++++++
20 files changed, 412 insertions(+), 3 deletions(-)
create mode 100644 public/images/logo.svg
create mode 100644 public/images/place.svg
create mode 100644 public/images/search.svg
create mode 100644 src/components/Header/Header.js
create mode 100644 src/components/Header/Header.scss
create mode 100644 src/components/input/Input.js
create mode 100644 src/components/input/Input.scss
create mode 100644 src/components/restaurantCard/RestaurantCard.js
create mode 100644 src/components/restaurantCard/RestaurantCard.scss
create mode 100644 src/components/restaurantsListPage/RestaurantsListPage.js
create mode 100644 src/components/restaurantsListPage/RestaurantsListPage.scss
create mode 100644 src/store/actions.js
create mode 100644 src/store/index.js
create mode 100644 src/store/selectors.js
create mode 100644 src/store/styles/extends.scss
create mode 100644 src/store/styles/index.scss
diff --git a/package.json b/package.json
index 7bd01de..f05337b 100755
--- a/package.json
+++ b/package.json
@@ -7,11 +7,16 @@
"author": "Mate Academy",
"license": "GPL-3.0",
"dependencies": {
+ "node-sass": "^4.13.0",
"prop-types": "^15.7.2",
"react": "^16.8.6",
"react-dom": "^16.8.6",
+ "react-redux": "^7.1.3",
"react-router-dom": "^5.0.1",
- "react-scripts": "3.0.1"
+ "react-scripts": "3.0.1",
+ "redux": "^4.0.5",
+ "redux-thunk": "^2.3.0",
+ "reselect": "^4.0.0"
},
"devDependencies": {
"@mate-academy/eslint-config-react": "*",
diff --git a/public/images/logo.svg b/public/images/logo.svg
new file mode 100644
index 0000000..7418753
--- /dev/null
+++ b/public/images/logo.svg
@@ -0,0 +1,4 @@
+
diff --git a/public/images/place.svg b/public/images/place.svg
new file mode 100644
index 0000000..a2f570f
--- /dev/null
+++ b/public/images/place.svg
@@ -0,0 +1,4 @@
+
diff --git a/public/images/search.svg b/public/images/search.svg
new file mode 100644
index 0000000..7e37c73
--- /dev/null
+++ b/public/images/search.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/index.html b/public/index.html
index 4269d67..80fd8f0 100644
--- a/public/index.html
+++ b/public/index.html
@@ -4,6 +4,8 @@
React Uber eats
+
+
diff --git a/src/App.js b/src/App.js
index d494a99..9aeb1e9 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,9 +1,17 @@
import React from 'react';
import './App.css';
+import RestaurantsListPage from
+ './components/restaurantsListPage/RestaurantsListPage';
+import Header from './components/Header/Header';
const App = () => (
+
-
React Uber eats
+
+
+
+
+
);
diff --git a/src/components/Header/Header.js b/src/components/Header/Header.js
new file mode 100644
index 0000000..f9a440b
--- /dev/null
+++ b/src/components/Header/Header.js
@@ -0,0 +1,44 @@
+import React from 'react';
+import Input from '../input/Input';
+import './Header.scss';
+
+const Header = () => (
+
+);
+
+export default Header;
diff --git a/src/components/Header/Header.scss b/src/components/Header/Header.scss
new file mode 100644
index 0000000..98a6eb3
--- /dev/null
+++ b/src/components/Header/Header.scss
@@ -0,0 +1,29 @@
+@import "../../store/styles/extends.scss";
+
+.header {
+ padding: 28px 0;
+
+ &__inner {
+ @extend %flex-row;
+ }
+
+ &__delivery-info {
+ @extend %flex-row;
+ margin-left: 7vw;
+ }
+
+ &__search {
+ margin-left: auto;
+ }
+
+ &__link {
+ margin-left: 40px;
+ color: #1f1f1f;
+ padding: 13px 0;
+ white-space: nowrap;
+
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+}
diff --git a/src/components/input/Input.js b/src/components/input/Input.js
new file mode 100644
index 0000000..e966e00
--- /dev/null
+++ b/src/components/input/Input.js
@@ -0,0 +1,55 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import './Input.scss';
+
+const Input = ({
+ iconUrl,
+ value,
+ onChange,
+ type,
+ placeholder,
+ name,
+ className,
+}) => {
+ const rootClass = `control ${className}`;
+
+ return (
+
+ {!!iconUrl
+ && (
+

+ )}
+
+
+ );
+};
+
+Input.propTypes = {
+ value: PropTypes.string.isRequired,
+ onChange: PropTypes.func.isRequired,
+ name: PropTypes.string.isRequired,
+ iconUrl: PropTypes.string,
+ type: PropTypes.string,
+ placeholder: PropTypes.string,
+ className: PropTypes.string,
+};
+
+Input.defaultProps = {
+ iconUrl: '',
+ type: 'text',
+ placeholder: '',
+ className: '',
+};
+
+export default Input;
diff --git a/src/components/input/Input.scss b/src/components/input/Input.scss
new file mode 100644
index 0000000..2688a3d
--- /dev/null
+++ b/src/components/input/Input.scss
@@ -0,0 +1,22 @@
+@import "../../store/styles/extends.scss";
+
+.control {
+ @extend %flex-row;
+
+ padding: 12px 16px;
+ border: 1px solid #e0e0e0;
+
+ &__icon {
+ width: 14px;
+ height: 14px;
+ margin-right: 10px;
+ }
+
+ &__input {
+ max-height: 24px;
+ }
+
+ & + & {
+ margin-left: 20px;
+ }
+}
diff --git a/src/components/restaurantCard/RestaurantCard.js b/src/components/restaurantCard/RestaurantCard.js
new file mode 100644
index 0000000..ba4e7dd
--- /dev/null
+++ b/src/components/restaurantCard/RestaurantCard.js
@@ -0,0 +1,37 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import './RestaurantCard.scss';
+
+export const RestaurantCard = ({
+ imageUrl,
+ title,
+ categories,
+ etaRange,
+ uuid,
+}) => (
+
+

+
{title}
+
+ {categories.join(' • ')}
+
+
{etaRange}
+
+);
+
+RestaurantCard.propTypes = {
+ imageUrl: PropTypes.string.isRequired,
+ title: PropTypes.string.isRequired,
+ categories: PropTypes.arrayOf(PropTypes.string),
+ etaRange: PropTypes.string,
+ uuid: PropTypes.string.isRequired,
+};
+
+RestaurantCard.defaultProps = {
+ categories: [],
+ etaRange: '',
+};
diff --git a/src/components/restaurantCard/RestaurantCard.scss b/src/components/restaurantCard/RestaurantCard.scss
new file mode 100644
index 0000000..397cce0
--- /dev/null
+++ b/src/components/restaurantCard/RestaurantCard.scss
@@ -0,0 +1,30 @@
+.restaurant-card {
+ $height: 367px;
+
+ height: $height;
+ width: 100%;
+ cursor: pointer;
+
+ &__img {
+ height: $height * 0.7;
+ width: 100%;
+ object-fit: cover;
+ }
+
+ &__title {
+ margin: 12px 0 4px;
+ }
+
+ &__categories {
+ font-size: 14px;
+ color: #757575;
+ margin-bottom: 4px;
+ }
+
+ &__eta {
+ padding: 2px 8px;
+ background-color: #f5f5f5;
+ font-size: 14px;
+ display: inline-block;
+ }
+}
diff --git a/src/components/restaurantsListPage/RestaurantsListPage.js b/src/components/restaurantsListPage/RestaurantsListPage.js
new file mode 100644
index 0000000..19c6516
--- /dev/null
+++ b/src/components/restaurantsListPage/RestaurantsListPage.js
@@ -0,0 +1,59 @@
+import React, { useEffect } from 'react';
+import PropTypes from 'prop-types';
+import { connect } from 'react-redux';
+import { uploadRestaurants } from '../../store/actions';
+import { seleсtRestaurantsList } from '../../store/selectors';
+import { RestaurantCard } from '../restaurantCard/RestaurantCard';
+import './RestaurantsListPage.scss';
+
+const RestaurantsListPage = ({ loadRestaurants, restaurantsData }) => {
+ useEffect(() => {
+ loadRestaurants();
+ }, []);
+
+ return (
+
+ {restaurantsData.map((restaurant) => {
+ const {
+ heroImageUrl,
+ title,
+ categories,
+ etaRange,
+ uuid,
+ } = restaurant;
+
+ return (
+
+ );
+ })}
+
+ );
+};
+
+const mapStateToProps = state => ({
+ restaurantsData: seleсtRestaurantsList(state),
+});
+
+const mapDispatchToProps = dispatch => ({
+ loadRestaurants: value => dispatch(uploadRestaurants(value)),
+});
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(RestaurantsListPage);
+
+RestaurantsListPage.propTypes = {
+ restaurantsData: PropTypes.arrayOf(PropTypes.shape({})),
+ loadRestaurants: PropTypes.func.isRequired,
+};
+
+RestaurantsListPage.defaultProps = {
+ restaurantsData: [],
+};
diff --git a/src/components/restaurantsListPage/RestaurantsListPage.scss b/src/components/restaurantsListPage/RestaurantsListPage.scss
new file mode 100644
index 0000000..a87ec4f
--- /dev/null
+++ b/src/components/restaurantsListPage/RestaurantsListPage.scss
@@ -0,0 +1,5 @@
+.restaurants-list {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(348px, 1fr));
+ gap: 40px 20px;
+}
diff --git a/src/index.js b/src/index.js
index b597a44..703b9e4 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,5 +1,12 @@
import React from 'react';
import ReactDOM from 'react-dom';
+import { Provider } from 'react-redux';
+import { store } from './store/index';
import App from './App';
-ReactDOM.render(, document.getElementById('root'));
+ReactDOM.render(
+
+
+ ,
+ document.getElementById('root')
+);
diff --git a/src/store/actions.js b/src/store/actions.js
new file mode 100644
index 0000000..0b23521
--- /dev/null
+++ b/src/store/actions.js
@@ -0,0 +1,16 @@
+export const ACTION_TYPES = {
+ SAVE_RESTAURANTS: 'SAVE_RESTAURANTS',
+};
+
+const saveRestaurants = value => ({
+ type: ACTION_TYPES.SAVE_RESTAURANTS,
+ payload: value,
+});
+
+export const uploadRestaurants = () => (dispatch) => {
+ fetch('https://mate-uber-eats-api.herokuapp.com/api/v1/restaurants')
+ .then(res => res.json())
+ .then(({ data }) => {
+ dispatch(saveRestaurants(data));
+ });
+};
diff --git a/src/store/index.js b/src/store/index.js
new file mode 100644
index 0000000..44792fa
--- /dev/null
+++ b/src/store/index.js
@@ -0,0 +1,28 @@
+import { createStore, compose, applyMiddleware } from 'redux';
+import thunk from 'redux-thunk';
+import { ACTION_TYPES } from './actions';
+import './styles/index.scss';
+
+const comoseEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
+const middleWares = [thunk];
+
+const initialState = {
+ restaurantsListData: null,
+};
+
+const reducer = (state = initialState, action) => {
+ switch (action.type) {
+ case ACTION_TYPES.SAVE_RESTAURANTS:
+ return {
+ ...state,
+ restaurantsListData: action.payload,
+ };
+ default:
+ return state;
+ }
+};
+
+export const store = createStore(
+ reducer,
+ comoseEnhancers(applyMiddleware(...middleWares))
+);
diff --git a/src/store/selectors.js b/src/store/selectors.js
new file mode 100644
index 0000000..1e8cf76
--- /dev/null
+++ b/src/store/selectors.js
@@ -0,0 +1,16 @@
+import { createSelector } from 'reselect';
+
+const rootSelector = state => state;
+
+export const seleсtRestaurantsList = createSelector(
+ rootSelector,
+ ({ restaurantsListData }) => {
+ if (!restaurantsListData) {
+ return [];
+ }
+
+ const { feedItems, storesMap } = restaurantsListData;
+
+ return feedItems.map(item => storesMap[item.uuid]);
+ }
+);
diff --git a/src/store/styles/extends.scss b/src/store/styles/extends.scss
new file mode 100644
index 0000000..a2e1538
--- /dev/null
+++ b/src/store/styles/extends.scss
@@ -0,0 +1,5 @@
+%flex-row {
+ display: flex;
+ flex-flow: row nowrap;
+ align-items: center;
+}
diff --git a/src/store/styles/index.scss b/src/store/styles/index.scss
new file mode 100644
index 0000000..c4e191b
--- /dev/null
+++ b/src/store/styles/index.scss
@@ -0,0 +1,30 @@
+*,
+::after,
+::before {
+ box-sizing: border-box;
+}
+
+body,
+button,
+a,
+input {
+ font-family: Roboto, sans-serif;
+ font-size: 16px;
+ line-height: 1.5;
+}
+
+input {
+ border: none;
+ background: transparent;
+ outline: none;
+}
+
+a {
+ text-decoration: none;
+}
+
+.content {
+ max-width: 1160px;
+ padding: 0 34px;
+ margin: 0 auto;
+}
From cef42ebe52fbb29add99408ffd2b2500f0280929 Mon Sep 17 00:00:00 2001
From: Alexandra Boyko
Date: Tue, 14 Jan 2020 18:05:48 +0200
Subject: [PATCH 2/4] readme changed
---
README.md | 22 +++++++++++-----------
package.json | 2 +-
2 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/README.md b/README.md
index 84c7a15..4cecd95 100644
--- a/README.md
+++ b/README.md
@@ -15,13 +15,13 @@ Base API url: https://mate-uber-eats-api.herokuapp.com/api/v1/
- use `redux-thunk` to handle async actions
- create separate component for restaurant card
- use https://mate-uber-eats-api.herokuapp.com/api/v1/restaurants endpoint to fetch data
- - accept optional query parameter `location`: `london|kyiv`
- - `RestaurantCard` should be clickable fully (not only the image)
+ - accept optional query parameter `location`: `london|kyiv`
+ - `RestaurantCard` should be clickable fully (not only the image)
2. Implement basic header markup
3. Implement footer
4. Add functionality and styles for mobile and tablet versions
- `Location`, `Deliver now` and `Search` should be replaced whith buttons on the small screens.
- Block with inputs should apper below the header after click on the button.
+ Block with inputs should apper below the header after click on the button.
5. Implement `RestaurantPage`
- use `uuid` in the URL
- fetch data from https://mate-uber-eats-api.herokuapp.com/api/v1/restaurants/:uuid
@@ -57,7 +57,7 @@ Base API url: https://mate-uber-eats-api.herokuapp.com/api/v1/
- open basket sidebar when click on busket button, close on click outside or close icon
- add item to basket when click submit in MenuItemModal
- show list added items in basket
- - add ability to change item count, remove item
+ - add ability to change item count, remove item
- when user click edit on item - open MenuItemModal with additional remove button. After submit, edit current item instead of add new one
- clear basket when user click order button
5. Restore user session after close-open tab
@@ -69,27 +69,27 @@ Base API url: https://mate-uber-eats-api.herokuapp.com/api/v1/
## Workflow
- Fork the repository with task
-- Clone forked repository
+- Clone forked repository
```bash
git clone git@github.com:/.git
```
- Run `npm install` to install dependencies.
- Then develop
-## Development mode
+## Development mode
- Run `npm start` to start development server on `http://localhost:3000`
- When you run server the command line window will no longer be available for
- writing commands until you stop server (`ctrl + c`). All other commands you
+ When you run server the command line window will no longer be available for
+ writing commands until you stop server (`ctrl + c`). All other commands you
need to run in new command line window.
- Follow [HTML, CSS styleguide](https://mate-academy.github.io/style-guides/htmlcss.html)
- Follow [the simplified JS styleguide](https://mate-academy.github.io/style-guides/javascript-standard-modified)
- run `npm run lint` to check code style
-- When you finished add correct `homepage` to `package.json` and run `npm run deploy`
+- When you finished add correct `homepage` to `package.json` and run `npm run deploy`
- Add links to your demo in readme.md.
- - `[DEMO LINK](https://.github.io//)` - this will be a
+ - `[DEMO LINK](https://aleksandra04.github.io/react_uber-eats/` - this will be a
link to your index.html
- Commit and push all recent changes.
-- Create `Pull Request` from forked repo `()` to original repo
+- Create `Pull Request` from forked repo `()` to original repo
(`master`).
- Add a link at `PR` to Google Spreadsheets.
diff --git a/package.json b/package.json
index f05337b..8263fc4 100755
--- a/package.json
+++ b/package.json
@@ -1,5 +1,5 @@
{
- "homepage": "https://mate-academy.github.io/react_uber-eats",
+ "homepage": "https://aleksandra04.github.io/react_uber-eats",
"name": "react_uber-eats",
"version": "0.1.0",
"private": true,
From 39eb5511f583a3981426bea755c6204b814072c0 Mon Sep 17 00:00:00 2001
From: Alexandra Boyko
Date: Thu, 16 Jan 2020 00:44:51 +0200
Subject: [PATCH 3/4] adaptive header and footer added
---
package.json | 1 +
public/images/app-store.png | Bin 0 -> 3656 bytes
public/images/arrow.svg | 3 +
public/images/facebook.svg | 3 +
public/images/footer-logo.svg | 4 +
public/images/google-play.png | Bin 0 -> 4112 bytes
public/images/instagram.svg | 5 +
public/images/twitter.svg | 3 +
public/images/world.svg | 5 +
public/images/x.svg | 3 +
src/App.css | 6 +-
src/App.js | 16 +-
src/components/Header/Header.js | 171 ++++++++++++++----
src/components/Header/Header.scss | 63 ++++++-
src/components/footer/Footer.js | 107 +++++++++++
src/components/footer/Footer.scss | 140 ++++++++++++++
src/components/input/Input.js | 72 ++++++--
src/components/input/Input.scss | 31 +++-
.../restaurantCard/RestaurantCard.scss | 12 +-
.../RestaurantsListPage.scss | 15 +-
src/components/select/Select.js | 51 ++++++
src/components/select/Select.scss | 34 ++++
src/store/styles/index.scss | 19 +-
src/store/styles/variables.scss | 6 +
24 files changed, 697 insertions(+), 73 deletions(-)
create mode 100644 public/images/app-store.png
create mode 100644 public/images/arrow.svg
create mode 100644 public/images/facebook.svg
create mode 100644 public/images/footer-logo.svg
create mode 100644 public/images/google-play.png
create mode 100644 public/images/instagram.svg
create mode 100644 public/images/twitter.svg
create mode 100644 public/images/world.svg
create mode 100644 public/images/x.svg
create mode 100644 src/components/footer/Footer.js
create mode 100644 src/components/footer/Footer.scss
create mode 100644 src/components/select/Select.js
create mode 100644 src/components/select/Select.scss
create mode 100644 src/store/styles/variables.scss
diff --git a/package.json b/package.json
index 8263fc4..c4667ca 100755
--- a/package.json
+++ b/package.json
@@ -7,6 +7,7 @@
"author": "Mate Academy",
"license": "GPL-3.0",
"dependencies": {
+ "classnames": "^2.2.6",
"node-sass": "^4.13.0",
"prop-types": "^15.7.2",
"react": "^16.8.6",
diff --git a/public/images/app-store.png b/public/images/app-store.png
new file mode 100644
index 0000000000000000000000000000000000000000..6c2ff057d7a82875e0cf1889073a0b8bb08f873d
GIT binary patch
literal 3656
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
aw*LaBZifUW!g}Wb0000
+
+
diff --git a/public/images/facebook.svg b/public/images/facebook.svg
new file mode 100644
index 0000000..22565b3
--- /dev/null
+++ b/public/images/facebook.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/images/footer-logo.svg b/public/images/footer-logo.svg
new file mode 100644
index 0000000..c3894d1
--- /dev/null
+++ b/public/images/footer-logo.svg
@@ -0,0 +1,4 @@
+
diff --git a/public/images/google-play.png b/public/images/google-play.png
new file mode 100644
index 0000000000000000000000000000000000000000..b988c6fc6c7bda6b791001fa65940fd525d51c4d
GIT binary patch
literal 4112
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
O0000
+
+
+
+
diff --git a/public/images/twitter.svg b/public/images/twitter.svg
new file mode 100644
index 0000000..9c8b7bd
--- /dev/null
+++ b/public/images/twitter.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/images/world.svg b/public/images/world.svg
new file mode 100644
index 0000000..953d851
--- /dev/null
+++ b/public/images/world.svg
@@ -0,0 +1,5 @@
+
diff --git a/public/images/x.svg b/public/images/x.svg
new file mode 100644
index 0000000..c784d0c
--- /dev/null
+++ b/public/images/x.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/App.css b/src/App.css
index 8b13789..46ebe73 100644
--- a/src/App.css
+++ b/src/App.css
@@ -1 +1,5 @@
-
+.page {
+ /* min-height: 100vh; */
+ padding-top: 32px;
+ margin-bottom: 82px;
+}
diff --git a/src/App.js b/src/App.js
index 9aeb1e9..a2d8750 100644
--- a/src/App.js
+++ b/src/App.js
@@ -3,16 +3,18 @@ import './App.css';
import RestaurantsListPage from
'./components/restaurantsListPage/RestaurantsListPage';
import Header from './components/Header/Header';
+import Footer from './components/footer/Footer';
const App = () => (
-
-
+ <>
-
-
-
-
-
+
+
+
+
+
+
+ >
);
export default App;
diff --git a/src/components/Header/Header.js b/src/components/Header/Header.js
index f9a440b..80c6062 100644
--- a/src/components/Header/Header.js
+++ b/src/components/Header/Header.js
@@ -1,44 +1,143 @@
-import React from 'react';
+import React, { useState } from 'react';
import Input from '../input/Input';
import './Header.scss';
-const Header = () => (
-
-
-
-

-
-
-
-);
+
+ );
+};
export default Header;
diff --git a/src/components/Header/Header.scss b/src/components/Header/Header.scss
index 98a6eb3..20b4a9b 100644
--- a/src/components/Header/Header.scss
+++ b/src/components/Header/Header.scss
@@ -1,19 +1,41 @@
@import "../../store/styles/extends.scss";
+@import "../../store/styles/variables.scss";
.header {
- padding: 28px 0;
+ padding: 16px 0;
+ position: sticky;
+ z-index: 1;
+ top: 0;
+ background-color: white;
&__inner {
@extend %flex-row;
}
+ &__logo {
+ height: 24px;
+ width: auto;
+
+ @media (max-width: $break-point-phone) {
+ height: 14px;
+ }
+ }
+
&__delivery-info {
@extend %flex-row;
margin-left: 7vw;
+
+ @media (max-width: $break-point-tablet) {
+ display: none;
+ }
}
&__search {
margin-left: auto;
+
+ @media (max-width: $break-point-tablet) {
+ display: none;
+ }
}
&__link {
@@ -26,4 +48,43 @@
text-decoration: underline;
}
}
+
+ &__toggle-buttons {
+ display: none;
+ margin-left: auto;
+
+ @media (max-width: $break-point-tablet) {
+ display: flex;
+ }
+ }
+
+ &__toggle-btn {
+ padding: 12px none;
+ cursor: pointer;
+
+ &:not(:last-of-type) {
+ margin-right: 34px;
+ }
+
+ &:focus {
+ transform: scale(1.2);
+ }
+ }
+}
+
+.mobile-controls {
+ display: none;
+ position: relative;
+ padding-top: 30px;
+
+ @media (max-width: $break-point-tablet) {
+ display: flex;
+ }
+
+ &__close {
+ position: absolute;
+ top: 10px;
+ right: 0;
+ cursor: pointer;
+ }
}
diff --git a/src/components/footer/Footer.js b/src/components/footer/Footer.js
new file mode 100644
index 0000000..7bb42be
--- /dev/null
+++ b/src/components/footer/Footer.js
@@ -0,0 +1,107 @@
+import React from 'react';
+import './Footer.scss';
+import Select from '../select/Select';
+
+const Footer = () => (
+
+);
+
+export default Footer;
diff --git a/src/components/footer/Footer.scss b/src/components/footer/Footer.scss
new file mode 100644
index 0000000..8cb9429
--- /dev/null
+++ b/src/components/footer/Footer.scss
@@ -0,0 +1,140 @@
+@import "../../store/styles/variables.scss";
+@import "../../store/styles/extends.scss";
+
+.footer {
+ padding: 64px 0;
+ background-color: #262626;
+
+ @media (max-width: $break-point-tablet) {
+ padding: 40px 0;
+ }
+
+ &__top-part {
+ border-bottom: 1px solid #c4c4c4;
+ padding-bottom: 40px;
+ margin-bottom: 40px;
+ }
+
+ &__main {
+ display: flex;
+ flex-flow: column nowrap;
+ justify-content: space-between;
+ }
+
+ &__logo-container {
+ @extend %flex-row;
+ margin-bottom: 40px;
+
+ @media (max-width: $break-point-tablet) {
+ flex-direction: column;
+ align-items: flex-start;
+ }
+ }
+
+ &__app {
+ display: inline-block;
+
+ @media (max-width: $break-point-tablet) {
+ display: block;
+ margin-bottom: 20px;
+ }
+
+ &:first-of-type {
+ margin-right: 20px;
+ }
+ }
+
+ &__logo {
+ margin-right: 40px;
+
+ @media (max-width: $break-point-tablet) {
+ margin-bottom: 40px;
+ }
+ }
+
+ &__top-part,
+ &__bottom-part {
+ @extend %flex-row;
+ align-items: flex-start;
+
+ @media (max-width: $break-point-phone-lg) {
+ flex-direction: column;
+ }
+ }
+
+ &__main,
+ &__copyright {
+ flex-basis: 45vw;
+ padding-right: 40px;
+
+ @media (max-width: $break-point-phone-lg) {
+ flex-basis: initial;
+ }
+ }
+
+ &__copyright {
+ color: white;
+ font-size: 14px;
+
+ @media (max-width: $break-point-phone-lg) {
+ order: 2;
+ }
+ }
+
+ &__top-links:last-of-type {
+ margin-left: 10vw;
+
+ @media (max-width: $break-point-phone-lg) {
+ margin-left: 0;
+ }
+ }
+
+ &__bottom-links {
+ @extend %flex-row;
+
+ margin-bottom: 40px;
+
+ @media (max-width: $break-point-phone-lg) {
+ flex-direction: column;
+ align-items: flex-start;
+ margin-top: 20px;
+ order: 2;
+
+ }
+
+ &:not(:last-of-type) {
+ margin-right: 40px;
+ }
+ }
+
+ &__misc {
+ @media (max-width: $break-point-phone-lg) {
+ display: flex;
+ flex-direction: column;
+ margin-top: 20px;
+ }
+ }
+}
+
+.links {
+ &__link {
+ display: block;
+ margin-bottom: 16px;
+ color: white;
+ white-space: nowrap;
+
+ &:not(:last-of-type) {
+ margin-right: 40px;
+ }
+
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+}
+
+.social {
+ &__link {
+ margin-right: 20px;
+ }
+}
diff --git a/src/components/input/Input.js b/src/components/input/Input.js
index e966e00..1e8aaa3 100644
--- a/src/components/input/Input.js
+++ b/src/components/input/Input.js
@@ -1,4 +1,5 @@
-import React from 'react';
+import React, { useState, createRef } from 'react';
+import cx from 'classnames';
import PropTypes from 'prop-types';
import './Input.scss';
@@ -10,28 +11,55 @@ const Input = ({
placeholder,
name,
className,
+ isSmall,
+ label,
}) => {
- const rootClass = `control ${className}`;
+ const [isFocused, setFocused] = useState(false);
+
+ const inputRef = createRef();
+
+ const inputWrapperClass = cx('control__input-wrapper', {
+ 'control__input-wrapperfocused': isFocused,
+ [className]: !!className,
+ });
+
+ const inputClass = cx('control__input', {
+ 'control__input--small': isSmall,
+ 'control__input--time': type === 'time',
+ });
return (
-
- {!!iconUrl
- && (
-

- )}
-
-
+ // eslint-disable-next-line
+
);
};
@@ -43,6 +71,8 @@ Input.propTypes = {
type: PropTypes.string,
placeholder: PropTypes.string,
className: PropTypes.string,
+ isSmall: PropTypes.bool,
+ label: PropTypes.string,
};
Input.defaultProps = {
@@ -50,6 +80,8 @@ Input.defaultProps = {
type: 'text',
placeholder: '',
className: '',
+ isSmall: true,
+ label: '',
};
export default Input;
diff --git a/src/components/input/Input.scss b/src/components/input/Input.scss
index 2688a3d..f96f4b7 100644
--- a/src/components/input/Input.scss
+++ b/src/components/input/Input.scss
@@ -1,10 +1,19 @@
@import "../../store/styles/extends.scss";
+@import "../../store/styles/variables.scss";
.control {
- @extend %flex-row;
+ &__input-wrapper {
+ @extend %flex-row;
- padding: 12px 16px;
- border: 1px solid #e0e0e0;
+ padding: 11px 16px;
+ border: 1px solid $color-gray;
+ transition: border-color 300ms;
+ box-sizing: border-box;
+ }
+
+ &__focused {
+ border-color: darken($color-gray, 25 );
+ }
&__icon {
width: 14px;
@@ -14,6 +23,22 @@
&__input {
max-height: 24px;
+
+ &--time {
+ max-width: 75px;
+ }
+
+ &--small {
+ @media (max-width: $break-point-tablet-lg) {
+ max-width: 75px;
+ }
+ }
+ }
+
+ &__label {
+ font-size: 11px;
+ margin-bottom: 8px;
+ color: #626262;
}
& + & {
diff --git a/src/components/restaurantCard/RestaurantCard.scss b/src/components/restaurantCard/RestaurantCard.scss
index 397cce0..7b7f52b 100644
--- a/src/components/restaurantCard/RestaurantCard.scss
+++ b/src/components/restaurantCard/RestaurantCard.scss
@@ -1,12 +1,18 @@
+@import "../../store/styles/variables.scss";
+
.restaurant-card {
- $height: 367px;
+ --height: 367px;
- height: $height;
+ height: var(--height);
width: 100%;
cursor: pointer;
+ @media (max-width: $break-point-phone) {
+ --height: 330px;
+ }
+
&__img {
- height: $height * 0.7;
+ height: calc(var(--height) * 0.7);
width: 100%;
object-fit: cover;
}
diff --git a/src/components/restaurantsListPage/RestaurantsListPage.scss b/src/components/restaurantsListPage/RestaurantsListPage.scss
index a87ec4f..ce4b903 100644
--- a/src/components/restaurantsListPage/RestaurantsListPage.scss
+++ b/src/components/restaurantsListPage/RestaurantsListPage.scss
@@ -1,5 +1,18 @@
+@import "../../store/styles/variables.scss";
+
.restaurants-list {
+ --card-width: 348px;
+
display: grid;
- grid-template-columns: repeat(auto-fill, minmax(348px, 1fr));
+ grid-template-columns: repeat(auto-fill, minmax(var(--card-width), 1fr));
gap: 40px 20px;
+
+ @media (max-width: $break-point-tablet-lg) {
+ --card-width: 340px;
+ row-gap: 20px;
+ }
+
+ @media (max-width: $break-point-phone) {
+ --card-width: 280px;
+ }
}
diff --git a/src/components/select/Select.js b/src/components/select/Select.js
new file mode 100644
index 0000000..96010fb
--- /dev/null
+++ b/src/components/select/Select.js
@@ -0,0 +1,51 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import './Select.scss';
+
+const Select = ({ name, value, onSelect, options, iconUrl }) => (
+
+
+

+

+
+);
+
+Select.propTypes = {
+ name: PropTypes.string.isRequired,
+ value: PropTypes.string.isRequired,
+ onSelect: PropTypes.func,
+ options: PropTypes.arrayOf(PropTypes.shape({
+ value: PropTypes.string,
+ label: PropTypes.string,
+ })),
+ iconUrl: PropTypes.string.isRequired,
+};
+
+Select.defaultProps = {
+ options: [],
+ onSelect: () => {},
+};
+
+export default Select;
diff --git a/src/components/select/Select.scss b/src/components/select/Select.scss
new file mode 100644
index 0000000..fd6b87c
--- /dev/null
+++ b/src/components/select/Select.scss
@@ -0,0 +1,34 @@
+@import "../../store/styles/variables.scss";
+
+.select {
+ position: relative;
+ margin-left: 40px;
+
+ @media (max-width: $break-point-tablet) {
+ margin-left: 0;
+ }
+
+ &__input {
+ -moz-appearance: none;
+ -webkit-appearance: none;
+ background-color: #262626;
+ border: 1px solid #979797;
+ padding: 10px 40px;
+ color: white;
+ cursor: pointer;
+ line-height: 19px;
+ outline: none;
+ }
+
+ &__icon {
+ position: absolute;
+ left: 10px;
+ top: 25%;
+ }
+
+ &__arrow {
+ position: absolute;
+ right: 10px;
+ top: 45%;
+ }
+}
diff --git a/src/store/styles/index.scss b/src/store/styles/index.scss
index c4e191b..e909a03 100644
--- a/src/store/styles/index.scss
+++ b/src/store/styles/index.scss
@@ -1,3 +1,5 @@
+@import "../../store/styles/variables.scss";
+
*,
::after,
::before {
@@ -7,7 +9,8 @@
body,
button,
a,
-input {
+input,
+select {
font-family: Roboto, sans-serif;
font-size: 16px;
line-height: 1.5;
@@ -19,6 +22,12 @@ input {
outline: none;
}
+button {
+ border: none;
+ background: none;
+ outline: none;
+}
+
a {
text-decoration: none;
}
@@ -27,4 +36,12 @@ a {
max-width: 1160px;
padding: 0 34px;
margin: 0 auto;
+
+ @media (max-width: $break-point-phone) {
+ padding: 0 20px;
+ }
+
+ @media (min-width: $break-point-large) {
+ max-width: 1520px;
+ }
}
diff --git a/src/store/styles/variables.scss b/src/store/styles/variables.scss
new file mode 100644
index 0000000..8e3afb2
--- /dev/null
+++ b/src/store/styles/variables.scss
@@ -0,0 +1,6 @@
+$break-point-large: 1281px;
+$break-point-tablet-lg: 975px;
+$break-point-tablet: 768px;
+$break-point-phone-lg: 620px;
+$break-point-phone: 550px;
+$color-gray: #e0e0e0;
From bb4c1e7621705b6a3b61a9714f043a089538d9f9 Mon Sep 17 00:00:00 2001
From: Alexandra Boyko
Date: Thu, 16 Jan 2020 19:06:07 +0200
Subject: [PATCH 4/4] Fixed header at small screens, links
---
src/components/error/Error.js | 22 ++++
src/components/error/Error.scss | 21 ++++
src/components/footer/Footer.js | 30 ++---
src/components/input/Input.js | 1 +
src/components/input/Input.scss | 14 ++-
src/components/loader/Loader.js | 23 ++++
src/components/loader/Loader.scss | 105 ++++++++++++++++++
.../RestaurantsListPage.js | 68 ++++++++----
src/components/select/Select.js | 1 +
src/store/actions.js | 22 +++-
src/store/index.js | 20 +---
src/store/rootReducer.js | 36 ++++++
src/store/selectors.js | 10 ++
src/store/styles/extends.scss | 10 ++
src/store/styles/variables.scss | 1 +
15 files changed, 326 insertions(+), 58 deletions(-)
create mode 100644 src/components/error/Error.js
create mode 100644 src/components/error/Error.scss
create mode 100644 src/components/loader/Loader.js
create mode 100644 src/components/loader/Loader.scss
create mode 100644 src/store/rootReducer.js
diff --git a/src/components/error/Error.js b/src/components/error/Error.js
new file mode 100644
index 0000000..6314745
--- /dev/null
+++ b/src/components/error/Error.js
@@ -0,0 +1,22 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import './Error.scss';
+
+export const Error = ({ message }) => (
+
+);
+
+Error.propTypes = {
+ message: PropTypes.string,
+};
+
+Error.defaultProps = {
+ message: 'Error occured',
+};
diff --git a/src/components/error/Error.scss b/src/components/error/Error.scss
new file mode 100644
index 0000000..984ae9f
--- /dev/null
+++ b/src/components/error/Error.scss
@@ -0,0 +1,21 @@
+@import "../../store/styles/extends.scss";
+@import "../../store/styles/variables.scss";
+
+.error {
+ @extend %absolute-center;
+ flex-flow: column nowrap;
+
+ &__text {
+ font-size: 24px;
+ font-weight: 500;
+ }
+
+ &__link {
+ font-size: 24px;
+ color: $color-green;
+
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+}
diff --git a/src/components/footer/Footer.js b/src/components/footer/Footer.js
index 7bb42be..f97f3d4 100644
--- a/src/components/footer/Footer.js
+++ b/src/components/footer/Footer.js
@@ -28,13 +28,13 @@ const Footer = () => (
/>
@@ -76,24 +76,24 @@ const Footer = () => (
diff --git a/src/components/input/Input.js b/src/components/input/Input.js
index 1e8aaa3..8ed38f2 100644
--- a/src/components/input/Input.js
+++ b/src/components/input/Input.js
@@ -26,6 +26,7 @@ const Input = ({
const inputClass = cx('control__input', {
'control__input--small': isSmall,
'control__input--time': type === 'time',
+ 'control__input--address': type === 'text',
});
return (
diff --git a/src/components/input/Input.scss b/src/components/input/Input.scss
index f96f4b7..1d9b83f 100644
--- a/src/components/input/Input.scss
+++ b/src/components/input/Input.scss
@@ -26,11 +26,23 @@
&--time {
max-width: 75px;
+
+ @media (max-width: $break-point-phone) {
+ max-width: 65px;
+ }
+ }
+
+ &--address {
+ max-width: 256px;
+
+ @media (max-width: $break-point-phone) {
+ max-width: 104px;
+ }
}
&--small {
@media (max-width: $break-point-tablet-lg) {
- max-width: 75px;
+ max-width: 65px;
}
}
}
diff --git a/src/components/loader/Loader.js b/src/components/loader/Loader.js
new file mode 100644
index 0000000..acb41df
--- /dev/null
+++ b/src/components/loader/Loader.js
@@ -0,0 +1,23 @@
+import React from 'react';
+import './Loader.scss';
+
+export const Loader = () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+
+export default Loader;
diff --git a/src/components/loader/Loader.scss b/src/components/loader/Loader.scss
new file mode 100644
index 0000000..d5844ea
--- /dev/null
+++ b/src/components/loader/Loader.scss
@@ -0,0 +1,105 @@
+@import "../../store/styles/extends.scss";
+@import "../../store/styles/variables.scss";
+
+.loader-container {
+ @extend %absolute-center;
+}
+
+.lds-default {
+ display: inline-block;
+ position: relative;
+ width: 80px * 2;
+ height: 80px * 2;
+}
+
+.lds-default div {
+ position: absolute;
+ width: 6px * 2;
+ height: 6px * 2;
+ background: $color-green;
+ border-radius: 50%;
+ animation: lds-default 1.2s linear infinite;
+}
+
+.lds-default div:nth-child(1) {
+ animation-delay: 0s;
+ top: 37px * 2;
+ left: 66px * 2;
+}
+
+.lds-default div:nth-child(2) {
+ animation-delay: -0.1s;
+ top: 22px * 2;
+ left: 62px * 2;
+}
+
+.lds-default div:nth-child(3) {
+ animation-delay: -0.2s;
+ top: 11px * 2;
+ left: 52px * 2;
+}
+
+.lds-default div:nth-child(4) {
+ animation-delay: -0.3s;
+ top: 7px * 2;
+ left: 37px * 2;
+}
+
+.lds-default div:nth-child(5) {
+ animation-delay: -0.4s;
+ top: 11px * 2;
+ left: 22px * 2;
+}
+
+.lds-default div:nth-child(6) {
+ animation-delay: -0.5s;
+ top: 22px * 2;
+ left: 11px * 2;
+}
+
+.lds-default div:nth-child(7) {
+ animation-delay: -0.6s;
+ top: 37px * 2;
+ left: 7px * 2;
+}
+
+.lds-default div:nth-child(8) {
+ animation-delay: -0.7s;
+ top: 52px * 2;
+ left: 11px * 2;
+}
+
+.lds-default div:nth-child(9) {
+ animation-delay: -0.8s;
+ top: 62px * 2;
+ left: 22px * 2;
+}
+
+.lds-default div:nth-child(10) {
+ animation-delay: -0.9s;
+ top: 66px * 2;
+ left: 37px * 2;
+}
+
+.lds-default div:nth-child(11) {
+ animation-delay: -1s;
+ top: 62px * 2;
+ left: 52px * 2;
+}
+
+.lds-default div:nth-child(12) {
+ animation-delay: -1.1s;
+ top: 52px * 2;
+ left: 62px * 2;
+}
+@keyframes lds-default {
+ 0%,
+ 20%,
+ 80%,
+ 100% {
+ transform: scale(1);
+ }
+ 50% {
+ transform: scale(1.5);
+ }
+}
diff --git a/src/components/restaurantsListPage/RestaurantsListPage.js b/src/components/restaurantsListPage/RestaurantsListPage.js
index 19c6516..deb2924 100644
--- a/src/components/restaurantsListPage/RestaurantsListPage.js
+++ b/src/components/restaurantsListPage/RestaurantsListPage.js
@@ -2,42 +2,60 @@ import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { uploadRestaurants } from '../../store/actions';
-import { seleсtRestaurantsList } from '../../store/selectors';
+import {
+ seleсtRestaurantsList,
+ seleсtRestaurantsListError,
+ seleсtIsLoading,
+} from '../../store/selectors';
import { RestaurantCard } from '../restaurantCard/RestaurantCard';
+import { Loader } from '../loader/Loader';
+import { Error } from '../error/Error';
+
import './RestaurantsListPage.scss';
-const RestaurantsListPage = ({ loadRestaurants, restaurantsData }) => {
+const RestaurantsListPage = ({
+ loadRestaurants,
+ restaurantsData,
+ error,
+ isLoading,
+}) => {
useEffect(() => {
loadRestaurants();
}, []);
return (
-
- {restaurantsData.map((restaurant) => {
- const {
- heroImageUrl,
- title,
- categories,
- etaRange,
- uuid,
- } = restaurant;
-
- return (
-
- );
- })}
-
+ <>
+ {isLoading &&
}
+ {error &&
}
+
+ {restaurantsData.map((restaurant) => {
+ const {
+ heroImageUrl,
+ title,
+ categories,
+ etaRange,
+ uuid,
+ } = restaurant;
+
+ return (
+
+ );
+ })}
+
+ >
);
};
const mapStateToProps = state => ({
restaurantsData: seleсtRestaurantsList(state),
+ error: seleсtRestaurantsListError(state),
+ isLoading: seleсtIsLoading(state),
});
const mapDispatchToProps = dispatch => ({
@@ -52,8 +70,12 @@ export default connect(
RestaurantsListPage.propTypes = {
restaurantsData: PropTypes.arrayOf(PropTypes.shape({})),
loadRestaurants: PropTypes.func.isRequired,
+ error: PropTypes.string,
+ isLoading: PropTypes.bool,
};
RestaurantsListPage.defaultProps = {
restaurantsData: [],
+ error: null,
+ isLoading: false,
};
diff --git a/src/components/select/Select.js b/src/components/select/Select.js
index 96010fb..162b02a 100644
--- a/src/components/select/Select.js
+++ b/src/components/select/Select.js
@@ -14,6 +14,7 @@ const Select = ({ name, value, onSelect, options, iconUrl }) => (
diff --git a/src/store/actions.js b/src/store/actions.js
index 0b23521..c7af0da 100644
--- a/src/store/actions.js
+++ b/src/store/actions.js
@@ -1,5 +1,8 @@
export const ACTION_TYPES = {
SAVE_RESTAURANTS: 'SAVE_RESTAURANTS',
+ SET_LOAD_RESTAURANTS_ERROR: 'SAVE_LOAD_RESTAURANTS_ERROR',
+ START_LOADING: 'START_LOADING',
+ STOP_LOADING: 'STOP_LOADING',
};
const saveRestaurants = value => ({
@@ -7,10 +10,27 @@ const saveRestaurants = value => ({
payload: value,
});
+const setRestaurantsError = error => ({
+ type: ACTION_TYPES.SET_LOAD_RESTAURANTS_ERROR,
+ payload: error,
+});
+
+const startLoading = () => ({
+ type: ACTION_TYPES.START_LOADING,
+});
+
+const stopLoading = () => ({
+ type: ACTION_TYPES.STOP_LOADING,
+});
+
export const uploadRestaurants = () => (dispatch) => {
+ dispatch(startLoading());
+
fetch('https://mate-uber-eats-api.herokuapp.com/api/v1/restaurants')
.then(res => res.json())
.then(({ data }) => {
dispatch(saveRestaurants(data));
- });
+ })
+ .catch(error => dispatch(setRestaurantsError(error.message)))
+ .finally(() => dispatch(stopLoading()));
};
diff --git a/src/store/index.js b/src/store/index.js
index 44792fa..0c684fd 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -1,28 +1,12 @@
import { createStore, compose, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
-import { ACTION_TYPES } from './actions';
+import { rootReducer } from './rootReducer';
import './styles/index.scss';
const comoseEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const middleWares = [thunk];
-const initialState = {
- restaurantsListData: null,
-};
-
-const reducer = (state = initialState, action) => {
- switch (action.type) {
- case ACTION_TYPES.SAVE_RESTAURANTS:
- return {
- ...state,
- restaurantsListData: action.payload,
- };
- default:
- return state;
- }
-};
-
export const store = createStore(
- reducer,
+ rootReducer,
comoseEnhancers(applyMiddleware(...middleWares))
);
diff --git a/src/store/rootReducer.js b/src/store/rootReducer.js
new file mode 100644
index 0000000..ffd5e36
--- /dev/null
+++ b/src/store/rootReducer.js
@@ -0,0 +1,36 @@
+import { ACTION_TYPES } from './actions';
+
+const initialState = {
+ restaurantsListData: null,
+ isLoading: false,
+ error: null,
+};
+
+export const rootReducer = (state = initialState, action) => {
+ switch (action.type) {
+ case ACTION_TYPES.SAVE_RESTAURANTS:
+ return {
+ ...state,
+ error: null,
+ restaurantsListData: action.payload,
+ };
+ case ACTION_TYPES.SET_LOAD_RESTAURANTS_ERROR:
+ return {
+ ...state,
+ error: action.payload,
+ restaurantsListData: null,
+ };
+ case ACTION_TYPES.START_LOADING:
+ return {
+ ...state,
+ isLoading: true,
+ };
+ case ACTION_TYPES.STOP_LOADING:
+ return {
+ ...state,
+ isLoading: false,
+ };
+ default:
+ return state;
+ }
+};
diff --git a/src/store/selectors.js b/src/store/selectors.js
index 1e8cf76..eb52b88 100644
--- a/src/store/selectors.js
+++ b/src/store/selectors.js
@@ -14,3 +14,13 @@ export const seleсtRestaurantsList = createSelector(
return feedItems.map(item => storesMap[item.uuid]);
}
);
+
+export const seleсtRestaurantsListError = createSelector(
+ rootSelector,
+ ({ error }) => error
+);
+
+export const seleсtIsLoading = createSelector(
+ rootSelector,
+ ({ isLoading }) => isLoading
+);
diff --git a/src/store/styles/extends.scss b/src/store/styles/extends.scss
index a2e1538..dfc412b 100644
--- a/src/store/styles/extends.scss
+++ b/src/store/styles/extends.scss
@@ -3,3 +3,13 @@
flex-flow: row nowrap;
align-items: center;
}
+
+%absolute-center {
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
diff --git a/src/store/styles/variables.scss b/src/store/styles/variables.scss
index 8e3afb2..b51d43c 100644
--- a/src/store/styles/variables.scss
+++ b/src/store/styles/variables.scss
@@ -4,3 +4,4 @@ $break-point-tablet: 768px;
$break-point-phone-lg: 620px;
$break-point-phone: 550px;
$color-gray: #e0e0e0;
+$color-green: #5fb709;