├── Procfile
├── public
├── favicon.ico
├── logo192.png
├── logo512.png
├── robots.txt
├── manifest.json
├── index.html
└── db.json
├── src
├── pages
│ ├── index.js
│ ├── Home.jsx
│ └── Cart.jsx
├── assets
│ └── img
│ │ ├── empty-cart.png
│ │ ├── grey-arrow-left.svg
│ │ ├── arrow-top.svg
│ │ ├── plus.svg
│ │ ├── trash.svg
│ │ ├── cart.svg
│ │ └── pizza-logo.svg
├── scss
│ ├── fonts
│ │ ├── ProximaNova-Bold.eot
│ │ ├── ProximaNova-Bold.ttf
│ │ ├── ProximaNova-Black.eot
│ │ ├── ProximaNova-Black.ttf
│ │ ├── ProximaNova-Black.woff
│ │ ├── ProximaNova-Bold.woff
│ │ ├── ProximaNova-Extrabld.eot
│ │ ├── ProximaNova-Extrabld.ttf
│ │ ├── ProximaNova-Extrabld.woff
│ │ ├── ProximaNova-Regular.eot
│ │ ├── ProximaNova-Regular.ttf
│ │ ├── ProximaNova-Regular.woff
│ │ ├── ProximaNova-Semibold.eot
│ │ ├── ProximaNova-Semibold.ttf
│ │ └── ProximaNova-Semibold.woff
│ ├── components
│ │ ├── _all.scss
│ │ ├── _header.scss
│ │ ├── _categories.scss
│ │ ├── _sort.scss
│ │ ├── _pizza-block.scss
│ │ └── _button.scss
│ ├── _variables.scss
│ ├── libs
│ │ └── _normalize.scss
│ ├── _fonts.scss
│ └── app.scss
├── redux
│ ├── actions
│ │ ├── filters.js
│ │ ├── cart.js
│ │ └── pizzas.js
│ ├── reducers
│ │ ├── index.js
│ │ ├── filters.js
│ │ ├── pizzas.js
│ │ └── cart.js
│ └── store.js
├── components
│ ├── index.js
│ ├── Button.jsx
│ ├── PizzaBlock
│ │ ├── LoadingBlock.jsx
│ │ └── index.jsx
│ ├── Categories.jsx
│ ├── SortPopup.jsx
│ ├── Header.jsx
│ └── CartItem.jsx
├── index.js
└── App.js
├── config
├── jest
│ ├── cssTransform.js
│ └── fileTransform.js
├── pnpTs.js
├── getHttpsConfig.js
├── paths.js
├── env.js
├── modules.js
├── webpackDevServer.config.js
└── webpack.config.js
├── .gitignore
├── server.js
├── README.md
├── scripts
├── test.js
├── start.js
└── build.js
└── package.json
/Procfile:
--------------------------------------------------------------------------------
1 | web: node server.js
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Archakov06/react-pizza/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Archakov06/react-pizza/HEAD/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Archakov06/react-pizza/HEAD/public/logo512.png
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/pages/index.js:
--------------------------------------------------------------------------------
1 | export { default as Home } from './Home';
2 | export { default as Cart } from './Cart';
3 |
--------------------------------------------------------------------------------
/src/assets/img/empty-cart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Archakov06/react-pizza/HEAD/src/assets/img/empty-cart.png
--------------------------------------------------------------------------------
/src/scss/fonts/ProximaNova-Bold.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Archakov06/react-pizza/HEAD/src/scss/fonts/ProximaNova-Bold.eot
--------------------------------------------------------------------------------
/src/scss/fonts/ProximaNova-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Archakov06/react-pizza/HEAD/src/scss/fonts/ProximaNova-Bold.ttf
--------------------------------------------------------------------------------
/src/scss/fonts/ProximaNova-Black.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Archakov06/react-pizza/HEAD/src/scss/fonts/ProximaNova-Black.eot
--------------------------------------------------------------------------------
/src/scss/fonts/ProximaNova-Black.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Archakov06/react-pizza/HEAD/src/scss/fonts/ProximaNova-Black.ttf
--------------------------------------------------------------------------------
/src/scss/fonts/ProximaNova-Black.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Archakov06/react-pizza/HEAD/src/scss/fonts/ProximaNova-Black.woff
--------------------------------------------------------------------------------
/src/scss/fonts/ProximaNova-Bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Archakov06/react-pizza/HEAD/src/scss/fonts/ProximaNova-Bold.woff
--------------------------------------------------------------------------------
/src/scss/fonts/ProximaNova-Extrabld.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Archakov06/react-pizza/HEAD/src/scss/fonts/ProximaNova-Extrabld.eot
--------------------------------------------------------------------------------
/src/scss/fonts/ProximaNova-Extrabld.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Archakov06/react-pizza/HEAD/src/scss/fonts/ProximaNova-Extrabld.ttf
--------------------------------------------------------------------------------
/src/scss/fonts/ProximaNova-Extrabld.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Archakov06/react-pizza/HEAD/src/scss/fonts/ProximaNova-Extrabld.woff
--------------------------------------------------------------------------------
/src/scss/fonts/ProximaNova-Regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Archakov06/react-pizza/HEAD/src/scss/fonts/ProximaNova-Regular.eot
--------------------------------------------------------------------------------
/src/scss/fonts/ProximaNova-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Archakov06/react-pizza/HEAD/src/scss/fonts/ProximaNova-Regular.ttf
--------------------------------------------------------------------------------
/src/scss/fonts/ProximaNova-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Archakov06/react-pizza/HEAD/src/scss/fonts/ProximaNova-Regular.woff
--------------------------------------------------------------------------------
/src/scss/fonts/ProximaNova-Semibold.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Archakov06/react-pizza/HEAD/src/scss/fonts/ProximaNova-Semibold.eot
--------------------------------------------------------------------------------
/src/scss/fonts/ProximaNova-Semibold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Archakov06/react-pizza/HEAD/src/scss/fonts/ProximaNova-Semibold.ttf
--------------------------------------------------------------------------------
/src/scss/fonts/ProximaNova-Semibold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Archakov06/react-pizza/HEAD/src/scss/fonts/ProximaNova-Semibold.woff
--------------------------------------------------------------------------------
/src/scss/components/_all.scss:
--------------------------------------------------------------------------------
1 | @import './header';
2 | @import './button';
3 | @import './categories';
4 | @import './sort';
5 | @import './pizza-block';
6 |
--------------------------------------------------------------------------------
/src/scss/_variables.scss:
--------------------------------------------------------------------------------
1 | $black: #232323;
2 | $background: #ffdf8c;
3 | $gray-line: #f6f6f6;
4 | $orange: #fe5f1e;
5 |
6 | $container-width: 90%;
7 |
8 | $duration: 0.15s;
9 |
--------------------------------------------------------------------------------
/src/assets/img/grey-arrow-left.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/redux/actions/filters.js:
--------------------------------------------------------------------------------
1 | export const setSortBy = ({ type, order }) => ({
2 | type: 'SET_SORT_BY',
3 | payload: { type, order },
4 | });
5 |
6 | export const setCategory = (catIndex) => ({
7 | type: 'SET_CATEGORY',
8 | payload: catIndex,
9 | });
10 |
--------------------------------------------------------------------------------
/src/redux/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 |
3 | import filters from './filters';
4 | import pizzas from './pizzas';
5 | import cart from './cart';
6 |
7 | const rootReducer = combineReducers({
8 | filters,
9 | pizzas,
10 | cart,
11 | });
12 |
13 | export default rootReducer;
14 |
--------------------------------------------------------------------------------
/src/redux/store.js:
--------------------------------------------------------------------------------
1 | import { createStore, compose, applyMiddleware } from 'redux';
2 | import thunk from 'redux-thunk';
3 |
4 | import rootReducer from './reducers';
5 |
6 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
7 |
8 | const store = createStore(rootReducer, composeEnhancers(applyMiddleware(thunk)));
9 |
10 | export default store;
11 |
--------------------------------------------------------------------------------
/config/jest/cssTransform.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // This is a custom Jest transformer turning style imports into empty objects.
4 | // http://facebook.github.io/jest/docs/en/webpack.html
5 |
6 | module.exports = {
7 | process() {
8 | return 'module.exports = {};';
9 | },
10 | getCacheKey() {
11 | // The output is always the same.
12 | return 'cssTransform';
13 | },
14 | };
15 |
--------------------------------------------------------------------------------
/src/components/index.js:
--------------------------------------------------------------------------------
1 | export { default as Button } from './Button';
2 | export { default as Header } from './Header';
3 | export { default as Categories } from './Categories';
4 | export { default as SortPopup } from './SortPopup';
5 | export { default as CartItem } from './CartItem';
6 | export { default as PizzaBlock } from './PizzaBlock';
7 | export { default as PizzaLoadingBlock } from './PizzaBlock/LoadingBlock';
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | const jsonServer = require('json-server');
2 | const server = jsonServer.create();
3 | const router = jsonServer.router('./public/db.json');
4 | const middlewares = jsonServer.defaults({
5 | static: './build',
6 | });
7 |
8 | const PORT = process.env.PORT || 3001;
9 |
10 | server.use(middlewares);
11 | server.use(router);
12 |
13 | server.listen(PORT, () => {
14 | console.log('Server is running');
15 | });
16 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Интернет-магазин по заказу пиццы - **React Pizza**
2 |
3 | - [Плейлист с полным курсом на YouTube](https://www.youtube.com/watch?v=bziVFvq8cLQ&list=PL0FGkDGJQjJFMRmP7wZ771m1Nx-m2_qXq)
4 | - [Как задеплоить React Pizza на бесплатный хостинг](https://www.youtube.com/watch?v=-pJN9faoa8E&t=1951s)
5 |
6 | **Stack:**
7 |
8 | - ReactJS + хуки
9 | - React Router
10 | - Redux
11 | - Redux thunk
12 | - json-server
13 | - Axios
14 | - classnames
15 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { BrowserRouter as Router } from 'react-router-dom';
4 | import { Provider } from 'react-redux';
5 |
6 | import store from './redux/store';
7 |
8 | import './scss/app.scss';
9 | import App from './App';
10 |
11 | ReactDOM.render(
12 |
13 |
14 |
15 |
16 | ,
17 | document.getElementById('root'),
18 | );
19 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Header } from './components';
4 | import { Home, Cart } from './pages';
5 | import { Route } from 'react-router-dom';
6 |
7 | function App() {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | );
17 | }
18 |
19 | export default App;
20 |
--------------------------------------------------------------------------------
/src/assets/img/arrow-top.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/components/Button.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import classNames from 'classnames';
4 |
5 | const Button = ({ onClick, className, outline, children }) => {
6 | return (
7 |
14 | );
15 | };
16 |
17 | Button.propTypes = {
18 | onClick: PropTypes.func,
19 | };
20 |
21 | export default Button;
22 |
--------------------------------------------------------------------------------
/src/redux/actions/cart.js:
--------------------------------------------------------------------------------
1 | export const addPizzaToCart = (pizzaObj) => ({
2 | type: 'ADD_PIZZA_CART',
3 | payload: pizzaObj,
4 | });
5 |
6 | export const clearCart = () => ({
7 | type: 'CLEAR_CART',
8 | });
9 |
10 | export const removeCartItem = (id) => ({
11 | type: 'REMOVE_CART_ITEM',
12 | payload: id,
13 | });
14 |
15 | export const plusCartItem = (id) => ({
16 | type: 'PLUS_CART_ITEM',
17 | payload: id,
18 | });
19 |
20 | export const minusCartItem = (id) => ({
21 | type: 'MINUS_CART_ITEM',
22 | payload: id,
23 | });
24 |
--------------------------------------------------------------------------------
/src/redux/reducers/filters.js:
--------------------------------------------------------------------------------
1 | const initialState = {
2 | category: null,
3 | sortBy: {
4 | type: 'popular',
5 | order: 'desc',
6 | },
7 | };
8 |
9 | const filters = (state = initialState, action) => {
10 | if (action.type === 'SET_SORT_BY') {
11 | return {
12 | ...state,
13 | sortBy: action.payload,
14 | };
15 | }
16 | if (action.type === 'SET_CATEGORY') {
17 | return {
18 | ...state,
19 | category: action.payload,
20 | };
21 | }
22 | return state;
23 | };
24 |
25 | export default filters;
26 |
--------------------------------------------------------------------------------
/src/redux/reducers/pizzas.js:
--------------------------------------------------------------------------------
1 | const initialState = {
2 | items: [],
3 | isLoaded: false,
4 | };
5 |
6 | const pizzas = (state = initialState, action) => {
7 | switch (action.type) {
8 | case 'SET_PIZZAS':
9 | return {
10 | ...state,
11 | items: action.payload,
12 | isLoaded: true,
13 | };
14 |
15 | case 'SET_LOADED':
16 | return {
17 | ...state,
18 | isLoaded: action.payload,
19 | };
20 |
21 | default:
22 | return state;
23 | }
24 | };
25 |
26 | export default pizzas;
27 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/src/redux/actions/pizzas.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | export const setLoaded = (payload) => ({
4 | type: 'SET_LOADED',
5 | payload,
6 | });
7 |
8 | export const fetchPizzas = (sortBy, category) => (dispatch) => {
9 | dispatch({
10 | type: 'SET_LOADED',
11 | payload: false,
12 | });
13 |
14 | axios
15 | .get(
16 | `/pizzas?${category !== null ? `category=${category}` : ''}&_sort=${sortBy.type}&_order=${
17 | sortBy.order
18 | }`,
19 | )
20 | .then(({ data }) => {
21 | dispatch(setPizzas(data));
22 | });
23 | };
24 |
25 | export const setPizzas = (items) => ({
26 | type: 'SET_PIZZAS',
27 | payload: items,
28 | });
29 |
--------------------------------------------------------------------------------
/src/assets/img/plus.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/config/pnpTs.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const { resolveModuleName } = require('ts-pnp');
4 |
5 | exports.resolveModuleName = (
6 | typescript,
7 | moduleName,
8 | containingFile,
9 | compilerOptions,
10 | resolutionHost
11 | ) => {
12 | return resolveModuleName(
13 | moduleName,
14 | containingFile,
15 | compilerOptions,
16 | resolutionHost,
17 | typescript.resolveModuleName
18 | );
19 | };
20 |
21 | exports.resolveTypeReferenceDirective = (
22 | typescript,
23 | moduleName,
24 | containingFile,
25 | compilerOptions,
26 | resolutionHost
27 | ) => {
28 | return resolveModuleName(
29 | moduleName,
30 | containingFile,
31 | compilerOptions,
32 | resolutionHost,
33 | typescript.resolveTypeReferenceDirective
34 | );
35 | };
36 |
--------------------------------------------------------------------------------
/src/scss/components/_header.scss:
--------------------------------------------------------------------------------
1 | @import 'variables';
2 |
3 | .header {
4 | border-bottom: 1px solid $gray-line;
5 | padding: 40px 0;
6 |
7 | .container {
8 | display: flex;
9 | align-items: center;
10 | justify-content: space-between;
11 | @media (max-width: 768px) {
12 | flex-direction: column;
13 | }
14 | }
15 |
16 | @media (max-width: 768px) {
17 | &__cart {
18 | margin-top: 30px;
19 | }
20 | }
21 |
22 | &__logo {
23 | display: flex;
24 |
25 | img {
26 | margin-right: 15px;
27 | }
28 |
29 | h1 {
30 | color: #181818;
31 | font-size: 24px;
32 | letter-spacing: 1%;
33 | text-transform: uppercase;
34 | font-weight: 800;
35 | }
36 |
37 | p {
38 | color: #7b7b7b;
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/components/PizzaBlock/LoadingBlock.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ContentLoader from 'react-content-loader';
3 |
4 | function LoadingBlock() {
5 | return (
6 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | );
21 | }
22 |
23 | export default LoadingBlock;
24 |
--------------------------------------------------------------------------------
/src/scss/components/_categories.scss:
--------------------------------------------------------------------------------
1 | @import 'variables';
2 |
3 | .categories {
4 | flex: 1;
5 | max-width: 80%;
6 | overflow: auto;
7 |
8 | @media (max-width: 768px) {
9 | max-width: 100%;
10 | margin-bottom: 30px;
11 | }
12 |
13 | ul {
14 | display: flex;
15 |
16 | li {
17 | background-color: #f9f9f9;
18 | padding: 13px 30px;
19 | border-radius: 30px;
20 | margin-right: 10px;
21 | font-weight: bold;
22 | cursor: pointer;
23 | transition: background-color 0.1s ease-in-out;
24 | @include noselect();
25 |
26 | &:hover {
27 | background-color: darken(#f9f9f9, 2%);
28 | }
29 |
30 | &:active {
31 | background-color: darken(#f9f9f9, 5%);
32 | }
33 |
34 | &.active {
35 | background-color: #282828;
36 | color: #fff;
37 | }
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/assets/img/trash.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/assets/img/cart.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/components/Categories.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const Categories = React.memo(function Categories({ activeCategory, items, onClickCategory }) {
5 | return (
6 |
7 |
8 | - onClickCategory(null)}>
11 | Все
12 |
13 | {items &&
14 | items.map((name, index) => (
15 | - onClickCategory(index)}
18 | key={`${name}_${index}`}>
19 | {name}
20 |
21 | ))}
22 |
23 |
24 | );
25 | });
26 |
27 | Categories.propTypes = {
28 | // activeCategory: PropTypes.oneOf([PropTypes.number, null]),
29 | items: PropTypes.arrayOf(PropTypes.string).isRequired,
30 | onClickCategory: PropTypes.func.isRequired,
31 | };
32 |
33 | Categories.defaultProps = { activeCategory: null, items: [] };
34 |
35 | export default Categories;
36 |
--------------------------------------------------------------------------------
/src/scss/libs/_normalize.scss:
--------------------------------------------------------------------------------
1 | * {
2 | padding: 0;
3 | margin: 0;
4 | list-style: none;
5 | outline: none;
6 | font-family: 'Proxima Nova', Roboto, system-ui, Tahoma, sans-serif;
7 | box-sizing: border-box;
8 | }
9 |
10 | html {
11 | -ms-text-size-adjust: 100%;
12 | -webkit-text-size-adjust: 100%;
13 | }
14 |
15 | body {
16 | -moz-osx-font-smoothing: grayscale;
17 | -webkit-font-smoothing: antialiased;
18 | color: $black;
19 | }
20 |
21 | a,
22 | span,
23 | p,
24 | b,
25 | h1,
26 | h2,
27 | h3,
28 | h4,
29 | h5 {
30 | color: $black;
31 | }
32 |
33 | h1 {
34 | font-size: 48px;
35 | }
36 |
37 | h2 {
38 | font-weight: 600;
39 | font-size: 28px;
40 | line-height: 30px;
41 | }
42 |
43 | a {
44 | text-decoration: none;
45 | }
46 |
47 | @mixin noselect {
48 | -webkit-touch-callout: none; /* iOS Safari */
49 | -webkit-user-select: none; /* Safari */
50 | -khtml-user-select: none; /* Konqueror HTML */
51 | -moz-user-select: none; /* Old versions of Firefox */
52 | -ms-user-select: none; /* Internet Explorer/Edge */
53 | user-select: none; /* Non-prefixed version, currently
54 | supported by Chrome, Opera and Firefox */
55 | }
56 |
--------------------------------------------------------------------------------
/src/scss/components/_sort.scss:
--------------------------------------------------------------------------------
1 | @import 'variables';
2 |
3 | .sort {
4 | margin-left: 20px;
5 | position: relative;
6 | &__label {
7 | display: flex;
8 | align-items: center;
9 |
10 | svg {
11 | margin-right: 8px;
12 | transform: rotate(180deg);
13 |
14 | &.rotated {
15 | transform: rotate(0);
16 | }
17 | }
18 |
19 | b {
20 | margin-right: 8px;
21 | }
22 |
23 | span {
24 | color: $orange;
25 | border-bottom: 1px dashed $orange;
26 | cursor: pointer;
27 | }
28 | }
29 |
30 | &__popup {
31 | position: absolute;
32 | right: 0;
33 | margin-top: 15px;
34 | background: #ffffff;
35 | box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.09);
36 | border-radius: 10px;
37 | overflow: hidden;
38 | padding: 10px 0;
39 | width: 160px;
40 |
41 | ul {
42 | overflow: hidden;
43 | li {
44 | padding: 12px 20px;
45 | cursor: pointer;
46 |
47 | &.active,
48 | &:hover {
49 | background: rgba(254, 95, 30, 0.05);
50 | }
51 |
52 | &.active {
53 | font-weight: bold;
54 | color: $orange;
55 | }
56 | }
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/config/jest/fileTransform.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 | const camelcase = require('camelcase');
5 |
6 | // This is a custom Jest transformer turning file imports into filenames.
7 | // http://facebook.github.io/jest/docs/en/webpack.html
8 |
9 | module.exports = {
10 | process(src, filename) {
11 | const assetFilename = JSON.stringify(path.basename(filename));
12 |
13 | if (filename.match(/\.svg$/)) {
14 | // Based on how SVGR generates a component name:
15 | // https://github.com/smooth-code/svgr/blob/01b194cf967347d43d4cbe6b434404731b87cf27/packages/core/src/state.js#L6
16 | const pascalCaseFilename = camelcase(path.parse(filename).name, {
17 | pascalCase: true,
18 | });
19 | const componentName = `Svg${pascalCaseFilename}`;
20 | return `const React = require('react');
21 | module.exports = {
22 | __esModule: true,
23 | default: ${assetFilename},
24 | ReactComponent: React.forwardRef(function ${componentName}(props, ref) {
25 | return {
26 | $$typeof: Symbol.for('react.element'),
27 | type: 'svg',
28 | ref: ref,
29 | key: null,
30 | props: Object.assign({}, props, {
31 | children: ${assetFilename}
32 | })
33 | };
34 | }),
35 | };`;
36 | }
37 |
38 | return `module.exports = ${assetFilename};`;
39 | },
40 | };
41 |
--------------------------------------------------------------------------------
/scripts/test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // Do this as the first thing so that any code reading it knows the right env.
4 | process.env.BABEL_ENV = 'test';
5 | process.env.NODE_ENV = 'test';
6 | process.env.PUBLIC_URL = '';
7 |
8 | // Makes the script crash on unhandled rejections instead of silently
9 | // ignoring them. In the future, promise rejections that are not handled will
10 | // terminate the Node.js process with a non-zero exit code.
11 | process.on('unhandledRejection', err => {
12 | throw err;
13 | });
14 |
15 | // Ensure environment variables are read.
16 | require('../config/env');
17 |
18 |
19 | const jest = require('jest');
20 | const execSync = require('child_process').execSync;
21 | let argv = process.argv.slice(2);
22 |
23 | function isInGitRepository() {
24 | try {
25 | execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' });
26 | return true;
27 | } catch (e) {
28 | return false;
29 | }
30 | }
31 |
32 | function isInMercurialRepository() {
33 | try {
34 | execSync('hg --cwd . root', { stdio: 'ignore' });
35 | return true;
36 | } catch (e) {
37 | return false;
38 | }
39 | }
40 |
41 | // Watch unless on CI or explicitly running all tests
42 | if (
43 | !process.env.CI &&
44 | argv.indexOf('--watchAll') === -1 &&
45 | argv.indexOf('--watchAll=false') === -1
46 | ) {
47 | // https://github.com/facebook/create-react-app/issues/5210
48 | const hasSourceControl = isInGitRepository() || isInMercurialRepository();
49 | argv.push(hasSourceControl ? '--watch' : '--watchAll');
50 | }
51 |
52 |
53 | jest.run(argv);
54 |
--------------------------------------------------------------------------------
/src/scss/components/_pizza-block.scss:
--------------------------------------------------------------------------------
1 | @import 'variables';
2 |
3 | .pizza-block {
4 | width: 100%;
5 | text-align: center;
6 | margin-bottom: 65px;
7 |
8 | @media (max-width: 1090px) {
9 | margin-bottom: 35px;
10 | }
11 |
12 | @media (max-width: 768px) {
13 | &:not(:last-of-type) {
14 | border-bottom: 1px solid #efefef;
15 | margin-bottom: 25px;
16 | padding-bottom: 45px;
17 | }
18 | }
19 |
20 | &__image {
21 | width: 260px;
22 | }
23 |
24 | &__title {
25 | font-size: 20px;
26 | font-weight: 900;
27 | letter-spacing: 1%;
28 | margin-bottom: 20px;
29 | }
30 |
31 | &__selector {
32 | display: flex;
33 | background-color: #f3f3f3;
34 | border-radius: 10px;
35 | flex-direction: column;
36 | padding: 6px;
37 |
38 | ul {
39 | display: flex;
40 | flex: 1;
41 |
42 | &:first-of-type {
43 | margin-bottom: 6px;
44 | }
45 |
46 | li {
47 | padding: 8px;
48 | flex: 1;
49 | cursor: pointer;
50 | font-weight: 600;
51 | font-size: 14px;
52 | @include noselect();
53 | &.active {
54 | background: #ffffff;
55 | box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.04);
56 | border-radius: 5px;
57 | cursor: auto;
58 | }
59 | &.disabled {
60 | opacity: 0.4;
61 | pointer-events: none;
62 | }
63 | }
64 | }
65 | }
66 |
67 | &__bottom {
68 | display: flex;
69 | align-items: center;
70 | justify-content: space-between;
71 | margin-top: 20px;
72 | }
73 |
74 | &__price {
75 | font-weight: bold;
76 | font-size: 22px;
77 | line-height: 27px;
78 | letter-spacing: 0.015em;
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/scss/_fonts.scss:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'Proxima Nova';
3 | src: url('./fonts/ProximaNova-Black.eot');
4 | src: local('./fonts/Proxima Nova Black'), local('ProximaNova-Black'),
5 | url('./fonts/ProximaNova-Black.eot?#iefix') format('embedded-opentype'),
6 | url('./fonts/ProximaNova-Black.woff') format('woff'),
7 | url('./fonts/ProximaNova-Black.ttf') format('truetype');
8 | font-weight: 900;
9 | font-style: normal;
10 | }
11 |
12 | @font-face {
13 | font-family: 'Proxima Nova';
14 | src: url('./fonts/ProximaNova-Bold.eot');
15 | src: local('./fonts/Proxima Nova Bold'), local('ProximaNova-Bold'),
16 | url('./fonts/ProximaNova-Bold.eot?#iefix') format('embedded-opentype'),
17 | url('./fonts/ProximaNova-Bold.woff') format('woff'),
18 | url('./fonts/ProximaNova-Bold.ttf') format('truetype');
19 | font-weight: bold;
20 | font-style: normal;
21 | }
22 |
23 | @font-face {
24 | font-family: 'Proxima Nova';
25 | src: url('./fonts/ProximaNova-Regular.eot');
26 | src: local('./fonts/Proxima Nova Regular'), local('ProximaNova-Regular'),
27 | url('./fonts/ProximaNova-Regular.eot?#iefix') format('embedded-opentype'),
28 | url('./fonts/ProximaNova-Regular.woff') format('woff'),
29 | url('./fonts/ProximaNova-Regular.ttf') format('truetype');
30 | font-weight: normal;
31 | font-style: normal;
32 | }
33 |
34 | @font-face {
35 | font-family: 'Proxima Nova';
36 | src: url('./fonts/ProximaNova-Extrabld.eot');
37 | src: local('./fonts/Proxima Nova Extrabold'), local('ProximaNova-Extrabld'),
38 | url('./fonts/ProximaNova-Extrabld.eot?#iefix') format('embedded-opentype'),
39 | url('./fonts/ProximaNova-Extrabld.woff') format('woff'),
40 | url('./fonts/ProximaNova-Extrabld.ttf') format('truetype');
41 | font-weight: 800;
42 | font-style: normal;
43 | }
44 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/config/getHttpsConfig.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('fs');
4 | const path = require('path');
5 | const crypto = require('crypto');
6 | const chalk = require('react-dev-utils/chalk');
7 | const paths = require('./paths');
8 |
9 | // Ensure the certificate and key provided are valid and if not
10 | // throw an easy to debug error
11 | function validateKeyAndCerts({ cert, key, keyFile, crtFile }) {
12 | let encrypted;
13 | try {
14 | // publicEncrypt will throw an error with an invalid cert
15 | encrypted = crypto.publicEncrypt(cert, Buffer.from('test'));
16 | } catch (err) {
17 | throw new Error(
18 | `The certificate "${chalk.yellow(crtFile)}" is invalid.\n${err.message}`
19 | );
20 | }
21 |
22 | try {
23 | // privateDecrypt will throw an error with an invalid key
24 | crypto.privateDecrypt(key, encrypted);
25 | } catch (err) {
26 | throw new Error(
27 | `The certificate key "${chalk.yellow(keyFile)}" is invalid.\n${
28 | err.message
29 | }`
30 | );
31 | }
32 | }
33 |
34 | // Read file and throw an error if it doesn't exist
35 | function readEnvFile(file, type) {
36 | if (!fs.existsSync(file)) {
37 | throw new Error(
38 | `You specified ${chalk.cyan(
39 | type
40 | )} in your env, but the file "${chalk.yellow(file)}" can't be found.`
41 | );
42 | }
43 | return fs.readFileSync(file);
44 | }
45 |
46 | // Get the https config
47 | // Return cert files if provided in env, otherwise just true or false
48 | function getHttpsConfig() {
49 | const { SSL_CRT_FILE, SSL_KEY_FILE, HTTPS } = process.env;
50 | const isHttps = HTTPS === 'true';
51 |
52 | if (isHttps && SSL_CRT_FILE && SSL_KEY_FILE) {
53 | const crtFile = path.resolve(paths.appPath, SSL_CRT_FILE);
54 | const keyFile = path.resolve(paths.appPath, SSL_KEY_FILE);
55 | const config = {
56 | cert: readEnvFile(crtFile, 'SSL_CRT_FILE'),
57 | key: readEnvFile(keyFile, 'SSL_KEY_FILE'),
58 | };
59 |
60 | validateKeyAndCerts({ ...config, keyFile, crtFile });
61 | return config;
62 | }
63 | return isHttps;
64 | }
65 |
66 | module.exports = getHttpsConfig;
67 |
--------------------------------------------------------------------------------
/config/paths.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 | const fs = require('fs');
5 | const getPublicUrlOrPath = require('react-dev-utils/getPublicUrlOrPath');
6 |
7 | // Make sure any symlinks in the project folder are resolved:
8 | // https://github.com/facebook/create-react-app/issues/637
9 | const appDirectory = fs.realpathSync(process.cwd());
10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
11 |
12 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer
13 | // "public path" at which the app is served.
14 | // webpack needs to know it to put the right