From f1a91de4879ce46d042504e8677ec13b0bac9e82 Mon Sep 17 00:00:00 2001 From: OlehTereshchuk Date: Wed, 15 Jan 2020 00:11:11 +0200 Subject: [PATCH] 1-4 --- .stylelintrc.js | 2 +- README.md | 22 ++-- package.json | 9 +- public/images/appstore.png | Bin 0 -> 3656 bytes public/images/arrow-down.svg | 3 + public/images/close.svg | 3 + public/images/delivery.svg | 4 + public/images/facebook.svg | 3 + public/images/google-play.png | Bin 0 -> 4112 bytes public/images/instagram.svg | 5 + public/images/logo-footer.svg | 4 + public/images/logo.svg | 4 + public/images/place.svg | 3 + public/images/search.svg | 3 + public/images/twitter.svg | 3 + public/images/world.svg | 3 + public/index.html | 2 + src/App.css | 1 - src/App.js | 17 ++- src/components/Error.js | 20 ++++ src/components/Footer.js | 80 ++++++++++++++ src/components/Header.js | 149 ++++++++++++++++++++++++++ src/components/Input.js | 80 ++++++++++++++ src/components/Loader.js | 24 +++++ src/components/RestaurantCard.js | 29 +++++ src/components/RestaurantsListPage.js | 72 +++++++++++++ src/components/Select.js | 51 +++++++++ src/index.js | 10 +- src/store/errorReducer.js | 17 +++ src/store/loadDataFromServer.js | 7 ++ src/store/loadingReducer.js | 23 ++++ src/store/restaurantsReducer.js | 17 +++ src/store/store.js | 43 ++++++++ src/styles/Error.scss | 20 ++++ src/styles/Footer.scss | 143 ++++++++++++++++++++++++ src/styles/Header.scss | 97 +++++++++++++++++ src/styles/Input.scss | 47 ++++++++ src/styles/Loader.scss | 102 ++++++++++++++++++ src/styles/Page.scss | 6 ++ src/styles/RestaurantCard.scss | 37 +++++++ src/styles/RestaurantsList.scss | 17 +++ src/styles/Select.scss | 33 ++++++ src/styles/default.scss | 47 ++++++++ src/styles/extends.scss | 16 +++ src/styles/variables.scss | 7 ++ 45 files changed, 1265 insertions(+), 20 deletions(-) create mode 100644 public/images/appstore.png create mode 100644 public/images/arrow-down.svg create mode 100644 public/images/close.svg create mode 100644 public/images/delivery.svg create mode 100644 public/images/facebook.svg create mode 100644 public/images/google-play.png create mode 100644 public/images/instagram.svg create mode 100644 public/images/logo-footer.svg create mode 100644 public/images/logo.svg create mode 100644 public/images/place.svg create mode 100644 public/images/search.svg create mode 100644 public/images/twitter.svg create mode 100644 public/images/world.svg delete mode 100644 src/App.css create mode 100644 src/components/Error.js create mode 100644 src/components/Footer.js create mode 100644 src/components/Header.js create mode 100644 src/components/Input.js create mode 100644 src/components/Loader.js create mode 100644 src/components/RestaurantCard.js create mode 100644 src/components/RestaurantsListPage.js create mode 100644 src/components/Select.js create mode 100644 src/store/errorReducer.js create mode 100644 src/store/loadDataFromServer.js create mode 100644 src/store/loadingReducer.js create mode 100644 src/store/restaurantsReducer.js create mode 100644 src/store/store.js create mode 100644 src/styles/Error.scss create mode 100644 src/styles/Footer.scss create mode 100644 src/styles/Header.scss create mode 100644 src/styles/Input.scss create mode 100644 src/styles/Loader.scss create mode 100644 src/styles/Page.scss create mode 100644 src/styles/RestaurantCard.scss create mode 100644 src/styles/RestaurantsList.scss create mode 100644 src/styles/Select.scss create mode 100644 src/styles/default.scss create mode 100644 src/styles/extends.scss create mode 100644 src/styles/variables.scss diff --git a/.stylelintrc.js b/.stylelintrc.js index f3a4e74..d5fdb09 100644 --- a/.stylelintrc.js +++ b/.stylelintrc.js @@ -1,4 +1,4 @@ module.exports = { extends: "@mate-academy/stylelint-config", - rules: {} + rules: {}, }; diff --git a/README.md b/README.md index 84c7a15..85b7b99 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://OlehTereshchuk.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 7bd01de..2068948 100755 --- a/package.json +++ b/package.json @@ -7,16 +7,21 @@ "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", "react-dom": "^16.8.6", + "react-redux": "^7.1.3", "react-router-dom": "^5.0.1", - "react-scripts": "3.0.1" + "react-scripts": "^3.3.0", + "redux": "^4.0.5", + "redux-thunk": "^2.3.0", + "reselect": "^4.0.0" }, "devDependencies": { "@mate-academy/eslint-config-react": "*", "@mate-academy/stylelint-config": "*", - "eslint": "^5.16.0", "gh-pages": "^2.1.1", "husky": "^1.3.1", "lint-staged": "^8.1.5", diff --git a/public/images/appstore.png b/public/images/appstore.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/close.svg b/public/images/close.svg new file mode 100644 index 0000000..c784d0c --- /dev/null +++ b/public/images/close.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/images/delivery.svg b/public/images/delivery.svg new file mode 100644 index 0000000..ea10186 --- /dev/null +++ b/public/images/delivery.svg @@ -0,0 +1,4 @@ + + + + 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/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/logo-footer.svg b/public/images/logo-footer.svg new file mode 100644 index 0000000..c3894d1 --- /dev/null +++ b/public/images/logo-footer.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/images/logo.svg b/public/images/logo.svg new file mode 100644 index 0000000..3124de7 --- /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..ea09167 --- /dev/null +++ b/public/images/place.svg @@ -0,0 +1,3 @@ + + + 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/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..b8806c7 --- /dev/null +++ b/public/images/world.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/index.html b/public/index.html index 4269d67..ef75689 100644 --- a/public/index.html +++ b/public/index.html @@ -3,6 +3,8 @@ + + React Uber eats diff --git a/src/App.css b/src/App.css deleted file mode 100644 index 8b13789..0000000 --- a/src/App.css +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/App.js b/src/App.js index d494a99..89be8ac 100644 --- a/src/App.js +++ b/src/App.js @@ -1,10 +1,19 @@ import React from 'react'; -import './App.css'; +import Header from './components/Header'; +import Footer from './components/Footer'; +import RestaurantsListPage from './components/RestaurantsListPage'; +import './styles/Page.scss'; const App = () => ( -
-

