├── .firebase
└── hosting.YnVpbGQ.cache
├── .firebaserc
├── .gitignore
├── README.md
├── firebase.json
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── img
│ ├── coat-female.png
│ ├── coat-male.png
│ ├── hat-female.png
│ ├── hat-male.png
│ ├── jacket-female.png
│ ├── jacket-male.png
│ ├── shirt-female.png
│ ├── shirt-male.png
│ ├── shoes-female.png
│ ├── shoes-male.png
│ ├── suit-female.png
│ ├── suit-male.png
│ ├── t-shirt-female.png
│ └── t-shirt-male.png
├── index.html
└── manifest.json
└── src
├── App.js
├── assets
├── home_page
│ ├── Sale.jpg
│ ├── slide_1.jpg
│ ├── slide_2.jpg
│ ├── slide_3.jpg
│ ├── slide_4.jpg
│ └── slide_5.jpg
├── icons
│ ├── arrow_bottom.png
│ ├── arrow_left.png
│ ├── arrow_right.png
│ ├── bars_white.png
│ ├── bin_white.png
│ ├── cart_white.png
│ ├── heart_white.png
│ ├── logo.png
│ ├── logout.png
│ ├── orders.png
│ ├── search_white.png
│ └── user.png
└── social_media
│ ├── fb.png
│ ├── g_plus.png
│ ├── instagram.png
│ ├── pinterest.png
│ └── twitter.png
├── axios.js
├── components
├── Navigation
│ ├── Navigation.scss
│ ├── NavigationItems
│ │ ├── NavigationItem.js
│ │ ├── NavigationItems.js
│ │ ├── NavigationItemsIcons.js
│ │ └── SideNavigation.js
│ ├── SideDrawer.js
│ └── Toolbar.js
└── UI
│ ├── Backdrop
│ ├── Backdrop.js
│ └── Backdrop.scss
│ ├── Button
│ ├── Button.js
│ └── Button.scss
│ ├── FakeForm
│ ├── FakeForm.js
│ └── FakeForm.scss
│ ├── Footer
│ ├── Footer.js
│ └── Footer.scss
│ ├── Input
│ ├── Input.js
│ └── Input.scss
│ ├── Modal
│ ├── Modal.js
│ └── Modal.scss
│ ├── SocialMedia
│ ├── SocialMedia.js
│ └── SocialMedia.scss
│ └── Spinner
│ ├── Spinner.js
│ └── Spinner.scss
├── containers
├── Auth
│ ├── Auth.js
│ ├── Auth.scss
│ └── Logout
│ │ └── Logout.js
├── Cart
│ ├── Cart.js
│ ├── Cart.scss
│ ├── ContactForm
│ │ ├── ContactForm.js
│ │ └── ContactForm.scss
│ └── OrderSummary
│ │ ├── OrderSummary.js
│ │ └── OrderSummary.scss
├── Contact
│ ├── Contact.js
│ └── Contact.scss
├── Details
│ ├── DetailItem
│ │ ├── DetailItem.js
│ │ └── DetailItem.scss
│ ├── Details.js
│ └── Details.scss
├── HomePage
│ ├── HomePage.js
│ └── HomePage.scss
├── Orders
│ ├── Order
│ │ ├── Order.js
│ │ └── Order.scss
│ ├── Orders.js
│ └── Orders.scss
├── ProductList
│ ├── Product
│ │ ├── Product.js
│ │ └── Product.scss
│ ├── ProductList.js
│ └── ProductList.scss
└── Wishlist
│ ├── Wishlist.js
│ ├── Wishlist.scss
│ └── WishlistItem
│ ├── WishlistItem.js
│ └── WishlistItem.scss
├── data
└── data.js
├── hoc
├── ErrorHandler.js
└── asyncComponent.js
├── index.js
├── index.scss
├── layout
├── Layout.js
└── Layout.scss
├── serviceWorker.js
├── shared
├── ScrollToTopOnMount.js
└── Validity.js
└── store
├── actions
├── actionTypes.js
├── authActions.js
├── index.js
├── interfaceActions.js
├── orderActions.js
└── productActions.js
└── reducers
├── authReducer.js
├── interfaceReducer.js
├── orderReducer.js
└── productReducer.js
/.firebase/hosting.YnVpbGQ.cache:
--------------------------------------------------------------------------------
1 | asset-manifest.json,1551108721750,16aa1e13138bc79122ffbb4b263704f8329fbcd4bcc7a47dd0fba55f9561f3b4
2 | favicon.ico,499162500000,eae62e993eb980ec8a25058c39d5a51feab118bd2100c4deebb2a9c158ec11f9
3 | manifest.json,499162500000,a40a4294484385ec155814f7d72caf5967a19f5efcbedf7a62b2cdff07e42711
4 | precache-manifest.0da69a23e8cd190871edf83250bd209a.js,1551108721749,cdfc3bbba11871d8198cdaa3fc8ab90c55aa751fe8cf076809d96f13e65d9dfd
5 | service-worker.js,1551108721749,7890061e54e22099bd4363f6de00b24febf3cedf5b32aae87e7a5cb8f93e0920
6 | index.html,1551108721749,7f80566dd20c664f530fa4d4e607375a2d0c9906b0f186f126e0276fcb9a20b3
7 | static/css/1.e374356c.chunk.css,1551108721772,7e479b7ff6e6ecdeac26f5ed055fc3cdea080c2519a0ec23c2c7a58978a48d96
8 | static/css/2.26008848.chunk.css,1551108721773,b654fe8fa6669f50d5aecc9987f890a52e54291d79fe0112fe64bee76e1940f1
9 | static/js/1.f3812235.chunk.js,1551108721772,2b0359198714197ef82a7b460931ee213bcf204c81690d6264fe0d833a81aa1c
10 | static/js/1.f3812235.chunk.js.map,1551108721774,49292a3f33e963080bb00788ae241bca141f14604786d7d92d92f732f12c4843
11 | static/css/main.2d3c0314.chunk.css,1551108721770,16a51383adfee853ab4cb753596265c476fbb4d2b38b4ae16e89cc5e37a8ddf5
12 | static/js/2.148dfadc.chunk.js,1551108721773,10171ba78b2ebe48b86e75684955cfc40f2ec1e386735e20d575cd01dfaaa00e
13 | static/js/2.148dfadc.chunk.js.map,1551108721774,1560832c321374ccb4e72151b26b92b578966c00919b59868de9691b4d2f19c3
14 | static/js/runtime~main.6d0d6f64.js,1551108721773,db7702fdad1c1c1a837dd2ede4064d41a7ac8fb4022524ed518fe6979b08c2cc
15 | static/js/runtime~main.6d0d6f64.js.map,1551108721774,1c68dc23eb06c640be164e5e53d4eccee62f76dc79cd0bb2cdaf3f195621cf2b
16 | static/media/cart_white.f2200d0c.png,1551108721771,155817d1e746621ef37b422a589deae1b2230ddbc45a16381641d1df90ea331c
17 | static/media/fb.92a6cf67.png,1551108721771,6c5f6b9df5c5acb5430901c895ed9271c7d8842655742010d18b963a1ee11efc
18 | static/media/heart_white.1bf6b78f.png,1551108721770,b456a648aa5cf4cde471165ebd2b493ff00a5ac03027bb92846ac7d552f1b7a3
19 | static/css/1.e374356c.chunk.css.map,1551108721773,8822b9c2479d75c7b264fbb7b083612396e5dc169bdd5e22c13f44348cc098aa
20 | static/css/2.26008848.chunk.css.map,1551108721773,9cde2c169c1c9da7d62c80b9c6059e2b6ed428ea5b7b1cc1ea7244bf0590a750
21 | static/css/main.2d3c0314.chunk.css.map,1551108721773,6fa5e3e3704f8de86ec48c0aeb790fc7fc3e05a6b2f27fe585b837503fbde6aa
22 | static/media/instagram.5be6ee4b.png,1551108721771,f9c31921854460bac1527c6700c26e35b102f02ff10d9f03d0f23358c2e41a50
23 | static/media/logo.79032ba6.png,1551108721758,80532c01ef19f9f7ca61240348bf002c8709d902b8c0da50ee757477f1cb247d
24 | static/media/pinterest.289716e2.png,1551108721771,945dde75182c573b341e60ce37531626ff3353cd9b9f3a92ba8a0f5483593d4b
25 | static/media/slide_3.65829597.jpg,1551108721772,74c76f7d68410f170532d3ad39858a8187ffb4f44f534bc100566dd6f6fb3011
26 | static/media/twitter.0deebf85.png,1551108721771,ead393e524c8fa3ea444efb55406fab2d2a332a86b2d1c16766d5d26e9884259
27 | static/media/slide_2.1305c6c1.jpg,1551108721772,7ce82bba75d5ae1cabb12d9c819af243f42493653650566c8d1dd91322124ca7
28 | static/media/slide_4.f6cc9c72.jpg,1551108721772,43ecd91f105c8e1615f0a4844f52446b38d2c12219b447d70e9c3c592997cefc
29 | static/media/slide_5.8d843ce7.jpg,1551108721773,75de31e111b1e55ddaa266507c63f420fad97d0bb287ffc555bbed3bd2afb854
30 | static/media/Sale.7952b961.jpg,1551108721773,15e42313dae57321003ce717ca5a1860d1909575758543e7eb1252d6a51deeec
31 | static/js/main.33cd07b8.chunk.js,1551108721770,92a3898febfc1c26cfc0f4fe5380e5527c26553641ef1184950811dbac83476f
32 | static/js/3.9c276360.chunk.js,1551108721773,6461d048556ef3199207eab1156917d06fc3b1861200d19972902d083d9387e3
33 | static/media/slide_1.204de98f.jpg,1551108721772,576369abf0ee586dbbc33aa36474607abd2081bf645ae692ac06113d6663f157
34 | static/js/main.33cd07b8.chunk.js.map,1551108721774,926d274f85784b029a6f45ee718d078eb97b34837ebc35e88d6cd97e98869190
35 | img/shirt-female.png,1547079989920,5f380b15160c1413b421991368981733c7c0ed394e1f8e1fe5dc05896d6db74e
36 | static/js/3.9c276360.chunk.js.map,1551108721774,bce55a7e626bb49e84ab8970a29b221b074426109d2a37429d57e3243705866c
37 | img/coat-female.png,1547079796133,eb6370f45c8694e1256882557510fd8702c60c74b7eaed444ab504c393b6ea84
38 | img/suit-male.png,1547080081670,7c8978dead8bd3daba1b19bf86bdebe713b4d6a505f23e186d6b509b5c1583bb
39 | img/t-shirt-male.png,1547080128745,3ffe7b7b768b77140c3b2dbc33a04c3a4f6b44dfacff2e2a177d6efe1d887ce3
40 | img/hat-female.png,1547079911630,9ad4112bfc66cd944f0de58c28f873edf5b701b52422c64bdf80d4037d46794a
41 | img/shirt-male.png,1547080006702,0f57ca02af55cc1d72d980e0e5e291820103c377413fe3c3cfb6c5c41026cdf3
42 | img/hat-male.png,1547079939450,677de89a0432e9e71a6c6d1d3f7405eeb240cbc015de0744c9fd4de7f8b88283
43 | img/shoes-male.png,1547080046360,9a9fbf6c88389f721410bc5b86851943df3da4d39172cec5815d8f774b8ca4a5
44 | img/t-shirt-female.png,1547080110411,096c3d917490d040937f5d97dd9a8dec442f8f5deb96d4b7aa2cb716a4c5210f
45 | img/jacket-male.png,1547079975075,8bb57d7307bff16e64c42fb2a684d48ba60a7ff5ad6da6719d7087fb4f60a977
46 | img/jacket-female.png,1547079957125,6b0c19cbe8e3309d15b52b3af7beac3199a72dd3ec365196ce02fcae0c2fdbca
47 | img/suit-female.png,1547080063475,61529557f63de732090d765ebf14583d31738af938c992c6e75d77dc36754f48
48 | img/shoes-female.png,1547080027164,ba742b8636517d5d46f09c6a23a4a1b77f0d2f6fffd1350bbf829080de8c4d9b
49 | img/coat-male.png,1547079878709,a1b875988cea440c2a3bcfd18b3d8c134688fc0cfed742cfad0584258c4386b7
50 |
--------------------------------------------------------------------------------
/.firebaserc:
--------------------------------------------------------------------------------
1 | {
2 | "projects": {
3 | "default": "react-elegant-store"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Overview
2 | Full stack project with authentication and database fired on firebase. The user can browse product lists using the filtering and product preview tools, choose the size and add selected products to the cart and wishlist. Next, after sign in/log in he can send an order and view all orders on the list of orders.
3 |
4 | 
5 |
6 | ## Used technologies
7 |
8 | - React & Redux on frontend.
9 | - Firebase on backend (storing data and authentication).
10 | - Adobe XD and Illustrator - design.
11 |
12 | ## Preview
13 |
14 | You can see this app in action.
15 | https://react-elegant-store.firebaseapp.com/
16 |
17 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
18 |
--------------------------------------------------------------------------------
/firebase.json:
--------------------------------------------------------------------------------
1 | {
2 | "hosting": {
3 | "public": "build",
4 | "ignore": [
5 | "firebase.json",
6 | "**/.*",
7 | "**/node_modules/**"
8 | ],
9 | "rewrites": [
10 | {
11 | "source": "**",
12 | "destination": "/index.html"
13 | }
14 | ]
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "elegant-store",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "axios": "^0.18.0",
7 | "enzyme": "^3.8.0",
8 | "enzyme-adapter-react-16": "^1.8.0",
9 | "node-sass": "^4.11.0",
10 | "prop-types": "^15.6.2",
11 | "react": "^16.7.0",
12 | "react-dom": "^16.7.0",
13 | "react-redux": "^6.0.0",
14 | "react-router-dom": "^4.3.1",
15 | "react-scripts": "2.1.3",
16 | "react-test-renderer": "^16.7.0",
17 | "react-transition-group": "^2.5.3",
18 | "redux": "^4.0.1",
19 | "redux-thunk": "^2.3.0"
20 | },
21 | "scripts": {
22 | "start": "react-scripts start",
23 | "build": "react-scripts build",
24 | "test": "react-scripts test",
25 | "eject": "react-scripts eject"
26 | },
27 | "eslintConfig": {
28 | "extends": "react-app"
29 | },
30 | "browserslist": [
31 | ">0.2%",
32 | "not dead",
33 | "not ie <= 11",
34 | "not op_mini all"
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartstc/react-simple-online-shop-concept/5eb02fcc416c3164730be000b80ce0288220cb9c/public/favicon.ico
--------------------------------------------------------------------------------
/public/img/coat-female.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartstc/react-simple-online-shop-concept/5eb02fcc416c3164730be000b80ce0288220cb9c/public/img/coat-female.png
--------------------------------------------------------------------------------
/public/img/coat-male.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartstc/react-simple-online-shop-concept/5eb02fcc416c3164730be000b80ce0288220cb9c/public/img/coat-male.png
--------------------------------------------------------------------------------
/public/img/hat-female.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartstc/react-simple-online-shop-concept/5eb02fcc416c3164730be000b80ce0288220cb9c/public/img/hat-female.png
--------------------------------------------------------------------------------
/public/img/hat-male.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartstc/react-simple-online-shop-concept/5eb02fcc416c3164730be000b80ce0288220cb9c/public/img/hat-male.png
--------------------------------------------------------------------------------
/public/img/jacket-female.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartstc/react-simple-online-shop-concept/5eb02fcc416c3164730be000b80ce0288220cb9c/public/img/jacket-female.png
--------------------------------------------------------------------------------
/public/img/jacket-male.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartstc/react-simple-online-shop-concept/5eb02fcc416c3164730be000b80ce0288220cb9c/public/img/jacket-male.png
--------------------------------------------------------------------------------
/public/img/shirt-female.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartstc/react-simple-online-shop-concept/5eb02fcc416c3164730be000b80ce0288220cb9c/public/img/shirt-female.png
--------------------------------------------------------------------------------
/public/img/shirt-male.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartstc/react-simple-online-shop-concept/5eb02fcc416c3164730be000b80ce0288220cb9c/public/img/shirt-male.png
--------------------------------------------------------------------------------
/public/img/shoes-female.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartstc/react-simple-online-shop-concept/5eb02fcc416c3164730be000b80ce0288220cb9c/public/img/shoes-female.png
--------------------------------------------------------------------------------
/public/img/shoes-male.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartstc/react-simple-online-shop-concept/5eb02fcc416c3164730be000b80ce0288220cb9c/public/img/shoes-male.png
--------------------------------------------------------------------------------
/public/img/suit-female.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartstc/react-simple-online-shop-concept/5eb02fcc416c3164730be000b80ce0288220cb9c/public/img/suit-female.png
--------------------------------------------------------------------------------
/public/img/suit-male.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartstc/react-simple-online-shop-concept/5eb02fcc416c3164730be000b80ce0288220cb9c/public/img/suit-male.png
--------------------------------------------------------------------------------
/public/img/t-shirt-female.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartstc/react-simple-online-shop-concept/5eb02fcc416c3164730be000b80ce0288220cb9c/public/img/t-shirt-female.png
--------------------------------------------------------------------------------
/public/img/t-shirt-male.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartstc/react-simple-online-shop-concept/5eb02fcc416c3164730be000b80ce0288220cb9c/public/img/t-shirt-male.png
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | Elegant Store
14 |
15 |
16 |
17 | You need to enable JavaScript to run this app.
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/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 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component, Fragment } from 'react';
2 | import { Switch, Route, Redirect, withRouter } from 'react-router-dom';
3 | import { connect } from 'react-redux';
4 | import * as actions from './store/actions';
5 | import asyncComponent from './hoc/asyncComponent';
6 |
7 | // COMPONENTS
8 | import Layout from './layout/Layout';
9 | import Cart from './containers/Cart/Cart';
10 | import Contact from './containers/Contact/Contact';
11 | import Details from './containers/Details/Details';
12 | import ProductList from './containers/ProductList/ProductList';
13 | import Wishlist from './containers/Wishlist/Wishlist';
14 | import HomePage from './containers/HomePage/HomePage';
15 | import Logout from './containers/Auth/Logout/Logout';
16 |
17 | const asyncOrders = asyncComponent(() => {
18 | return import('./containers/Orders/Orders');
19 | });
20 |
21 | const asyncAuth = asyncComponent(() => {
22 | return import('./containers/Auth/Auth');
23 | });
24 |
25 | class App extends Component {
26 | componentDidMount() {
27 | this.props.onTryAutoSignup();
28 | };
29 |
30 | render() {
31 | const { isAuth } = this.props;
32 |
33 | return (
34 |
35 |
36 |
37 | {!isAuth && }
38 | {isAuth && }
39 | {isAuth && }
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | );
51 | }
52 | };
53 |
54 | const mapStateToProps = state => {
55 | return {
56 | isAuth: state.auth.token !== null
57 | };
58 | };
59 |
60 | const mapDispatchToProps = dispatch => {
61 | return {
62 | onTryAutoSignup: () => dispatch(actions.authCheckState())
63 | };
64 | };
65 |
66 | export default withRouter(connect(mapStateToProps, mapDispatchToProps)(App));
67 |
--------------------------------------------------------------------------------
/src/assets/home_page/Sale.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartstc/react-simple-online-shop-concept/5eb02fcc416c3164730be000b80ce0288220cb9c/src/assets/home_page/Sale.jpg
--------------------------------------------------------------------------------
/src/assets/home_page/slide_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartstc/react-simple-online-shop-concept/5eb02fcc416c3164730be000b80ce0288220cb9c/src/assets/home_page/slide_1.jpg
--------------------------------------------------------------------------------
/src/assets/home_page/slide_2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartstc/react-simple-online-shop-concept/5eb02fcc416c3164730be000b80ce0288220cb9c/src/assets/home_page/slide_2.jpg
--------------------------------------------------------------------------------
/src/assets/home_page/slide_3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartstc/react-simple-online-shop-concept/5eb02fcc416c3164730be000b80ce0288220cb9c/src/assets/home_page/slide_3.jpg
--------------------------------------------------------------------------------
/src/assets/home_page/slide_4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartstc/react-simple-online-shop-concept/5eb02fcc416c3164730be000b80ce0288220cb9c/src/assets/home_page/slide_4.jpg
--------------------------------------------------------------------------------
/src/assets/home_page/slide_5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartstc/react-simple-online-shop-concept/5eb02fcc416c3164730be000b80ce0288220cb9c/src/assets/home_page/slide_5.jpg
--------------------------------------------------------------------------------
/src/assets/icons/arrow_bottom.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartstc/react-simple-online-shop-concept/5eb02fcc416c3164730be000b80ce0288220cb9c/src/assets/icons/arrow_bottom.png
--------------------------------------------------------------------------------
/src/assets/icons/arrow_left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartstc/react-simple-online-shop-concept/5eb02fcc416c3164730be000b80ce0288220cb9c/src/assets/icons/arrow_left.png
--------------------------------------------------------------------------------
/src/assets/icons/arrow_right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartstc/react-simple-online-shop-concept/5eb02fcc416c3164730be000b80ce0288220cb9c/src/assets/icons/arrow_right.png
--------------------------------------------------------------------------------
/src/assets/icons/bars_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartstc/react-simple-online-shop-concept/5eb02fcc416c3164730be000b80ce0288220cb9c/src/assets/icons/bars_white.png
--------------------------------------------------------------------------------
/src/assets/icons/bin_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartstc/react-simple-online-shop-concept/5eb02fcc416c3164730be000b80ce0288220cb9c/src/assets/icons/bin_white.png
--------------------------------------------------------------------------------
/src/assets/icons/cart_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartstc/react-simple-online-shop-concept/5eb02fcc416c3164730be000b80ce0288220cb9c/src/assets/icons/cart_white.png
--------------------------------------------------------------------------------
/src/assets/icons/heart_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartstc/react-simple-online-shop-concept/5eb02fcc416c3164730be000b80ce0288220cb9c/src/assets/icons/heart_white.png
--------------------------------------------------------------------------------
/src/assets/icons/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartstc/react-simple-online-shop-concept/5eb02fcc416c3164730be000b80ce0288220cb9c/src/assets/icons/logo.png
--------------------------------------------------------------------------------
/src/assets/icons/logout.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartstc/react-simple-online-shop-concept/5eb02fcc416c3164730be000b80ce0288220cb9c/src/assets/icons/logout.png
--------------------------------------------------------------------------------
/src/assets/icons/orders.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartstc/react-simple-online-shop-concept/5eb02fcc416c3164730be000b80ce0288220cb9c/src/assets/icons/orders.png
--------------------------------------------------------------------------------
/src/assets/icons/search_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartstc/react-simple-online-shop-concept/5eb02fcc416c3164730be000b80ce0288220cb9c/src/assets/icons/search_white.png
--------------------------------------------------------------------------------
/src/assets/icons/user.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartstc/react-simple-online-shop-concept/5eb02fcc416c3164730be000b80ce0288220cb9c/src/assets/icons/user.png
--------------------------------------------------------------------------------
/src/assets/social_media/fb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartstc/react-simple-online-shop-concept/5eb02fcc416c3164730be000b80ce0288220cb9c/src/assets/social_media/fb.png
--------------------------------------------------------------------------------
/src/assets/social_media/g_plus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartstc/react-simple-online-shop-concept/5eb02fcc416c3164730be000b80ce0288220cb9c/src/assets/social_media/g_plus.png
--------------------------------------------------------------------------------
/src/assets/social_media/instagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartstc/react-simple-online-shop-concept/5eb02fcc416c3164730be000b80ce0288220cb9c/src/assets/social_media/instagram.png
--------------------------------------------------------------------------------
/src/assets/social_media/pinterest.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartstc/react-simple-online-shop-concept/5eb02fcc416c3164730be000b80ce0288220cb9c/src/assets/social_media/pinterest.png
--------------------------------------------------------------------------------
/src/assets/social_media/twitter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartstc/react-simple-online-shop-concept/5eb02fcc416c3164730be000b80ce0288220cb9c/src/assets/social_media/twitter.png
--------------------------------------------------------------------------------
/src/axios.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | const instance = axios.create({
4 | baseURL: 'https://react-elegant-store.firebaseio.com/'
5 | });
6 |
7 | export default instance;
--------------------------------------------------------------------------------
/src/components/Navigation/Navigation.scss:
--------------------------------------------------------------------------------
1 | @import '../../index.scss';
2 |
3 | .header {
4 | position: fixed;
5 | z-index: 10;
6 | background-color: #000;
7 | width: 100%;
8 | height: 42px;
9 | display: flex;
10 | align-items: center;
11 | justify-content: space-between;
12 | padding: 5px 15px;
13 |
14 | .left-wrapper {
15 | display: flex;
16 | align-items: center;
17 | }
18 | }
19 |
20 | .toggle-side-drawer {
21 | background: transparent;
22 | border: none;
23 | cursor: pointer;
24 | display: flex;
25 | align-items: center;
26 |
27 | @include mediaMd {
28 | display: none;
29 | }
30 |
31 | img {
32 | width: 36px;
33 | }
34 | }
35 |
36 | .logo {
37 | width: 110px;
38 | display: none;
39 |
40 | @include mediaMd {
41 | display: block;
42 | }
43 | }
44 |
45 | .navigation {
46 | display: none;
47 |
48 | @include mediaMd {
49 | display: block;
50 | }
51 |
52 | .nav-list {
53 | display: flex;
54 | padding-left: 10px;
55 | color: #fff;
56 |
57 | .nav-link {
58 | color: #fff;
59 | margin: 0 14px;
60 | cursor: pointer;
61 | border-bottom: 1px solid transparent;
62 | transition: all .2s ease-in-out;
63 |
64 | &:hover {
65 | border-bottom: 1px solid #fff;
66 | }
67 | }
68 | }
69 | }
70 |
71 | .navigation-icons {
72 | &-list {
73 | display: flex;
74 | position: relative;
75 |
76 | .nav-link {
77 | color: #fff;
78 | cursor: pointer;
79 |
80 | .icon {
81 | width: 34px;
82 | }
83 | }
84 |
85 | .products-amount {
86 | position: absolute;
87 | z-index: -1;
88 | color: #fff;
89 | font-size: .6em;
90 | top: 62%;
91 | left: 83%;
92 | transform: translate(-50%, -50%);
93 | }
94 | }
95 | }
96 |
97 | // === SIDE DRAWER ===
98 | .side-drawer {
99 | position: fixed;
100 | display: flex;
101 | flex-direction: column;
102 | align-items: flex-start;
103 | z-index: 30;
104 | height: 100%;
105 | width: 74%;
106 | max-width: 240px;
107 | background: #fff;
108 | transition: all .2s ease-in-out;
109 |
110 | @include mediaMd {
111 | display: none;
112 | }
113 |
114 | .logo-wrapper {
115 | background: #000;
116 | width: 100%;
117 | height: 42px;
118 | display: flex;
119 | align-items: center;
120 | justify-content: space-between;
121 | padding: 0 10px;
122 | }
123 |
124 | .logo {
125 | display: block;
126 | }
127 | }
128 |
129 | .side-navigation-wrapper {
130 | padding-left: 15px;
131 | }
132 |
133 | .side-navigation {
134 | display: block;
135 |
136 | &-list {
137 | .nav-link {
138 | cursor: pointer;
139 | border-bottom: 1px solid transparent;
140 | transition: all .2s ease-in-out;
141 | color: #000;
142 | font-size: 1em;
143 |
144 | &.main {
145 | line-height: 20px;
146 | font-size: 1.05em;
147 | font-weight: $fw-medium;
148 | }
149 |
150 | &:hover {
151 | border-bottom: 1px solid #000;
152 | }
153 | }
154 | }
155 | }
156 |
157 | .open {
158 | transform: translateX(0);
159 | }
160 |
161 | .close {
162 | transform: translateX(-100%);
163 | }
--------------------------------------------------------------------------------
/src/components/Navigation/NavigationItems/NavigationItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { NavLink } from 'react-router-dom';
3 |
4 | const navigationItem = ({ clicked, style, linkType, link, exact, children }) => (
5 |
8 |
14 | {children}
15 |
16 |
17 | );
18 |
19 | export default navigationItem;
--------------------------------------------------------------------------------
/src/components/Navigation/NavigationItems/NavigationItems.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from 'react-redux';
4 | import { filterProducts } from '../../../store/actions';
5 |
6 | import NavigationItem from './NavigationItem';
7 |
8 | const navigationItems = ({ filterProducts, isAuth }) => (
9 |
10 | filterProducts('female')}
12 | link="/productlist/female"
13 | exact>Women
14 | filterProducts('male')}
16 | link="/productlist/male"
17 | exact>Men
18 | Contact
19 | Home
20 | {isAuth ? Orders : null}
21 |
22 | );
23 |
24 | NavigationItem.propTypes = {
25 | isAuth: PropTypes.bool,
26 | filterProducts: PropTypes.func
27 | };
28 |
29 | const mapStateToProps = ({ auth }) => ({ isAuth: auth.token !== null });
30 |
31 | export default connect(mapStateToProps, { filterProducts })(navigationItems);
--------------------------------------------------------------------------------
/src/components/Navigation/NavigationItems/NavigationItemsIcons.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import NavigationItem from './NavigationItem';
4 | import { connect } from 'react-redux';
5 |
6 | import userIcon from '../../../assets/icons/user.png';
7 | import heartIcon from '../../../assets/icons/heart_white.png';
8 | import cartIcon from '../../../assets/icons/cart_white.png';
9 | import logoutIcon from '../../../assets/icons/logout.png';
10 |
11 | const navigationItemsIcons = ({ cartItems, isAuth }) => (
12 |
29 | );
30 |
31 | navigationItemsIcons.propTypes = {
32 | cartItems: PropTypes.array,
33 | isAuth: PropTypes.bool
34 | };
35 |
36 | const mapStateToProps = ({ products, auth }) => ({
37 | cartItems: products.cart,
38 | isAuth: auth.token !== null
39 | });
40 |
41 | export default connect(mapStateToProps)(navigationItemsIcons);
--------------------------------------------------------------------------------
/src/components/Navigation/NavigationItems/SideNavigation.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from 'react-redux';
4 | import { filterProducts } from '../../../store/actions';
5 | import '../Navigation.scss';
6 |
7 | import NavigationItem from './NavigationItem';
8 |
9 | const femaleCategories = [
10 | {
11 | category: 'female',
12 | content: 'Women',
13 | linkType: 'main'
14 | },
15 | {
16 | category: 'women-coats',
17 | content: 'Coats'
18 | },
19 | {
20 | category: 'women-jackets',
21 | content: 'Jackets',
22 | },
23 | {
24 | category: 'women-suits',
25 | content: 'Suits',
26 | },
27 | {
28 | category: 'women-shirts',
29 | content: 'Shirts',
30 | },
31 | {
32 | category: 'women-t-shirts',
33 | content: 'T-shirts',
34 | },
35 | {
36 | category: 'women-shoes',
37 | content: 'Shoes',
38 | },
39 | {
40 | category: 'women-hats',
41 | content: 'Hats',
42 | },
43 | {
44 | category: 'male',
45 | content: 'Men',
46 | linkType: 'main'
47 | },
48 | {
49 | category: 'men-coats',
50 | content: 'Coats',
51 | },
52 | {
53 | category: 'men-jackets',
54 | content: 'Jackets',
55 | },
56 | {
57 | category: 'men-suits',
58 | content: 'Suits',
59 | },
60 | {
61 | category: 'men-shirts',
62 | content: 'Shirts',
63 | },
64 | {
65 | category: 'men-t-shirts',
66 | content: 'T-shirts',
67 | },
68 | {
69 | category: 'men-shoes',
70 | content: 'Shoes',
71 | },
72 | {
73 | category: 'men-hats',
74 | content: 'Hats',
75 | },
76 | ];
77 |
78 | const sideNavigation = ({ filterProducts, children }) => (
79 |
80 |
81 | {femaleCategories.map(femaleCategory => {
82 | const { category, linkType, content } = femaleCategory;
83 |
84 | return (
85 | filterProducts(category)}
88 | linkType={linkType}
89 | link={`/productlist/${category}`}>
90 | {content}
91 |
92 | )
93 | })}
94 | {children}
95 |
96 |
97 | );
98 |
99 | sideNavigation.propTypes = {
100 | filterProducts: PropTypes.func.isRequired
101 | };
102 |
103 | export default connect(null, { filterProducts })(sideNavigation);
--------------------------------------------------------------------------------
/src/components/Navigation/SideDrawer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from 'react-redux';
4 | import { toggleSideDrawer } from '../../store/actions';
5 | import './Navigation.scss';
6 |
7 | import logo from '../../assets/icons/logo.png';
8 | import closeBtnIcon from '../../assets/icons/arrow_left.png';
9 |
10 | import SideNavigation from './NavigationItems/SideNavigation';
11 | import NavigationItem from './NavigationItems/NavigationItem';
12 | import Backdrop from '../UI/Backdrop/Backdrop';
13 |
14 | const sideDrawer = ({ toggleSideDrawer, showSideDrawer, isAuth }) => {
15 | let attachedClasses = ["side-drawer", "close"];
16 | if (showSideDrawer) {
17 | attachedClasses = ["side-drawer", "open"];
18 | };
19 |
20 | return (
21 | <>
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | Contact
36 | Home
40 | {isAuth ? Orders : null}
44 |
45 |
46 |
47 | >
48 | );
49 | };
50 |
51 | sideDrawer.propTypes = {
52 | showSideDrawer: PropTypes.bool.isRequired,
53 | isAuth: PropTypes.bool.isRequired,
54 | toggleSideDrawer: PropTypes.func.isRequired
55 | };
56 |
57 | const mapStateToProps = state => {
58 | return {
59 | showSideDrawer: state.interface.sideDrawerShowed,
60 | isAuth: state.auth.token !== null
61 | };
62 | };
63 |
64 | export default connect(mapStateToProps, { toggleSideDrawer })(sideDrawer);
--------------------------------------------------------------------------------
/src/components/Navigation/Toolbar.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from 'react-redux';
4 | import { toggleSideDrawer } from '../../store/actions';
5 | import './Navigation.scss';
6 |
7 | import NavigationItems from './NavigationItems/NavigationItems';
8 | import NavigationItemsIcons from './NavigationItems/NavigationItemsIcons';
9 |
10 | import logo from '../../assets/icons/logo.png';
11 | import menuIcon from '../../assets/icons/bars_white.png';
12 |
13 | const toolbar = ({ toggleSideDrawer }) => (
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | );
29 |
30 | toolbar.propTypes = {
31 | toggleSideDrawer: PropTypes.func.isRequired
32 | };
33 |
34 | export default connect(null, { toggleSideDrawer })(toolbar);
--------------------------------------------------------------------------------
/src/components/UI/Backdrop/Backdrop.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './Backdrop.scss';
3 |
4 | const backdrop = ({ show, clicked }) => (
5 | show ?
: null
6 | );
7 |
8 | export default backdrop;
--------------------------------------------------------------------------------
/src/components/UI/Backdrop/Backdrop.scss:
--------------------------------------------------------------------------------
1 | .Backdrop {
2 | width: 100%;
3 | height: 100%;
4 | position: fixed;
5 | z-index: 20;
6 | left: 0;
7 | top: 0;
8 | background-color: rgba(#fff, .8);
9 | }
--------------------------------------------------------------------------------
/src/components/UI/Button/Button.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './Button.scss';
3 |
4 | const button = ({ disabled, clicked, btnType, children }) => (
5 |
10 | {children}
11 |
12 | );
13 |
14 | export default button;
--------------------------------------------------------------------------------
/src/components/UI/Button/Button.scss:
--------------------------------------------------------------------------------
1 | @import '../../../index.scss';
2 |
3 | .btn {
4 | background: #fff;
5 | width: 100%;
6 | border: 1px solid #000;
7 | padding: 5px 7px;
8 | margin-top: 5px;
9 | outline: none;
10 | cursor: pointer;
11 | max-width: 180px;
12 | transition: all .3s ease-in-out;
13 |
14 | &:hover {
15 | background: #000;
16 | color: #fff;
17 | }
18 |
19 | &.small {
20 | padding: 2.5px;
21 |
22 | @include mediaMd {
23 | padding: 7px;
24 | }
25 | }
26 |
27 | &.mobile {
28 | width: 100%;
29 | max-width: 100%;
30 | }
31 |
32 | &.dark {
33 | background: #000;
34 | color: #fff;
35 |
36 | &:disabled:hover {
37 | background: #777;
38 | color: #fff;
39 | }
40 | }
41 | }
42 |
43 | button:disabled {
44 | color: #666;
45 | border: 1px solid #666;
46 | cursor: no-drop;
47 |
48 | &:hover {
49 | color: #666;
50 | background: #fff;
51 | border: 1px solid #666;
52 | }
53 | }
--------------------------------------------------------------------------------
/src/components/UI/FakeForm/FakeForm.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './FakeForm.scss';
3 |
4 | const fakeForm = () => (
5 |
11 | );
12 |
13 | export default fakeForm;
--------------------------------------------------------------------------------
/src/components/UI/FakeForm/FakeForm.scss:
--------------------------------------------------------------------------------
1 | @import '../../../index.scss';
2 |
3 | .questions-form {
4 | display: flex;
5 | flex-direction: column;
6 | padding: 1.2em 1em;
7 | margin: 0 auto;
8 | width: 100%;
9 | max-width: 400px;
10 |
11 | input,
12 | textarea {
13 | background: transparent;
14 | border: none;
15 | border-bottom: 1px solid #fff;
16 | margin-bottom: .6em;
17 | color: #fff;
18 |
19 | &::placeholder {
20 | color: #fff;
21 | }
22 | }
23 |
24 | button {
25 | background: transparent;
26 | border: 1px solid #fff;
27 | color: #fff;
28 |
29 | @include mediaSm {
30 | width: 140px;
31 | align-self: flex-end;
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/src/components/UI/Footer/Footer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './Footer.scss';
3 |
4 | import FakeForm from '../FakeForm/FakeForm';
5 | import SocialMedia from '../SocialMedia/SocialMedia';
6 |
7 | const footer = () => (
8 |
9 |
10 |
11 |
12 |
Questions
13 |
Lorem ipsum dolor sit amet, consectetur adipiscing elit. In tempus massa ac pretium dapibus. Proin consectetur eros id suscipit tristique.
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
Buy from Us
23 |
Women
24 |
Men
25 |
26 |
27 |
Online Shop
28 |
How to Buy
29 |
Questions
30 |
Sales
31 |
Delivery cost
32 |
Regulations
33 |
34 |
35 |
About Us
36 |
Who We Are
37 |
Work with Us
38 |
Social Media
39 |
Our Stores
40 |
41 |
42 |
Support
43 |
Contact
44 |
Find Store
45 |
Customer Service
46 |
Privacy & Cookies
47 |
Security
48 |
49 |
50 |
51 | Made by Bart.dsn
52 |
53 | );
54 |
55 | export default footer;
--------------------------------------------------------------------------------
/src/components/UI/Footer/Footer.scss:
--------------------------------------------------------------------------------
1 | @import '../../../index.scss';
2 |
3 | .footer {
4 | width: 100%;
5 | margin-top: 2em;
6 |
7 | .questions {
8 | background: #000;
9 | color: #fff;
10 | text-align: center;
11 | padding: 1em;
12 |
13 | &-wrapper {
14 | max-width: 1200px;
15 | margin: 0 auto;
16 |
17 | @include mediaMd {
18 | display: flex;
19 | }
20 | }
21 | }
22 |
23 | .questions-text {
24 | margin: auto;
25 | max-width: 400px;
26 | }
27 | }
28 |
29 | .title {
30 | font-weight: $fw-semibold;
31 | font-size: 1.4em;
32 |
33 | @include mediaMd {
34 | font-size: 1.6em;
35 | }
36 | }
37 |
38 | .subtitle {
39 | font-size: .8em;
40 |
41 | @include mediaMd {
42 | font-size: 1em;
43 | }
44 | }
45 |
46 | .support-section {
47 | margin-top: .6em;
48 | padding: 1em;
49 | background: #000;
50 | color: #fff;
51 |
52 |
53 | .support-wrapper {
54 | display: grid;
55 | grid-template-columns: 1fr 1fr;
56 | grid-gap: 1.4em;
57 | max-width: 1200px;
58 | padding: 1em calc(1em + 3vw);
59 | margin: 0 auto;
60 |
61 | div {
62 | display: flex;
63 | flex-direction: column;
64 | align-items: flex-start;
65 | }
66 |
67 | @include mediaSm {
68 | grid-template-columns: repeat(4, 1fr);
69 | padding: 1em 0;
70 | grid-gap: .3em;
71 | }
72 |
73 | @include mediaMd {
74 | padding: 1em 3.2em;
75 | }
76 | }
77 |
78 | h4 {
79 | font-weight: $fw-semibold;
80 | font-size: .9em;
81 | margin-bottom: .3em;
82 |
83 | @include mediaMd {
84 | font-size: 1.3em;
85 | }
86 | }
87 |
88 | p {
89 | font-size: .75em;
90 | border-bottom: 1px solid transparent;
91 | cursor: pointer;
92 | transition: border-bottom .2s ease-in-out;
93 |
94 | &:hover {
95 | border-bottom: 1px solid #fff;
96 | }
97 |
98 | @include mediaMd {
99 | font-size: 1em;
100 | }
101 | }
102 | }
103 |
104 | .advertising {
105 | line-height: 2.4em;
106 | text-align: center;
107 | font-size: .9em;
108 | }
--------------------------------------------------------------------------------
/src/components/UI/Input/Input.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './Input.scss';
3 |
4 | const input = (props) => {
5 | const { invalid, shouldValidate, touched, elementType, elementConfig, value, changed, label } = props;
6 |
7 | let inputElement = null;
8 | const inputClasses = ["input"];
9 |
10 | if (invalid && shouldValidate && touched) {
11 | inputClasses.push("invalid");
12 | };
13 |
14 | switch (elementType) {
15 |
16 | case ('input'):
17 | inputElement = ;
23 | break;
24 | case ('textarea'):
25 | inputElement =
31 | break;
32 | case ('select'):
33 | inputElement = (
34 |
38 | {elementConfig.options.map(option => (
39 | {option.displayValue}
40 | ))}
41 |
42 | )
43 | break;
44 |
45 | default:
46 | inputElement =
51 | }
52 |
53 | return (
54 |
55 | {label}
56 | {inputElement}
57 |
58 | );
59 | };
60 |
61 | export default input;
--------------------------------------------------------------------------------
/src/components/UI/Input/Input.scss:
--------------------------------------------------------------------------------
1 | @import '../../../index.scss';
2 |
3 | .input-wrapper {
4 | margin-top: .8em;
5 | }
6 |
7 | .label {
8 | width: 100%;
9 | }
10 |
11 | .input {
12 | width: 100%;
13 | line-height: 1.8em;
14 | outline: none;
15 | border: none;
16 | border-bottom: 1px solid #000;
17 | background: #fff;
18 |
19 | &::placeholder {
20 | color: #000;
21 | }
22 | }
23 |
24 | .select-input {
25 | width: 100%;
26 | outline: none;
27 | border: 1px solid #000;
28 | }
29 |
30 | .invalid {
31 | background-color: rgb(255, 11, 11);
32 | }
--------------------------------------------------------------------------------
/src/components/UI/Modal/Modal.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './Modal.scss';
3 |
4 | import Backdrop from '../Backdrop/Backdrop';
5 |
6 | const Modal = ({ showBackdrop, closeModal, modalType, showModal, children }) => (
7 | <>
8 |
9 |
15 | {children}
16 |
17 | >
18 | )
19 |
20 | export default Modal;
--------------------------------------------------------------------------------
/src/components/UI/Modal/Modal.scss:
--------------------------------------------------------------------------------
1 | @import '../../../index.scss';
2 |
3 | .modal {
4 | position: fixed;
5 | z-index: 500;
6 | background-color: white;
7 | width: 85%;
8 | max-width: 370px;
9 | height: 85%;
10 | padding: 12px;
11 | padding-top: 38px;
12 | top: 10%;
13 | left: calc(-50vw + 50%);
14 | right: calc(-50vw + 50%);
15 | margin-left: auto;
16 | margin-right: auto;
17 | transition: all 0.3s ease-out;
18 | overflow-y: scroll;
19 | overflow-x: hidden;
20 |
21 | &.small {
22 | width: 290px;
23 | height: 120px;
24 | top: 40%;
25 | padding: 20px;
26 | overflow: hidden;
27 | display: flex;
28 | text-align: center;
29 | flex-direction: column;
30 | align-items: center;
31 | justify-content: center;
32 | }
33 |
34 | @include mediaMd {
35 | max-width: 500px;
36 | }
37 |
38 | .close-modal-btn {
39 | position: fixed;
40 | top: 0;
41 | left: 0;
42 | background: #000;
43 | border: none;
44 | color: #fff;
45 | width: 32px;
46 | height: 32px;
47 | cursor: pointer;
48 | }
49 | }
50 |
51 | .btn-wrapper {
52 | display: flex;
53 | flex-direction: column;
54 | }
55 |
56 | .modal-title {
57 | font-size: 1em;
58 | font-weight: $fw-medium;
59 |
60 | @include mediaSm {
61 | font-size: 1.2em;
62 | }
63 | }
64 |
65 | .modal-subtitle {
66 | font-size: .9em;
67 |
68 | @include mediaSm {
69 | font-size: 1em;
70 | }
71 | }
--------------------------------------------------------------------------------
/src/components/UI/SocialMedia/SocialMedia.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './SocialMedia.scss';
3 |
4 | import fb from '../../../assets/social_media/fb.png';
5 | import gplus from '../../../assets/social_media/g_plus.png';
6 | import instagram from '../../../assets/social_media/instagram.png';
7 | import pinterest from '../../../assets/social_media/pinterest.png';
8 | import twitter from '../../../assets/social_media/twitter.png';
9 |
10 | const SocialMedia = () => (
11 |
12 |
Social Media
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | );
22 |
23 | export default SocialMedia;
--------------------------------------------------------------------------------
/src/components/UI/SocialMedia/SocialMedia.scss:
--------------------------------------------------------------------------------
1 | @import '../../../index.scss';
2 |
3 | .social-media {
4 | width: 100%;
5 | text-align: center;
6 | padding: .4em 0;
7 |
8 | .title {
9 | margin: .4em 0;
10 | }
11 |
12 | .icons-wrapper {
13 | width: 60%;
14 | max-width: 300px;
15 | margin: 0 auto;
16 | display: flex;
17 | align-items: center;
18 | justify-content: space-between;
19 |
20 | a img {
21 | width: 34px;
22 | }
23 | }
24 | }
25 |
26 | .title {
27 | font-weight: $fw-semibold;
28 | font-size: 1.4em;
29 |
30 | @include mediaMd {
31 | font-size: 1.6em;
32 | }
33 | }
--------------------------------------------------------------------------------
/src/components/UI/Spinner/Spinner.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './Spinner.scss';
3 |
4 | const spinner = () => (
5 | Loading...
6 | );
7 |
8 | export default spinner;
--------------------------------------------------------------------------------
/src/components/UI/Spinner/Spinner.scss:
--------------------------------------------------------------------------------
1 | .loader,
2 | .loader:before,
3 | .loader:after {
4 | border-radius: 50%;
5 | }
6 | .loader {
7 | color: #000000;
8 | font-size: 11px;
9 | text-indent: -99999em;
10 | margin: 55px auto;
11 | position: relative;
12 | width: 10em;
13 | height: 10em;
14 | box-shadow: inset 0 0 0 1em;
15 | -webkit-transform: translateZ(0);
16 | -ms-transform: translateZ(0);
17 | transform: translateZ(0);
18 | }
19 | .loader:before,
20 | .loader:after {
21 | position: absolute;
22 | content: '';
23 | }
24 | .loader:before {
25 | width: 5.2em;
26 | height: 10.2em;
27 | background: #ffffff;
28 | border-radius: 10.2em 0 0 10.2em;
29 | top: -0.1em;
30 | left: -0.1em;
31 | -webkit-transform-origin: 5.2em 5.1em;
32 | transform-origin: 5.2em 5.1em;
33 | -webkit-animation: load2 2s infinite ease 1.5s;
34 | animation: load2 2s infinite ease 1.5s;
35 | }
36 | .loader:after {
37 | width: 5.2em;
38 | height: 10.2em;
39 | background: #ffffff;
40 | border-radius: 0 10.2em 10.2em 0;
41 | top: -0.1em;
42 | left: 5.1em;
43 | -webkit-transform-origin: 0px 5.1em;
44 | transform-origin: 0px 5.1em;
45 | -webkit-animation: load2 2s infinite ease;
46 | animation: load2 2s infinite ease;
47 | }
48 | @-webkit-keyframes load2 {
49 | 0% {
50 | -webkit-transform: rotate(0deg);
51 | transform: rotate(0deg);
52 | }
53 | 100% {
54 | -webkit-transform: rotate(360deg);
55 | transform: rotate(360deg);
56 | }
57 | }
58 | @keyframes load2 {
59 | 0% {
60 | -webkit-transform: rotate(0deg);
61 | transform: rotate(0deg);
62 | }
63 | 100% {
64 | -webkit-transform: rotate(360deg);
65 | transform: rotate(360deg);
66 | }
67 | }
--------------------------------------------------------------------------------
/src/containers/Auth/Auth.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import './Auth.scss';
3 | import * as actions from '../../store/actions';
4 | import { connect } from 'react-redux';
5 | import { Redirect } from 'react-router-dom';
6 | import { checkValidity } from '../../shared/Validity';
7 | import PropTypes from 'prop-types';
8 |
9 | import Input from '../../components/UI/Input/Input';
10 | import Button from '../../components/UI/Button/Button';
11 | import Spinner from '../../components/UI/Spinner/Spinner';
12 | import ScrollToTopOnMount from '../../shared/ScrollToTopOnMount';
13 |
14 | class Auth extends Component {
15 | state = {
16 | controls: {
17 | email: {
18 | elementType: 'input',
19 | elementConfig: {
20 | type: 'email',
21 | placeholder: 'Mail Address'
22 | },
23 | value: '',
24 | validation: {
25 | required: true,
26 | isEmail: true
27 | },
28 | valid: false,
29 | touched: false
30 | },
31 | password: {
32 | elementType: 'input',
33 | elementConfig: {
34 | type: 'password',
35 | placeholder: 'Password'
36 | },
37 | value: '',
38 | validation: {
39 | required: true,
40 | minLength: 6
41 | },
42 | valid: false,
43 | touched: false
44 | },
45 | },
46 | isSignup: true
47 | };
48 |
49 | inputChangedHandler = (e, controlName) => {
50 | const updatedControls = {
51 | ...this.state.controls,
52 |
53 | // update only one input
54 | [controlName]: {
55 | ...this.state.controls[controlName],
56 | value: e.target.value,
57 | valid: checkValidity(e.target.value, this.state.controls[controlName].validation),
58 | touched: true
59 | }
60 | };
61 |
62 | this.setState({
63 | controls: updatedControls
64 | });
65 | };
66 |
67 | submitHandler = e => {
68 | e.preventDefault();
69 |
70 | this.props.onAuth(this.state.controls.email.value, this.state.controls.password.value, this.state.isSignup);
71 | };
72 |
73 | switchAuthModeHandler = () => {
74 | this.setState(prevState => {
75 | return {
76 | isSignup: !prevState.isSignup
77 | };
78 | });
79 | };
80 |
81 | render() {
82 | const { loading, error, isAuth } = this.props;
83 | const { controls, isSignup } = this.state;
84 |
85 | // convert object of objects into array of objects
86 | const formElementsArray = [];
87 | for (let key in controls) {
88 | formElementsArray.push({
89 | id: key,
90 | config: controls[key]
91 | });
92 | };
93 |
94 | let form = formElementsArray.map(formElement => (
95 | this.inputChangedHandler(e, formElement.id)}
104 | />
105 | ));
106 |
107 | // Display Spinner
108 | if (loading) {
109 | form =
110 | };
111 |
112 | // Display Firebase Error Message
113 | let errorMessage = null;
114 | if (error) {
115 | errorMessage = (
116 | {error.message}
117 | )
118 | };
119 |
120 | // redirect after signin/login
121 | let authRedirect = null;
122 | if (isAuth) {
123 | authRedirect =
124 | };
125 |
126 | let title = You don't have an account yet? Create them below.
127 | if (!isSignup) title = Do you already have an account? Log in below. ;
128 |
129 | return (
130 | <>
131 |
132 |
133 | {title}
134 |
135 | SWITCH TO {isSignup ? 'SINGIN' : 'SIGNUP'}
138 |
139 | {authRedirect}
140 | {errorMessage}
141 |
142 | {form}
143 | Submit
144 |
145 |
146 | >
147 | );
148 | }
149 | };
150 |
151 | Auth.propTypes = {
152 | loading: PropTypes.bool.isRequired,
153 | error: PropTypes.object,
154 | isAuth: PropTypes.bool.isRequired,
155 | onAuth: PropTypes.func.isRequired
156 | };
157 |
158 | const mapStateToProps = state => {
159 | return {
160 | loading: state.auth.loading,
161 | error: state.auth.error,
162 | isAuth: state.auth.token !== null
163 | };
164 | };
165 |
166 | const mapDispatchToProps = dispatch => {
167 | return {
168 | onAuth: (email, password, isSignup) => dispatch(actions.auth(email, password, isSignup))
169 | };
170 | };
171 |
172 | export default connect(mapStateToProps, mapDispatchToProps)(Auth);
--------------------------------------------------------------------------------
/src/containers/Auth/Auth.scss:
--------------------------------------------------------------------------------
1 | @import '../../index.scss';
2 |
3 | .auth-container {
4 | padding: 0 .5em;
5 | max-width: 400px;
6 | margin: 0 auto;
7 | display: flex;
8 | flex-direction: column;
9 | }
10 |
11 | .switch {
12 | margin-bottom: 1.2em;
13 | display: flex;
14 | flex-direction: column;
15 | align-items: center;
16 | }
17 |
18 | .auth-title {
19 | font-weight: $fw-semibold;
20 | font-size: 1em;
21 | text-transform: uppercase;
22 | margin-bottom: 1.4em;
23 | text-align: center;
24 |
25 | @include mediaSm {
26 | font-size: 1.2em;
27 | }
28 | }
--------------------------------------------------------------------------------
/src/containers/Auth/Logout/Logout.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { logout } from '../../../store/actions';
3 | import { connect } from 'react-redux';
4 | import { Redirect } from 'react-router-dom';
5 | import PropTypes from 'prop-types';
6 |
7 | class Logout extends Component {
8 | componentDidMount() {
9 | this.props.logout();
10 | };
11 |
12 | render() {
13 | return ;
14 | }
15 | };
16 |
17 | Logout.propTypes = {
18 | logout: PropTypes.func.isRequired
19 | };
20 |
21 | export default connect(null, { logout })(Logout);
--------------------------------------------------------------------------------
/src/containers/Cart/Cart.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import './Cart.scss';
3 | import { connect } from 'react-redux';
4 | import * as actions from '../../store/actions';
5 | import { Redirect } from 'react-router-dom';
6 | import TransitionGroup from 'react-transition-group/TransitionGroup';
7 | import CSSTransition from 'react-transition-group/CSSTransition';
8 | import PropTypes from 'prop-types';
9 |
10 | import Button from '../../components/UI/Button/Button';
11 | import OrderSummary from './OrderSummary/OrderSummary';
12 | import ContactForm from './ContactForm/ContactForm';
13 | import ScrollToTopOnMount from '../../shared/ScrollToTopOnMount';
14 |
15 | class Cart extends Component {
16 | state = {
17 | orderSummaryAccepted: false
18 | };
19 |
20 | componentDidMount() {
21 | this.props.calculateOrder();
22 | };
23 |
24 | componentDidUpdate() {
25 | this.props.calculateOrder();
26 | };
27 |
28 | acceptOrder = () => {
29 | if (this.props.isAuth) {
30 | this.setState({
31 | orderSummaryAccepted: true
32 | });
33 | } else {
34 | this.props.history.push('/auth');
35 | };
36 | };
37 |
38 | render() {
39 | const { cartItems, clearCart, isAuth, purchased } = this.props;
40 |
41 | let selected = You select {cartItems.length} products.
42 | if (cartItems.length === 1) selected = You select 1 product.
;
43 |
44 | let list;
45 | (cartItems.length === 0) ? list = You do not have any products on the list yet.
:
46 | list = (
47 |
48 | {cartItems.map(item => {
49 | const { id, img, title, size, price, total, amount } = item;
50 | const { remove, handleProductAmount } = this.props;
51 |
52 | return (
53 |
54 |
55 |
56 |
57 |
58 |
59 |
{title}
60 |
Size: {size}
61 |
Quantity:
62 |
63 | handleProductAmount(id, 'decrement')} className="size">-
64 | {amount}
65 | handleProductAmount(id, 'increment')} className="size">+
66 |
67 |
Price: {price}.00 $
68 |
Total: {total}.00 $
69 |
remove(id)} btnType="small">Remove
70 |
71 |
72 |
73 | )
74 | })}
75 |
76 | );
77 |
78 | return (
79 | <>
80 |
81 |
82 |
Shopping Cart
83 | {selected}
84 | {cartItems.length > 0 &&
85 |
Clear Cart
86 | }
87 |
88 | {list}
89 |
90 | {cartItems.length > 0 && }
91 | {cartItems.length > 0 && this.state.orderSummaryAccepted && }
92 |
93 |
94 | {purchased &&
}
95 |
96 | >
97 | );
98 | }
99 | };
100 |
101 | Cart.propTypes = {
102 | cartItems: PropTypes.array.isRequired,
103 | purchased: PropTypes.bool.isRequired,
104 | isAuth: PropTypes.bool.isRequired,
105 | remove: PropTypes.func.isRequired,
106 | handleProductAmount: PropTypes.func.isRequired,
107 | calculateOrder: PropTypes.func.isRequired,
108 | clearCart: PropTypes.func.isRequired,
109 | };
110 |
111 | const mapStateToProps = state => {
112 | return {
113 | cartItems: state.products.cart,
114 | purchased: state.order.purchased,
115 | isAuth: state.auth.token !== null
116 | };
117 | };
118 |
119 | const mapDispatchToProps = dispatch => {
120 | return {
121 | remove: id => dispatch(actions.removeCartItem(id)),
122 | handleProductAmount: (id, value) => dispatch(actions.handleProductAmount(id, value)),
123 | calculateOrder: () => dispatch(actions.calculateOrder()),
124 | clearCart: () => dispatch(actions.clearCart())
125 | };
126 | };
127 |
128 | export default connect(mapStateToProps, mapDispatchToProps)(Cart);
--------------------------------------------------------------------------------
/src/containers/Cart/Cart.scss:
--------------------------------------------------------------------------------
1 | @import '../../index.scss';
2 |
3 | .cart-container {
4 | padding: 0 .5em;
5 |
6 | .content-wrapper {
7 | @include mediaMd {
8 | display: grid;
9 | grid-template-columns: 60% 40%;
10 | grid-gap: 1em;
11 | }
12 | }
13 | }
14 |
15 | .cart-list {
16 | max-width: 360px;
17 | margin: 0 auto;
18 | margin-top: 1em;
19 | list-style: none;
20 |
21 | @include mediaMd {
22 | max-width: 500px;
23 | margin: auto 0;
24 | margin-top: 1em;
25 | }
26 | }
27 |
28 | .bold {
29 | font-weight: $fw-semibold;
30 | }
31 |
32 | .cart-item {
33 | display: flex;
34 | margin-bottom: 1.2em;
35 |
36 | .img-wrapper {
37 | flex: 1;
38 | padding-top: .4em;
39 | }
40 |
41 | .cart-item-content {
42 | flex: 1.2;
43 | padding-left: .4em;
44 |
45 | .name {
46 | font-size: .9em;
47 | font-weight: $fw-medium;
48 |
49 | @include mediaMd {
50 | font-size: 1.1em;
51 | }
52 | }
53 |
54 | .value {
55 | font-size: .9em;
56 |
57 | @include mediaMd {
58 | font-size: 1em;
59 | }
60 | }
61 |
62 | .button-wrapper {
63 | display: flex;
64 | }
65 |
66 | .size {
67 | display: block;
68 | width: 25px;
69 | height: 25px;
70 | margin-right: .5em;
71 | background: none;
72 | border: 1px solid #000;
73 | line-height: 1.5em;
74 | text-align: center;
75 | transition: all .2s ease-in-out;
76 | outline: none;
77 | }
78 |
79 | button.size:hover {
80 | background: #000;
81 | color: #fff;
82 | cursor: pointer;
83 | }
84 |
85 | button.size:disabled {
86 | border: 1px solid #666;
87 | background: transparent;
88 | color: #666;
89 | cursor: no-drop;
90 | }
91 | }
92 | }
93 |
94 |
--------------------------------------------------------------------------------
/src/containers/Cart/ContactForm/ContactForm.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import './ContactForm.scss';
3 | import axios from '../../../axios';
4 | import { connect } from 'react-redux';
5 | import ErrorHandler from '../../../hoc/ErrorHandler';
6 | import * as actions from '../../../store/actions';
7 | import { checkValidity } from '../../../shared/Validity';
8 | import PropTypes from 'prop-types';
9 |
10 | import Button from '../../../components/UI/Button/Button';
11 | import Spinner from '../../../components/UI/Spinner/Spinner';
12 | import Input from '../../../components/UI/Input/Input';
13 |
14 | class ContactForm extends Component {
15 | state = {
16 | orderForm: {
17 | name: {
18 | elementType: 'input',
19 | elementConfig: {
20 | type: 'text',
21 | placeholder: 'Name'
22 | },
23 | value: '',
24 | validation: {
25 | required: true
26 | },
27 | valid: false,
28 | touched: false
29 | },
30 | street: {
31 | elementType: 'input',
32 | elementConfig: {
33 | type: 'text',
34 | placeholder: 'Street'
35 | },
36 | value: '',
37 | validation: {
38 | required: true
39 | },
40 | valid: false,
41 | touched: false
42 | },
43 | zipCode: {
44 | elementType: 'input',
45 | elementConfig: {
46 | type: 'number',
47 | placeholder: 'ZIP Code'
48 | },
49 | value: '',
50 | validation: {
51 | required: true,
52 | minLength: 5,
53 | maxLength: 5
54 | },
55 | valid: false,
56 | touched: false
57 | },
58 | country: {
59 | elementType: 'input',
60 | elementConfig: {
61 | type: 'text',
62 | placeholder: 'Country'
63 | },
64 | value: '',
65 | validation: {
66 | required: true
67 | },
68 | valid: false,
69 | touched: false
70 | },
71 | email: {
72 | elementType: 'input',
73 | elementConfig: {
74 | type: 'text',
75 | placeholder: 'E-mail'
76 | },
77 | value: '',
78 | validation: {
79 | required: true,
80 | isEmail: true
81 | },
82 | valid: false,
83 | touched: false
84 | },
85 | deliveryMethod: {
86 | elementType: 'select',
87 | elementConfig: {
88 | options: [
89 | { value: 'fastest', displayValue: 'Fastest' },
90 | { value: 'cheapest', displayValue: 'Cheapest' },
91 | ]
92 | },
93 | value: 'fastest',
94 | valid: true,
95 | validation: {}
96 | },
97 | },
98 | formIsValid: false
99 | };
100 |
101 | orderHandler = (e) => {
102 | e.preventDefault();
103 |
104 | const formData = {};
105 | for (let formElementIndentifier in this.state.orderForm) {
106 | formData[formElementIndentifier] = this.state.orderForm[formElementIndentifier].value
107 | }
108 |
109 | const order = {
110 | products: this.props.cartItems,
111 | price: this.props.price,
112 | orderData: formData,
113 | userId: this.props.userId
114 | };
115 |
116 | this.props.purchaseOrder(order, this.props.token);
117 | };
118 |
119 | // ====== Immutably changind input values ======
120 | inputChangedHandler = (e, inputIndentifier) => {
121 | // clone of orderForm
122 | const updatedOrderForm = {
123 | ...this.state.orderForm
124 | };
125 | // clone each elements (name, street, ...)
126 | const updatedFormElement = {
127 | ...updatedOrderForm[inputIndentifier]
128 | };
129 |
130 | // listener for each value of elements
131 | updatedFormElement.value = e.target.value;
132 |
133 | // VALIDATION
134 | updatedFormElement.valid = checkValidity(updatedFormElement.value, updatedFormElement.validation);
135 | updatedFormElement.touched = true;
136 |
137 | let formIsValid = true;
138 | for (let inputIdentifier in updatedOrderForm) {
139 | formIsValid = updatedOrderForm[inputIdentifier].valid && formIsValid;
140 | };
141 |
142 | // change each element value in orderForm elements
143 | updatedOrderForm[inputIndentifier] = updatedFormElement;
144 |
145 | this.setState({
146 | orderForm: updatedOrderForm,
147 | formIsValid
148 | });
149 | };
150 |
151 | render() {
152 | // convert object of objects into array of objects
153 | const formElementsArray = [];
154 | for (let key in this.state.orderForm) { // keys are name, street, ...
155 | formElementsArray.push({
156 | id: key,
157 | config: this.state.orderForm[key]
158 | });
159 | };
160 |
161 | let form = (
162 |
163 | Enter Your Contact Data
164 | {/* */}
165 | {formElementsArray.map(formElement => (
166 | this.inputChangedHandler(e, formElement.id)}
175 | />
176 | ))}
177 | Order
181 |
182 | );
183 |
184 | if (this.props.loading) {
185 | form =
186 | }
187 |
188 | return form;
189 | }
190 | };
191 |
192 | ContactForm.propTypes = {
193 | cartItems: PropTypes.array.isRequired,
194 | price: PropTypes.number.isRequired,
195 | loading: PropTypes.bool.isRequired,
196 | token: PropTypes.string,
197 | userId: PropTypes.string,
198 | purchaseOrder: PropTypes.func.isRequired
199 | };
200 |
201 | const mapStateToProps = state => {
202 | return {
203 | cartItems: state.products.cart,
204 | price: state.products.orderTotal,
205 | loading: state.order.loading,
206 | token: state.auth.token,
207 | userId: state.auth.userId
208 | };
209 | };
210 |
211 | const mapDispatchToProps = dispatch => {
212 | return {
213 | purchaseOrder: (orderData, token) => dispatch(actions.purchaseOrder(orderData, token))
214 | };
215 | };
216 |
217 | export default connect(mapStateToProps, mapDispatchToProps)(ErrorHandler(ContactForm, axios));
--------------------------------------------------------------------------------
/src/containers/Cart/ContactForm/ContactForm.scss:
--------------------------------------------------------------------------------
1 | @import '../../../index.scss';
2 |
3 | .contact-form {
4 | max-width: 360px;
5 | margin: 0 auto;
6 | margin-top: 1.4em;
7 |
8 | @include mediaMd {
9 | max-width: 400px;
10 | margin-right: 1.6em;
11 | }
12 | }
13 |
14 | .title {
15 | font-size: 1.15em;
16 | margin-top: .8em;
17 |
18 | @include mediaMd {
19 | font-size: 1.2em;
20 | }
21 | }
--------------------------------------------------------------------------------
/src/containers/Cart/OrderSummary/OrderSummary.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import './OrderSummary.scss';
3 | import { connect } from 'react-redux';
4 | import PropTypes from 'prop-types';
5 |
6 | import Button from '../../../components/UI/Button/Button';
7 |
8 | class OrderSummary extends Component {
9 | render() {
10 | const { priceTotal, delivery, orderTotal, acceptOrder } = this.props;
11 |
12 | return (
13 |
14 |
Order Summary
15 |
Free delivery below three products.
16 |
17 |
Total products price:
18 |
{priceTotal}.00 $
19 |
20 |
21 |
Delivery:
22 |
{delivery}.00 $
23 |
24 |
25 |
Order total:
26 |
{orderTotal}.00 $
27 |
28 |
{this.props.isAuth ? 'To Payment' : 'Sign Up to Order'}
29 |
30 | )
31 | }
32 | };
33 |
34 | OrderSummary.propTypes = {
35 | priceTotal: PropTypes.number.isRequired,
36 | delivery: PropTypes.number.isRequired,
37 | orderTotal: PropTypes.number.isRequired
38 | };
39 |
40 | const mapStateToProps = state => {
41 | return {
42 | priceTotal: state.products.priceTotal,
43 | delivery: state.products.delivery,
44 | orderTotal: state.products.orderTotal
45 | }
46 | };
47 |
48 | export default connect(mapStateToProps)(OrderSummary);
--------------------------------------------------------------------------------
/src/containers/Cart/OrderSummary/OrderSummary.scss:
--------------------------------------------------------------------------------
1 | @import '../../../index.scss';
2 |
3 | .order-summary {
4 | max-width: 360px;
5 | margin: 0 auto;
6 |
7 | @include mediaMd {
8 | max-width: 400px;
9 | margin-right: 1.6em;
10 | }
11 |
12 | .title {
13 | font-size: 1.15em;
14 | margin-top: .8em;
15 |
16 | @include mediaMd {
17 | font-size: 1.3em;
18 | }
19 | }
20 |
21 | .info {
22 | font-size: .8em;
23 | }
24 |
25 | .delivery-info {
26 | margin-bottom: 1.4em;
27 | }
28 |
29 | .promo-info {
30 | margin-top: 1.4em;
31 | }
32 |
33 | .wrapper {
34 | display: flex;
35 | justify-content: space-between;
36 | align-items: center;
37 | margin-top: .2em;
38 |
39 | .subtitle {
40 | font-size: 1em;
41 | }
42 |
43 | .value {
44 | font-size: 1em;
45 | font-weight: $fw-medium;
46 | }
47 |
48 | input {
49 | border: none;
50 | border-bottom: 1px solid #000;
51 | outline: none;
52 |
53 | &::placeholder {
54 | color: #000;
55 | }
56 | }
57 | }
58 | }
--------------------------------------------------------------------------------
/src/containers/Contact/Contact.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './Contact.scss';
3 | import ScrollToTopOnMount from '../../shared/ScrollToTopOnMount';
4 |
5 | const contact = () => (
6 | <>
7 |
8 |
9 |
Contact
10 |
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec eleifend ligula neque, at faucibus metus rutrum sed. Fusce interdum at est eget aliquet. Suspendisse potenti. Curabitur ac luctus magna. Donec eleifend ligula neque, at faucibus metus rutrum sed. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
11 |
Phone number:
12 |
555-444-333 (free connection)
13 |
Work hours:
14 |
Monday - Friday: 9.00 - 20.00
15 |
Saturday - SUnday: 10.00 - 16.00
16 |
17 | >
18 | );
19 |
20 | export default contact;
--------------------------------------------------------------------------------
/src/containers/Contact/Contact.scss:
--------------------------------------------------------------------------------
1 | @import '../../index.scss';
2 |
3 | .contact-container {
4 | padding: 0 .5em;
5 |
6 | .title {
7 | font-size: 1.1em;
8 | margin-top: .8em;
9 |
10 | @include mediaMd {
11 | font-size: 1.2em;
12 | }
13 | }
14 | }
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/containers/Details/DetailItem/DetailItem.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import './DetailItem.scss';
3 | import { connect } from 'react-redux';
4 | import { Redirect } from 'react-router-dom';
5 | import PropTypes from 'prop-types';
6 |
7 | class DetailItem extends Component {
8 | render() {
9 | if (this.props.detailProduct === null) return ;
10 |
11 | const { title, subtitle, img, description, price } = this.props.detailProduct;
12 |
13 | return (
14 |
15 | {title}
16 | {subtitle}
17 |
18 |
19 |
20 |
21 |
22 |
Description:
23 |
{description}
24 |
Price: {price}.00 $
25 | {this.props.children}
26 |
27 |
28 |
29 | )
30 | }
31 | };
32 |
33 | DetailItem.propTypes = {
34 | detailProduct: PropTypes.object
35 | };
36 |
37 | const mapStateToProps = state => {
38 | return {
39 | detailProduct: state.products.detailProduct
40 | };
41 | };
42 |
43 | export default connect(mapStateToProps)(DetailItem);
--------------------------------------------------------------------------------
/src/containers/Details/DetailItem/DetailItem.scss:
--------------------------------------------------------------------------------
1 | @import '../../../index.scss';
2 |
3 | .detail-item {
4 | margin-top: 1.3em;
5 |
6 | .detail-title {
7 | font-weight: $fw-medium;
8 |
9 | @include mediaMd {
10 | font-size: 1.35em;
11 | }
12 | }
13 |
14 | .detail-content {
15 | margin-top: .4em;
16 |
17 | @include mediaMd {
18 | display: flex;
19 | }
20 |
21 | .detail-img-wrapper {
22 | flex: 1;
23 | padding-top: .4em;
24 | }
25 |
26 | .detail-info {
27 | flex: 1;
28 |
29 | @include mediaMd {
30 | padding-left: .8em;
31 | }
32 | }
33 | }
34 |
35 | .detail-subtitle {
36 | font-weight: $fw-medium;
37 | font-size: 1.1em;
38 |
39 | &:not(:nth-child(1)) {
40 | margin-top: .4em;
41 | }
42 | }
43 |
44 | .detail-value {
45 | font-size: .9em;
46 |
47 | @include mediaMd {
48 | font-size: 1em;
49 | }
50 | }
51 | }
--------------------------------------------------------------------------------
/src/containers/Details/Details.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import './Details.scss';
3 | import { connect } from 'react-redux';
4 | import * as actions from '../../store/actions';
5 | import { Redirect } from 'react-router-dom';
6 | import PropTypes from 'prop-types';
7 |
8 | import DetailItem from './DetailItem/DetailItem';
9 | import Button from '../../components/UI/Button/Button';
10 | import Modal from '../../components/UI/Modal/Modal';
11 | import ScrollToTopOnMount from '../../shared/ScrollToTopOnMount';
12 |
13 | class Details extends Component {
14 | state = {
15 | value: ''
16 | };
17 |
18 | handleChange = e => {
19 | this.setState({
20 | value: e.target.value
21 | });
22 | };
23 |
24 | handleAddToCart = () => {
25 | (this.state.value === '') ?
26 | this.props.openModal() :
27 | this.props.addToCart(this.props.detailProduct.id, this.state.value);
28 | };
29 |
30 | render() {
31 | const { detailProduct, modalShowed, closeModal, addToWishlist } = this.props;
32 | const { value } = this.state;
33 |
34 | if (!detailProduct) return
35 |
36 | return (
37 | <>
38 |
39 |
44 | You must select size.
45 | Got it
46 |
47 |
117 | >
118 | )
119 | }
120 | };
121 |
122 | Details.propTypes = {
123 | modalShowed: PropTypes.bool.isRequired,
124 | detailProduct: PropTypes.object,
125 | openModal: PropTypes.func.isRequired,
126 | closeModal: PropTypes.func.isRequired,
127 | addToCart: PropTypes.func.isRequired,
128 | addToWishlist: PropTypes.func.isRequired
129 | };
130 |
131 | const mapStateToProps = state => {
132 | return {
133 | modalShowed: state.interface.modalShowed,
134 | detailProduct: state.products.detailProduct
135 | }
136 | };
137 |
138 | const mapDispatchToProps = dispatch => {
139 | return {
140 | openModal: id => dispatch(actions.openModal(id)),
141 | closeModal: () => dispatch(actions.closeModal()),
142 | addToCart: (id, size) => dispatch(actions.addToCart(id, size)),
143 | addToWishlist: id => dispatch(actions.addToWishlist(id))
144 | }
145 | };
146 |
147 | export default connect(mapStateToProps, mapDispatchToProps)(Details);
--------------------------------------------------------------------------------
/src/containers/Details/Details.scss:
--------------------------------------------------------------------------------
1 | @import '../../index.scss';
2 |
3 | .details-container {
4 | padding: 0 .5em;
5 |
6 | .detail-subtitle {
7 | font-weight: $fw-medium;
8 | font-size: 1.1em;
9 |
10 | &:not(:nth-child(1)) {
11 | margin-top: .4em;
12 | }
13 | }
14 |
15 | .switch-field {
16 | margin: .4em 0;
17 | }
18 |
19 | .switch-field input {
20 | position: absolute;
21 | height: 1px;
22 | width: 1px;
23 | border: 0;
24 | overflow: hidden;
25 | }
26 |
27 | .switch-field label {
28 | display: inline-block;
29 | width: 40px;
30 | font-size: 14px;
31 | text-align: center;
32 | padding: 8px;
33 | margin-right: .7em;
34 | border: 1px solid #000;
35 | transition: all 0.1s ease-in-out;
36 | cursor: pointer;
37 | }
38 |
39 | .switch-field input:checked + label {
40 | background-color: #000;
41 | color: #fff;
42 | }
43 |
44 | .button-wrapper {
45 | display: flex;
46 | flex-direction: column;
47 | }
48 | }
--------------------------------------------------------------------------------
/src/containers/HomePage/HomePage.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import './HomePage.scss';
3 | import { connect } from 'react-redux';
4 | import * as actions from '../../store/actions';
5 | import PropTypes from 'prop-types';
6 |
7 | import Modal from '../../components/UI/Modal/Modal';
8 | import Button from '../../components/UI/Button/Button';
9 | import ScrollToTopOnMount from '../../shared/ScrollToTopOnMount';
10 |
11 | import saleBg from '../../assets/home_page/Sale.jpg';
12 | import slideOne from '../../assets/home_page/slide_1.jpg';
13 | import slideTwo from '../../assets/home_page/slide_2.jpg';
14 | import slideThree from '../../assets/home_page/slide_3.jpg';
15 | import slideFour from '../../assets/home_page/slide_4.jpg';
16 | import slideFive from '../../assets/home_page/slide_5.jpg';
17 |
18 | const slides = [];
19 | slides.push(slideOne, slideTwo, slideThree, slideFour, slideFive);
20 |
21 | class HomePage extends Component {
22 | closeModal = () => {
23 | this.props.purchaseInit();
24 | this.props.closeModal();
25 | };
26 |
27 | render() {
28 | return (
29 | <>
30 |
31 |
36 | Order completed successfully.
37 | Got it
38 |
39 |
40 |
41 |
Stylish black and white clothes for every occasion.
42 |
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec eleifend ligula neque, at faucibus metus rutrum sed. Fusce interdum at est eget aliquet. Suspendisse potenti. Curabitur ac luctus magna.
43 |
44 |
45 |
Sale
46 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Odit dolores hic sint excepturi, omnis minus. Perferendis, velit ut? In, voluptas.
47 |
48 |
49 | {slides.map(slide => (
50 |
51 |
New Collection
52 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Voluptas, voluptate!
53 |
54 | ))}
55 |
56 |
57 | >
58 | )
59 | }
60 | };
61 |
62 | HomePage.propTypes = {
63 | purchased: PropTypes.bool.isRequired,
64 | purchaseInit: PropTypes.func.isRequired,
65 | closeModal: PropTypes.func.isRequired
66 | };
67 |
68 | const mapStateToProps = state => {
69 | return {
70 | purchased: state.order.purchased
71 | };
72 | };
73 |
74 | const mapDispatchToProps = dispatch => {
75 | return {
76 | purchaseInit: () => dispatch(actions.purchaseInit()),
77 | closeModal: () => dispatch(actions.closeModal())
78 | };
79 | };
80 |
81 | export default connect(mapStateToProps, mapDispatchToProps)(HomePage);
--------------------------------------------------------------------------------
/src/containers/HomePage/HomePage.scss:
--------------------------------------------------------------------------------
1 | @import '../../index.scss';
2 |
3 | .home-container {
4 | width: 100%;
5 | max-width: 1200px;
6 |
7 | .showcase {
8 | padding: 0 .5em;
9 |
10 | .main-title {
11 | text-align: center;
12 | }
13 |
14 | .main-info {
15 | text-align: center;
16 | display: none;
17 |
18 | @include mediaMd {
19 | display: block;
20 | }
21 | }
22 | }
23 |
24 | .sale {
25 | height: 350px;
26 | background-size: cover;
27 | background-position: center;
28 | color: #fff;
29 | padding: 1em;
30 | margin: 1.2em 0 1.8em 0;
31 |
32 | @include mediaMd {
33 | height: calc(400px + 16vw);
34 | }
35 |
36 | .title {
37 | font-size: 2.6em;
38 | text-transform: uppercase;
39 | font-weight: $fw-bold;
40 | margin-top: 1.6em;
41 |
42 | @include mediaMd {
43 | font-size: 3.6em;
44 | }
45 | }
46 |
47 | .subtitle {
48 | max-width: 370px;
49 |
50 | @include mediaMd {
51 | max-width: 450px;
52 | }
53 | }
54 | }
55 | }
56 |
57 | $slides: 5;
58 | $slideAnimation: 4s;
59 | $totalAnimationTime: 20s;
60 |
61 | .slider {
62 | width: 100%;
63 | height: 350px;
64 | overflow: hidden;
65 | position: relative;
66 |
67 | @include mediaMd {
68 | height: calc(400px + 16vw);
69 | }
70 |
71 | .slide {
72 | width: 100%;
73 | height: 100%;
74 | background-position: center;
75 | background-size: cover;
76 | position: absolute;
77 | animation: anime 20s infinite;
78 | opacity: 0;
79 | padding-left: 1em;
80 | display: flex;
81 | flex-direction: column;
82 | align-items: flex-start;
83 | }
84 |
85 | .slide:nth-child(1) {
86 | animation-delay: 16s;
87 | color: #fff;
88 | }
89 |
90 | .slide:nth-child(2) {
91 | animation-delay: 12s;
92 | color: #fff;
93 | }
94 |
95 | .slide:nth-child(3) {
96 | animation-delay: 8s;
97 | }
98 |
99 | .slide:nth-child(4) {
100 | animation-delay: 4s;
101 | color: #fff;
102 | }
103 |
104 | .slide:nth-child(5) {
105 | animation-delay: 0s;
106 | }
107 |
108 | .title {
109 | font-size: 1.6em;
110 | text-transform: uppercase;
111 | font-weight: $fw-bold;
112 | margin-top: 5em;
113 |
114 | @include mediaMd {
115 | font-size: 3.6em;
116 | margin-top: 5em;
117 | }
118 | }
119 |
120 | .subtitle {
121 | font-size: 1em;
122 | max-width: 260px;
123 | }
124 | }
125 |
126 | @keyframes anime {
127 | 25%{
128 | opacity:1;
129 | }
130 | 40%{
131 | opacity:0;
132 | }
133 | }
--------------------------------------------------------------------------------
/src/containers/Orders/Order/Order.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './Order.scss';
3 |
4 | const order = (props) => {
5 | return (
6 |
7 |
8 | {props.products.map(product => (
9 |
10 | Name: {product.title}
11 | Size: {product.size}
12 | Amount: {product.amount}
13 |
14 |
15 | ))}
16 |
17 | Total Price: {props.price}.00 $
18 |
19 | );
20 | };
21 |
22 | export default order;
--------------------------------------------------------------------------------
/src/containers/Orders/Order/Order.scss:
--------------------------------------------------------------------------------
1 | @import '../../../index.scss';
2 |
3 | .order-item {
4 | margin-top: 2.8em;
5 | }
6 |
7 | .order-price {
8 | font-weight: $fw-medium;
9 | margin-top: .6em;
10 | }
11 |
12 | span.bold {
13 | font-weight: $fw-medium;
14 | }
--------------------------------------------------------------------------------
/src/containers/Orders/Orders.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import './Orders.scss';
3 | import axios from '../../axios';
4 | import ErrorHandler from '../../hoc/ErrorHandler';
5 | import { connect } from 'react-redux';
6 | import * as actions from '../../store/actions';
7 | import PropTypes from 'prop-types';
8 |
9 | import Spinner from '../../components/UI/Spinner/Spinner';
10 | import Order from './Order/Order';
11 | import ScrollToTopOnMount from '../../shared/ScrollToTopOnMount';
12 |
13 | class Orders extends Component {
14 | componentDidMount() {
15 | this.props.fetchOrders(this.props.token, this.props.userId);
16 | };
17 |
18 | render() {
19 | let orders = ;
20 | if (!this.props.loading) {
21 | (this.props.orders.length === 0)
22 | ? orders = You do not have any orders yet.
23 | : orders = this.props.orders.map(order => (
24 |
25 | ))
26 | };
27 |
28 | return (
29 | <>
30 |
31 |
32 |
Your Orders
33 |
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec eleifend ligula neque, at faucibus metus trum sedru.
34 |
37 |
38 | >
39 | );
40 | }
41 | };
42 |
43 | Orders.propTypes = {
44 | orders: PropTypes.array.isRequired,
45 | loading: PropTypes.bool.isRequired,
46 | token: PropTypes.string.isRequired,
47 | userId: PropTypes.string.isRequired,
48 | fetchOrders: PropTypes.func.isRequired
49 | };
50 |
51 | const mapStateToProps = state => {
52 | return {
53 | orders: state.order.orders,
54 | loading: state.order.loading,
55 | token: state.auth.token,
56 | userId: state.auth.userId
57 | };
58 | };
59 |
60 | const mapDispatchToProps = dispatch => {
61 | return {
62 | fetchOrders: (token, userId) => dispatch(actions.fetchOrders(token, userId))
63 | };
64 | };
65 |
66 | export default connect(mapStateToProps, mapDispatchToProps)(ErrorHandler(Orders, axios));
--------------------------------------------------------------------------------
/src/containers/Orders/Orders.scss:
--------------------------------------------------------------------------------
1 | @import '../../index.scss';
2 |
3 | .orders-container {
4 | padding: 0 .5em;
5 |
6 | .order-list {
7 | margin-top: 1.8em;
8 | }
9 | }
--------------------------------------------------------------------------------
/src/containers/ProductList/Product/Product.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import './Product.scss';
3 | import { Link } from 'react-router-dom';
4 |
5 | import Button from '../../../components/UI/Button/Button';
6 | import Spinner from '../../../components/UI/Spinner/Spinner';
7 |
8 | class Product extends Component {
9 | state = {
10 | isLoaded: false,
11 | isError: false
12 | };
13 |
14 | componentDidMount() {
15 | this.load(this.img);
16 | };
17 |
18 | load = img => {
19 | let image = img;
20 | image.src = this.props.product.img;
21 | image.onload = () => {
22 | this.onImageLoaded();
23 | };
24 |
25 | image.onError = () => {
26 | this.onImageLoadError(this.props.product.img);
27 | };
28 | };
29 |
30 | onImageLoaded = () => {
31 | this.setState({
32 | isLoaded: true
33 | });
34 | };
35 |
36 | onImageLoadError = () => {
37 | this.setState({
38 | isError: true
39 | });
40 | };
41 |
42 | render() {
43 | const { id, img, price, title } = this.props.product;
44 |
45 | return (
46 |
47 |
48 | {!this.state.isLoaded &&
}
49 |
this.img = img} onClick={() => this.props.showModal(id)} className="product-img" src={img} alt={title} />
50 |
51 |
{title}
52 |
Price: {price}.00 $
53 |
54 |
55 | this.props.showDetails(id)}
57 | btnType="mobile">Show Details
58 |
59 |
60 |
61 |
62 | )
63 | }
64 | };
65 |
66 | export default Product;
--------------------------------------------------------------------------------
/src/containers/ProductList/Product/Product.scss:
--------------------------------------------------------------------------------
1 | @import '../../../index.scss';
2 |
3 | .product {
4 |
5 | .info {
6 | font-size: .75em;
7 | font-weight: $fw-medium;
8 | margin-top: 5px;
9 | line-height: 1.1em;
10 |
11 | @include mediaSm {
12 | font-size: .95em;
13 | }
14 | }
15 |
16 | .img-wrapper {
17 | overflow: hidden;
18 |
19 | .product-img {
20 | transition: all .1s ease-in-out;
21 | cursor: pointer;
22 |
23 | &:hover {
24 | transform: scale(1.05);
25 | }
26 | }
27 | }
28 |
29 | & * {
30 | font-size: 14px;
31 | }
32 | }
--------------------------------------------------------------------------------
/src/containers/ProductList/ProductList.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import './ProductList.scss';
3 | import { Redirect, Link } from 'react-router-dom';
4 | import { connect } from 'react-redux';
5 | import * as actions from '../../store/actions';
6 | import PropTypes from 'prop-types';
7 |
8 | import SideNavigation from '../../components/Navigation/NavigationItems/SideNavigation';
9 | import Product from './Product/Product';
10 | import Modal from '../../components/UI/Modal/Modal';
11 | import Button from '../../components/UI/Button/Button';
12 | import ScrollToTopOnMount from '../../shared/ScrollToTopOnMount';
13 |
14 | class ProductList extends Component {
15 | state = {
16 | checkboxValue: 'relevance',
17 | };
18 |
19 | handleChange = (e) => {
20 | this.props.handleCheckboxValue(e.target.value);
21 | this.props.handleDirection();
22 | this.props.sortProducts('price', 'id');
23 | };
24 |
25 | showDetailsByModal = id => {
26 | this.props.showDetails(id);
27 | this.props.closeModal();
28 | }
29 |
30 | render() {
31 | const { products, modalShowed, closeModal, openModal, showDetails, checkboxValue } = this.props;
32 |
33 | if (!products || products.length === 0) {
34 | return
35 | };
36 |
37 | const { title, img, subtitle, price, id } = this.props.modalProduct;
38 |
39 | return (
40 | <>
41 |
42 |
43 |
47 | x
48 | {title}
49 |
50 | Info:
51 | {subtitle}
52 | Price: {price}.00 $
53 | Sizes: S, M, L, XL, XXL
54 |
55 |
56 | this.showDetailsByModal(id)}>Show Details
57 |
58 |
59 |
60 |
61 | Sort by:
62 |
63 | Relevance
64 | Price - low to high
65 | Price - high to low
66 |
67 |
Products amount: {products.length}
68 |
69 |
70 |
71 |
72 |
73 |
74 | {products.map(product => (
75 |
81 | ))}
82 |
83 |
84 |
85 | >
86 | );
87 | };
88 | };
89 |
90 | ProductList.propTypes = {
91 | products: PropTypes.array.isRequired,
92 | modalShowed: PropTypes.bool.isRequired,
93 | modalProduct: PropTypes.object,
94 | checkboxValue: PropTypes.string.isRequired,
95 | openModal: PropTypes.func.isRequired,
96 | closeModal: PropTypes.func.isRequired,
97 | sortProducts: PropTypes.func.isRequired,
98 | handleDirection: PropTypes.func.isRequired,
99 | handleCheckboxValue: PropTypes.func.isRequired,
100 | showDetails: PropTypes.func.isRequired,
101 | };
102 |
103 | const mapStateToProps = state => {
104 | return {
105 | products: state.products.products,
106 | modalShowed: state.interface.modalShowed,
107 | modalProduct: state.interface.modalProduct,
108 | checkboxValue: state.products.sortCheckboxValue
109 | };
110 | };
111 |
112 | const mapDispatchToProps = dispatch => {
113 | return {
114 | openModal: id => dispatch(actions.openModal(id)),
115 | closeModal: () => dispatch(actions.closeModal()),
116 | sortProducts: (priceKey, idKey) => dispatch(actions.sortProducts(priceKey, idKey)),
117 | handleDirection: () => dispatch(actions.handleDirection()),
118 | handleCheckboxValue: value => dispatch(actions.handleCheckboxValue(value)),
119 | showDetails: id => dispatch(actions.showDetails(id))
120 | };
121 | };
122 |
123 | export default connect(mapStateToProps, mapDispatchToProps)(ProductList);
--------------------------------------------------------------------------------
/src/containers/ProductList/ProductList.scss:
--------------------------------------------------------------------------------
1 | @import '../../index.scss';
2 |
3 | .product-container {
4 | padding: 0 .5em;
5 | }
6 |
7 | .filter-panel {
8 | font-weight: $fw-medium;
9 | margin-bottom: .8em;
10 |
11 | select {
12 | height: 26px;
13 | font-size: .95em;
14 | margin-left: 8px;
15 | outline: none;
16 | background: #fff;
17 | border: 1px solid #000;
18 | }
19 |
20 | .products-amount {
21 | font-weight: $fw-medium;
22 | margin: .3em 0;
23 | }
24 | }
25 |
26 | .product-list-wrapper {
27 | display: grid;
28 | grid-template-columns: 1fr;
29 |
30 | @include mediaMd {
31 | grid-template-columns: 17% 83%;
32 | }
33 | }
34 |
35 | .product-list {
36 | display: grid;
37 | grid-template-columns: 1fr 1fr;
38 | grid-column-gap: .5em;
39 | grid-row-gap: 1em;
40 |
41 | @include mediaMd {
42 | grid-column-gap: 1em;
43 | grid-row-gap: 2em;
44 | }
45 |
46 | @include mediaLg {
47 | grid-template-columns: 1fr 1fr 1fr;
48 | }
49 | }
--------------------------------------------------------------------------------
/src/containers/Wishlist/Wishlist.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './Wishlist.scss';
3 | import { connect } from 'react-redux';
4 | import * as actions from '../../store/actions';
5 | import TransitionGroup from 'react-transition-group/TransitionGroup';
6 | import CSSTransition from 'react-transition-group/CSSTransition';
7 | import PropTypes from 'prop-types';
8 |
9 | import WishlistItem from './WishlistItem/WishlistItem';
10 | import Button from '../../components/UI/Button/Button';
11 | import ScrollToTopOnMount from '../../shared/ScrollToTopOnMount';
12 |
13 | const wishlist = (props) => {
14 | let list;
15 | (props.wishlistItems.length === 0) ? list = You do not have any products on the list yet.
:
16 | list = (
17 |
18 | {props.wishlistItems.map(item => (
19 |
20 |
21 |
22 | ))}
23 |
24 | );
25 |
26 | return (
27 | <>
28 |
29 |
30 |
Wishlist
31 |
Lorem ipsum dolor adipiscing elit. Donec eleifend ligula neque, at faucibus metus trum sedru.
32 | {list}
33 | {props.wishlistItems.length > 0 &&
34 |
Clear Wishlist
35 | }
36 |
37 | >
38 | )
39 | };
40 |
41 | wishlist.propTypes = {
42 | wishlistItems: PropTypes.array.isRequired,
43 | clearWishlist: PropTypes.func.isRequired
44 | };
45 |
46 | const mapStateToProps = state => {
47 | return {
48 | wishlistItems: state.products.wishlist
49 | };
50 | };
51 |
52 | const mapDispatchToProps = dispatch => {
53 | return {
54 | clearWishlist: () => dispatch(actions.clearWishlist())
55 | };
56 | };
57 |
58 | export default connect(mapStateToProps, mapDispatchToProps)(wishlist);
--------------------------------------------------------------------------------
/src/containers/Wishlist/Wishlist.scss:
--------------------------------------------------------------------------------
1 | @import '../../index.scss';
2 |
3 | .wishlist-container {
4 | padding: 0 .5em;
5 | }
--------------------------------------------------------------------------------
/src/containers/Wishlist/WishlistItem/WishlistItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './WishlistItem.scss';
3 | import { connect } from 'react-redux';
4 | import * as actions from '../../../store/actions';
5 | import { Link } from 'react-router-dom';
6 | import PropTypes from 'prop-types';
7 |
8 | import Button from '../../../components/UI/Button/Button';
9 |
10 | const wishlistItem = (props) => {
11 | const { id, title, subtitle, img, description, price } = props.item;
12 |
13 | return (
14 |
15 | {title}
16 | {subtitle}
17 |
18 |
19 |
20 |
21 |
22 |
Description:
23 |
{description}
24 |
Price: {price}.00 $
25 |
26 |
27 | props.showDetails(id)}>Show Details
28 |
29 | props.removeWishlistItem(id)} btnType="dark">Remove
30 |
31 |
32 |
33 |
34 | );
35 | };
36 |
37 | wishlistItem.propTypes = {
38 | showDetails: PropTypes.func.isRequired,
39 | removeWishlistItem: PropTypes.func.isRequired
40 | };
41 |
42 | const mapDispatchToProps = dispatch => {
43 | return {
44 | showDetails: id => dispatch(actions.showDetails(id)),
45 | removeWishlistItem: id => dispatch(actions.removeWishlistItem(id))
46 | };
47 | };
48 |
49 | export default connect(null, mapDispatchToProps)(wishlistItem);
--------------------------------------------------------------------------------
/src/containers/Wishlist/WishlistItem/WishlistItem.scss:
--------------------------------------------------------------------------------
1 | @import '../../../index.scss';
2 |
3 | .wishlist-item {
4 | margin-top: 1.3em;
5 | margin-bottom: .6em;
6 |
7 | .wishlist-title {
8 | font-weight: $fw-medium;
9 |
10 | @include mediaMd {
11 | font-size: 1.35em;
12 | }
13 | }
14 |
15 | .wishlist-content {
16 | margin-top: .4em;
17 |
18 | @include mediaMd {
19 | display: flex;
20 | }
21 |
22 | .wishlist-img-wrapper {
23 | flex: 1;
24 | padding-top: .4em;
25 | }
26 |
27 | .wishlist-info {
28 | flex: 1;
29 |
30 | @include mediaMd {
31 | padding-left: .8em;
32 | }
33 | }
34 | }
35 |
36 | .wishlist-subtitle {
37 | font-weight: $fw-medium;
38 | font-size: 1.1em;
39 |
40 | &:not(:nth-child(1)) {
41 | margin-top: .4em;
42 | }
43 | }
44 |
45 | .wishlist-value {
46 | font-size: .9em;
47 |
48 | @include mediaMd {
49 | font-size: 1em;
50 | }
51 | }
52 | }
--------------------------------------------------------------------------------
/src/data/data.js:
--------------------------------------------------------------------------------
1 | // change to json file
2 |
3 | export const productList = [
4 | // WOMEN COATS
5 | {
6 | id: 101,
7 | img: '../img/coat-female.png',
8 | title: 'Womans coat one',
9 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
10 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
11 | price: 240,
12 | inCart: false,
13 | inWishlist: false,
14 | amount: 0,
15 | total: 0,
16 | size: null,
17 | category: 'women-coats',
18 | gender: 'female'
19 | },
20 | {
21 | id: 102,
22 | img: '../img/coat-female.png',
23 | title: 'Womans coat two',
24 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
25 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
26 | price: 260,
27 | inCart: false,
28 | inWishlist: false,
29 | amount: 0,
30 | total: 0,
31 | size: null,
32 | category: 'women-coats',
33 | gender: 'female'
34 | },
35 | {
36 | id: 103,
37 | img: '../img/coat-female.png',
38 | title: 'Womans coat three',
39 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
40 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
41 | price: 280,
42 | inCart: false,
43 | inWishlist: false,
44 | amount: 0,
45 | total: 0,
46 | size: null,
47 | category: 'women-coats',
48 | gender: 'female'
49 | },
50 | {
51 | id: 104,
52 | img: '../img/coat-female.png',
53 | title: 'Womans coat four',
54 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
55 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
56 | price: 210,
57 | inCart: false,
58 | inWishlist: false,
59 | amount: 0,
60 | total: 0,
61 | size: null,
62 | category: 'women-coats',
63 | gender: 'female'
64 | },
65 | {
66 | id: 105,
67 | img: '../img/coat-female.png',
68 | title: 'Womans coat five',
69 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
70 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
71 | price: 330,
72 | inCart: false,
73 | inWishlist: false,
74 | amount: 0,
75 | total: 0,
76 | size: null,
77 | category: 'women-coats',
78 | gender: 'female'
79 | },
80 | {
81 | id: 106,
82 | img: '../img/coat-female.png',
83 | title: 'Womans coat six',
84 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
85 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
86 | price: 440,
87 | inCart: false,
88 | inWishlist: false,
89 | amount: 0,
90 | total: 0,
91 | size: null,
92 | category: 'women-coats',
93 | gender: 'female'
94 | },
95 |
96 | // WOMEN JACKETS
97 | {
98 | id: 111,
99 | img: '../img/jacket-female.png',
100 | title: 'Womans jacket one',
101 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
102 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
103 | price: 300,
104 | inCart: false,
105 | inWishlist: false,
106 | amount: 0,
107 | total: 0,
108 | size: null,
109 | category: 'women-jackets',
110 | gender: 'female'
111 | },
112 | {
113 | id: 112,
114 | img: '../img/jacket-female.png',
115 | title: 'Womans jacket two',
116 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
117 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
118 | price: 290,
119 | inCart: false,
120 | inWishlist: false,
121 | amount: 0,
122 | total: 0,
123 | size: null,
124 | category: 'women-jackets',
125 | gender: 'female'
126 | },
127 | {
128 | id: 113,
129 | img: '../img/jacket-female.png',
130 | title: 'Womans jacket three',
131 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
132 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
133 | price: 280,
134 | inCart: false,
135 | inWishlist: false,
136 | amount: 0,
137 | total: 0,
138 | size: null,
139 | category: 'women-jackets',
140 | gender: 'female'
141 | },
142 | {
143 | id: 114,
144 | img: '../img/jacket-female.png',
145 | title: 'Womans jacket four',
146 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
147 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
148 | price: 260,
149 | inCart: false,
150 | inWishlist: false,
151 | amount: 0,
152 | total: 0,
153 | size: null,
154 | category: 'women-jackets',
155 | gender: 'female'
156 | },
157 | {
158 | id: 115,
159 | img: '../img/jacket-female.png',
160 | title: 'Womans jacket five',
161 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
162 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
163 | price: 230,
164 | inCart: false,
165 | inWishlist: false,
166 | amount: 0,
167 | total: 0,
168 | size: null,
169 | category: 'women-jackets',
170 | gender: 'female'
171 | },
172 | {
173 | id: 116,
174 | img: '../img/jacket-female.png',
175 | title: 'Womans jacket six',
176 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
177 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
178 | price: 400,
179 | inCart: false,
180 | inWishlist: false,
181 | amount: 0,
182 | total: 0,
183 | size: null,
184 | category: 'women-jackets',
185 | gender: 'female'
186 | },
187 |
188 | // WOMEN HATS
189 | {
190 | id: 121,
191 | img: '../img/hat-female.png',
192 | title: 'Womans hat one',
193 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
194 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
195 | price: 120,
196 | inCart: false,
197 | inWishlist: false,
198 | amount: 0,
199 | total: 0,
200 | size: null,
201 | category: 'women-hats',
202 | gender: 'female'
203 | },
204 | {
205 | id: 122,
206 | img: '../img/hat-female.png',
207 | title: 'Womans hat two',
208 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
209 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
210 | price: 50,
211 | inCart: false,
212 | inWishlist: false,
213 | amount: 0,
214 | total: 0,
215 | size: null,
216 | category: 'women-hats',
217 | gender: 'female'
218 | },
219 | {
220 | id: 123,
221 | img: '../img/hat-female.png',
222 | title: 'Womans hat three',
223 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
224 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
225 | price: 120,
226 | inCart: false,
227 | inWishlist: false,
228 | amount: 0,
229 | total: 0,
230 | size: null,
231 | category: 'women-hats',
232 | gender: 'female'
233 | },
234 | {
235 | id: 124,
236 | img: '../img/hat-female.png',
237 | title: 'Womans hat four',
238 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
239 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
240 | price: 100,
241 | inCart: false,
242 | inWishlist: false,
243 | amount: 0,
244 | total: 0,
245 | size: null,
246 | category: 'women-hats',
247 | gender: 'female'
248 | },
249 | {
250 | id: 125,
251 | img: '../img/hat-female.png',
252 | title: 'Womans hat five',
253 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
254 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
255 | price: 80,
256 | inCart: false,
257 | inWishlist: false,
258 | amount: 0,
259 | total: 0,
260 | size: null,
261 | category: 'women-hats',
262 | gender: 'female'
263 | },
264 | {
265 | id: 126,
266 | img: '../img/hat-female.png',
267 | title: 'Womans hat six',
268 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
269 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
270 | price: 90,
271 | inCart: false,
272 | inWishlist: false,
273 | amount: 0,
274 | total: 0,
275 | size: null,
276 | category: 'women-hats',
277 | gender: 'female'
278 | },
279 |
280 | // WOMEN SHIRTS
281 | {
282 | id: 131,
283 | img: '../img/shirt-female.png',
284 | title: 'Womans shirt one',
285 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
286 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
287 | price: 200,
288 | inCart: false,
289 | inWishlist: false,
290 | amount: 0,
291 | total: 0,
292 | size: null,
293 | category: 'women-shirts',
294 | gender: 'female'
295 | },
296 | {
297 | id: 132,
298 | img: '../img/shirt-female.png',
299 | title: 'Womans shirt two',
300 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
301 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
302 | price: 120,
303 | inCart: false,
304 | inWishlist: false,
305 | amount: 0,
306 | total: 0,
307 | size: null,
308 | category: 'women-shirts',
309 | gender: 'female'
310 | },
311 | {
312 | id: 133,
313 | img: '../img/shirt-female.png',
314 | title: 'Womans shirt three',
315 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
316 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
317 | price: 160,
318 | inCart: false,
319 | inWishlist: false,
320 | amount: 0,
321 | total: 0,
322 | size: null,
323 | category: 'women-shirts',
324 | gender: 'female'
325 | },
326 | {
327 | id: 134,
328 | img: '../img/shirt-female.png',
329 | title: 'Womans shirt four',
330 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
331 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
332 | price: 190,
333 | inCart: false,
334 | inWishlist: false,
335 | amount: 0,
336 | total: 0,
337 | size: null,
338 | category: 'women-shirts',
339 | gender: 'female'
340 | },
341 | {
342 | id: 135,
343 | img: '../img/shirt-female.png',
344 | title: 'Womans shirt five',
345 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
346 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
347 | price: 100,
348 | inCart: false,
349 | inWishlist: false,
350 | amount: 0,
351 | total: 0,
352 | size: null,
353 | category: 'women-shirts',
354 | gender: 'female'
355 | },
356 | {
357 | id: 136,
358 | img: '../img/shirt-female.png',
359 | title: 'Womans shirt six',
360 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
361 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
362 | price: 110,
363 | inCart: false,
364 | inWishlist: false,
365 | amount: 0,
366 | total: 0,
367 | size: null,
368 | category: 'women-shirts',
369 | gender: 'female'
370 | },
371 |
372 | // WOMEN SHOES
373 | {
374 | id: 141,
375 | img: '../img/shoes-female.png',
376 | title: 'Womans shoes one',
377 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
378 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
379 | price: 290,
380 | inCart: false,
381 | inWishlist: false,
382 | amount: 0,
383 | total: 0,
384 | size: null,
385 | category: 'women-shoes',
386 | gender: 'female'
387 | },
388 | {
389 | id: 142,
390 | img: '../img/shoes-female.png',
391 | title: 'Womans shoes two',
392 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
393 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
394 | price: 280,
395 | inCart: false,
396 | inWishlist: false,
397 | amount: 0,
398 | total: 0,
399 | size: null,
400 | category: 'women-shoes',
401 | gender: 'female'
402 | },
403 | {
404 | id: 143,
405 | img: '../img/shoes-female.png',
406 | title: 'Womans shoes three',
407 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
408 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
409 | price: 300,
410 | inCart: false,
411 | inWishlist: false,
412 | amount: 0,
413 | total: 0,
414 | size: null,
415 | category: 'women-shoes',
416 | gender: 'female'
417 | },
418 | {
419 | id: 144,
420 | img: '../img/shoes-female.png',
421 | title: 'Womans shoes four',
422 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
423 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
424 | price: 500,
425 | inCart: false,
426 | inWishlist: false,
427 | amount: 0,
428 | total: 0,
429 | size: null,
430 | category: 'women-shoes',
431 | gender: 'female'
432 | },
433 | {
434 | id: 145,
435 | img: '../img/shoes-female.png',
436 | title: 'Womans shoes five',
437 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
438 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
439 | price: 180,
440 | inCart: false,
441 | inWishlist: false,
442 | amount: 0,
443 | total: 0,
444 | size: null,
445 | category: 'women-shoes',
446 | gender: 'female'
447 | },
448 | {
449 | id: 146,
450 | img: '../img/shoes-female.png',
451 | title: 'Womans shoes six',
452 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
453 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
454 | price: 220,
455 | inCart: false,
456 | inWishlist: false,
457 | amount: 0,
458 | total: 0,
459 | size: null,
460 | category: 'women-shoes',
461 | gender: 'female'
462 | },
463 |
464 | // WOMEN SUITS
465 | {
466 | id: 151,
467 | img: '../img/suit-female.png',
468 | title: 'Womans suit one',
469 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
470 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
471 | price: 500,
472 | inCart: false,
473 | inWishlist: false,
474 | amount: 0,
475 | total: 0,
476 | size: null,
477 | category: 'women-suits',
478 | gender: 'female'
479 | },
480 | {
481 | id: 152,
482 | img: '../img/suit-female.png',
483 | title: 'Womans suit two',
484 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
485 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
486 | price: 590,
487 | inCart: false,
488 | inWishlist: false,
489 | amount: 0,
490 | total: 0,
491 | size: null,
492 | category: 'women-suits',
493 | gender: 'female'
494 | },
495 | {
496 | id: 153,
497 | img: '../img/suit-female.png',
498 | title: 'Womans suit three',
499 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
500 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
501 | price: 580,
502 | inCart: false,
503 | inWishlist: false,
504 | amount: 0,
505 | total: 0,
506 | size: null,
507 | category: 'women-suits',
508 | gender: 'female'
509 | },
510 | {
511 | id: 154,
512 | img: '../img/suit-female.png',
513 | title: 'Womans suit four',
514 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
515 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
516 | price: 560,
517 | inCart: false,
518 | inWishlist: false,
519 | amount: 0,
520 | total: 0,
521 | size: null,
522 | category: 'women-suits',
523 | gender: 'female'
524 | },
525 | {
526 | id: 155,
527 | img: '../img/suit-female.png',
528 | title: 'Womans suit five',
529 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
530 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
531 | price: 530,
532 | inCart: false,
533 | inWishlist: false,
534 | amount: 0,
535 | total: 0,
536 | size: null,
537 | category: 'women-suits',
538 | gender: 'female'
539 | },
540 | {
541 | id: 156,
542 | img: '../img/suit-female.png',
543 | title: 'Womans suit six',
544 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
545 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
546 | price: 500,
547 | inCart: false,
548 | inWishlist: false,
549 | amount: 0,
550 | total: 0,
551 | size: null,
552 | category: 'women-suits',
553 | gender: 'female'
554 | },
555 |
556 | // WOMEN T-SHIRTS
557 | {
558 | id: 161,
559 | img: '../img/t-shirt-female.png',
560 | title: 'Womans t-shirt one',
561 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
562 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
563 | price: 120,
564 | inCart: false,
565 | inWishlist: false,
566 | amount: 0,
567 | total: 0,
568 | size: null,
569 | category: 'women-t-shirts',
570 | gender: 'female'
571 | },
572 | {
573 | id: 162,
574 | img: '../img/t-shirt-female.png',
575 | title: 'Womans t-shirt two',
576 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
577 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
578 | price: 80,
579 | inCart: false,
580 | inWishlist: false,
581 | amount: 0,
582 | total: 0,
583 | size: null,
584 | category: 'women-t-shirts',
585 | gender: 'female'
586 | },
587 | {
588 | id: 163,
589 | img: '../img/t-shirt-female.png',
590 | title: 'Womans t-shirt three',
591 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
592 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
593 | price: 110,
594 | inCart: false,
595 | inWishlist: false,
596 | amount: 0,
597 | total: 0,
598 | size: null,
599 | category: 'women-t-shirts',
600 | gender: 'female'
601 | },
602 | {
603 | id: 164,
604 | img: '../img/t-shirt-female.png',
605 | title: 'Womans t-shirt four',
606 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
607 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
608 | price: 200,
609 | inCart: false,
610 | inWishlist: false,
611 | amount: 0,
612 | total: 0,
613 | size: null,
614 | category: 'women-t-shirts',
615 | gender: 'female'
616 | },
617 | {
618 | id: 165,
619 | img: '../img/t-shirt-female.png',
620 | title: 'Womans t-shirt five',
621 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
622 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
623 | price: 180,
624 | inCart: false,
625 | inWishlist: false,
626 | amount: 0,
627 | total: 0,
628 | size: null,
629 | category: 'women-t-shirts',
630 | gender: 'female'
631 | },
632 | {
633 | id: 166,
634 | img: '../img/t-shirt-female.png',
635 | title: 'Womans t-shirt six',
636 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
637 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
638 | price: 150,
639 | inCart: false,
640 | inWishlist: false,
641 | amount: 0,
642 | total: 0,
643 | size: null,
644 | category: 'women-t-shirts',
645 | gender: 'female'
646 | },
647 |
648 | // MEN COATS
649 | {
650 | id: 201,
651 | img: '../img/coat-male.png',
652 | title: 'Mens coat one',
653 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
654 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
655 | price: 400,
656 | inCart: false,
657 | inWishlist: false,
658 | amount: 0,
659 | total: 0,
660 | size: null,
661 | category: 'men-coats',
662 | gender: 'male'
663 | },
664 | {
665 | id: 202,
666 | img: '../img/coat-male.png',
667 | title: 'Mens coat two',
668 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
669 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
670 | price: 440,
671 | inCart: false,
672 | inWishlist: false,
673 | amount: 0,
674 | total: 0,
675 | size: null,
676 | category: 'men-coats',
677 | gender: 'male'
678 | },
679 | {
680 | id: 203,
681 | img: '../img/coat-male.png',
682 | title: 'Mens coat three',
683 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
684 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
685 | price: 290,
686 | inCart: false,
687 | inWishlist: false,
688 | amount: 0,
689 | total: 0,
690 | size: null,
691 | category: 'men-coats',
692 | gender: 'male'
693 | },
694 | {
695 | id: 204,
696 | img: '../img/coat-male.png',
697 | title: 'Mens coat four',
698 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
699 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
700 | price: 220,
701 | inCart: false,
702 | inWishlist: false,
703 | amount: 0,
704 | total: 0,
705 | size: null,
706 | category: 'men-coats',
707 | gender: 'male'
708 | },
709 | {
710 | id: 205,
711 | img: '../img/coat-male.png',
712 | title: 'Mens coat five',
713 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
714 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
715 | price: 230,
716 | inCart: false,
717 | inWishlist: false,
718 | amount: 0,
719 | total: 0,
720 | size: null,
721 | category: 'men-coats',
722 | gender: 'male'
723 | },
724 | {
725 | id: 206,
726 | img: '../img/coat-male.png',
727 | title: 'Mens coat six',
728 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
729 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
730 | price: 350,
731 | inCart: false,
732 | inWishlist: false,
733 | amount: 0,
734 | total: 0,
735 | size: null,
736 | category: 'men-coats',
737 | gender: 'male'
738 | },
739 |
740 | // MEN HATS
741 | {
742 | id: 211,
743 | img: '../img/hat-male.png',
744 | title: 'Mens hat one',
745 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
746 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
747 | price: 90,
748 | inCart: false,
749 | inWishlist: false,
750 | amount: 0,
751 | total: 0,
752 | size: null,
753 | category: 'men-hats',
754 | gender: 'male'
755 | },
756 | {
757 | id: 212,
758 | img: '../img/hat-male.png',
759 | title: 'Mens hat two',
760 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
761 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
762 | price: 80,
763 | inCart: false,
764 | inWishlist: false,
765 | amount: 0,
766 | total: 0,
767 | size: null,
768 | category: 'men-hats',
769 | gender: 'male'
770 | },
771 | {
772 | id: 213,
773 | img: '../img/hat-male.png',
774 | title: 'Mens hat three',
775 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
776 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
777 | price: 120,
778 | inCart: false,
779 | inWishlist: false,
780 | amount: 0,
781 | total: 0,
782 | size: null,
783 | category: 'men-hats',
784 | gender: 'male'
785 | },
786 | {
787 | id: 214,
788 | img: '../img/hat-male.png',
789 | title: 'Mens hat four',
790 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
791 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
792 | price: 200,
793 | inCart: false,
794 | inWishlist: false,
795 | amount: 0,
796 | total: 0,
797 | size: null,
798 | category: 'men-hats',
799 | gender: 'male'
800 | },
801 | {
802 | id: 215,
803 | img: '../img/hat-male.png',
804 | title: 'Mens hat five',
805 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
806 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
807 | price: 110,
808 | inCart: false,
809 | inWishlist: false,
810 | amount: 0,
811 | total: 0,
812 | size: null,
813 | category: 'men-hats',
814 | gender: 'male'
815 | },
816 | {
817 | id: 216,
818 | img: '../img/hat-male.png',
819 | title: 'Mens hat six',
820 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
821 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
822 | price: 180,
823 | inCart: false,
824 | inWishlist: false,
825 | amount: 0,
826 | total: 0,
827 | size: null,
828 | category: 'men-hats',
829 | gender: 'male'
830 | },
831 |
832 | // MEN JACKETS
833 | {
834 | id: 221,
835 | img: '../img/jacket-male.png',
836 | title: 'Mens jacket one',
837 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
838 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
839 | price: 200,
840 | inCart: false,
841 | inWishlist: false,
842 | amount: 0,
843 | total: 0,
844 | size: null,
845 | category: 'men-jackets',
846 | gender: 'male'
847 | },
848 | {
849 | id: 222,
850 | img: '../img/jacket-male.png',
851 | title: 'Mens jacket two',
852 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
853 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
854 | price: 220,
855 | inCart: false,
856 | inWishlist: false,
857 | amount: 0,
858 | total: 0,
859 | size: null,
860 | category: 'men-jackets',
861 | gender: 'male'
862 | },
863 | {
864 | id: 223,
865 | img: '../img/jacket-male.png',
866 | title: 'Mens jacket three',
867 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
868 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
869 | price: 290,
870 | inCart: false,
871 | inWishlist: false,
872 | amount: 0,
873 | total: 0,
874 | size: null,
875 | category: 'men-jackets',
876 | gender: 'male'
877 | },
878 | {
879 | id: 224,
880 | img: '../img/jacket-male.png',
881 | title: 'Mens jacket four',
882 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
883 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
884 | price: 180,
885 | inCart: false,
886 | inWishlist: false,
887 | amount: 0,
888 | total: 0,
889 | size: null,
890 | category: 'men-jackets',
891 | gender: 'male'
892 | },
893 | {
894 | id: 225,
895 | img: '../img/jacket-male.png',
896 | title: 'Mens jacket five',
897 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
898 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
899 | price: 320,
900 | inCart: false,
901 | inWishlist: false,
902 | amount: 0,
903 | total: 0,
904 | size: null,
905 | category: 'men-jackets',
906 | gender: 'male'
907 | },
908 | {
909 | id: 226,
910 | img: '../img/jacket-male.png',
911 | title: 'Mens jacket six',
912 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
913 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
914 | price: 330,
915 | inCart: false,
916 | inWishlist: false,
917 | amount: 0,
918 | total: 0,
919 | size: null,
920 | category: 'men-jackets',
921 | gender: 'male'
922 | },
923 |
924 | // MEN SHIRTS
925 | {
926 | id: 231,
927 | img: '../img/shirt-male.png',
928 | title: 'Mens shirt one',
929 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
930 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
931 | price: 200,
932 | inCart: false,
933 | inWishlist: false,
934 | amount: 0,
935 | total: 0,
936 | size: null,
937 | category: 'men-shirts',
938 | gender: 'male'
939 | },
940 | {
941 | id: 232,
942 | img: '../img/shirt-male.png',
943 | title: 'Mens shirt two',
944 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
945 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
946 | price: 220,
947 | inCart: false,
948 | inWishlist: false,
949 | amount: 0,
950 | total: 0,
951 | size: null,
952 | category: 'men-shirts',
953 | gender: 'male'
954 | },
955 | {
956 | id: 233,
957 | img: '../img/shirt-male.png',
958 | title: 'Mens shirt three',
959 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
960 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
961 | price: 290,
962 | inCart: false,
963 | inWishlist: false,
964 | amount: 0,
965 | total: 0,
966 | size: null,
967 | category: 'men-shirts',
968 | gender: 'male'
969 | },
970 | {
971 | id: 234,
972 | img: '../img/shirt-male.png',
973 | title: 'Mens shirt four',
974 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
975 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
976 | price: 180,
977 | inCart: false,
978 | inWishlist: false,
979 | amount: 0,
980 | total: 0,
981 | size: null,
982 | category: 'men-shirts',
983 | gender: 'male'
984 | },
985 | {
986 | id: 235,
987 | img: '../img/shirt-male.png',
988 | title: 'Mens shirt five',
989 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
990 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
991 | price: 300,
992 | inCart: false,
993 | inWishlist: false,
994 | amount: 0,
995 | total: 0,
996 | size: null,
997 | category: 'men-shirts',
998 | gender: 'male'
999 | },
1000 | {
1001 | id: 236,
1002 | img: '../img/shirt-male.png',
1003 | title: 'Mens shirt six',
1004 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
1005 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
1006 | price: 400,
1007 | inCart: false,
1008 | inWishlist: false,
1009 | amount: 0,
1010 | total: 0,
1011 | size: null,
1012 | category: 'men-shirts',
1013 | gender: 'male'
1014 | },
1015 |
1016 | // MEN SHOES
1017 | {
1018 | id: 241,
1019 | img: '../img/shoes-male.png',
1020 | title: 'Mens shoes one',
1021 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
1022 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
1023 | price: 180,
1024 | inCart: false,
1025 | inWishlist: false,
1026 | amount: 0,
1027 | total: 0,
1028 | size: null,
1029 | category: 'men-shoes',
1030 | gender: 'male'
1031 | },
1032 | {
1033 | id: 242,
1034 | img: '../img/shoes-male.png',
1035 | title: 'Mens shoes two',
1036 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
1037 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
1038 | price: 200,
1039 | inCart: false,
1040 | inWishlist: false,
1041 | amount: 0,
1042 | total: 0,
1043 | size: null,
1044 | category: 'men-shoes',
1045 | gender: 'male'
1046 | },
1047 | {
1048 | id: 243,
1049 | img: '../img/shoes-male.png',
1050 | title: 'Mens shoes three',
1051 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
1052 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
1053 | price: 340,
1054 | inCart: false,
1055 | inWishlist: false,
1056 | amount: 0,
1057 | total: 0,
1058 | size: null,
1059 | category: 'men-shoes',
1060 | gender: 'male'
1061 | },
1062 | {
1063 | id: 244,
1064 | img: '../img/shoes-male.png',
1065 | title: 'Mens shoes four',
1066 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
1067 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
1068 | price: 270,
1069 | inCart: false,
1070 | inWishlist: false,
1071 | amount: 0,
1072 | total: 0,
1073 | size: null,
1074 | category: 'men-shoes',
1075 | gender: 'male'
1076 | },
1077 | {
1078 | id: 245,
1079 | img: '../img/shoes-male.png',
1080 | title: 'Mens shoes five',
1081 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
1082 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
1083 | price: 280,
1084 | inCart: false,
1085 | inWishlist: false,
1086 | amount: 0,
1087 | total: 0,
1088 | size: null,
1089 | category: 'men-shoes',
1090 | gender: 'male'
1091 | },
1092 | {
1093 | id: 246,
1094 | img: '../img/shoes-male.png',
1095 | title: 'Mens shoes six',
1096 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
1097 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
1098 | price: 290,
1099 | inCart: false,
1100 | inWishlist: false,
1101 | amount: 0,
1102 | total: 0,
1103 | size: null,
1104 | category: 'men-shoes',
1105 | gender: 'male'
1106 | },
1107 |
1108 | // MEN SUITS
1109 | {
1110 | id: 251,
1111 | img: '../img/suit-male.png',
1112 | title: 'Mens suit one',
1113 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
1114 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
1115 | price: 400,
1116 | inCart: false,
1117 | inWishlist: false,
1118 | amount: 0,
1119 | total: 0,
1120 | size: null,
1121 | category: 'men-suits',
1122 | gender: 'male'
1123 | },
1124 | {
1125 | id: 252,
1126 | img: '../img/suit-male.png',
1127 | title: 'Mens suit two',
1128 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
1129 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
1130 | price: 500,
1131 | inCart: false,
1132 | inWishlist: false,
1133 | amount: 0,
1134 | total: 0,
1135 | size: null,
1136 | category: 'men-suits',
1137 | gender: 'male'
1138 | },
1139 | {
1140 | id: 253,
1141 | img: '../img/suit-male.png',
1142 | title: 'Mens suit three',
1143 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
1144 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
1145 | price: 550,
1146 | inCart: false,
1147 | inWishlist: false,
1148 | amount: 0,
1149 | total: 0,
1150 | size: null,
1151 | category: 'men-suits',
1152 | gender: 'male'
1153 | },
1154 | {
1155 | id: 254,
1156 | img: '../img/suit-male.png',
1157 | title: 'Mens suit four',
1158 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
1159 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
1160 | price: 600,
1161 | inCart: false,
1162 | inWishlist: false,
1163 | amount: 0,
1164 | total: 0,
1165 | size: null,
1166 | category: 'men-suits',
1167 | gender: 'male'
1168 | },
1169 | {
1170 | id: 255,
1171 | img: '../img/suit-male.png',
1172 | title: 'Mens suit five',
1173 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
1174 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
1175 | price: 380,
1176 | inCart: false,
1177 | inWishlist: false,
1178 | amount: 0,
1179 | total: 0,
1180 | size: null,
1181 | category: 'men-suits',
1182 | gender: 'male'
1183 | },
1184 | {
1185 | id: 256,
1186 | img: '../img/suit-male.png',
1187 | title: 'Mens suit six',
1188 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
1189 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
1190 | price: 420,
1191 | inCart: false,
1192 | inWishlist: false,
1193 | amount: 0,
1194 | total: 0,
1195 | size: null,
1196 | category: 'men-suits',
1197 | gender: 'male'
1198 | },
1199 |
1200 | // MEN T-SHIRTS
1201 | {
1202 | id: 261,
1203 | img: '../img/t-shirt-male.png',
1204 | title: 'Mens t-shirt one',
1205 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
1206 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
1207 | price: 120,
1208 | inCart: false,
1209 | inWishlist: false,
1210 | amount: 0,
1211 | total: 0,
1212 | size: null,
1213 | category: 'men-t-shirts',
1214 | gender: 'male'
1215 | },
1216 | {
1217 | id: 262,
1218 | img: '../img/t-shirt-male.png',
1219 | title: 'Mens t-shirt two',
1220 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
1221 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
1222 | price: 100,
1223 | inCart: false,
1224 | inWishlist: false,
1225 | amount: 0,
1226 | total: 0,
1227 | size: null,
1228 | category: 'men-t-shirts',
1229 | gender: 'male'
1230 | },
1231 | {
1232 | id: 263,
1233 | img: '../img/t-shirt-male.png',
1234 | title: 'Mens t-shirt three',
1235 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
1236 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
1237 | price: 180,
1238 | inCart: false,
1239 | inWishlist: false,
1240 | amount: 0,
1241 | total: 0,
1242 | size: null,
1243 | category: 'men-t-shirts',
1244 | gender: 'male'
1245 | },
1246 | {
1247 | id: 264,
1248 | img: '../img/t-shirt-male.png',
1249 | title: 'Mens t-shirt four',
1250 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
1251 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
1252 | price: 200,
1253 | inCart: false,
1254 | inWishlist: false,
1255 | amount: 0,
1256 | total: 0,
1257 | size: null,
1258 | category: 'men-t-shirts',
1259 | gender: 'male'
1260 | },
1261 | {
1262 | id: 265,
1263 | img: '../img/t-shirt-male.png',
1264 | title: 'Mens t-shirt five',
1265 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
1266 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
1267 | price: 70,
1268 | inCart: false,
1269 | inWishlist: false,
1270 | amount: 0,
1271 | total: 0,
1272 | size: null,
1273 | category: 'men-t-shirts',
1274 | gender: 'male'
1275 | },
1276 | {
1277 | id: 266,
1278 | img: '../img/t-shirt-male.png',
1279 | title: 'Mens t-shirt six',
1280 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
1281 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
1282 | price: 160,
1283 | inCart: false,
1284 | inWishlist: false,
1285 | amount: 0,
1286 | total: 0,
1287 | size: null,
1288 | category: 'men-t-shirts',
1289 | gender: 'male'
1290 | },
1291 | ]
1292 |
1293 | export const initProduct = {
1294 | id: 101,
1295 | img: '../img/coat-female.png',
1296 | title: 'Womans coat one',
1297 | subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
1298 | description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam, quis deserunt. Aperiam minima facere dolorem saepe deleniti aspernatur amet molestiae doloremque, sed quam ullam cum veritatis corporis eius sequi magnam ipsum aliquid eum! Saepe dolorum debitis voluptate culpa veniam molestiae mollitia dignissimos velit reprehenderit voluptatibus numquam omnis aperiam, cum facere.',
1299 | price: 240,
1300 | inCart: false,
1301 | inWishlist: false,
1302 | amount: 0,
1303 | total: 0,
1304 | size: null,
1305 | category: 'women-coats',
1306 | gender: 'female'
1307 | };
--------------------------------------------------------------------------------
/src/hoc/ErrorHandler.js:
--------------------------------------------------------------------------------
1 | import React, { Component, Fragment } from 'react';
2 |
3 | import Modal from '../components/UI/Modal/Modal';
4 |
5 | const withErrorHandler = (WrappedComponent, axios) => {
6 | return class extends Component {
7 | state = {
8 | error: null,
9 | };
10 |
11 | componentWillMount() {
12 | this.reqInterceptor = axios.interceptors.request.use(req => {
13 | this.setState({
14 | error: null
15 | });
16 |
17 | return req;
18 | });
19 | this.resInterceptor = axios.interceptors.response.use(res => res, err => {
20 | this.setState({
21 | error: err
22 | });
23 | });
24 | };
25 |
26 | componentWillUnmount() {
27 | axios.interceptors.request.eject(this.reqInterceptor);
28 | axios.interceptors.response.eject(this.resInterceptor);
29 | };
30 |
31 | errorConfirmedHandler = () => {
32 | this.setState({
33 | error: null
34 | });
35 | };
36 |
37 | render() {
38 | return (
39 |
40 |
45 | {this.state.error ? this.state.error.message : null}
46 |
47 |
48 |
49 | )
50 | }
51 | }
52 | }
53 |
54 | export default withErrorHandler;
--------------------------------------------------------------------------------
/src/hoc/asyncComponent.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | const asyncComponent = (importComponent) => {
4 | return class extends Component {
5 | state = {
6 | component: null
7 | }
8 |
9 | componentDidMount() {
10 | importComponent()
11 | .then(cmp => {
12 | this.setState({ component: cmp.default });
13 | });
14 | }
15 |
16 | render() {
17 | const C = this.state.component;
18 |
19 | return C ? : null;
20 | }
21 | }
22 | }
23 |
24 | export default asyncComponent;
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.scss';
4 | import App from './App';
5 | import * as serviceWorker from './serviceWorker';
6 | import { BrowserRouter } from 'react-router-dom';
7 | import { Provider } from 'react-redux';
8 | import { createStore, combineReducers, compose, applyMiddleware } from 'redux';
9 | import thunk from 'redux-thunk';
10 |
11 | import productReducer from './store/reducers/productReducer';
12 | import interfaceReducer from './store/reducers/interfaceReducer';
13 | import orderReducer from './store/reducers/orderReducer';
14 | import authReducer from './store/reducers/authReducer';
15 |
16 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
17 |
18 | const rootReducer = combineReducers({
19 | products: productReducer,
20 | interface: interfaceReducer,
21 | order: orderReducer,
22 | auth: authReducer
23 | });
24 |
25 | const store = createStore(
26 | rootReducer,
27 | composeEnhancers(
28 | applyMiddleware(thunk)
29 | )
30 | );
31 |
32 | const app = (
33 |
34 |
35 |
36 |
37 |
38 | );
39 |
40 | ReactDOM.render(app, document.getElementById('root'));
41 |
42 | // If you want your app to work offline and load faster, you can change
43 | // unregister() to register() below. Note this comes with some pitfalls.
44 | // Learn more about service workers: http://bit.ly/CRA-PWA
45 | serviceWorker.unregister();
46 |
--------------------------------------------------------------------------------
/src/index.scss:
--------------------------------------------------------------------------------
1 | $ff-primary: 'Poppins', sans-serif;
2 |
3 | $fw-extralight: 200;
4 | $fw-light: 300;
5 | $fw-medium: 500;
6 | $fw-semibold: 600;
7 | $fw-bold: 700;
8 |
9 | @mixin mediaSm {
10 | @media screen and (min-width: 480px) {
11 | @content;
12 | }
13 | }
14 |
15 | @mixin mediaMd {
16 | @media screen and (min-width: 768px) {
17 | @content;
18 | }
19 | }
20 |
21 | @mixin mediaLg {
22 | @media screen and (min-width: 1224px) {
23 | @content;
24 | }
25 | }
26 |
27 | * {
28 | margin: 0;
29 | padding: 0;
30 | box-sizing: border-box;
31 | font-family: $ff-primary;
32 | font-weight: $fw-light;
33 | }
34 |
35 | body {
36 | width: 100%;
37 | }
38 |
39 | // === RESET ===
40 | article,
41 | aside,
42 | details,
43 | figcaption,
44 | figure,
45 | footer,
46 | header,
47 | hgroup,
48 | menu,
49 | nav,
50 | section {
51 | display: block;
52 | }
53 |
54 | body,
55 | html {
56 | width: 100%;
57 | height: 100%;
58 | }
59 |
60 | body {
61 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
62 | }
63 |
64 | ol,
65 | ul {
66 | list-style: none;
67 | }
68 |
69 | a {
70 | text-decoration: none;
71 | color: #000;
72 | }
73 |
74 | img {
75 | width: 100%;
76 | display: block;
77 | }
78 |
79 | .main-title {
80 | font-size: 1.4em;
81 | font-weight: $fw-semibold;
82 |
83 | @include mediaMd {
84 | font-size: calc(.8em + 1.3vw);
85 | }
86 | }
87 |
88 | .main-info {
89 | font-size: .9em;
90 |
91 | @include mediaSm {
92 | font-size: 1em;
93 | }
94 | }
95 |
96 | .fade-enter {
97 | opacity: 0;
98 | }
99 |
100 | .fade-enter-active {
101 | opacity: 1;
102 | transition: opacity .3s ease-out;
103 | }
104 |
105 | .fade-exit {
106 | opacity: 1;
107 | }
108 |
109 | .fade-exit-active {
110 | opacity: 0;
111 | transition: opacity .3s ease-out;
112 | }
--------------------------------------------------------------------------------
/src/layout/Layout.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './Layout.scss';
3 |
4 | import Toolbar from '../components/Navigation/Toolbar';
5 | import SideDrawer from '../components/Navigation/SideDrawer';
6 | import Footer from '../components/UI/Footer/Footer';
7 |
8 | const Layout = (props) => (
9 | <>
10 |
11 |
12 |
13 | {props.children}
14 |
15 |
16 | >
17 | );
18 |
19 | export default Layout;
--------------------------------------------------------------------------------
/src/layout/Layout.scss:
--------------------------------------------------------------------------------
1 | main {
2 | width: 100%;
3 | max-width: 1200px;
4 | padding-top: 74px;
5 | margin: 0 auto;
6 | }
--------------------------------------------------------------------------------
/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read http://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.1/8 is considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit http://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See http://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl)
104 | .then(response => {
105 | // Ensure service worker exists, and that we really are getting a JS file.
106 | const contentType = response.headers.get('content-type');
107 | if (
108 | response.status === 404 ||
109 | (contentType != null && contentType.indexOf('javascript') === -1)
110 | ) {
111 | // No service worker found. Probably a different app. Reload the page.
112 | navigator.serviceWorker.ready.then(registration => {
113 | registration.unregister().then(() => {
114 | window.location.reload();
115 | });
116 | });
117 | } else {
118 | // Service worker found. Proceed as normal.
119 | registerValidSW(swUrl, config);
120 | }
121 | })
122 | .catch(() => {
123 | console.log(
124 | 'No internet connection found. App is running in offline mode.'
125 | );
126 | });
127 | }
128 |
129 | export function unregister() {
130 | if ('serviceWorker' in navigator) {
131 | navigator.serviceWorker.ready.then(registration => {
132 | registration.unregister();
133 | });
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/src/shared/ScrollToTopOnMount.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | class ScrollToTopOnMount extends React.Component {
4 | componentDidMount(prevProps) {
5 | window.scrollTo(0, 0)
6 | }
7 |
8 | render() {
9 | return null
10 | }
11 | }
12 |
13 | export default ScrollToTopOnMount;
--------------------------------------------------------------------------------
/src/shared/Validity.js:
--------------------------------------------------------------------------------
1 | export const checkValidity = (value, rules) => {
2 | let isValid = true;
3 |
4 | if (rules.required) {
5 | isValid = value.trim() !== '' && isValid; // trim() -> pominięcie spacji
6 | };
7 |
8 | if (rules.minLength) {
9 | isValid = value.length >= rules.minLength && isValid;
10 | };
11 |
12 | if (rules.maxLength) {
13 | isValid = value.length <= rules.maxLength && isValid;
14 | };
15 |
16 | if (rules.isEmail) {
17 | const pattern = /[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/;
18 | isValid = pattern.test(value) && isValid
19 | }
20 |
21 | if (rules.isNumeric) {
22 | const pattern = /^\d+$/;
23 | isValid = pattern.test(value) && isValid
24 | }
25 |
26 | return isValid;
27 | };
--------------------------------------------------------------------------------
/src/store/actions/actionTypes.js:
--------------------------------------------------------------------------------
1 | // PRODUCT REDUCER
2 | // filter products by category
3 | export const FILTER_PRODUCTS = 'FILTER_PRODUCTS';
4 |
5 | export const ADD_TO_CART = 'ADD_TO_CART';
6 | export const ADD_TO_WISHLIST = 'ADD_TO_WISHLIST';
7 |
8 | // handle sort utilities in ProductList component
9 | export const SORT_PRODUCTS = 'SORT_PRODUCTS';
10 | export const HANDLE_DIRECTION = 'HANDLE_DIRECTION';
11 | export const HANDLE_CHECKBOX_VALUE = 'HANDLE_CHECKBOX_VALUE';
12 |
13 | export const SHOW_DETAILS = 'SHOW_DETAILS';
14 |
15 | export const CALCULATE_ORDER = 'CALCULATE_ORDER';
16 | export const REMOVE_CART_ITEM = 'REMOVE_CART_ITEM';
17 | export const HANDLE_PRODUCT_AMOUNT = 'HANDLE_PRODUCT_AMOUNT';
18 | export const CLEAR_CART = 'CLEAR_CART';
19 |
20 | export const REMOVE_WISHLIST_ITEM = 'REMOVE_WISHLIST_ITEM';
21 | export const CLEAR_WISHLIST = 'CLEAR_WISHLIST';
22 |
23 | // INTERFACE REDUCER
24 | export const OPEN_MODAL = 'OPEN_MODAL';
25 | export const CLOSE_MODAL = 'CLOSE_MODAL';
26 | export const TOGGLE_SIDEDRAWER = 'TOGGLE_SIDEDRAWER';
27 |
28 | // ORDER REDUCER
29 | // handle form submit
30 | export const PURCHASE_INIT = 'PURCHASE_INIT';
31 | export const PURCHASE_ORDER_SUCCESS = 'PURCHASE_ORDER_SUCCESS';
32 | export const PURCHASE_ORDER_FAIL = 'PURCHASE_ORDER_FAIL';
33 | export const PURCHASE_ORDER_START = 'PURCHASE_ORDER_START';
34 |
35 | // handle orders list
36 | export const FETCH_ORDERS_START = 'FETCH_ORDERS_START';
37 | export const FETCH_ORDERS_SUCCESS = 'FETCH_ORDERS_SUCCESS';
38 | export const FETCH_ORDERS_FAIL = 'FETCH_ORDERS_FAIL';
39 |
40 | // AUTH REDUCER
41 | export const AUTH_START = 'AUTH_START';
42 | export const AUTH_SUCCESS = 'AUTH_SUCCESS';
43 | export const AUTH_FAIL = 'AUTH_FAIL';
44 | export const AUTH_LOGOUT = 'AUTH_LOGOUT';
45 | export const SET_AUTH_REDIRECT_PATH = 'SET_AUTH_REDIRECT_PATH';
--------------------------------------------------------------------------------
/src/store/actions/authActions.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import * as actionTypes from './actionTypes';
3 |
4 | export const authStart = () => {
5 | return {
6 | type: actionTypes.AUTH_START
7 | };
8 | };
9 |
10 | export const authSuccess = (idToken, userId) => {
11 | return {
12 | type: actionTypes.AUTH_SUCCESS,
13 | idToken,
14 | userId
15 | };
16 | };
17 |
18 | export const authFail = (error) => {
19 | return {
20 | type: actionTypes.AUTH_FAIL,
21 | error
22 | };
23 | };
24 |
25 | export const logout = () => {
26 | localStorage.removeItem('token');
27 | localStorage.removeItem('expirationDate');
28 | localStorage.removeItem('userId');
29 |
30 | return {
31 | type: actionTypes.AUTH_LOGOUT
32 | };
33 | };
34 |
35 | export const checkAuthTimeout = (expirationTime) => {
36 | return dispatch => {
37 | setTimeout(() => {
38 | dispatch(logout());
39 | }, expirationTime * 1000);
40 | };
41 | };
42 |
43 | export const auth = (email, password, isSignup) => {
44 | return dispatch => {
45 | dispatch(authStart());
46 | const authData = {
47 | email,
48 | password,
49 | returnSecureToken: true
50 | };
51 | let url = 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/signupNewUser?key=AIzaSyDTrZE1DWzMHeavsHSkGlSYr4dpBVqOlPY';
52 | if (!isSignup) {
53 | url = 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key=AIzaSyDTrZE1DWzMHeavsHSkGlSYr4dpBVqOlPY'
54 | };
55 | axios.post(url, authData)
56 | .then(res => {
57 | // save login state in localStorage
58 | const expirationDate = new Date(new Date().getTime() + res.data.expiresIn * 1000);
59 | localStorage.setItem('token', res.data.idToken);
60 | localStorage.setItem('expirationDate', expirationDate);
61 | localStorage.setItem('userId', res.data.localId);
62 |
63 | dispatch(authSuccess(res.data.idToken, res.data.localId));
64 | dispatch(checkAuthTimeout(res.data.expiresIn));
65 | })
66 | .catch(err => {
67 | dispatch(authFail(err.response.data.error));
68 | })
69 | };
70 | };
71 |
72 | export const setAuthRedirectPath = path => {
73 | return {
74 | type: actionTypes.SET_AUTH_REDIRECT_PATH,
75 | path
76 | };
77 | };
78 |
79 | export const authCheckState = () => {
80 | return dispatch => {
81 | const token = localStorage.getItem('token');
82 | if (!token) {
83 | dispatch(logout());
84 | } else {
85 | const expirationDate = new Date(localStorage.getItem('expirationDate')); // with new Date we can convert string(localStorage) into date object
86 | if (expirationDate <= new Date()) {
87 | dispatch(logout());
88 | } else {
89 | const userId = localStorage.getItem('userId');
90 | dispatch(authSuccess(token, userId));
91 | dispatch(checkAuthTimeout((expirationDate.getTime() - new Date().getTime()) / 1000));
92 | };
93 | };
94 | };
95 | };
--------------------------------------------------------------------------------
/src/store/actions/index.js:
--------------------------------------------------------------------------------
1 | export {
2 | filterProducts,
3 | addToCart,
4 | addToWishlist,
5 | sortProducts,
6 | handleDirection,
7 | handleCheckboxValue,
8 | showDetails,
9 | calculateOrder,
10 | removeCartItem,
11 | handleProductAmount,
12 | clearCart,
13 | removeWishlistItem,
14 | clearWishlist
15 | } from './productActions';
16 |
17 | export {
18 | openModal,
19 | closeModal,
20 | toggleSideDrawer
21 | } from './interfaceActions';
22 |
23 | export {
24 | purchaseOrder,
25 | purchaseInit,
26 | fetchOrders
27 | } from './orderActions';
28 |
29 | export {
30 | auth,
31 | logout,
32 | authCheckState
33 | } from './authActions';
--------------------------------------------------------------------------------
/src/store/actions/interfaceActions.js:
--------------------------------------------------------------------------------
1 | import * as actionTypes from './actionTypes';
2 |
3 | export const openModal = (id = 101) => {
4 | return {
5 | type: actionTypes.OPEN_MODAL,
6 | id
7 | };
8 | };
9 |
10 | export const closeModal = (id) => {
11 | return {
12 | type: actionTypes.CLOSE_MODAL
13 | };
14 | };
15 |
16 | export const toggleSideDrawer = () => {
17 | return {
18 | type: actionTypes.TOGGLE_SIDEDRAWER,
19 | };
20 | };
--------------------------------------------------------------------------------
/src/store/actions/orderActions.js:
--------------------------------------------------------------------------------
1 | import * as actionTypes from './actionTypes';
2 | import * as actions from './index';
3 | import axios from '../../axios';
4 |
5 | // ====== HANDLE ORDER FORM ======
6 | export const purchaseOrderStart = () => {
7 | return {
8 | type: actionTypes.PURCHASE_ORDER_START
9 | }
10 | };
11 |
12 | export const purchaseOrderSuccess = (id, orderData) => {
13 | return {
14 | type: actionTypes.PURCHASE_ORDER_SUCCESS,
15 | id,
16 | orderData
17 | }
18 | };
19 |
20 | export const purchaseOrderFail = error => {
21 | return {
22 | type: actionTypes.PURCHASE_ORDER_FAIL,
23 | error
24 | }
25 | };
26 |
27 | export const purchaseInit = () => {
28 | return {
29 | type: actionTypes.PURCHASE_INIT
30 | }
31 | };
32 |
33 | // after order button click
34 | export const purchaseOrder = (orderData, token) => {
35 | return dispatch => {
36 | dispatch(purchaseOrderStart());
37 |
38 | axios.post('/orders.json?auth=' + token, orderData)
39 | .then(res => {
40 | dispatch(purchaseOrderSuccess(res.data.name, orderData));
41 | dispatch(actions.clearCart());
42 | })
43 | .catch(err => {
44 | dispatch(purchaseOrderFail(err));
45 | });
46 | }
47 | };
48 |
49 | // ====== HANDLE ORDER PAGE ======
50 | export const fetchOrdersSuccess = (orders) => {
51 | return {
52 | type: actionTypes.FETCH_ORDERS_SUCCESS,
53 | orders
54 | };
55 | };
56 |
57 | export const fetchOrdersFail = (error) => {
58 | return {
59 | type: actionTypes.FETCH_ORDERS_FAIL,
60 | error
61 | };
62 | };
63 |
64 | export const fetchOrdersStart = () => {
65 | return {
66 | type: actionTypes.FETCH_ORDERS_START
67 | };
68 | };
69 |
70 | export const fetchOrders = (token, userId) => {
71 | return dispatch => {
72 | // for start spinner ...
73 | dispatch(fetchOrdersStart());
74 | // query params (for check auth and filter orders)
75 | const queryParams = '?auth=' + token + '&orderBy="userId"&equalTo="' + userId + '"';
76 |
77 | axios.get('/orders.json' + queryParams)
78 | .then(res => {
79 | const fetchedOrders = [];
80 | for (let key in res.data) {
81 | fetchedOrders.push({
82 | ...res.data[key],
83 | id: key
84 | });
85 | };
86 |
87 | // instead setState we dispatch actions
88 | dispatch(fetchOrdersSuccess(fetchedOrders));
89 | })
90 | .catch(err => {
91 | dispatch(fetchOrdersFail(err));
92 | })
93 | }
94 | };
--------------------------------------------------------------------------------
/src/store/actions/productActions.js:
--------------------------------------------------------------------------------
1 | import * as actionTypes from './actionTypes';
2 |
3 | export const filterProducts = category => {
4 | return {
5 | type: actionTypes.FILTER_PRODUCTS,
6 | category
7 | }
8 | };
9 |
10 | export const addToCart = (id, size) => {
11 | return {
12 | type: actionTypes.ADD_TO_CART,
13 | id,
14 | size
15 | }
16 | };
17 |
18 | export const addToWishlist = id => {
19 | return {
20 | type: actionTypes.ADD_TO_WISHLIST,
21 | id
22 | }
23 | };
24 |
25 | export const sortProducts = (priceKey, idKey) => {
26 | return {
27 | type: actionTypes.SORT_PRODUCTS,
28 | priceKey,
29 | idKey
30 | }
31 | };
32 |
33 | export const handleDirection = () => {
34 | return {
35 | type: actionTypes.HANDLE_DIRECTION
36 | }
37 | };
38 |
39 | export const handleCheckboxValue = value => {
40 | return {
41 | type: actionTypes.HANDLE_CHECKBOX_VALUE,
42 | value
43 | }
44 | }
45 |
46 | export const showDetails = id => {
47 | return {
48 | type: actionTypes.SHOW_DETAILS,
49 | id
50 | }
51 | };
52 |
53 | export const calculateOrder = () => {
54 | return {
55 | type: actionTypes.CALCULATE_ORDER
56 | }
57 | };
58 |
59 | export const removeCartItem = id => {
60 | return {
61 | type: actionTypes.REMOVE_CART_ITEM,
62 | id
63 | }
64 | };
65 |
66 | export const handleProductAmount = (id, value) => {
67 | return {
68 | type: actionTypes.HANDLE_PRODUCT_AMOUNT,
69 | id,
70 | value
71 | }
72 | };
73 |
74 | export const clearCart = () => {
75 | return {
76 | type: actionTypes.CLEAR_CART
77 | }
78 | };
79 |
80 | export const removeWishlistItem = id => {
81 | return {
82 | type: actionTypes.REMOVE_WISHLIST_ITEM,
83 | id
84 | }
85 | };
86 |
87 | export const clearWishlist = () => {
88 | return {
89 | type: actionTypes.CLEAR_WISHLIST
90 | }
91 | };
--------------------------------------------------------------------------------
/src/store/reducers/authReducer.js:
--------------------------------------------------------------------------------
1 | import * as actionTypes from '../actions/actionTypes';
2 |
3 | const initialState = {
4 | token: null,
5 | userId: null,
6 | error: null,
7 | loading: false
8 | };
9 |
10 | const authStart = (state, action) => {
11 | return {
12 | ...state,
13 | error: null,
14 | loading: true
15 | };
16 | };
17 |
18 | const authSuccess = (state, action) => {
19 | return {
20 | ...state,
21 | token: action.idToken,
22 | userId: action.userId,
23 | error: null,
24 | loading: false
25 | };
26 | };
27 |
28 | const authFail = (state, action) => {
29 | return {
30 | ...state,
31 | error: action.error,
32 | loading: false
33 | };
34 | };
35 |
36 | const authLogout = (state, action) => {
37 | return {
38 | ...state,
39 | token: null,
40 | userId: null
41 | };
42 | };
43 |
44 | const authReducer = (state = initialState, action) => {
45 | switch (action.type) {
46 | case actionTypes.AUTH_START:
47 | return authStart(state, action);
48 |
49 | case actionTypes.AUTH_SUCCESS:
50 | return authSuccess(state, action);
51 |
52 | case actionTypes.AUTH_FAIL:
53 | return authFail(state, action);
54 |
55 | case actionTypes.AUTH_LOGOUT:
56 | return authLogout(state, action);
57 |
58 | default:
59 | return state;
60 | }
61 | };
62 |
63 | export default authReducer;
--------------------------------------------------------------------------------
/src/store/reducers/interfaceReducer.js:
--------------------------------------------------------------------------------
1 | import { productList, initProduct } from '../../data/data';
2 | import * as actionTypes from '../actions/actionTypes';
3 |
4 | const initialState = {
5 | modalShowed: false,
6 | sideDrawerShowed: false,
7 | modalProduct: initProduct
8 | };
9 |
10 | const openModal = (state, action) => {
11 | return {
12 | ...state,
13 | modalProduct: getItem(action.id),
14 | modalShowed: true
15 | };
16 | };
17 |
18 | const closeModal = (state, action) => {
19 | return {
20 | ...state,
21 | modalShowed: false
22 | };
23 | };
24 |
25 | const toggleSidedrawer = (state, action) => {
26 | return {
27 | ...state,
28 | sideDrawerShowed: !state.sideDrawerShowed,
29 | };
30 | };
31 |
32 | const getItem = id => productList.find(item => item.id === id);
33 |
34 | const interfaceReducer = (state = initialState, action) => {
35 | switch (action.type) {
36 | case (actionTypes.OPEN_MODAL):
37 | return openModal(state, action);
38 |
39 | case (actionTypes.CLOSE_MODAL):
40 | return closeModal(state, action);
41 |
42 | case (actionTypes.TOGGLE_SIDEDRAWER):
43 | return toggleSidedrawer(state, action);
44 |
45 | default:
46 | return state;
47 | }
48 | };
49 |
50 | export default interfaceReducer;
--------------------------------------------------------------------------------
/src/store/reducers/orderReducer.js:
--------------------------------------------------------------------------------
1 | import * as actionTypes from '../actions/actionTypes';
2 |
3 | const initialState = {
4 | orders: [],
5 | loading: false,
6 | purchased: false
7 | };
8 |
9 | const purchaseOrderStart = (state, action) => {
10 | return {
11 | ...state,
12 | loading: true
13 | };
14 | };
15 |
16 | const purchaseOrderSuccess = (state, action) => {
17 | const newOrder = {
18 | ...action.orderData,
19 | id: action.id
20 | }
21 |
22 | return {
23 | ...state,
24 | loading: false,
25 | purchased: true,
26 | orders: state.orders.concat(newOrder)
27 | };
28 | };
29 |
30 | const purchaseOrderFail = (state, action) => {
31 | return {
32 | ...state,
33 | loading: false
34 | };
35 | };
36 |
37 | const purchaseInit = (state, action) => {
38 | return {
39 | ...state,
40 | purchased: false
41 | };
42 | };
43 |
44 | const fetchOrderStart = (state, action) => {
45 | return {
46 | ...state,
47 | loading: true
48 | };
49 | };
50 |
51 | const fetchOrderSuccess = (state, action) => {
52 | return {
53 | ...state,
54 | orders: action.orders,
55 | loading: false
56 | };
57 | };
58 |
59 | const fetchOrderFail = (state, action) => {
60 | return {
61 | ...state,
62 | loading: false
63 | };
64 | };
65 |
66 | const orderReducer = (state = initialState, action) => {
67 | switch (action.type) {
68 | case actionTypes.PURCHASE_ORDER_START:
69 | return purchaseOrderStart(state, action);
70 |
71 | case actionTypes.PURCHASE_ORDER_SUCCESS:
72 | return purchaseOrderSuccess(state, action);
73 |
74 | case actionTypes.PURCHASE_ORDER_FAIL:
75 | return purchaseOrderFail(state, action);
76 |
77 | case actionTypes.PURCHASE_INIT:
78 | return purchaseInit(state, action);
79 |
80 | // ORDERS PAGE CASES
81 | case actionTypes.FETCH_ORDERS_START:
82 | return fetchOrderStart(state, action);
83 |
84 | case actionTypes.FETCH_ORDERS_SUCCESS:
85 | return fetchOrderSuccess(state, action);
86 |
87 | case actionTypes.FETCH_ORDERS_FAIL:
88 | return fetchOrderFail(state, action);
89 |
90 | default:
91 | return state;
92 | }
93 | };
94 |
95 | export default orderReducer;
--------------------------------------------------------------------------------
/src/store/reducers/productReducer.js:
--------------------------------------------------------------------------------
1 | import { productList } from '../../data/data';
2 | import * as actionTypes from '../actions/actionTypes';
3 |
4 | const initialState = {
5 | productList,
6 | products: [],
7 | cart: [],
8 | wishlist: [],
9 | direction: {
10 | price: 'relevance'
11 | },
12 | sortCheckboxValue: 'relevance',
13 | detailProduct: null,
14 | priceTotal: 0,
15 | delivery: 0,
16 | orderTotal: 0
17 | };
18 |
19 | const getItem = id => productList.find(item => item.id === id);
20 |
21 | const fliterProducts = (state, action) => {
22 | let tempProducts;
23 | (action.category === 'female' || action.category === 'male')
24 | ? tempProducts = state.productList.filter(item => item.gender === action.category)
25 | : tempProducts = state.productList.filter(item => item.category === action.category);
26 |
27 | return {
28 | ...state,
29 | products: tempProducts,
30 | direction: { price: 'relevance' },
31 | sortCheckboxValue: 'relevance'
32 | };
33 | };
34 |
35 | const addToCart = (state, action) => {
36 | const updatedList = [...state.productList];
37 | const cartItemIndex = updatedList.indexOf(getItem(action.id));
38 | const cartItem = updatedList[cartItemIndex];
39 |
40 | cartItem.inCart = true;
41 | cartItem.amount = 1;
42 | cartItem.size = action.size;
43 | const price = cartItem.price;
44 | cartItem.total = price;
45 |
46 | const updatedDetailProduct = { ...state.detailProduct };
47 | updatedDetailProduct.inCart = true;
48 | return {
49 | ...state,
50 | productList: updatedList,
51 | cart: [...state.cart, cartItem],
52 | detailProduct: updatedDetailProduct
53 | };
54 | };
55 |
56 | const addToWishlist = (state, action) => {
57 | const updatedList = [...state.productList];
58 | const wishlistItemIndex = updatedList.indexOf(getItem(action.id));
59 | const wishlistItem = updatedList[wishlistItemIndex];
60 |
61 | wishlistItem.inWishlist = true;
62 |
63 | const updatedDetailProduct = { ...state.detailProduct };
64 | updatedDetailProduct.inWishlist = true;
65 | return {
66 | ...state,
67 | productList: updatedList,
68 | wishlist: [...state.wishlist, wishlistItem],
69 | detailProduct: updatedDetailProduct
70 | };
71 | };
72 |
73 | const sortProducts = (state, action) => {
74 | const sortedProducts = [...state.products];
75 | sortedProducts.sort((a, b) => {
76 | switch (state.direction[action.priceKey]) {
77 | case 'relevance':
78 | return a[action.idKey] - b[action.idKey];
79 | case 'price - low to high':
80 | return a[action.priceKey] - b[action.priceKey];
81 | case 'price - high to low':
82 | return b[action.priceKey] - a[action.priceKey];
83 | default:
84 | return sortedProducts
85 | }
86 | });
87 | return {
88 | ...state,
89 | products: sortedProducts
90 | };
91 | };
92 |
93 | const handleDirection = (state, action) => {
94 | return {
95 | ...state,
96 | direction: { price: state.sortCheckboxValue }
97 | };
98 | };
99 |
100 | const handleCheckboxValue = (state, action) => {
101 | return {
102 | ...state,
103 | sortCheckboxValue: action.value
104 | };
105 | };
106 |
107 | const showDetails = (state, action) => {
108 | const detailProduct = getItem(action.id);
109 | return {
110 | ...state,
111 | detailProduct
112 | };
113 | };
114 |
115 | const calculateOrder = (state, action) => {
116 | let priceTotal = 0;
117 | state.cart.map(item => (priceTotal += item.total));
118 |
119 | let productAmount = 0;
120 | state.cart.map(item => (productAmount += item.amount));
121 | let delivery = 0;
122 | if (productAmount > 3) delivery = productAmount * 10;
123 |
124 | let orderTotal = priceTotal + delivery;
125 | return {
126 | ...state,
127 | priceTotal,
128 | delivery,
129 | orderTotal
130 | };
131 | };
132 |
133 | const removeCartItem = (state, action) => {
134 | let updatedProducts = [...state.productList];
135 | let tempCart = [...state.cart];
136 | tempCart = tempCart.filter(item => item.id !== action.id);
137 |
138 | const index = updatedProducts.indexOf(getItem(action.id));
139 | let removedCartItem = updatedProducts[index];
140 | removedCartItem.inCart = false;
141 | removedCartItem.amount = 0;
142 | removedCartItem.total = 0;
143 | removedCartItem.size = null;
144 |
145 | return {
146 | ...state,
147 | cart: [...tempCart],
148 | productList: [...updatedProducts]
149 | };
150 | };
151 |
152 | const handleProductAmount = (state, action) => {
153 | let tempCart = [...state.cart];
154 | const selectedProduct = tempCart.find(item => item.id === action.id);
155 |
156 | const index = tempCart.indexOf(selectedProduct);
157 | const incrementedProduct = tempCart[index];
158 |
159 | if (action.value === 'increment') {
160 | incrementedProduct.amount = incrementedProduct.amount + 1;
161 | } else if (action.value === 'decrement') {
162 | incrementedProduct.amount = incrementedProduct.amount - 1;
163 | }
164 | incrementedProduct.total = incrementedProduct.amount * incrementedProduct.price;
165 |
166 | return {
167 | ...state,
168 | cart: [...tempCart]
169 | };
170 | };
171 |
172 | const clearCart = (state, action) => {
173 | const updatedProductList = [...state.productList];
174 | updatedProductList.forEach(product => {
175 | product.total = 0;
176 | product.size = null;
177 | product.amount = 0;
178 | product.inCart = false;
179 | });
180 |
181 | return {
182 | ...state,
183 | productList: updatedProductList,
184 | cart: []
185 | };
186 | };
187 |
188 | const removeWishlistItem = (state, action) => {
189 | let updatedProducts = [...state.productList];
190 | let tempWishlist = [...state.wishlist];
191 | tempWishlist = tempWishlist.filter(item => item.id !== action.id);
192 |
193 | const index = updatedProducts.indexOf(getItem(action.id));
194 | let removedWishlistItem = updatedProducts[index];
195 | removedWishlistItem.inWishlist = false;
196 |
197 | return {
198 | ...state,
199 | wishlist: [...tempWishlist],
200 | productList: [...updatedProducts]
201 | };
202 | };
203 |
204 | const clearWishlist = (state, action) => {
205 | const updatedProductList = [...state.productList];
206 | updatedProductList.forEach(product => {
207 | product.inWishlist = false;
208 | });
209 |
210 | return {
211 | ...state,
212 | productList: updatedProductList,
213 | wishlist: []
214 | };
215 | };
216 |
217 | const productReducer = (state = initialState, action) => {
218 | switch (action.type) {
219 | case actionTypes.FILTER_PRODUCTS:
220 | return fliterProducts(state, action);
221 |
222 | case actionTypes.ADD_TO_CART:
223 | return addToCart(state, action);
224 |
225 | case actionTypes.ADD_TO_WISHLIST:
226 | return addToWishlist(state, action);
227 |
228 | case actionTypes.SORT_PRODUCTS:
229 | return sortProducts(state, action);
230 |
231 | case actionTypes.HANDLE_DIRECTION:
232 | return handleDirection(state, action);
233 |
234 | case actionTypes.HANDLE_CHECKBOX_VALUE:
235 | return handleCheckboxValue(state, action);
236 |
237 | case actionTypes.SHOW_DETAILS:
238 | return showDetails(state, action);
239 |
240 | case actionTypes.CALCULATE_ORDER:
241 | return calculateOrder(state, action);
242 |
243 | case actionTypes.REMOVE_CART_ITEM:
244 | return removeCartItem(state, action);
245 |
246 | case actionTypes.HANDLE_PRODUCT_AMOUNT:
247 | return handleProductAmount(state, action);
248 |
249 | case actionTypes.CLEAR_CART:
250 | return clearCart(state, action);
251 |
252 | case actionTypes.REMOVE_WISHLIST_ITEM:
253 | return removeWishlistItem(state, action);
254 |
255 | case actionTypes.CLEAR_WISHLIST:
256 | return clearWishlist(state, action);
257 |
258 | default:
259 | return state;
260 | }
261 | }
262 |
263 | export default productReducer;
--------------------------------------------------------------------------------