React Uber eats

-
+ <> +
+
+
+ +
+
+
+ ); export default App; diff --git a/src/components/Error.js b/src/components/Error.js new file mode 100644 index 0000000..daa5ba4 --- /dev/null +++ b/src/components/Error.js @@ -0,0 +1,20 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import '../styles/Error.scss'; + +const Error = ({ message }) => ( +
+

{message}

+ Go to home +
+); + +Error.propTypes = { + message: PropTypes.string, +}; + +Error.defaultProps = { + message: 'Sorry, something went wrong', +}; + +export default Error; diff --git a/src/components/Footer.js b/src/components/Footer.js new file mode 100644 index 0000000..7c180a5 --- /dev/null +++ b/src/components/Footer.js @@ -0,0 +1,80 @@ +import React from 'react'; +import Select from './Select'; +import '../styles/Footer.scss'; + +const Footer = () => ( +
+
+
+
+
+ Uber Eats + + +
+ +
+ +
+ +
+ + + +
+ + Sign in +
+ + {(isMobileDeliveryVisible || isMobileSearchVisible) && ( +
+ {isMobileSearchVisible && ( + + )} + + {isMobileDeliveryVisible && ( + <> + + + + )} + + +
+ )} +
+
+ ); +}; + +export default Header; diff --git a/src/components/Input.js b/src/components/Input.js new file mode 100644 index 0000000..fc69950 --- /dev/null +++ b/src/components/Input.js @@ -0,0 +1,80 @@ +import React, { useState, createRef } from 'react'; +import cx from 'classnames'; +import PropTypes from 'prop-types'; +import '../styles/Input.scss'; + +const Input = ({ + iconUrl, value, onChange, placeholder, name, className, type, isSmall, label, +}) => { + const [isFocused, setFocused] = useState(false); + + const handleFocus = () => setFocused(true); + const handleBlur = () => setFocused(false); + const inputRef = createRef(); + + const inputWrapperClass = cx('control__input-wrapper', { + 'control__input-wrapper--focused': isFocused, + [className]: className, + }); + + const inputClass = cx({ + 'control__input--small': isSmall, + 'control__input--time': type === 'time', + }); + + return ( + // eslint-disable-next-line + + ); +}; + +Input.propTypes = { + name: PropTypes.string.isRequired, + value: PropTypes.string.isRequired, + onChange: PropTypes.func.isRequired, + className: PropTypes.string, + iconUrl: PropTypes.string, + placeholder: PropTypes.string, + isSmall: PropTypes.bool, + label: PropTypes.string, + type: PropTypes.string, +}; + +Input.defaultProps = { + type: '', + label: '', + className: '', + iconUrl: '', + placeholder: '', + isSmall: true, +}; + +export default Input; diff --git a/src/components/Loader.js b/src/components/Loader.js new file mode 100644 index 0000000..3d17287 --- /dev/null +++ b/src/components/Loader.js @@ -0,0 +1,24 @@ +import React from 'react'; +import '../styles/Loader.scss'; + +const Loader = () => ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +); + +export default Loader; diff --git a/src/components/RestaurantCard.js b/src/components/RestaurantCard.js new file mode 100644 index 0000000..93356aa --- /dev/null +++ b/src/components/RestaurantCard.js @@ -0,0 +1,29 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import '../styles/RestaurantCard.scss'; + +const RestaurantCard = ({ imageUrl, title, categories, etaRange, uuid }) => ( +
+ {title} +

{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: '', +}; + +export default RestaurantCard; diff --git a/src/components/RestaurantsListPage.js b/src/components/RestaurantsListPage.js new file mode 100644 index 0000000..c643ce4 --- /dev/null +++ b/src/components/RestaurantsListPage.js @@ -0,0 +1,72 @@ +import React, { useEffect } from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import RestaurantCard from './RestaurantCard'; +import { + loadData, getRestaurants, getIsLoading, getError, +} from '../store/store'; +import '../styles/RestaurantsList.scss'; +import Loader from './Loader'; +import Error from './Error'; + +const DEFAULT_ETA_RANGE = '20 - 30 min'; + +const RestaurantsListPage = ({ + restaurants, loadDataFromServer, isLoading, error, +}) => { + useEffect(() => { + loadDataFromServer(); + }, [loadDataFromServer]); + + if (isLoading) { + return ; + } + + if (error) { + return ; + } + + return ( +
+ {restaurants.map(( + { heroImageUrl, title, categories, etaRange, uuid }, + ) => ( + + ))} +
+ ); +}; + +const mapStateToProps = state => ({ + restaurants: getRestaurants(state), + isLoading: getIsLoading(state), + error: getError(state), +}); + +const mapMethodsToProps = { + loadDataFromServer: loadData, +}; + +RestaurantsListPage.propTypes = { + restaurants: PropTypes.arrayOf(PropTypes.object), + loadDataFromServer: PropTypes.func.isRequired, + error: PropTypes.string, + isLoading: PropTypes.bool, +}; + +RestaurantsListPage.defaultProps = { + restaurants: [], + error: null, + isLoading: false, +}; + +export default connect(mapStateToProps, mapMethodsToProps)(RestaurantsListPage); diff --git a/src/components/Select.js b/src/components/Select.js new file mode 100644 index 0000000..f0106e0 --- /dev/null +++ b/src/components/Select.js @@ -0,0 +1,51 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import '../styles/Select.scss'; + +const Select = ({ name, value, onSelect, options, iconUrl }) => ( +
+ + {iconUrl && ( + select icon + )} + + arrow down +
+); + +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, +}; + +Select.defaultProps = { + onSelect: () => {}, + options: [], + iconUrl: '', +}; + +export default Select; diff --git a/src/index.js b/src/index.js index b597a44..f1c1672 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,13 @@ import React from 'react'; +import { Provider } from 'react-redux'; import ReactDOM from 'react-dom'; +import store from './store/store'; import App from './App'; +import './styles/default.scss'; -ReactDOM.render(, document.getElementById('root')); +ReactDOM.render( + + + , + document.getElementById('root'), +); diff --git a/src/store/errorReducer.js b/src/store/errorReducer.js new file mode 100644 index 0000000..ca021c5 --- /dev/null +++ b/src/store/errorReducer.js @@ -0,0 +1,17 @@ +const LOAD_ERROR = 'LOAD_ERROR'; + +export const setLoadError = error => ({ + type: LOAD_ERROR, + error, +}); + +const errorReducer = (error = null, action) => { + switch (action.type) { + case LOAD_ERROR: + return action.error; + default: + return error; + } +}; + +export default errorReducer; diff --git a/src/store/loadDataFromServer.js b/src/store/loadDataFromServer.js new file mode 100644 index 0000000..971b93a --- /dev/null +++ b/src/store/loadDataFromServer.js @@ -0,0 +1,7 @@ +const URL = 'https://mate-uber-eats-api.herokuapp.com/api/v1/restaurants'; + +export const loadDataFromServer = async() => { + const response = await fetch(URL); + + return response.json(); +}; diff --git a/src/store/loadingReducer.js b/src/store/loadingReducer.js new file mode 100644 index 0000000..73680a0 --- /dev/null +++ b/src/store/loadingReducer.js @@ -0,0 +1,23 @@ +const START_LOADING = 'START_LOADING'; +const STOP_LOADING = 'STOP_LOADING'; + +export const startLoading = () => ({ + type: START_LOADING, +}); + +export const stopLoading = () => ({ + type: STOP_LOADING, +}); + +const loadingReducer = (isLoading = false, action) => { + switch (action.type) { + case START_LOADING: + return true; + case STOP_LOADING: + return false; + default: + return isLoading; + } +}; + +export default loadingReducer; diff --git a/src/store/restaurantsReducer.js b/src/store/restaurantsReducer.js new file mode 100644 index 0000000..d50296f --- /dev/null +++ b/src/store/restaurantsReducer.js @@ -0,0 +1,17 @@ +const SAVE_RESTAURANTS = 'SAVE_RESTAURANTS'; + +export const saveRestaurants = restaurants => ({ + type: SAVE_RESTAURANTS, + restaurants, +}); + +const restaurantsReducer = (restaurants = null, action) => { + switch (action.type) { + case SAVE_RESTAURANTS: + return action.restaurants; + default: + return restaurants; + } +}; + +export default restaurantsReducer; diff --git a/src/store/store.js b/src/store/store.js new file mode 100644 index 0000000..46974f5 --- /dev/null +++ b/src/store/store.js @@ -0,0 +1,43 @@ +import { createStore, applyMiddleware, combineReducers } from 'redux'; +import thunk from 'redux-thunk'; +import { loadDataFromServer } from './loadDataFromServer'; +import restaurantsReducer, { saveRestaurants } from './restaurantsReducer'; +import loadingReducer, { startLoading, stopLoading } from './loadingReducer'; +import errorReducer, { setLoadError } from './errorReducer'; + +export const loadData = () => async(dispatch) => { + dispatch(startLoading()); + + try { + const restaurants = await loadDataFromServer(); + + dispatch(saveRestaurants(restaurants.data)); + } catch (e) { + dispatch(setLoadError(e.message)); + } + + dispatch(stopLoading()); +}; + +const rootReducer = combineReducers({ + restaurants: restaurantsReducer, + isLoading: loadingReducer, + error: errorReducer, +}); + +export const getRestaurants = (state) => { + if (!state.restaurants) { + return []; + } + + const { feedItems, storesMap } = state.restaurants; + + return feedItems.map(({ uuid }) => storesMap[uuid]); +}; + +export const getIsLoading = state => state.isLoading; +export const getError = state => state.error; + +const store = createStore(rootReducer, applyMiddleware(thunk)); + +export default store; diff --git a/src/styles/Error.scss b/src/styles/Error.scss new file mode 100644 index 0000000..955a701 --- /dev/null +++ b/src/styles/Error.scss @@ -0,0 +1,20 @@ +@import 'extends'; + +.error { + @extend %absolute-center; + flex-flow: column nowrap; + + &__text { + font-size: 30px; + font-weight: 500; + } + + &__link { + font-size: 24px; + + &:hover { + text-decoration: underline; + color: #5eb707; + } + } + } diff --git a/src/styles/Footer.scss b/src/styles/Footer.scss new file mode 100644 index 0000000..9be3409 --- /dev/null +++ b/src/styles/Footer.scss @@ -0,0 +1,143 @@ +@import "extends"; +@import "variables"; + +.footer { + background-color: #262626; + padding: 64px 0; + + @media (max-width: $breakpoint-tablet) { + padding: 40px 0; + } + + &__main { + display: flex; + flex-flow: column nowrap; + justify-content: space-between; + } + + &__top-part { + border-bottom: 1px solid #c4c4c4; + margin-bottom: 40px; + padding-bottom: 40px; + } + + &__logo { + margin-right: 40px; + + @media (max-width: $breakpoint-tablet) { + margin-bottom: 40px; + } + } + + &__logo-container { + @extend %flex-row; + margin-bottom: 40px; + + @media (max-width: $breakpoint-tablet) { + flex-direction: column; + align-items: flex-start; + } + } + + &__top-part, + &__bottom-part { + @extend %flex-row; + align-items: flex-start; + + @media (max-width: $breakpoint-phone-lg) { + flex-direction: column; + } + } + + &__main, + &__copyright { + flex-basis: 45vw; + padding-right: 40px; + + @media (max-width: $breakpoint-tablet) { + margin-left: 0; + flex-basis: initial; + } + } + + &__copyright { + color: white; + font-size: 14px; + + @media (max-width: $breakpoint-phone-lg) { + order: 1; + } + } + + &__mobile-app { + display: inline-block; + + @media (max-width: $breakpoint-tablet) { + display: block; + margin-bottom: 20px; + } + + &:first-of-type { + margin-right: 20px; + + @media (max-width: $breakpoint-tablet) { + margin-right: 0; + margin-bottom: 20px; + } + } + } + + &__top-links { + &:last-of-type { + margin-left: 10vw; + + @media (max-width: $breakpoint-phone-lg) { + margin-left: 0; + } + } + } + + &__bottom-links { + @extend %flex-row; + margin-bottom: 40px; + + .links__link:not(:last-of-type) { + margin-right: 40px; + } + + @media (max-width: $breakpoint-phone-lg) { + flex-direction: column; + align-items: flex-start; + margin-top: 20px; + order: 1; + } + } + + &__misc { + @media (max-width: $breakpoint-phone-lg) { + display: flex; + flex-direction: column; + margin-top: 20px; + } + } +} + +.links { + &__link { + display: block; + margin-bottom: 16px; + font-weight: 500; + color: white; + white-space: nowrap; + + &:hover { + text-decoration: underline; + } + } +} + +.social { + &__link { + margin-right: 20px; + } +} diff --git a/src/styles/Header.scss b/src/styles/Header.scss new file mode 100644 index 0000000..c467e06 --- /dev/null +++ b/src/styles/Header.scss @@ -0,0 +1,97 @@ +@import "extends"; +@import "variables"; + +.header { + position: sticky; + top: 0; + z-index: 1; + background-color: white; + padding: 16px 0; + + &__inner { + @extend %flex-row; + } + + &__logo { + height: 24px; + width: auto; + + @media (max-width: $breakpoint-phone) { + height: 14px; + } + } + + &__delivery-info { + @extend %flex-row; + margin-left: 7vw; + + @media (max-width: $breakpoint-tablet) { + display: none; + } + } + + &__search { + margin-left: auto; + + @media (max-width: $breakpoint-tablet) { + display: none; + } + } + + &__link { + margin-left: 40px; + padding: 12px 0; + color: #1f1f1f; + white-space: nowrap; + + &:hover { + text-decoration: underline; + } + } + + &__toggle-buttons { + display: none; + margin-left: auto; + + @media (max-width: $breakpoint-tablet) { + display: flex; + } + } + + &__toggle-btn { + display: flex; + align-items: center; + border: 1px solid transparent; + padding: 11px 5px; + cursor: pointer; + + &:not(:last-of-type) { + margin-right: 20px; + } + + &:focus { + border-color: #e0e0e0; + } + + &--place { + width: 16px; + height: 16px; + } + } +} + +.mobile-controls { + display: none; + position: relative; + padding-top: 30px; + + @media (max-width: $breakpoint-tablet) { + display: flex; + } + + &__close { + position: absolute; + top: 10px; + right: 0; + } +} diff --git a/src/styles/Input.scss b/src/styles/Input.scss new file mode 100644 index 0000000..fecbf82 --- /dev/null +++ b/src/styles/Input.scss @@ -0,0 +1,47 @@ +@import "extends"; +@import "variables"; + +.control { + + &__input-wrapper { + @extend %flex-row; + padding: 11px 16px; + border: 1px solid $border-color; + transition: border-color 300ms; + + &--focused { + border-color: darken($border-color, 20); + } + } + + &__icon { + width: 14px; + height: 14px; + margin-right: 10px; + } + + &__input { + max-height: 24px; + line-height: 24px; + + &--time { + max-width: 75px; + } + + &--small { + @media (max-width: $breakpoint-tablet-lg) { + max-width: 75px; + } + } + } + + &__label { + font-size: 14px; + margin-bottom: 8px; + color: #626262; + } + + & + & { + margin-left: 20px; + } +} diff --git a/src/styles/Loader.scss b/src/styles/Loader.scss new file mode 100644 index 0000000..1b50b0a --- /dev/null +++ b/src/styles/Loader.scss @@ -0,0 +1,102 @@ +@import 'extends'; + +.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: #5eb707; + 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/styles/Page.scss b/src/styles/Page.scss new file mode 100644 index 0000000..4a63ea4 --- /dev/null +++ b/src/styles/Page.scss @@ -0,0 +1,6 @@ +.page { + position: relative; + padding-top: 40px; + min-height: 100vh; + margin-bottom: 40px; +} diff --git a/src/styles/RestaurantCard.scss b/src/styles/RestaurantCard.scss new file mode 100644 index 0000000..a163d16 --- /dev/null +++ b/src/styles/RestaurantCard.scss @@ -0,0 +1,37 @@ +@import "variables"; + +.restaurant-card { + --height: 367px; + + height: var(--height); + width: 100%; + cursor: pointer; + + @media (max-width: $breakpoint-phone) { + --height: 330px; + } + + &__img { + height: calc(var(--height) * 0.7); + width: 100%; + object-fit: cover; + } + + &__title { + margin: 12px 0 4px; + } + + &__categories { + font-size: 14px; + color: #757575; + margin-bottom: 4px; + } + + &__eta { + display: inline-block; + font-size: 14px; + font-weight: 700; + padding: 2px 8px; + background-color: #f5f5f5; + } +} diff --git a/src/styles/RestaurantsList.scss b/src/styles/RestaurantsList.scss new file mode 100644 index 0000000..0339e02 --- /dev/null +++ b/src/styles/RestaurantsList.scss @@ -0,0 +1,17 @@ +@import "variables"; + +.restaurants-list { + --card-width: 348px; + display: grid; + grid-template-columns: repeat(auto-fill, minmax(var(--card-width), 1fr)); + gap: 40px 20px; + + @media (max-width: $breakpoint-tablet-lg) { + --card-width: 340px; + row-gap: 20px; + } + + @media (max-width: $breakpoint-phone) { + --card-width: 280px; + } +} diff --git a/src/styles/Select.scss b/src/styles/Select.scss new file mode 100644 index 0000000..d51c967 --- /dev/null +++ b/src/styles/Select.scss @@ -0,0 +1,33 @@ +.select { + position: relative; + + &__input { + -moz-appearance: none; + -webkit-appearance: none; + + background-color: transparent; + border: 1px solid #979797; + border-radius: 0; + padding: 10px 40px; + color: white; + cursor: pointer; + } + + &__icon { + position: absolute; + top: 50%; + transform: translateY(-50%); + left: 10px; + width: 20px; + height: 20px; + } + + &__arrow { + position: absolute; + top: 50%; + right: 10px; + width: 14px; + height: 8px; + transform: translateY(-50%); + } +} diff --git a/src/styles/default.scss b/src/styles/default.scss new file mode 100644 index 0000000..e2b1e8f --- /dev/null +++ b/src/styles/default.scss @@ -0,0 +1,47 @@ +@import "variables"; + +*, +::before, +::after { + box-sizing: border-box; +} + +body, +input, +a, +select, +button { + font-family: Roboto, sans-serif; + font-size: 16px; + line-height: 1.5; +} + +input { + border: none; + outline: none; + background-color: transparent; +} + +a { + text-decoration: none; +} + +button { + border: none; + background: none; + outline: none; +} + +.content { + max-width: 1160px; + padding: 0 34px; + margin: 0 auto; + + @media (max-width: $breakpoint-phone) { + padding: 0 20px; + } + + @media (min-width: $breakpoint-large) { + max-width: 1520px; + } +} diff --git a/src/styles/extends.scss b/src/styles/extends.scss new file mode 100644 index 0000000..15deefc --- /dev/null +++ b/src/styles/extends.scss @@ -0,0 +1,16 @@ +%flex-row { + display: flex; + flex-flow: row nowrap; + align-items: center; +} + +%absolute-center { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; +} diff --git a/src/styles/variables.scss b/src/styles/variables.scss new file mode 100644 index 0000000..0ea9a9c --- /dev/null +++ b/src/styles/variables.scss @@ -0,0 +1,7 @@ +$breakpoint-large: 1281px; +$breakpoint-tablet-lg: 976px; +$breakpoint-tablet: 768px; +$breakpoint-phone-lg: 620px; +$breakpoint-phone: 550px; +$border-color: #e0e0e0; +