├── client
├── src
│ ├── components
│ │ ├── Table-admin-list.js
│ │ ├── Empty.js
│ │ ├── Loading-gif.js
│ │ ├── Items-list-filter-size.js
│ │ ├── Button-internal-link.js
│ │ ├── Rating-stars.js
│ │ ├── Buttons-color-select.js
│ │ ├── Item-list-filter-priceBar.js
│ │ ├── Button-link-gender-page.js
│ │ ├── Secret.js
│ │ ├── Footer.js
│ │ ├── Paginator.js
│ │ ├── Checkout-mini-summary.js
│ │ ├── Items-list-htags-labels.js
│ │ ├── Button-size-select.js
│ │ ├── Hero-banner.js
│ │ ├── Admin-form-delete-item.js
│ │ ├── Button-add-to-cart.js
│ │ ├── Button-sorter.js
│ │ ├── Checkout-modal.js
│ │ ├── Checkout-mini-summary-preview.js
│ │ ├── Item-list-filter-keywords.js
│ │ ├── Checkout-step-three.js
│ │ ├── Each-item-in-list.js
│ │ ├── Admin-table-items.js
│ │ ├── Items-list-sidebar.js
│ │ ├── Submenu.js
│ │ ├── Items-list-banner.js
│ │ ├── Admin-table-orders.js
│ │ ├── Checkout-step-one.js
│ │ ├── Button-filter-mobile.js
│ │ ├── Admin.js
│ │ ├── Admin-form-add-item.js
│ │ ├── Cart.js
│ │ ├── Admin-history-log.js
│ │ ├── Breadcrumbs.js
│ │ ├── Carousel-homepage.js
│ │ ├── Carousel-item.js
│ │ ├── Items-list-gender-homepage.js
│ │ ├── Admin-modal-update.js
│ │ ├── Items-list.js
│ │ ├── Checkout.js
│ │ ├── Item.js
│ │ └── Checkout-step-two.js
│ ├── actions
│ │ ├── UsersActions.js
│ │ ├── CartActions.js
│ │ └── DataFetchingActions.js
│ ├── reducers
│ │ ├── usersReducer.js
│ │ ├── categoriesProductsReducer.js
│ │ ├── cartReducer.js
│ │ └── listFetchReducer.js
│ ├── index.js
│ ├── selectors
│ │ ├── selector_list_statistics.js
│ │ └── selector_list_products_filter_sorter.js
│ ├── containers
│ │ ├── Homepage-container.js
│ │ ├── Cart-container.js
│ │ ├── Item-container.js
│ │ ├── Items-list-container.js
│ │ ├── Admin-container.js
│ │ ├── Checkout-container.js
│ │ └── Navbar-container.js
│ ├── style
│ │ ├── transition.css
│ │ ├── rangeslider.min.css
│ │ └── checkbox.min.css
│ ├── constants.js
│ ├── storeConfig.js
│ └── Router.js
├── .gitignore
├── public
│ ├── favicon.ico
│ ├── up-arrow.png
│ ├── down-arrow.png
│ ├── images
│ │ ├── logo.png
│ │ ├── menCat.jpg
│ │ ├── menCat2.jpg
│ │ ├── womenCat.jpg
│ │ ├── banner-men.jpg
│ │ ├── gifLoading.gif
│ │ ├── womenCat2.jpg
│ │ ├── banner-men-pc.jpg
│ │ ├── banner-cover-pc.jpg
│ │ ├── banner-women-pc.jpg
│ │ ├── banner-men-mobile.jpg
│ │ ├── Polo-red-blue-stripe1.jpg
│ │ ├── banner-cover-mobile.jpg
│ │ ├── banner-women-mobile.jpg
│ │ └── loader.svg
│ ├── manifest.json
│ └── index.html
└── package.json
├── .gitignore
├── config.js
├── models
├── ModelLog.js
├── ModelOrders.js
├── ModelProducts.js
└── ModelAdmin.js
├── package.json
├── README.md
├── index.js
├── controllers
└── authentication.js
├── services
└── passport.js
└── routes
└── router.js
/client/src/components/Table-admin-list.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .env
3 | yarn.lock
4 | client/yarn.lock
--------------------------------------------------------------------------------
/client/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | src/components/Checkout-paypal-btn.js
--------------------------------------------------------------------------------
/client/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elibenjii/ecommerce-react/HEAD/client/public/favicon.ico
--------------------------------------------------------------------------------
/client/public/up-arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elibenjii/ecommerce-react/HEAD/client/public/up-arrow.png
--------------------------------------------------------------------------------
/client/public/down-arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elibenjii/ecommerce-react/HEAD/client/public/down-arrow.png
--------------------------------------------------------------------------------
/client/public/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elibenjii/ecommerce-react/HEAD/client/public/images/logo.png
--------------------------------------------------------------------------------
/client/public/images/menCat.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elibenjii/ecommerce-react/HEAD/client/public/images/menCat.jpg
--------------------------------------------------------------------------------
/client/public/images/menCat2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elibenjii/ecommerce-react/HEAD/client/public/images/menCat2.jpg
--------------------------------------------------------------------------------
/client/public/images/womenCat.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elibenjii/ecommerce-react/HEAD/client/public/images/womenCat.jpg
--------------------------------------------------------------------------------
/client/public/images/banner-men.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elibenjii/ecommerce-react/HEAD/client/public/images/banner-men.jpg
--------------------------------------------------------------------------------
/client/public/images/gifLoading.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elibenjii/ecommerce-react/HEAD/client/public/images/gifLoading.gif
--------------------------------------------------------------------------------
/client/public/images/womenCat2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elibenjii/ecommerce-react/HEAD/client/public/images/womenCat2.jpg
--------------------------------------------------------------------------------
/client/public/images/banner-men-pc.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elibenjii/ecommerce-react/HEAD/client/public/images/banner-men-pc.jpg
--------------------------------------------------------------------------------
/client/public/images/banner-cover-pc.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elibenjii/ecommerce-react/HEAD/client/public/images/banner-cover-pc.jpg
--------------------------------------------------------------------------------
/client/public/images/banner-women-pc.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elibenjii/ecommerce-react/HEAD/client/public/images/banner-women-pc.jpg
--------------------------------------------------------------------------------
/client/public/images/banner-men-mobile.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elibenjii/ecommerce-react/HEAD/client/public/images/banner-men-mobile.jpg
--------------------------------------------------------------------------------
/client/src/components/Empty.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const Empty = () =>
{return (handleColorSelection(x), validateColorSelection('valid'))}} style={styles(x, selectedColor)} />)
23 | );
24 |
25 | ButtonsColorSelect.propTypes = propTypes;
26 |
27 | export default ButtonsColorSelect;
28 |
--------------------------------------------------------------------------------
/client/src/components/Item-list-filter-priceBar.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react';
3 | import Slider from 'react-rangeslider';
4 | import '../style/rangeslider.min.css';
5 |
6 | const propTypes = {
7 | actionPriceRangeFilter: PropTypes.func.isRequired,
8 | reducerPriceRangeFilter: PropTypes.number.isRequired,
9 | };
10 |
11 | const styles = {
12 | display: 'flex',
13 | justifyContent: 'space-around'
14 | };
15 |
16 | const ProductListFilterPriceBar = ({
17 | actionPriceRangeFilter,
18 | reducerPriceRangeFilter
19 | }) => (
20 |
21 |
27 |
28 |
29 | min
30 |
31 |
32 | max
33 |
34 |
35 |
36 | );
37 |
38 | ProductListFilterPriceBar.propTypes = propTypes;
39 |
40 | export default ProductListFilterPriceBar;
--------------------------------------------------------------------------------
/client/src/components/Button-link-gender-page.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react'
3 | import {
4 | isMobile
5 | } from "react-device-detect";
6 | import { Button } from 'reactstrap';
7 | import { Link } from 'react-router-dom'
8 |
9 | const propTypes = {
10 | gender: PropTypes.string.isRequired
11 | };
12 |
13 | const styles = {
14 | centerButtons: {
15 | textAlign: 'center',
16 | padding: '30px'
17 | },
18 | buttonStylePc: {
19 | margin:'20px',
20 | padding: '15px'
21 | },
22 | buttonStyleMobile: {
23 | margin:'5px',
24 | padding: '15px'
25 | }
26 | }
27 |
28 | const ButtonLinkGenderPage = ({gender, content=gender}) => {
29 |
30 | const {buttonStylePc, buttonStyleMobile } = styles
31 |
32 | return
33 | }
34 |
35 | ButtonLinkGenderPage.propTypes = propTypes;
36 |
37 | export default ButtonLinkGenderPage
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 |
2 | const express = require('express')
3 | const morgan = require('morgan')
4 | const bodyParser = require('body-parser')
5 | const mongoose = require('mongoose').set('debug', true);
6 | const path = require('path');
7 | require('dotenv').config();
8 | require('./services/passport.js')
9 | const compression = require('compression')
10 | const config = require('./config.js');
11 |
12 | const env = process.env.NODE_ENV || 'development';
13 |
14 | mongoose.connect(env === 'development' ? config.DB_URI_DEV : config.DB_URI, {useUnifiedTopology: true, useNewUrlParser: true})
15 |
16 | const app = express()
17 | app.use(compression())
18 |
19 | env !== 'development' && app.use(express.static(path.join(__dirname, 'client/build')));
20 |
21 |
22 | env === 'development' && app.use(morgan('dev'))
23 | app.use(bodyParser.json())
24 | app.use('/api', require('./routes/router'))
25 |
26 | env !== 'development' && app.get('*', (req, res) => {
27 | res.sendFile(path.join(__dirname+'/client/build/index.html'));
28 | });
29 |
30 | app.listen(process.env.PORT || 5000)
31 |
--------------------------------------------------------------------------------
/client/src/components/Secret.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import axios from 'axios';
3 | import { Link } from "react-router-dom";
4 | import Admin from './Admin';
5 |
6 |
7 | class Secret extends Component {
8 | constructor(props){
9 | super(props);
10 | this.state = {
11 | authorization: false,
12 | apiAuth: false
13 | };
14 | }
15 |
16 | async componentDidMount() {
17 | try {
18 | const response = await axios({
19 | method:'get',
20 | url:'/api/secret',
21 | headers: {'Authorization': localStorage.getItem("token")}
22 | })
23 | const apiAuth = await response.data.authorization;
24 | this.setState({ apiAuth })
25 | } catch (error) {
26 | console.log(error);
27 | }
28 | }
29 |
30 | render() {
31 | const showAdminPanel = this.state.apiAuth ?
:
Authorization is required, please login here: login
32 |
33 | return (
34 |
35 | {showAdminPanel}
36 |
37 | )
38 | }
39 | }
40 |
41 | export default Secret
42 |
--------------------------------------------------------------------------------
/controllers/authentication.js:
--------------------------------------------------------------------------------
1 | const User = require('../models/ModelAdmin');
2 | const config = require('../config');
3 | const jwt = require('jwt-simple');
4 |
5 | function tokenForUser(user){
6 | const timestamp = new Date().getTime();
7 | return jwt.encode({sub: user.id, iat: timestamp}, config.secret);
8 | }
9 |
10 | exports.signin = function(req, res, next){
11 | res.send({token: tokenForUser(req.user)});
12 | }
13 |
14 | exports.signup = function(req, res, next){
15 | const email = req.body.email;
16 | const password = req.body.password;
17 |
18 | if(!email || !password){
19 | return res.status(422).json({error: 'You must provide email and password.'});
20 | }
21 |
22 | User.findOne({email: email}, function(err, existingUser){
23 | if(err){return next(err);}
24 |
25 | if(existingUser){
26 | return res.status(422).json({error: 'Email is in use'});
27 | }
28 |
29 | const user = new User({
30 | email: email,
31 | password: password
32 | });
33 |
34 | user.save(function(err){
35 | if(err){return next(err);}
36 | res.json({token: tokenForUser(user)});
37 | });
38 | });
39 | }
--------------------------------------------------------------------------------
/client/src/storeConfig.js:
--------------------------------------------------------------------------------
1 | import { createStore, combineReducers, applyMiddleware } from 'redux'
2 | import thunk from 'redux-thunk';
3 | import {
4 | listFetchDataSuccess,
5 | listHasError,
6 | listIsLoading,
7 | sortArgsForFilter,
8 | keywordsForFilter,
9 | sortSizeForFilter,
10 | itemFetchDataSuccess,
11 | itemHasError,
12 | itemIsLoading,
13 | reducerPriceRangeFilter
14 | } from './reducers/listFetchReducer';
15 | import { categoriesProducts } from './reducers/categoriesProductsReducer'
16 | import { getUserAddress } from './reducers/usersReducer'
17 | import { cartReducer } from './reducers/cartReducer'
18 |
19 | const rootReducer = combineReducers({
20 | listFetchDataSuccess,
21 | itemFetchDataSuccess,
22 | listHasError,
23 | listIsLoading,
24 | itemHasError,
25 | itemIsLoading,
26 | sortArgsForFilter,
27 | keywordsForFilter,
28 | sortSizeForFilter,
29 | categoriesProducts,
30 | cartReducer,
31 | reducerPriceRangeFilter,
32 | getUserAddress
33 | })
34 |
35 | export const Store = createStore(
36 | rootReducer,
37 | applyMiddleware(thunk)
38 | )
39 |
--------------------------------------------------------------------------------
/client/src/components/Footer.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Container, Row, Col } from 'reactstrap';
3 |
4 | const styles = {
5 | backgroundColor: '#072a48',
6 | paddingTop: '50px',
7 | paddingBottom: '50px',
8 | color: 'white',
9 | textAlign: 'center'
10 | }
11 |
12 | const Footer = () => (
13 |
14 |
15 |
16 | Your footer
17 |
18 |
19 | About the brand
20 | Career
21 | Instagram
22 |
23 |
24 | Order status
25 | Our ecological actions
26 | Facebook
27 |
28 |
29 | Contact us
30 | I like Som tum
31 | Pinterest
32 |
33 |
34 | Copyright your website © 2018 All Rights Reserved
35 |
36 |
37 |
38 | )
39 |
40 | export default Footer;
--------------------------------------------------------------------------------
/client/src/components/Paginator.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import {
4 | Pagination,
5 | PaginationItem,
6 | PaginationLink
7 | } from 'reactstrap';
8 |
9 | const propTypes = {
10 | currentPage: PropTypes.number,
11 | maxPages: PropTypes.number,
12 | onPageChange: PropTypes.func
13 | }
14 |
15 | const previousBtn = (currentPage, onPageChange) => currentPage > 1 &&
onPageChange('previous')}/>
16 | const nextBtn = (currentPage, maxPages, onPageChange) => currentPage < maxPages && onPageChange('next')}/>
17 |
18 | const Paginator = ({currentPage, maxPages, onPageChange}) => (
19 |
20 |
21 |
22 | {previousBtn(currentPage, onPageChange)}
23 |
24 | {[...Array(maxPages)].map((x, i) =>(
25 |
26 | onPageChange(i)}>
27 | {i+1}
28 |
29 |
30 | )
31 | )}
32 |
33 |
34 | {nextBtn(currentPage, maxPages, onPageChange)}
35 |
36 | );
37 |
38 | Paginator.propTypes = propTypes;
39 |
40 | export default Paginator;
--------------------------------------------------------------------------------
/client/src/components/Checkout-mini-summary.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react'
3 | import { ListGroup, ListGroupItem, Col, Row } from 'reactstrap';
4 |
5 | const propTypes = { getCart: PropTypes.array.isRequired };
6 |
7 | const CheckoutMiniSummary = ({ getCart, selectorTotalAmountCart, totalDelivery }) => (
8 |
9 | Order Summary
10 |
11 | {
12 | getCart.map(x=>
13 |
14 | x{x.quantity} {x.title}
15 | {x.price}$
16 |
17 | )
18 | }
19 |
20 |
21 |
22 | Subtotal
23 | {selectorTotalAmountCart}$
24 |
25 |
26 | Shipping
27 | {totalDelivery}$
28 |
29 |
30 | Tax
31 | 0$
32 |
33 |
34 |
35 |
36 | Total
37 | {selectorTotalAmountCart+totalDelivery}$
38 |
39 |
40 |
41 | );
42 |
43 | CheckoutMiniSummary.propTypes = propTypes;
44 |
45 | export default CheckoutMiniSummary;
46 |
--------------------------------------------------------------------------------
/client/src/components/Items-list-htags-labels.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react';
3 | import { Badge } from 'reactstrap';
4 |
5 | const propTypes = {
6 | reducerPriceRangeFilter: PropTypes.number.isRequired,
7 | sortArgsForFilter: PropTypes.string.isRequired,
8 | sortSizeForFilter: PropTypes.string.isRequired,
9 | keywordsForFilter: PropTypes.array.isRequired,
10 | };
11 |
12 | const ItemsListHtagsLabel = ({
13 | reducerPriceRangeFilter,
14 | sortSizeForFilter,
15 | keywordsForFilter,
16 | sortArgsForFilter
17 | }) => {
18 |
19 | const priceLabel = keywordsForFilter.length> 0 && {`#Price<${reducerPriceRangeFilter} $`}
20 | const sizeLabel = keywordsForFilter.length> 0 && {`#Size${sortSizeForFilter === 'All' ? 's' : ''}: ${sortSizeForFilter}`}
21 | const sortLabel = keywordsForFilter.length> 0 && {`#Sort: ${sortArgsForFilter}`}
22 | const categoriesLabel = keywordsForFilter.map(x=>{`#${x}`} )
23 | return (
24 |
25 | { sortLabel }
26 | { priceLabel }
27 | { sizeLabel }
28 | { categoriesLabel }
29 |
30 | )
31 | };
32 |
33 | ItemsListHtagsLabel.propTypes = propTypes;
34 |
35 | export default ItemsListHtagsLabel;
36 |
--------------------------------------------------------------------------------
/client/src/components/Button-size-select.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React, { Component } from 'react';
3 | import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap';
4 |
5 | const propTypes = {
6 | handleSizeSelection: PropTypes.func.isRequired,
7 | sizesArray: PropTypes.array.isRequired,
8 | selectedSize: PropTypes.string.isRequired,
9 | validateSizeSelection: PropTypes.func.isRequired
10 | };
11 |
12 | class ButtonSizeSelect extends Component {
13 | constructor(props) {
14 | super(props);
15 | this.state = {
16 | dropdownOpen: false
17 | };
18 | };
19 |
20 | toggle = () => {
21 | this.setState(prevState => ({
22 | dropdownOpen: !prevState.dropdownOpen
23 | }));
24 | };
25 |
26 | render() {
27 | const { handleSizeSelection, sizesArray, selectedSize, validateSizeSelection} = this.props;
28 | const dropDownList = sizesArray.map(x=>
29 | {return (handleSizeSelection(x), validateSizeSelection('valid'))}}>{x}
30 | );
31 | return (
32 |
33 |
34 | Size: {selectedSize.length>0 ? selectedSize : 'Click to choose'}
35 |
36 |
37 | {dropDownList}
38 |
39 |
40 | );
41 | };
42 | };
43 |
44 | ButtonSizeSelect.propTypes = propTypes;
45 |
46 | export default ButtonSizeSelect;
--------------------------------------------------------------------------------
/client/src/components/Hero-banner.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {
3 | isMobile
4 | } from "react-device-detect";
5 | import ButtonLinkGenderPage from './Button-link-gender-page'
6 | import { Jumbotron, Container } from 'reactstrap';
7 |
8 | const styles = {
9 | bannerCoverPc: {
10 | backgroundImage: 'url("/images/banner-cover-pc.jpg")',
11 | backgroundSize: 'cover'
12 | },
13 | bannerCoverMobile: {
14 | backgroundImage: 'url("/images/banner-cover-mobile.jpg")',
15 | backgroundSize: 'cover'
16 | },
17 | textBanner: {
18 | textShadow: "3px 3px 3px grey",
19 | textAlign: 'center',
20 | color:'white'
21 | },
22 | centerButtons: {
23 | textAlign: 'center',
24 | padding: '30px'
25 | },
26 | titleH1Pc: {
27 | fontSize: '80px'
28 | },
29 | titleH1Mobile: {
30 | fontSize: '60px'
31 | }
32 | };
33 |
34 | const { bannerCoverPc, bannerCoverMobile, textBanner, centerButtons, titleH1Mobile, titleH1Pc } = styles
35 |
36 | const HeroBanner = () => (
37 |
38 |
39 |
40 |
Fashion shop
41 |
Cool shop
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | );
50 |
51 | export default HeroBanner;
--------------------------------------------------------------------------------
/services/passport.js:
--------------------------------------------------------------------------------
1 | const passport = require('passport');
2 | const User = require('../models/ModelAdmin');
3 | const config = require('../config.js');
4 | const JwtStrategy = require('passport-jwt').Strategy;
5 | const ExtractJwt = require('passport-jwt').ExtractJwt;
6 | const LocalStrategy = require('passport-local');
7 |
8 | //Create local strategy
9 | const localOptions = {
10 | usernameField: 'email'
11 | };
12 |
13 | passport.use(new LocalStrategy(localOptions, function(email,password,done){
14 | //Verify this email and password, call done with the user
15 | //if it is the correct email and password
16 | //otherwise, call done with false
17 | User.findOne({email: email}, function(err, user){
18 | if(err){return done(err);}
19 | if(!user){return done(null, false);}
20 |
21 | //compare passwords - is password equal to user.password?
22 | user.comparePassword(password, function(err, isMatch){
23 | if(err){return done(err);}
24 | if(!isMatch){return done(null, false);}
25 | return done(null, user);
26 | })
27 |
28 | });
29 | }));
30 |
31 | //Setup options foor JWT Strategy
32 | const jwtOptions = {
33 | jwtFromRequest: ExtractJwt.fromHeader('authorization'),
34 | secretOrKey: config.secret
35 | };
36 |
37 | //Create JWT Strategy
38 | passport.use(new JwtStrategy(jwtOptions, function(payload, done){
39 | //See if the user ID in the payload exists in our database
40 | //If it does, call 'done' with that user
41 | //otherwise, call done without a user object
42 | User.findById(payload.sub, function(err, user){
43 | //second argument is user object if we found one
44 | if(err) {return done(err, false);}
45 |
46 | if(user){
47 | done(null, user);
48 | }else{
49 | done(null, false);
50 | }
51 | });
52 | }));
53 |
54 |
--------------------------------------------------------------------------------
/client/src/components/Admin-form-delete-item.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap';
3 | import { FiXCircle } from 'react-icons/fi';
4 | import axios from 'axios';
5 |
6 | class AdminFormDeleteItem extends Component {
7 | constructor(props) {
8 | super(props);
9 | this.state = {
10 | modalEdit: false,
11 | };
12 | }
13 |
14 | handleDelete = id => {
15 | axios.delete('/api/delete/item/', {
16 | params: {
17 | id
18 | }
19 | })
20 | .then(response => {
21 | console.log(response);
22 | this.setState({ modalEdit: !this.state.modalEdit });
23 | })
24 | .then(() => {
25 | window.location.reload(true)
26 | })
27 | .catch(error => {
28 | console.log(error);
29 | });
30 | }
31 |
32 | toggle = () => this.setState({ modalEdit: !this.state.modalEdit });
33 |
34 | render() {
35 | const { title, id } = this.props
36 | return (
37 |
38 |
39 |
40 | {title}
41 |
42 | You confirm deleting item ID: {id}?
43 |
44 |
45 | {' '}
48 |
49 |
50 |
51 |
52 | );
53 | }
54 | }
55 |
56 | export default AdminFormDeleteItem;
57 |
--------------------------------------------------------------------------------
/client/src/components/Button-add-to-cart.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React, { Fragment } from 'react';
3 | import {
4 | Button
5 | } from 'reactstrap';
6 |
7 | const propTypes = {
8 | addToCart: PropTypes.func.isRequired,
9 | sizeBtn: PropTypes.string.isRequired,
10 | infoItem: PropTypes.object.isRequired,
11 | selectedSize: PropTypes.string.isRequired,
12 | selectedColor: PropTypes.string.isRequired,
13 | toggleModal: PropTypes.func.isRequired,
14 | validateSizeSelection: PropTypes.func.isRequired,
15 | validateColorSelection: PropTypes.func.isRequired,
16 | colorSelectionMissingRemark: PropTypes.string.isRequired,
17 | sizeSelectionMissingRemark: PropTypes.string.isRequired
18 | };
19 |
20 | const ButtonAddToCart = ({
21 | addToCart,
22 | sizeBtn,
23 | infoItem,
24 | selectedSize,
25 | selectedColor,
26 | toggleModal,
27 | validateSizeSelection,
28 | validateColorSelection,
29 | colorSelectionMissingRemark,
30 | sizeSelectionMissingRemark
31 | }) => {
32 |
33 | const colorBtn = (colorSelectionMissingRemark.length > 0 || sizeSelectionMissingRemark.length > 0) ? 'danger' : 'success'
34 |
35 | return (
36 |
37 |
42 |
43 |
44 | );};
45 |
46 | ButtonAddToCart.propTypes = propTypes;
47 |
48 | export default ButtonAddToCart;
49 |
50 |
--------------------------------------------------------------------------------
/client/src/reducers/cartReducer.js:
--------------------------------------------------------------------------------
1 |
2 | import {
3 | ADD_TO_CART,
4 | DELETE_FROM_CART,
5 | DELETE_ALL_FROM_CART
6 | } from '../constants';
7 |
8 | export const cartReducer = (state = [], action) => {
9 |
10 | const selectItemInCart = (state, action) => action !== undefined && state.filter(x => x.selectedSize === action.selectedSize && x.selectedColor === action.selectedColor && x._id === action._id)[0];
11 | const cartWithoutItem = (state, action) => action !== undefined && state.filter(x => selectItemInCart(state, action) !== x);
12 |
13 |
14 | switch (action.type) {
15 |
16 | case ADD_TO_CART:
17 | return (
18 | selectItemInCart(state, action.item) ? // Check if selected item through action (action.item) is already in cart array
19 | [...cartWithoutItem(state, action.item), {...selectItemInCart(state, action.item), quantity: selectItemInCart(state, action.item).quantity+1}] : // If Yes: return the cart array without the item to avoid duplicate. Then select the item by lookup the id of action (action.item) to match the id of cart array (state) to increment +1 to its quantity propriety.
20 | [...state, {...action.item, quantity: 1}] // If No: return cart array. Then add object of added item by concatenation, initialize its propriety quantity by 1.
21 | );
22 |
23 | case DELETE_FROM_CART:
24 | return (
25 | action.item.quantity === 1 ?
26 | [...cartWithoutItem(state, action.item)] :
27 | [...cartWithoutItem(state, action.item), {...action.item, quantity: selectItemInCart(state, action.item).quantity-1}]
28 | );
29 |
30 |
31 | case DELETE_ALL_FROM_CART:
32 | return cartWithoutItem(state, action.item);
33 |
34 |
35 | default:
36 | return state;
37 | }
38 | };
39 |
40 |
--------------------------------------------------------------------------------
/client/src/components/Button-sorter.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React, { Component, Fragment } from 'react'
3 | import {
4 | ButtonDropdown,
5 | DropdownToggle,
6 | DropdownMenu,
7 | DropdownItem
8 | } from 'reactstrap';
9 |
10 | const propTypes = {
11 | dispatchToSortList: PropTypes.func.isRequired,
12 | sortArgsForFilter: PropTypes.string.isRequired,
13 | };
14 |
15 | class ButtonSorter extends Component {
16 |
17 | constructor(props) {
18 | super(props);
19 | this.state = {
20 | dropdownShowNumberItemsOpen: false,
21 | dropdownSortOpen: false
22 | };
23 | }
24 |
25 | render() {
26 | const { dispatchToSortList, sortArgsForFilter } = this.props
27 | const { dropdownSortOpen } = this.state
28 |
29 | const dropDownWithArrow = (x) =>
30 | x.indexOf("Asc") > 0 ?
31 |
{' '+x} :
32 |
{' '+x}
33 |
34 |
35 | const eachDropDown = (optionsArray) => optionsArray.map(x=>(
36 | dispatchToSortList(x)} key={x}>
37 | {dropDownWithArrow(x)}
38 |
39 | ))
40 |
41 | return (
42 | this.setState({dropdownSortOpen: !dropdownSortOpen})}>
43 |
44 | Sort: {dropDownWithArrow(sortArgsForFilter)}
45 |
46 |
47 | {eachDropDown(['titleAsc', 'titleDesc', 'priceAsc', 'priceDesc'])}
48 |
49 |
50 | );
51 | };
52 | };
53 |
54 | ButtonSorter.propTypes = propTypes;
55 |
56 | export default ButtonSorter;
--------------------------------------------------------------------------------
/client/src/components/Checkout-modal.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react';
3 | import { Link } from 'react-router-dom';
4 | import {
5 | Button,
6 | Modal,
7 | ModalHeader,
8 | ModalBody,
9 | ModalFooter,
10 | Col,
11 | Row
12 | } from 'reactstrap'
13 |
14 | const propTypes = {
15 | infoItem: PropTypes.object.isRequired,
16 | toggleModal: PropTypes.func.isRequired,
17 | selectedSize: PropTypes.string.isRequired,
18 | selectedColor: PropTypes.string.isRequired,
19 | openModal: PropTypes.bool.isRequired,
20 | totalItemsSelectorStats: PropTypes.number.isRequired
21 | };
22 |
23 | const CheckoutModal = ({
24 | infoItem,
25 | openModal,
26 | toggleModal,
27 | selectedSize,
28 | selectedColor,
29 | totalItemsSelectorStats
30 | }) => (
31 |
32 | You have {totalItemsSelectorStats} item{totalItemsSelectorStats>1 && 's'} in your cart
33 |
34 | {totalItemsSelectorStats>1 && 'last item added:'}
35 |
36 |
37 |
38 | {infoItem.title}
39 | {infoItem.price} $
40 | color: {selectedColor}
41 | size: {selectedSize}
42 |
43 |
44 |
45 |
46 | {' '}
47 |
48 |
49 |
50 | );
51 |
52 | CheckoutModal.propTypes = propTypes;
53 |
54 | export default CheckoutModal;
--------------------------------------------------------------------------------
/client/src/components/Checkout-mini-summary-preview.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React, { Fragment } from 'react';
3 | import { ListGroup, ListGroupItem, Col, Row, Button } from 'reactstrap';
4 | import { Link } from 'react-router-dom';
5 |
6 | const propTypes = {
7 | getCart: PropTypes.array.isRequired,
8 | empty: PropTypes.bool.isRequired
9 | };
10 |
11 | const CheckoutMiniSummaryPreview = ({
12 | getCart,
13 | empty
14 | }) => {
15 |
16 | const styles = {
17 | fontSize: {
18 | fontSize: '13px'
19 | },
20 | centerButtons: {
21 | display:'flex',
22 | justifyContent: 'center',
23 | margin: '10px',
24 | textDecoration: 'none'
25 | }
26 | };
27 |
28 | const CardPreview =
29 |
30 | {
31 | !empty ?
32 |
33 |
34 | {
35 | getCart.map(x=>
36 |
37 | x{x.quantity} {x.title} {x.price}$ size: {x.selectedSize} color: {x.selectedColor}
38 |
39 |
40 |
41 | )
42 | }
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | :
52 |
53 | Empty card
54 |
55 | }
56 | ;
57 |
58 |
59 | return CardPreview;
60 |
61 | }
62 |
63 | CheckoutMiniSummaryPreview.propTypes = propTypes;
64 |
65 | export default CheckoutMiniSummaryPreview;
66 |
67 |
--------------------------------------------------------------------------------
/client/src/selectors/selector_list_products_filter_sorter.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect'
2 |
3 | const listToFilter = state => state.listFetchDataSuccess
4 | const argsForFilter = state => state.keywordsForFilter
5 | const sortForFilter = state => state.sortArgsForFilter
6 | const getSize = state => state.sortSizeForFilter
7 | const getPriceRange = state => state.reducerPriceRangeFilter
8 |
9 | const getFilteredList = (listFetchDataSuccess, keywordsForFilter, sortArgsForFilter, sortSizeForFilter, reducerPriceRangeFilter) => {
10 |
11 | const FilteredListByKeywords = listFetchDataSuccess.filter(x=>
12 | keywordsForFilter.some(el => x.tags.includes(el))
13 | )
14 |
15 | const FilteredListBySize = sortSizeForFilter === 'All' ? FilteredListByKeywords :
16 | FilteredListByKeywords.filter(y=>
17 | y.size.includes(sortSizeForFilter)
18 | )
19 |
20 | const FilteredListByPriceRange = FilteredListBySize.filter(x => x.price <= reducerPriceRangeFilter)
21 |
22 | return FilteredListByPriceRange.sort((a, b) => {
23 | switch(sortArgsForFilter) {
24 | case 'titleAsc':
25 | if (a.title > b.title) return 1;
26 | if (a.title < b.title) return -1;
27 |
28 | case 'titleDesc':
29 | if (a.title < b.title) return 1;
30 | if (a.title > b.title) return -1;
31 |
32 | case 'priceAsc':
33 | if (a.price > b.price) return 1;
34 | if (a.price < b.price) return -1;
35 |
36 | case 'priceDesc':
37 | if (a.price < b.price) return 1;
38 | if (a.price > b.price) return -1;
39 |
40 | default:
41 | if (a.title > b.title) return 1;
42 | if (a.title < b.title) return -1;
43 | }
44 | })
45 | }
46 |
47 |
48 | export const selectorListFilterSorter = createSelector(
49 | listToFilter,
50 | argsForFilter,
51 | sortForFilter,
52 | getSize,
53 | getPriceRange,
54 | getFilteredList
55 | )
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/client/src/components/Item-list-filter-keywords.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React, { Component } from 'react'
3 | import '../style/checkbox.min.css'
4 |
5 | const propTypes = {
6 | gender: PropTypes.string.isRequired,
7 | keywordsSelectAction: PropTypes.func.isRequired,
8 | categoriesProducts: PropTypes.object.isRequired,
9 | keywordsForFilter: PropTypes.array.isRequired
10 | };
11 |
12 | class ItemsListFilterKeywords extends Component {
13 |
14 | constructor(props){
15 | super(props);
16 | this.state = {
17 | cat: this.props.categoriesProductslength === 0
18 | }
19 | };
20 |
21 | componentDidMount = () => {
22 | const { keywordsForFilter, actionFillFilters, categoriesProducts, gender } = this.props
23 | keywordsForFilter.length === 0 ?
24 | gender === 'men' ? (() => {actionFillFilters(categoriesProducts.men)})() : (() => {actionFillFilters(categoriesProducts.women)})()
25 | : console.log('gender err')
26 | };
27 |
28 | render(){
29 |
30 | const {
31 | categoriesProducts,
32 | keywordsSelectAction,
33 | keywordsForFilter,
34 | gender
35 | } = this.props;
36 |
37 | const cat = () => gender === 'men' ? categoriesProducts.men : categoriesProducts.women;
38 |
39 | const stateIncludesCategory = category => keywordsForFilter.includes(category);
40 |
41 | return (
42 | cat().map(x=>
43 |
44 |
45 | {/* Empty onChange to avoid unrelevant msg error */}
46 |
keywordsSelectAction(x)} checked={stateIncludesCategory(x)} onChange={()=>{}}/>
47 |
48 |
49 |
50 |
51 |
52 | )
53 | );
54 |
55 | };
56 | };
57 |
58 | ItemsListFilterKeywords.propTypes = propTypes;
59 |
60 | export default ItemsListFilterKeywords;
61 |
62 |
--------------------------------------------------------------------------------
/client/src/containers/Item-container.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { fetchItemApi } from '../actions/DataFetchingActions';
3 | import { addToCart } from '../actions/CartActions';
4 | import { connect } from 'react-redux';
5 | import { selectorTotalItemsCart } from '../selectors/selector_list_statistics';
6 | import Item from '../components/Item';
7 |
8 | class ItemContainer extends Component {
9 |
10 | constructor(props){
11 | super(props);
12 | this.state = {
13 | selectedSize: '',
14 | selectedColor: '',
15 | sizeSelectionMissingRemark: '',
16 | colorSelectionMissingRemark: ''
17 | }
18 | }
19 |
20 | componentDidMount = () => this.props.fetchItemApi(`/api/productsdata/${this.props.match.params.id}`);
21 |
22 | handleSizeSelection = selectedSize => this.setState({ selectedSize });
23 |
24 | handleColorSelection = selectedColor => this.setState({ selectedColor });
25 |
26 | validateSizeSelection = remark => remark === 'valid' ? this.setState({ sizeSelectionMissingRemark: '' }) : this.setState({ sizeSelectionMissingRemark: remark });
27 |
28 | validateColorSelection = remark => remark === 'valid' ? this.setState({ colorSelectionMissingRemark: '' }) : this.setState({ colorSelectionMissingRemark: remark });
29 |
30 | render = () => ;
31 |
32 | };
33 |
34 | const mapStateToProps = state => ({
35 | infoItem: state.itemFetchDataSuccess,
36 | loading: state.itemIsLoading,
37 | errorFetching: state.itemHasErrorm,
38 | totalItemsSelectorStats: selectorTotalItemsCart(state)
39 | });
40 |
41 | const mapDispatchToProps = dispatch => ({
42 | fetchItemApi: url => dispatch(fetchItemApi(url)),
43 | addToCart: x => dispatch(addToCart(x))
44 | });
45 |
46 | export default connect(mapStateToProps, mapDispatchToProps)(ItemContainer);
47 |
48 |
--------------------------------------------------------------------------------
/client/src/components/Checkout-step-three.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react'
3 | import {
4 | Badge,
5 | Button,
6 | ListGroupItem,
7 | Collapse,
8 | Col,
9 | Row
10 | } from 'reactstrap';
11 |
12 | const propTypes = {
13 | toggle: PropTypes.func.isRequired,
14 | step3: PropTypes.bool.isRequired,
15 | step3Unlock: PropTypes.bool.isRequired,
16 | getUserAddress: PropTypes.object.isRequired,
17 | onSubmitOrder: PropTypes.func.isRequired
18 | };
19 |
20 | const CheckoutStepThree = ({
21 | styles,
22 | step3,
23 | step3Unlock,
24 | toggle,
25 | getUserAddress,
26 | onSubmitOrder,
27 | selectorTotalAmountCart,
28 | totalDelivery
29 | }) => {
30 |
31 | const {
32 | address1,
33 | address2,
34 | city,
35 | country,
36 | firstName,
37 | lastName,
38 | phoneNumber,
39 | postalCode,
40 | province
41 | } = getUserAddress
42 |
43 | return (
44 |
45 |
46 | step3Unlock && toggle('step3')} >
47 | 3 Customer
48 |
49 |
50 |
51 |
52 | Delivery address:
53 | First Name: {firstName}
54 | Last Name: {lastName}
55 | Tel: {phoneNumber}
56 | Country: {country}
57 | City: {city}
58 | State/Province: {province}
59 | Postal Code: {postalCode}
60 | Address: {address1 + ' ' + address2}
61 |
62 |
63 | Payment:
64 |
65 |
66 |
67 |
68 |
69 | )
70 | }
71 |
72 | CheckoutStepThree.propTypes = propTypes;
73 |
74 | export default CheckoutStepThree
75 |
--------------------------------------------------------------------------------
/client/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
14 |
15 |
24 | React App
25 |
26 |
27 |
30 |
31 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/client/src/components/Each-item-in-list.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react';
3 | import { Link } from 'react-router-dom';
4 | import StarRatings from 'react-star-ratings';
5 | import {
6 | Col,
7 | Card,
8 | CardImg
9 | } from 'reactstrap';
10 |
11 | const propTypes = {
12 | FilteredSortedList: PropTypes.array.isRequired,
13 | currentPage: PropTypes.number.isRequired,
14 | itemsMaxPage: PropTypes.number.isRequired
15 | };
16 |
17 | const styles = {
18 | spaceColumn: {
19 | marginLeft: '25px',
20 | marginRight: '25px',
21 | marginBottom: '50px',
22 | border: 'none'
23 | },
24 | fontSize: {
25 | fontSize: '15px'
26 | },
27 | marginLeftBtn: {
28 | marginLeft: '30px'
29 | },
30 | containerPaddingTop: {
31 | paddingTop: '35px'
32 | }
33 | };
34 |
35 | const EachItemInList = ({
36 | FilteredSortedList,
37 | currentPage,
38 | itemsMaxPage
39 | }) => {
40 | return (FilteredSortedList.slice((currentPage-1)*itemsMaxPage,itemsMaxPage*currentPage).map(x =>
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
{x.title}
49 |
58 |
{x.price} $
59 |
60 |
61 |
62 | ));
63 | };
64 |
65 | EachItemInList.propTypes = propTypes;
66 |
67 | export default EachItemInList;
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/client/src/components/Admin-table-items.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import axios from 'axios';
3 | import { Table } from 'reactstrap';
4 | import AdminModalUpdate from './Admin-modal-update';
5 | import AdminFormDeleteItem from './Admin-form-delete-item';
6 |
7 | export default class Admin extends Component {
8 | constructor(props){
9 | super(props);
10 | this.state={
11 | apiList: []
12 | }
13 | }
14 |
15 | async componentDidMount() {
16 | try {
17 | const response = await axios.get('/api/productsdata')
18 | const apiList = await response.data;
19 | this.setState({ apiList })
20 | } catch (error) {
21 | console.log(error);
22 | }
23 | }
24 |
25 | render() {
26 | const stylesColor = (color) => ({
27 | textDecoration: 'underline',
28 | textDecorationColor: color
29 | })
30 |
31 | const { stylesTab2 } = this.props
32 | const { apiList } = this.state
33 | return (
34 |
35 |
36 |
37 | | # |
38 | Title |
39 | Id |
40 | Price |
41 | Colors |
42 | Sizes |
43 | Tags |
44 | Images |
45 | Description |
46 | Edit |
47 | Delete |
48 |
49 |
50 |
51 | {
52 | apiList.map((x, index)=>
53 |
54 | | {index+1} |
55 | {x.title} |
56 | {x._id} |
57 | {x.price}$ |
58 | {x.color.map(x=>{x} / )} |
59 | {x.size.map(x=>x+' / ')} |
60 | {x.tags} |
61 | {x.images.length} |
62 | {x.description.substring(0, 30)+'... '} |
63 | |
64 | |
65 |
66 | )
67 | }
68 |
69 |
70 | )
71 | }
72 | };
73 |
--------------------------------------------------------------------------------
/client/src/components/Items-list-sidebar.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react'
3 | import ItemsListFilterKeywords from './Item-list-filter-keywords'
4 | import ItemsListFilterSize from './Items-list-filter-size'
5 | import ProductListFilterPriceBar from './Item-list-filter-priceBar'
6 |
7 | const propTypes = {
8 | gender: PropTypes.string.isRequired,
9 | dispatchSize: PropTypes.func.isRequired,
10 | sortSizeForFilter: PropTypes.string.isRequired,
11 | keywordsSelectAction: PropTypes.func.isRequired,
12 | keywordsForFilter: PropTypes.array.isRequired,
13 | categoriesProducts: PropTypes.object.isRequired,
14 | actionPriceRangeFilter: PropTypes.func.isRequired,
15 | reducerPriceRangeFilter: PropTypes.number.isRequired,
16 | oneKeywordForFilter: PropTypes.func.isRequired,
17 | actionFillFilters: PropTypes.func.isRequired
18 | };
19 |
20 | const styles = {
21 | subTitles:{
22 | color: 'grey',
23 | marginTop:'20px'
24 | }
25 | }
26 |
27 | const ItemsListSidebar = ({
28 | gender,
29 | dispatchSize,
30 | sortSizeForFilter,
31 | keywordsSelectAction,
32 | keywordsForFilter,
33 | categoriesProducts,
34 | actionPriceRangeFilter,
35 | reducerPriceRangeFilter,
36 | oneKeywordForFilter,
37 | actionFillFilters
38 | }) => (
39 |
40 |
Categories
41 |
49 |
Size
50 |
54 |
Price {`< ${reducerPriceRangeFilter}$`}
55 |
59 |
60 | )
61 |
62 | ItemsListSidebar.propTypes = propTypes;
63 |
64 | export default ItemsListSidebar;
65 |
66 |
--------------------------------------------------------------------------------
/client/src/components/Submenu.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react';
3 | import {
4 | Row,
5 | Col,
6 | } from 'reactstrap';
7 | import { Link } from 'react-router-dom';
8 |
9 | const propTypes = {
10 | sendOneKeyword: PropTypes.func.isRequired,
11 | gender: PropTypes.string.isRequired,
12 | itemsListByGender: PropTypes.array.isRequired,
13 | handleSubMenuExit: PropTypes.func.isRequired
14 | }
15 |
16 |
17 | const styles = {
18 | subMenu: {
19 | width: '100%',
20 | height: '250px',
21 | backgroundColor:'rgba(255, 255, 255, 0.9)',
22 | position: 'absolute',
23 | top:'0',
24 | left:'0',
25 | zIndex:'2'
26 | },
27 | subMenuImage: {
28 | width: '100%',
29 | maxHeight: '300px',
30 | padding:'70px'
31 | },
32 | subMenuCategories: {
33 | paddingTop:'70px'
34 | },
35 | subMenuCategoriesUl: {
36 | listStyleType: 'none',
37 | fontSize: '15px'
38 | },
39 | subMenuCategory: {
40 | color: '#343a40'
41 | }
42 | }
43 |
44 | const Submenu = ({
45 | sendOneKeyword,
46 | gender,
47 | itemsListByGender,
48 | handleSubMenuExit
49 | }) => (
50 |
51 |
52 |
53 |
56 |
57 |
58 | Categories
59 |
60 | {
61 | itemsListByGender.map(x =>
sendOneKeyword(x)}> {x}
)
62 | }
63 |
64 |
65 |
66 |
67 | );
68 |
69 | Submenu.propTypes = propTypes;
70 |
71 | export default Submenu;
72 |
--------------------------------------------------------------------------------
/client/src/containers/Items-list-container.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import {
4 | sortArgsForFilter,
5 | keywordsForFilter,
6 | actionSizeForFilter,
7 | oneKeywordForFilter,
8 | fetchDataApi,
9 | actionPriceRangeFilter,
10 | actionFillFilters
11 | } from '../actions/DataFetchingActions';
12 | import { selectorListFilterSorter } from '../selectors/selector_list_products_filter_sorter';
13 | import ItemsList from '../components/Items-list';
14 |
15 | class ProductsListContainer extends Component {
16 |
17 | constructor(props){
18 | super(props);
19 | this.state = {
20 | currentPage: 1,
21 | itemsMaxPage: 9
22 | };
23 | };
24 |
25 | componentDidMount = () => this.props.fetchDataApi('/api/productsdata');
26 |
27 |
28 | currentPageHandler = x => {
29 | const { currentPage } = this.state;
30 | x === 'next' ? this.setState({currentPage: currentPage + 1}) :
31 | x === 'previous' ? this.setState({currentPage: currentPage - 1} ) :
32 | x === 'empty' ? this.setState({currentPage: 1}) :
33 | Number.isInteger(x) && this.setState({currentPage: x + 1});
34 | };
35 |
36 | render = () =>
41 |
42 | }
43 |
44 | const mapStateToProps = state => {
45 | const { keywordsForFilter, sortSizeForFilter, categoriesProducts, listIsLoading, reducerPriceRangeFilter,sortArgsForFilter } = state;
46 | return {
47 | listIsLoading,
48 | FilteredSortedList: selectorListFilterSorter(state),
49 | reducerPriceRangeFilter,
50 | keywordsForFilter,
51 | sortSizeForFilter,
52 | sortArgsForFilter,
53 | categoriesProducts
54 | }
55 | };
56 |
57 | const mapDispatchToProps = dispatch => ({
58 | fetchDataApi: url => dispatch(fetchDataApi(url)),
59 | oneKeywordForFilter: x => dispatch(oneKeywordForFilter(x)),
60 | actionFillFilters: x => dispatch(actionFillFilters(x)),
61 | dispatchSize: x => dispatch(actionSizeForFilter(x)),
62 | actionPriceRangeFilter: x => dispatch(actionPriceRangeFilter(x)),
63 | dispatchToSortList: x => dispatch(sortArgsForFilter(x)),
64 | keywordsSelectAction: x => dispatch(keywordsForFilter(x))
65 | });
66 |
67 | export default connect(mapStateToProps, mapDispatchToProps)(ProductsListContainer);
68 |
69 |
--------------------------------------------------------------------------------
/client/src/Router.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Switch, Route } from 'react-router-dom';
3 | import Empty from './components/Empty';
4 | import Loadable from 'react-loadable';
5 | import NavbarContainer from './containers/Navbar-container';
6 | import Footer from './components/Footer';
7 | import './style/transition.css';
8 |
9 |
10 | const Loading = () => ;
11 |
12 | const ItemContainer = Loadable({
13 | loader: () => import('./containers/Item-container'),
14 | loading: Loading
15 | });
16 |
17 | const CheckoutContainer = Loadable({
18 | loader: () => import('./containers/Checkout-container'),
19 | loading: Loading
20 | });
21 |
22 | const CartContainer = Loadable({
23 | loader: () => import('./containers/Cart-container'),
24 | loading: Loading
25 | });
26 |
27 | const HomepageContainer = Loadable({
28 | loader: () => import('./containers/Homepage-container'),
29 | loading: Loading
30 | });
31 |
32 | const ItemsListContainer = Loadable({
33 | loader: () => import('./containers/Items-list-container'),
34 | loading: Loading
35 | });
36 |
37 | const ItemsListGenderHomepage = Loadable({
38 | loader: () => import('./components/Items-list-gender-homepage'),
39 | loading: Loading
40 | });
41 |
42 | const AdminContainer = Loadable({
43 | loader: () => import('./containers/Admin-container'),
44 | loading: Loading
45 | });
46 |
47 | const Secret = Loadable({
48 | loader: () => import('./components/Secret'),
49 | loading: Loading
50 | });
51 |
52 | const Router = () => (
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | );
70 |
71 | export default Router;
72 |
--------------------------------------------------------------------------------
/client/src/components/Items-list-banner.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react'
3 | import {
4 | Jumbotron,
5 | Container,
6 | } from 'reactstrap';
7 | import {
8 | isMobile
9 | } from "react-device-detect";
10 | import ItemsListHtagsLabels from './Items-list-htags-labels'
11 |
12 | const propTypes = {
13 | gender: PropTypes.string,
14 | reducerPriceRangeFilter: PropTypes.number,
15 | sortArgsForFilter: PropTypes.string,
16 | sortSizeForFilter: PropTypes.string,
17 | keywordsForFilter: PropTypes.array,
18 | }
19 |
20 | const styles={
21 | bannerCoverMenPc: {
22 | backgroundImage: 'url("/images/banner-men-pc.jpg")',
23 | backgroundSize: 'cover'
24 | },
25 | bannerCoverWomenPc: {
26 | backgroundImage: 'url("/images/banner-women-pc.jpg")',
27 | backgroundSize: 'cover'
28 | },
29 | bannerCoverMenMobile: {
30 | backgroundImage: 'url("/images/banner-men-mobile.jpg")',
31 | backgroundSize: 'cover'
32 | },
33 | bannerCoverWomenMobile: {
34 | backgroundImage: 'url("/images/banner-women-mobile.jpg")',
35 | backgroundSize: 'cover'
36 | },
37 | textBanner: {
38 | textShadow: "3px 3px 3px grey",
39 | color:'white'
40 | },
41 | titleH1Pc: {
42 | fontSize: '80px'
43 | }
44 | }
45 |
46 | const ItemsListBanner = ({
47 | gender,
48 | reducerPriceRangeFilter,
49 | sortArgsForFilter,
50 | sortSizeForFilter,
51 | keywordsForFilter
52 | }) => {
53 |
54 | const { bannerCoverMenPc, bannerCoverMenMobile, bannerCoverWomenMobile, bannerCoverWomenPc, textBanner, titleH1Pc } = styles
55 | const backgroundJumbotron =
56 | isMobile && gender === 'men' ? bannerCoverMenMobile :
57 | isMobile && gender === 'women' ? bannerCoverWomenMobile :
58 | isMobile === false && gender === 'men' ? bannerCoverMenPc :
59 | bannerCoverWomenPc
60 |
61 | return (
62 |
63 |
64 | {gender === 'men' ? 'Men' : 'Women' }
65 |
71 |
72 |
73 | )
74 | };
75 |
76 | ItemsListBanner.propTypes = propTypes;
77 |
78 | export default ItemsListBanner;
--------------------------------------------------------------------------------
/client/public/images/loader.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/src/components/Admin-table-orders.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import axios from 'axios';
3 | import { Table } from 'reactstrap';
4 |
5 | export default class AdminTableOrders extends Component {
6 | constructor(props){
7 | super(props);
8 | this.state={
9 | apiList: []
10 | }
11 | }
12 |
13 | async componentDidMount() {
14 | try {
15 | const response = await axios.get('/api/orders')
16 | const apiList = await response.data;
17 | this.setState({ apiList })
18 | } catch (error) {
19 | console.log(error);
20 | }
21 | }
22 |
23 | render() {
24 | const stylesColor = (color) => ({
25 | textDecoration: 'underline',
26 | textDecorationColor: color
27 | })
28 | const { stylesTab1 } = this.props
29 | const { apiList } = this.state
30 | return (
31 |
32 |
33 |
34 | | # |
35 | Id |
36 | Ref |
37 | Date |
38 | Order |
39 | Order Amount |
40 | Delivery Amount |
41 | Email |
42 | Last Name |
43 | First Name |
44 | Country |
45 | City |
46 | Province |
47 | Postal Code |
48 | Phone Number |
49 | Address |
50 |
51 |
52 |
53 | {
54 | apiList.map((x, index)=>
55 |
56 | | {index+1} |
57 | {x._id} |
58 | {x.ref} |
59 | {x.createdAt} |
60 | {x.order.map(item=> {`x${item.quantity} ${item.idItem}(${item.titleItem}[${item.selectedSize}, ${item.selectedColor}] ${item.price}$) `} )} |
61 | {x.totalAmount+ ' $'} |
62 | {x.totalDelivery+ ' $'} |
63 | {x.customerinfo.email} |
64 | {x.customerinfo.lastName} |
65 | {x.customerinfo.firstName} |
66 | {x.customerinfo.country} |
67 | {x.customerinfo.city} |
68 | {x.customerinfo.province} |
69 | {x.customerinfo.postalCode} |
70 | {x.customerinfo.phoneNumber} |
71 | {x.customerinfo.address1 + ' ' +x.customerinfo.address2} |
72 |
73 | )
74 | }
75 |
76 |
77 | )
78 | }
79 | };
80 |
--------------------------------------------------------------------------------
/client/src/components/Checkout-step-one.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react'
3 | import {
4 | Row,
5 | Col,
6 | Input,
7 | Badge,
8 | Button,
9 | ListGroupItem,
10 | Collapse,
11 | Label,
12 | CustomInput,
13 | FormFeedback
14 | } from 'reactstrap';
15 | import { Link } from 'react-router-dom';
16 |
17 | const propTypes = {
18 | email: PropTypes.string.isRequired,
19 | step1: PropTypes.bool.isRequired,
20 | toggle: PropTypes.func.isRequired,
21 | stepsUnlock: PropTypes.func.isRequired,
22 | onChangeEmail: PropTypes.func.isRequired,
23 | emailIsValid: PropTypes.bool.isRequired,
24 | handleEmailValidation: PropTypes.func.isRequired,
25 | emailIsValid: PropTypes.bool.isRequired,
26 | };
27 |
28 | const CheckoutStepOne = ({
29 | styles,
30 | email,
31 | step1,
32 | toggle,
33 | stepsUnlock,
34 | emailIsValid,
35 | handleEmailValidation,
36 | onChangeEmail
37 | }) => {
38 | const emailValidation = (/^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i).test(String(email).toLowerCase())
39 | return (
40 |
41 | toggle('step1')} >
42 | 1 Customer
43 |
44 |
45 | Enter your email for express checkout, an email will be sent to you allowing account creation
46 |
47 |
48 |
49 |
58 |
59 | Email is not valid
60 |
61 |
62 |
63 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | )
81 | }
82 |
83 | CheckoutStepOne.propTypes = propTypes;
84 |
85 | export default CheckoutStepOne
86 |
--------------------------------------------------------------------------------
/client/src/components/Button-filter-mobile.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React, { Component } from 'react';
3 | import { Button, Modal, ModalHeader, ModalBody } from 'reactstrap';
4 | import ItemsListFilterKeywords from './Item-list-filter-keywords'
5 | import ItemsListFilterSize from './Items-list-filter-size'
6 | import ItemListFilterPriceBar from './Item-list-filter-priceBar'
7 |
8 | const propTypes = {
9 | dispatchSize: PropTypes.func.isRequired,
10 | gender: PropTypes.string.isRequired,
11 | sortSizeForFilter: PropTypes.string.isRequired,
12 | keywordsSelectAction: PropTypes.func.isRequired,
13 | categoriesProducts: PropTypes.object.isRequired,
14 | keywordsForFilter: PropTypes.array.isRequired,
15 | actionPriceRangeFilter: PropTypes.func.isRequired,
16 | reducerPriceRangeFilter: PropTypes.number.isRequired,
17 | listLength: PropTypes.number.isRequired
18 | };
19 |
20 | class ButtonFilterMobile extends Component {
21 |
22 | constructor(props) {
23 | super(props);
24 | this.state = {
25 | modal: false
26 | };
27 | };
28 |
29 | toggle = () => {
30 | this.setState({
31 | modal: !this.state.modal
32 | });
33 | };
34 |
35 | render() {
36 |
37 | const {
38 | dispatchSize,
39 | gender,
40 | sortSizeForFilter,
41 | keywordsSelectAction,
42 | categoriesProducts,
43 | keywordsForFilter,
44 | actionPriceRangeFilter,
45 | reducerPriceRangeFilter,
46 | listLength
47 | } = this.props;
48 |
49 |
50 | return (
51 |
52 |
53 |
54 | Results: {listLength}
55 |
56 | Categories
57 |
63 | Size
64 |
68 | Price {`< ${reducerPriceRangeFilter}$`}
69 |
73 |
74 |
75 |
76 | );
77 | }
78 | }
79 |
80 | ButtonFilterMobile.propTypes = propTypes;
81 |
82 | export default ButtonFilterMobile;
--------------------------------------------------------------------------------
/client/src/components/Admin.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import axios from 'axios';
3 | import AdminTablesItems from './Admin-table-items'
4 | import AdminFormAddItem from './Admin-form-add-item'
5 | import AdminHistoryLog from './Admin-history-log'
6 | import AdminTableOrders from './Admin-table-orders'
7 | import { TabContent, TabPane, Nav, NavItem, NavLink } from 'reactstrap';
8 |
9 | export default class Admin extends Component {
10 | constructor(props){
11 | super(props);
12 | this.state={
13 | apiList: [],
14 | activeTab: '2'
15 | }
16 | }
17 |
18 | componentDidMount = async () => {
19 | try {
20 | const response = await axios.get('/api/productsdata')
21 | const apiList = await response.data;
22 | this.setState({ apiList })
23 | } catch (error) {
24 | console.log(error);
25 | }
26 | }
27 |
28 | toggle = tab => this.state.activeTab !== tab && this.setState({ activeTab: tab });
29 |
30 | render() {
31 |
32 | const styles = {
33 | tab1: {
34 | cursor: 'pointer',
35 | backgroundColor: '#cd5957',
36 | color:'white'
37 | },
38 | tab2: {
39 | cursor: 'pointer',
40 | backgroundColor: '#78a4a2',
41 | color:'white'
42 | },
43 | tab3: {
44 | cursor: 'pointer',
45 | backgroundColor: '#66bceb',
46 | color:'white'
47 | },
48 | tab4: {
49 | cursor: 'pointer',
50 | backgroundColor: '#ffce56',
51 | color:'white'
52 | },
53 |
54 | }
55 |
56 | return (
57 |
58 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 | )
96 | }
97 | };
98 |
--------------------------------------------------------------------------------
/client/src/reducers/listFetchReducer.js:
--------------------------------------------------------------------------------
1 | import {
2 | LIST_ERROR,
3 | LIST_IS_LOADING,
4 | LIST_FETCH_SUCCESS,
5 | FILTER_CATEGORIES_MULTIPLE_KEYWORDS,
6 | FILTER_CATEGORIES_ONE_KEYWORD,
7 | FILTER_PRICE_RANGE,
8 | FILTER_SIZE,
9 | RESET_KEYWORDS,
10 | ITEM_ERROR,
11 | ITEM_IS_LOADING,
12 | FILTER_ARGS,
13 | FILL_FILTER,
14 | ITEM_FETCH_SUCCESS
15 | } from '../constants.js';
16 |
17 | export const listHasError = (state = false, action) => {
18 | switch (action.type) {
19 | case LIST_ERROR:
20 | return action.hasErrored;
21 |
22 | default:
23 | return state;
24 | }
25 | };
26 |
27 | export const itemHasError = (state = false, action) => {
28 | switch (action.type) {
29 | case ITEM_ERROR:
30 | return action.hasErrored;
31 |
32 | default:
33 | return state;
34 | }
35 | };
36 |
37 | export const listIsLoading = (state = false, action) => {
38 | switch (action.type) {
39 | case LIST_IS_LOADING:
40 | return action.isLoading;
41 |
42 | default:
43 | return state;
44 | }
45 | };
46 |
47 | export const itemIsLoading = (state = false, action) => {
48 | switch (action.type) {
49 | case ITEM_IS_LOADING:
50 | return action.isLoading;
51 |
52 | default:
53 | return state;
54 | }
55 | };
56 |
57 | export const sortArgsForFilter = (state = 'titleAsc', action) => {
58 | switch (action.type) {
59 | case FILTER_ARGS:
60 | return action.sortArg;
61 |
62 | default:
63 | return state;
64 | }
65 | };
66 |
67 | export const sortSizeForFilter = (state = 'All', action) => {
68 | switch (action.type) {
69 | case FILTER_SIZE:
70 | return action.getSize;
71 |
72 | default:
73 | return state;
74 | }
75 | };
76 |
77 | export const reducerPriceRangeFilter = (state = 200, action) => {
78 | switch (action.type) {
79 | case FILTER_PRICE_RANGE:
80 | return action.getPriceRange;
81 |
82 | default:
83 | return state;
84 | }
85 | };
86 |
87 | export const keywordsForFilter = (state = [], action) => {
88 | switch (action.type) {
89 |
90 | case FILTER_CATEGORIES_MULTIPLE_KEYWORDS:
91 | return (state.includes(action.keywords) ?
92 | state = state.filter(x => x !== action.keywords) :
93 | state = [...state, action.keywords])
94 |
95 | case FILTER_CATEGORIES_ONE_KEYWORD:
96 | return state = [action.keywords]
97 |
98 | case FILL_FILTER:
99 | return state = action.keywords
100 |
101 | case RESET_KEYWORDS:
102 | return state = []
103 |
104 | default:
105 | return state;
106 | }
107 | };
108 |
109 | export const listFetchDataSuccess = (state = [], action) => {
110 | switch (action.type) {
111 | case LIST_FETCH_SUCCESS:
112 | return action.items;
113 |
114 | default:
115 | return state;
116 | }
117 | };
118 |
119 | export const itemFetchDataSuccess = (state = [], action) => {
120 | switch (action.type) {
121 | case ITEM_FETCH_SUCCESS:
122 | return action.item;
123 |
124 | default:
125 | return state;
126 | }
127 | };
128 |
--------------------------------------------------------------------------------
/client/src/style/rangeslider.min.css:
--------------------------------------------------------------------------------
1 | .rangeslider{margin:20px 0;position:relative;background:#e6e6e6;-ms-touch-action:none;touch-action:none}.rangeslider,.rangeslider .rangeslider__fill{display:block;box-shadow:inset 0 1px 3px rgba(0,0,0,.4)}.rangeslider .rangeslider__handle{background:#fff;border:1px solid #ccc;cursor:pointer;display:inline-block;position:absolute;box-shadow:0 1px 3px rgba(0,0,0,.4),0 -1px 3px rgba(0,0,0,.4)}.rangeslider .rangeslider__handle .rangeslider__active{opacity:1}.rangeslider .rangeslider__handle-tooltip{width:40px;height:40px;text-align:center;position:absolute;background-color:rgba(0,0,0,.8);font-weight:400;font-size:14px;transition:all .1s ease-in;border-radius:4px;display:inline-block;color:#fff;left:50%;transform:translate3d(-50%,0,0)}.rangeslider .rangeslider__handle-tooltip span{margin-top:12px;display:inline-block;line-height:100%}.rangeslider .rangeslider__handle-tooltip:after{content:' ';position:absolute;width:0;height:0}.rangeslider-horizontal{height:12px;border-radius:10px}.rangeslider-horizontal .rangeslider__fill{height:100%;background-color:#7cb342;border-radius:10px;top:0}.rangeslider-horizontal .rangeslider__handle{width:30px;height:30px;border-radius:30px;top:50%;transform:translate3d(-50%,-50%,0)}.rangeslider-horizontal .rangeslider__handle:after{content:' ';position:absolute;width:16px;height:16px;top:6px;left:6px;border-radius:50%;background-color:#dadada;box-shadow:0 1px 3px rgba(0,0,0,.4) inset,0 -1px 3px rgba(0,0,0,.4) inset}.rangeslider-horizontal .rangeslider__handle-tooltip{top:-55px}.rangeslider-horizontal .rangeslider__handle-tooltip:after{border-left:8px solid transparent;border-right:8px solid transparent;border-top:8px solid rgba(0,0,0,.8);left:50%;bottom:-8px;transform:translate3d(-50%,0,0)}.rangeslider-vertical{margin:20px auto;height:150px;max-width:10px;background-color:transparent}.rangeslider-vertical .rangeslider__fill,.rangeslider-vertical .rangeslider__handle{position:absolute}.rangeslider-vertical .rangeslider__fill{width:100%;background-color:#7cb342;box-shadow:none;bottom:0}.rangeslider-vertical .rangeslider__handle{width:30px;height:10px;left:-10px;box-shadow:none}.rangeslider-vertical .rangeslider__handle-tooltip{left:-100%;top:50%;transform:translate3d(-50%,-50%,0)}.rangeslider-vertical .rangeslider__handle-tooltip:after{border-top:8px solid transparent;border-bottom:8px solid transparent;border-left:8px solid rgba(0,0,0,.8);left:100%;top:12px}.rangeslider-reverse.rangeslider-horizontal .rangeslider__fill{right:0}.rangeslider-reverse.rangeslider-vertical .rangeslider__fill{top:0;bottom:inherit}.rangeslider__labels{position:relative}.rangeslider-vertical .rangeslider__labels{position:relative;list-style-type:none;margin:0 0 0 24px;padding:0;text-align:left;width:250px;height:100%;left:10px}.rangeslider-vertical .rangeslider__labels .rangeslider__label-item{position:absolute;transform:translate3d(0,-50%,0)}.rangeslider-vertical .rangeslider__labels .rangeslider__label-item::before{content:'';width:10px;height:2px;background:#000;position:absolute;left:-14px;top:50%;transform:translateY(-50%);z-index:-1}.rangeslider__labels .rangeslider__label-item{position:absolute;font-size:14px;cursor:pointer;display:inline-block;top:10px;transform:translate3d(-50%,0,0)}
--------------------------------------------------------------------------------
/client/src/actions/DataFetchingActions.js:
--------------------------------------------------------------------------------
1 | import {
2 | LIST_ERROR,
3 | LIST_IS_LOADING,
4 | LIST_FETCH_SUCCESS,
5 | FILTER_ARGS,
6 | FILTER_CATEGORIES_MULTIPLE_KEYWORDS,
7 | FILTER_CATEGORIES_ONE_KEYWORD,
8 | FILTER_SIZE,
9 | FILTER_PRICE_RANGE,
10 | RESET_KEYWORDS,
11 | ITEM_ERROR,
12 | ITEM_IS_LOADING,
13 | FILL_FILTER,
14 | ITEM_FETCH_SUCCESS
15 | } from '../constants';
16 |
17 | export const listHasError = bool => ({
18 | type: LIST_ERROR,
19 | hasErrored: bool
20 | });
21 |
22 | export const itemHasError = bool => ({
23 | type: ITEM_ERROR,
24 | hasErrored: bool
25 | });
26 |
27 | export const listIsLoading = bool => ({
28 | type: LIST_IS_LOADING,
29 | isLoading: bool
30 | });
31 |
32 | export const itemIsLoading = bool => ({
33 | type: ITEM_IS_LOADING,
34 | isLoading: bool
35 | });
36 |
37 | export const listFetchDataSuccess = items => ({
38 | type: LIST_FETCH_SUCCESS,
39 | items
40 | });
41 |
42 | export const listFilteredByKeywords = items => ({
43 | type: LIST_FETCH_SUCCESS,
44 | items
45 | });
46 |
47 | export const actionSizeForFilter = getSize => ({
48 | type: FILTER_SIZE,
49 | getSize
50 | });
51 |
52 | export const actionPriceRangeFilter = getPriceRange => ({
53 | type: FILTER_PRICE_RANGE,
54 | getPriceRange
55 | });
56 |
57 | export const sortArgsForFilter = sortArg => ({
58 | type: FILTER_ARGS,
59 | sortArg
60 | });
61 |
62 | export const keywordsForFilter = keywords => ({
63 | type: FILTER_CATEGORIES_MULTIPLE_KEYWORDS,
64 | keywords
65 | });
66 |
67 | export const oneKeywordForFilter = keywords => ({
68 | type: FILTER_CATEGORIES_ONE_KEYWORD,
69 | keywords
70 | });
71 |
72 | export const actionFillFilters = keywords => ({
73 | type: FILL_FILTER,
74 | keywords
75 | });
76 |
77 | export const resetKeywords = () => ({ type: RESET_KEYWORDS });
78 |
79 | export const itemFetchDataSuccess = item => ({
80 | type: ITEM_FETCH_SUCCESS,
81 | item
82 | });
83 |
84 | export const fetchDataApi = url => {
85 | return dispatch => {
86 | dispatch(listIsLoading(true));
87 | fetch(url)
88 | .then(response => {
89 | if (!response.ok) {
90 | throw Error(response.statusText);
91 | }
92 | return response;
93 | })
94 | .then(response => response.json())
95 | .then(items => dispatch(listIsLoading(false)) && dispatch(listFetchDataSuccess(items)))
96 | .catch(() => dispatch(listHasError(true)));
97 | }
98 | };
99 |
100 | export const fetchItemApi = url => {
101 | return dispatch => {
102 | dispatch(itemIsLoading(true))
103 | fetch(url)
104 | .then(response => {
105 | if (!response.ok) {
106 | throw Error(response.statusText);
107 | }
108 | return response;
109 | })
110 | .then(response => response.json())
111 | .then(items => dispatch(itemFetchDataSuccess(items)))
112 | .then(() => dispatch(itemIsLoading(false)))
113 | .catch(() => dispatch(itemHasError(true)));
114 | }
115 | };
--------------------------------------------------------------------------------
/client/src/containers/Admin-container.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import axios from 'axios';
3 | import { Link, Redirect } from "react-router-dom";
4 | import { Button, Form, FormGroup, Label, Input, Container, Col } from 'reactstrap';
5 |
6 | class AdminContainer extends Component {
7 |
8 | constructor(props){
9 | super(props);
10 | this.state = {
11 | email: 'admin',
12 | password: 'admin',
13 | apiResponse: ''
14 | };
15 | }
16 |
17 | onChangeEmail = (e) => {
18 | this.setState({email: e.target.value})
19 | }
20 |
21 | onChangePassword = (e) => {
22 | this.setState({password: e.target.value})
23 | }
24 |
25 | onLogin = async() => {
26 | try {
27 | const req = axios.post('/api/signin', {
28 | email: this.state.email,
29 | password: this.state.password
30 | })
31 | const response = await req;
32 | localStorage.setItem("token", response.data.token);
33 | this.setState({apiResponse: 'success'})
34 | console.log(response)
35 | } catch (error) {
36 | this.setState({ apiResponse: error.response.statusText})
37 | console.log(error.response);
38 | }
39 |
40 | }
41 |
42 | onSignup = () => {
43 | axios.post('/api/signup', {
44 | email: this.state.email,
45 | password: this.state.password
46 | })
47 | .then(function (response) {
48 | console.log(response);
49 | })
50 | .catch(function (error) {
51 | console.log(error.response.data.error);
52 | });
53 | }
54 |
55 | redirectLoginSuccessListener = () => {
56 | if (this.state.apiResponse === 'success') {
57 | return
62 |
63 | }
64 | }
65 |
66 | render() {
67 | const errors =
68 | this.state.apiResponse === 'Bad Request' ?
69 | 'Please fill the email and password fields' :
70 | this.state.apiResponse === 'Unauthorized' &&
71 | 'Email or password incorrect'
72 |
73 | return (
74 |
75 | {this.redirectLoginSuccessListener()}
76 |
77 | Admin Dashboard
78 |
107 |
108 |
109 | )
110 |
111 | }
112 | }
113 |
114 | export default AdminContainer;
--------------------------------------------------------------------------------
/client/src/components/Admin-form-add-item.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Button, Input, Form, FormGroup, Label, Container, Alert } from 'reactstrap';
3 | import axios from 'axios';
4 |
5 | class AdminFormAddItem extends Component {
6 | constructor(props) {
7 | super(props);
8 | this.state = {
9 | modalEdit: false,
10 | title: 'tteeest',
11 | color: 'blue, red',
12 | size: 'XS, L',
13 | tags: 'Polos',
14 | images: 'https://i.ytimg.com/vi/Bor5lkRyeGo/hqdefault.jpg, https://i.ytimg.com/vi/Bor5lkRyeGo/hqdefault.jpg',
15 | description: '',
16 | price: 0,
17 | success: false
18 | };
19 | }
20 |
21 | toggle = () => {
22 | this.setState({
23 | modalEdit: !this.state.modalEdit
24 | });
25 | }
26 |
27 | onSubmit = (title, price, color, size, tags, images, description) => {
28 | axios.post('/api/add/item', {
29 | title,
30 | price,
31 | color: (color.slice(0)+'').replace(/\s/g,'').split(','),
32 | size: (size.slice(0)+'').replace(/\s/g,'').split(','),
33 | tags: (tags.slice(0)+'').replace(/\s/g,'').split(','),
34 | images: (images.slice(0)+'').replace(/\s/g,'').split(','),
35 | description
36 | })
37 | .then(() => {
38 | window.location.reload(true)
39 | })
40 | .catch(function (error) {
41 | console.log(error);
42 | });
43 | }
44 |
45 | onChangeTitle = (e) => this.setState({title: e.target.value})
46 | onChangePrice = (e) => this.setState({price: e.target.value})
47 | onChangeColor = (e) => this.setState({color: [e.target.value]})
48 | onChangesize = (e) => this.setState({size: e.target.value})
49 | onChangeTags = (e) => this.setState({tags: [e.target.value]})
50 | onChangeImages = (e) => this.setState({images: [e.target.value]})
51 | onChangeDescription = (e) => this.setState({ description: e.target.value })
52 |
53 |
54 | render() {
55 | const { title, price, color, size, tags, images, description } = this.state
56 | return (
57 |
58 | Add new item
59 |
89 |
98 |
99 | );
100 | }
101 | }
102 |
103 | export default AdminFormAddItem;
--------------------------------------------------------------------------------
/routes/router.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const passport = require('passport');
3 | const Authentication = require('../controllers/authentication');
4 | const router = express.Router();
5 | const ModelProducts = require('../models/ModelProducts');
6 | const ModelLog = require('../models/ModelLog');
7 | const ModelOrders = require('../models/ModelOrders');
8 |
9 | router.get('/productsdata', (req, res)=> {
10 | ModelProducts.find((err, x) => {
11 | err ? res.status(500).send(err) :
12 | res.json(x)
13 | })
14 | })
15 |
16 | router.get('/productsdata/:id', (req, res)=> {
17 | ModelProducts.findById(req.params.id)
18 | .then(x => {
19 | if (!x) return res.status(404).end()
20 | return res.json(x)
21 | })
22 | .catch(err => next(err))
23 | })
24 |
25 | router.put('/update/item', (req, res, next)=> {
26 | const { id, title, price, color, size, tags, images, description } = req.body
27 | ModelProducts.findById(id, (err, model) => {
28 | if (err) return handleError(err);
29 |
30 | model.set({ title, price, color, size, tags, images, description });
31 | model.save(function (err, updatedItem) {
32 | if (err) return console.log(err);
33 | next();
34 | });
35 | }).then(()=>{
36 | const logUpdate = new ModelLog({
37 | type: 'Update',
38 | time: new Date(),
39 | itemid: id,
40 | itemtitle: title
41 | });
42 | logUpdate.save(function(err, logSaved){
43 | if(err){return next(err);}
44 | res.end();
45 | });
46 | }).catch(err => next(err));
47 | })
48 |
49 | router.post('/add/item', (req, res, next)=> {
50 | const { title, price, description, size, tags, images, color } = req.body
51 | const newItem = new ModelProducts({
52 | title,
53 | price,
54 | color,
55 | size,
56 | tags,
57 | images,
58 | description
59 | });
60 | newItem.save((err, saveditem) => {
61 | if (err) return console.log(err);
62 | next();
63 | })
64 | const logAdd = new ModelLog({
65 | type: 'Create',
66 | time: new Date(),
67 | itemid: newItem._id,
68 | itemtitle: title
69 | });
70 | logAdd.save(function(err, logSaved){
71 | if(err){return next(err);}
72 | res.end();
73 | });
74 | })
75 |
76 | router.post('/add/orders', (req, res, next)=> {
77 | const { ref, customerinfo, order, totalDelivery, totalAmount } = req.body
78 | const newOrder = new ModelOrders({
79 | ref,
80 | customerinfo,
81 | order,
82 | totalDelivery,
83 | totalAmount
84 | });
85 | newOrder.save((err, saveditem) => {
86 | if (err) return console.log(err);
87 | res.end();
88 | })
89 | })
90 |
91 | router.get('/orders', (req, res)=> {
92 | ModelOrders.find((err, x) => {
93 | err ? res.status(500).send(err) :
94 | res.json(x)
95 | })
96 | })
97 |
98 |
99 | router.delete('/delete/item/:id', (req, res, next)=> {
100 | const { id } = req.params
101 | ModelProducts.findByIdAndRemove(id, (err, model) => {
102 | if (err) return handleError(err);
103 | res.send('item: '+id+' deleted');
104 | })
105 | .then(()=>{
106 | const logDelete = new ModelLog({
107 | type: 'Delete',
108 | time: new Date(),
109 | itemid: id
110 | });
111 | logDelete.save(function(err, logSaved){
112 | if(err){return next(err);}
113 | res.end();
114 | });
115 | }).catch(err => next(err));
116 | });
117 |
118 | router.get('/log', (req, res)=> {
119 | ModelLog.find((err, x) => {
120 | err ? res.status(500).send(err) :
121 | res.json(x)
122 | })
123 | })
124 |
125 |
126 | router.get('/secret', passport.authenticate('jwt', {session: false}), (req, res)=> {
127 | res.json({authorization: true});
128 | });
129 |
130 | router.post('/signin', passport.authenticate('local', {session:false}), Authentication.signin);
131 |
132 | router.post('/signup', Authentication.signup);
133 |
134 |
135 | module.exports = router;
136 |
--------------------------------------------------------------------------------
/client/src/components/Cart.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react'
3 | import { Table, Container, Button } from 'reactstrap';
4 | import { Link } from 'react-router-dom';
5 |
6 | const propTypes = {
7 | getCart: PropTypes.array.isRequired,
8 | addToCart: PropTypes.func.isRequired,
9 | deleteFromCart: PropTypes.func.isRequired,
10 | deleteALlFromCart: PropTypes.func.isRequired,
11 | };
12 |
13 | const styles = {
14 | centerh1: {
15 | textAlign: 'center',
16 | padding: '30px'
17 | },
18 | images: {
19 | width: '30px'
20 | },
21 | checkoutBtn: {
22 | textAlign: 'right'
23 | },
24 | btnIncrement: {
25 | color: '#072a48',
26 | backgroundColor: 'white',
27 | border: 'solid',
28 | borderColor: '#072a48',
29 | width: '30px',
30 | cursor: 'pointer',
31 | borderWidth: '0.1ex'
32 | },
33 | btnDelete: {
34 | color: 'white',
35 | backgroundColor: '#072a48',
36 | border: 'solid',
37 | borderColor: '#072a48',
38 | width: '30px',
39 | cursor: 'pointer',
40 | borderWidth: '0.1ex'
41 | },
42 | containerPadding: {
43 | paddingTop: '70px',
44 | paddingBottom: '150px'
45 | }
46 | }
47 |
48 | const Cart = ({
49 | getCart,
50 | addToCart,
51 | deleteFromCart,
52 | deleteALlFromCart
53 | }) => {
54 |
55 |
56 | const { centerh1, images, checkoutBtn, btnIncrement, btnDelete, containerPadding } = styles
57 | const reducePrice = (getCart.reduce((acc, x) => (acc + (x.quantity * x.price)), 0))
58 |
59 | return (
60 |
61 |
62 | Cart
63 |
64 |
65 |
66 | | Item |
67 | Price |
68 | Quantity |
69 | |
70 |
71 |
72 |
73 | {
74 | getCart.sort((a, b) => a._id === b._id ? a.selectedSize === b.selectedSize ? a.selectedColor < b.selectedColor : a.selectedSize < b.selectedSize : a._id < b._id).map(x =>
75 |
76 |
77 |
78 | {x.title} | {x.selectedSize} | {x.selectedColor}
79 | |
80 |
81 | {x.price}$
82 | |
83 |
84 | {' '+x.quantity+' '}
85 | |
86 |
87 |
88 | |
89 |
90 | )
91 | }
92 |
93 | | |
94 |
95 | Subtotal
96 | |
97 |
98 | {reducePrice} $
99 | |
100 |
101 |
102 | | |
103 |
104 | Shipping
105 | |
106 |
107 | 3 $
108 | |
109 |
110 |
111 | | |
112 |
113 | Total
114 | |
115 |
116 | {reducePrice + 3} $
117 | |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 | )
127 | }
128 |
129 | Cart.propTypes = propTypes;
130 |
131 | export default Cart;
132 |
--------------------------------------------------------------------------------
/client/src/components/Admin-history-log.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { Table, Row, Col } from 'reactstrap';
3 | import axios from 'axios';
4 | import {Doughnut,Line} from 'react-chartjs-2';
5 |
6 |
7 |
8 | export class AdminHistoryLog extends Component {
9 |
10 | constructor(props){
11 | super(props);
12 | this.state = {
13 | logData: []
14 | }
15 | }
16 |
17 | componentDidMount = async () => {
18 | try {
19 | const response = await axios.get('/api/log')
20 | const logData = await response.data;
21 | this.setState({ logData })
22 | } catch (error) {
23 | console.log(error);
24 | }
25 | }
26 |
27 | render() {
28 | const { stylesTab4 } = this.props
29 | const dataDoughnut = {
30 | labels: [
31 | 'Delete',
32 | 'Create',
33 | 'Update'
34 | ],
35 | datasets: [{
36 | data: [
37 | this.state.logData.filter(x=>x.type === 'Delete').length,
38 | this.state.logData.filter(x=>x.type === 'Create').length,
39 | this.state.logData.filter(x=>x.type === 'Update').length,
40 | ],
41 | backgroundColor: [
42 | '#FF6384',
43 | '#072a48',
44 | '#FFCE56'
45 | ],
46 | hoverBackgroundColor: [
47 | '#FF6384',
48 | '#36A2EB',
49 | '#FFCE56'
50 | ]
51 | }]
52 | };
53 |
54 | const dataLine = {
55 | labels: this.state.logData.map(x=>
56 | new Date(x.time).toLocaleDateString('en-TH', {
57 | year: 'numeric',
58 | month: 'long',
59 | day: 'numeric',
60 | hour: '2-digit'
61 | }
62 | )
63 | ),
64 | datasets: [
65 | {
66 | label: 'Dashboard activity',
67 | fill: false,
68 | lineTension: 0.1,
69 | backgroundColor: 'rgba(75,192,192,0.4)',
70 | borderColor: 'rgba(75,192,192,1)',
71 | borderCapStyle: 'butt',
72 | borderDash: [],
73 | borderDashOffset: 0.0,
74 | borderJoinStyle: 'miter',
75 | pointBorderColor: 'rgba(75,192,192,1)',
76 | pointBackgroundColor: '#fff',
77 | pointBorderWidth: 1,
78 | pointHoverRadius: 5,
79 | pointHoverBackgroundColor: 'rgba(75,192,192,1)',
80 | pointHoverBorderColor: 'rgba(220,220,220,1)',
81 | pointHoverBorderWidth: 2,
82 | pointRadius: 1,
83 | pointHitRadius: 10,
84 | data: this.state.logData.map((x,index)=>index)
85 | }
86 | ]
87 | };
88 |
89 | const rows =
90 | this.state.logData.map((x, index)=>
91 |
92 | | {index+1} |
93 | {x.itemid} |
94 | {x.itemtitle} |
95 | {x.type} |
96 | { new Date(x.time).toLocaleDateString('en-TH', {
97 | year: 'numeric',
98 | month: 'long',
99 | day: 'numeric',
100 | hour: '2-digit'
101 | }
102 | )} |
103 |
104 | )
105 | return (
106 |
107 |
108 |
109 |
110 |
111 |
112 | | # |
113 | Product ID |
114 | Product title |
115 | Type |
116 | Time stamp |
117 |
118 |
119 |
120 | {rows}
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 | )
139 | }
140 | }
141 |
142 | export default AdminHistoryLog
143 |
--------------------------------------------------------------------------------
/client/src/components/Breadcrumbs.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React, { Fragment } from 'react';
3 | import { Container, Row, Col } from 'reactstrap';
4 | import { isMobile } from "react-device-detect";
5 | import { Link } from 'react-router-dom';
6 | import ButtonSorter from './Button-sorter';
7 | import ButtonFilterMobile from './Button-filter-mobile';
8 |
9 |
10 | const propTypes = {
11 | gender: PropTypes.string,
12 | selectedCategory: PropTypes.array,
13 | backgroundColor: PropTypes.string,
14 | textColor: PropTypes.string,
15 | marginTop: PropTypes.number,
16 | showSortBtn: PropTypes.bool,
17 | showFilterBtn: PropTypes.bool,
18 | dispatchToSortList: PropTypes.func,
19 | sortArgsForFilter: PropTypes.string,
20 | dispatchSize: PropTypes.func,
21 | sortSizeForFilter: PropTypes.string,
22 | keywordsSelectAction: PropTypes.func,
23 | categoriesProducts: PropTypes.object,
24 | keywordsForFilter: PropTypes.array,
25 | actionPriceRangeFilter: PropTypes.func,
26 | reducerPriceRangeFilter: PropTypes.number,
27 | listLength: PropTypes.number,
28 | };
29 |
30 | const Breadcrumbs = ({
31 | gender,
32 | selectedCategory,
33 | backgroundColor,
34 | textColor,
35 | marginTop,
36 | showSortBtn,
37 | showFilterBtn,
38 | dispatchToSortList,
39 | sortArgsForFilter,
40 | dispatchSize,
41 | sortSizeForFilter,
42 | keywordsSelectAction,
43 | categoriesProducts,
44 | keywordsForFilter,
45 | actionPriceRangeFilter,
46 | reducerPriceRangeFilter,
47 | listLength
48 | }) => {
49 |
50 | const styles= {
51 | containerPcScreen: {
52 | height: '80px',
53 | backgroundColor: backgroundColor,
54 | marginTop: marginTop,
55 | color: textColor,
56 | display: 'flex',
57 | justifyContent: 'left',
58 | alignItems: 'center'
59 | },
60 | containerMobileScreen: {
61 | height: '100px',
62 | backgroundColor: backgroundColor,
63 | marginTop: marginTop,
64 | color: textColor,
65 | display: 'flex',
66 | justifyContent: 'left',
67 | alignItems: 'center'
68 | },
69 | sortBtnMobileScreen: {
70 | display: 'flex',
71 | justifyContent: 'center',
72 | alignItems: 'center'
73 | },
74 | linkColor: {
75 | color: textColor
76 | }
77 | }
78 |
79 | const sortBtn =
80 | showSortBtn &&
81 |
82 | {showFilterBtn &&
83 |
95 | }
96 |
100 |
101 |
102 | const genderLink = gender && {` ${gender && gender.charAt(0).toUpperCase() + gender.substr(1)}'s Apparels`} >;
103 | const selectedItem = selectedCategory.length === 1 ? ' '+selectedCategory : selectedCategory.length > 1 ? ' Multiple criterias' : ' Category selection'
104 | return (
105 |
106 |
107 |
108 |
109 |
110 | Home >
111 | {genderLink}
112 | {selectedItem}
113 |
114 |
115 | { sortBtn }
116 |
117 |
118 |
119 | );
120 | }
121 |
122 | Breadcrumbs.propTypes = propTypes;
123 |
124 | export default Breadcrumbs;
125 |
--------------------------------------------------------------------------------
/client/src/components/Carousel-homepage.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import {
3 | Carousel,
4 | CarouselItem,
5 | CarouselControl,
6 | Col,
7 | Row,
8 | } from 'reactstrap';
9 | import ButtonInternalLink from './Button-internal-link';
10 |
11 | const styles = {
12 | sliderContent: {
13 | display: 'flex',
14 | alignItems: 'center',
15 | justifyContent: 'center',
16 | flexDirection: 'column',
17 | color:'white'
18 | }
19 | }
20 |
21 | const items = [
22 | {
23 | src: 'https://images.unsplash.com/photo-1489987707025-afc232f7ea0f?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=b17958c40e777065643cb65954bede79&dpr=1&auto=format&fit=crop&w=1000&q=80&cs=tinysrgb',
24 | altText: 'Slide 1',
25 | caption: 'Slide 1',
26 | title: 'Fashion is cool',
27 | subtitle: 'Check our nice collection',
28 | btn: {
29 | content: 'Go to the collection',
30 | link: '/category/men'
31 |
32 | }
33 | },
34 | {
35 | src: 'https://images.unsplash.com/photo-1488826701985-4c18de62b405?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=9ce707ffe1b3f8c031acc5788cf6aef2&dpr=1&auto=format&fit=crop&w=1000&q=80&cs=tinysrgb',
36 | altText: 'Slide 2',
37 | caption: 'Slide 2',
38 | title: "Nice for couples",
39 | subtitle: 'Collection summer 2018',
40 | btn: {
41 | content: 'Cool collection',
42 | link: '/category/men'
43 |
44 | }
45 | },
46 | {
47 | src: 'https://images.unsplash.com/photo-1485331129317-1717811a2b75?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=1b713c86ebb20befc80029db6bc98dae&dpr=1&auto=format&fit=crop&w=1000&q=80&cs=tinysrgb',
48 | altText: 'Slide 3',
49 | caption: 'Slide 3',
50 | title: 'Nice for everyone',
51 | subtitle: 'Amazing clothes!',
52 | btn: {
53 | content: 'Go to the collection',
54 | link: '/category/men'
55 |
56 | }
57 | }
58 | ];
59 |
60 | class CarouselHomepage extends Component {
61 | constructor(props) {
62 | super(props);
63 | this.state = { activeIndex: 0 };
64 | }
65 |
66 | onExiting = () => {
67 | this.animating = true;
68 | }
69 |
70 | onExited = () => {
71 | this.animating = false;
72 | }
73 |
74 | next = () => {
75 | if (this.animating) return;
76 | const nextIndex = this.state.activeIndex === items.length - 1 ? 0 : this.state.activeIndex + 1;
77 | this.setState({ activeIndex: nextIndex });
78 | }
79 |
80 | previous = () => {
81 | if (this.animating) return;
82 | const nextIndex = this.state.activeIndex === 0 ? items.length - 1 : this.state.activeIndex - 1;
83 | this.setState({ activeIndex: nextIndex });
84 | }
85 |
86 | goToIndex = (newIndex) => {
87 | if (this.animating) return;
88 | this.setState({ activeIndex: newIndex });
89 | }
90 |
91 | render() {
92 | const { activeIndex } = this.state;
93 |
94 | const slides = items.map(x => {
95 | return (
96 |
101 |
102 |
103 |
104 |
105 |
106 | {x.title}
107 | {x.subtitle}
108 |
114 |
115 |
116 |
117 | );
118 | });
119 |
120 | return (
121 |
126 | {slides}
127 |
128 |
129 |
130 | );
131 | }
132 | }
133 |
134 |
135 | export default CarouselHomepage;
136 |
--------------------------------------------------------------------------------
/client/src/components/Carousel-item.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import {
3 | Carousel,
4 | CarouselItem,
5 | CarouselControl,
6 | CarouselIndicators
7 | } from 'reactstrap';
8 |
9 | const items = [
10 | {
11 | src: 'data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%22800%22%20height%3D%22400%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20800%20400%22%20preserveAspectRatio%3D%22none%22%3E%3Cdefs%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%23holder_15ba800aa1d%20text%20%7B%20fill%3A%23555%3Bfont-weight%3Anormal%3Bfont-family%3AHelvetica%2C%20monospace%3Bfont-size%3A40pt%20%7D%20%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22holder_15ba800aa1d%22%3E%3Crect%20width%3D%22800%22%20height%3D%22400%22%20fill%3D%22%23777%22%3E%3C%2Frect%3E%3Cg%3E%3Ctext%20x%3D%22285.921875%22%20y%3D%22218.3%22%3EFirst%20slide%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E',
12 | altText: 'Slide 1',
13 | caption: 'Slide 1'
14 | },
15 | {
16 | src: 'data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%22800%22%20height%3D%22400%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20800%20400%22%20preserveAspectRatio%3D%22none%22%3E%3Cdefs%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%23holder_15ba800aa20%20text%20%7B%20fill%3A%23444%3Bfont-weight%3Anormal%3Bfont-family%3AHelvetica%2C%20monospace%3Bfont-size%3A40pt%20%7D%20%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22holder_15ba800aa20%22%3E%3Crect%20width%3D%22800%22%20height%3D%22400%22%20fill%3D%22%23666%22%3E%3C%2Frect%3E%3Cg%3E%3Ctext%20x%3D%22247.3203125%22%20y%3D%22218.3%22%3ESecond%20slide%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E',
17 | altText: 'Slide 2',
18 | caption: 'Slide 2'
19 | },
20 | {
21 | src: 'data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%22800%22%20height%3D%22400%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20800%20400%22%20preserveAspectRatio%3D%22none%22%3E%3Cdefs%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%23holder_15ba800aa21%20text%20%7B%20fill%3A%23333%3Bfont-weight%3Anormal%3Bfont-family%3AHelvetica%2C%20monospace%3Bfont-size%3A40pt%20%7D%20%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22holder_15ba800aa21%22%3E%3Crect%20width%3D%22800%22%20height%3D%22400%22%20fill%3D%22%23555%22%3E%3C%2Frect%3E%3Cg%3E%3Ctext%20x%3D%22277%22%20y%3D%22218.3%22%3EThird%20slide%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E',
22 | altText: 'Slide 3',
23 | caption: 'Slide 3'
24 | }
25 | ];
26 |
27 | class CarouselItemPage extends Component {
28 | constructor(props) {
29 | super(props);
30 | this.state = { activeIndex: 0 };
31 | }
32 |
33 | onExiting = () => {
34 | this.animating = true;
35 | }
36 |
37 | onExited = () => {
38 | this.animating = false;
39 | }
40 |
41 | next = () => {
42 | if (this.animating) return;
43 | const nextIndex = this.state.activeIndex === this.props.imgsArray.length - 1 ? 0 : this.state.activeIndex + 1;
44 | this.setState({ activeIndex: nextIndex });
45 | }
46 |
47 | previous = () => {
48 | if (this.animating) return;
49 | const nextIndex = this.state.activeIndex === 0 ? this.props.imgsArray.length - 1 : this.state.activeIndex - 1;
50 | this.setState({ activeIndex: nextIndex });
51 | }
52 |
53 | goToIndex = (newIndex) => {
54 | if (this.animating) return;
55 | this.setState({ activeIndex: newIndex });
56 | }
57 |
58 | render() {
59 | const { activeIndex } = this.state;
60 |
61 | const slides = this.props.imgsArray.map((x, index) => {
62 | return (
63 |
68 |
69 |
70 | );
71 | });
72 |
73 | return (
74 |
79 |
80 | {slides}
81 |
82 |
83 |
84 | );
85 | }
86 | }
87 |
88 |
89 | export default CarouselItemPage;
90 |
--------------------------------------------------------------------------------
/client/src/components/Items-list-gender-homepage.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 | import { connect } from 'react-redux';
4 | import {
5 | oneKeywordForFilter
6 | } from '../actions/DataFetchingActions';
7 | import {
8 | Row,
9 | Col,
10 | Button,
11 | Card,
12 | CardImg,
13 | CardBody,
14 | Alert,
15 | Container
16 | } from 'reactstrap';
17 |
18 | const styles = {
19 | cardTitle: {
20 | display: 'flex',
21 | justifyContent: 'center',
22 | fontSize: '20px'
23 | },
24 | cardBtn: {
25 | textAlign:'center'
26 | },
27 | card: {
28 | marginBottom: '20px'
29 | }
30 | };
31 |
32 | const categoriesMenData = [
33 | {
34 | imgSrc:'https://images.unsplash.com/photo-1490367532201-b9bc1dc483f6?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=717ae13fa0e515d1c7c9e92456439845&dpr=1&auto=format&fit=crop&w=1000&q=80&cs=tinysrgb',
35 | cardTitle:'Polos',
36 | linkCategory:'Polos'
37 | },
38 | {
39 | imgSrc:'https://images.unsplash.com/photo-1530856021941-02c71be5926f?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=8d29673aa02db77423cf5ca770bd6d8e&dpr=1&auto=format&fit=crop&w=1000&q=80&cs=tinysrgb',
40 | cardTitle:'Shirts',
41 | linkCategory:'Shirts'
42 | },
43 | {
44 | imgSrc:'https://images.unsplash.com/photo-1421986386270-978ed214ec60?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=0f6cec57034960f50caf64c5af9c26d4&dpr=1&auto=format&fit=crop&w=1000&q=80&cs=tinysrgb',
45 | cardTitle:'Pants',
46 | linkCategory:'Pants'
47 | },
48 | {
49 | imgSrc:'https://images.unsplash.com/photo-1530856021941-02c71be5926f?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=8d29673aa02db77423cf5ca770bd6d8e&dpr=1&auto=format&fit=crop&w=1000&q=80&cs=tinysrgb',
50 | cardTitle:'Jackets',
51 | linkCategory:'Jackets'
52 | }
53 | ];
54 |
55 | const categoriesWomenData = [
56 | {
57 | imgSrc:'https://images.unsplash.com/photo-1472746729193-36ad213ac4a5?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=27aecaf25f31cf45d2de3ad774dad6ed&dpr=1&auto=format&fit=crop&w=1000&q=80&cs=tinysrgb',
58 | cardTitle:'Dresses',
59 | linkCategory:'Dresses'
60 | },
61 | {
62 | imgSrc:'https://images.unsplash.com/photo-1485527691629-8e370684924c?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=d6f450a6506599df62dc29593779a1b3&dpr=1&auto=format&fit=crop&w=1000&q=80&cs=tinysrgb',
63 | cardTitle:'Cardigans',
64 | linkCategory:'Cardigans'
65 | },
66 | {
67 | imgSrc:'https://images.unsplash.com/photo-1518622358385-8ea7d0794bf6?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=42226a7bf3b99eec89267859b4f36114&dpr=1&auto=format&fit=crop&w=1000&q=80&cs=tinysrgb',
68 | cardTitle:'Tops',
69 | linkCategory:'Tops'
70 | },
71 | {
72 | imgSrc:'https://images.unsplash.com/photo-1508445861827-7711f397113a?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=9b8561c6e78192892aae3c6943928c93&dpr=1&auto=format&fit=crop&w=1000&q=80&cs=tinysrgb',
73 | cardTitle:'Trench Coats',
74 | linkCategory:'Trench Coats'
75 | }
76 | ];
77 |
78 | const decorationData = gender => gender === 'women' ? categoriesWomenData : categoriesMenData;
79 |
80 | const eachCategory = (gender, oneKeywordForFilter) => (
81 | decorationData(gender).map(x=>(
82 |
83 |
84 | oneKeywordForFilter(x.cardTitle)}>
85 |
86 |
87 |
88 | {x.cardTitle}
89 |
90 |
91 |
92 |
93 |
94 |
95 | ))
96 | );
97 |
98 |
99 | const ItemsListGenderHomepage = props => {
100 | const { gender } = props.match.params
101 | const { oneKeywordForFilter } = props
102 | return (
103 |
104 |
105 | {gender.charAt(0).toUpperCase()+gender.slice(1)} selection
106 |
107 | {eachCategory(gender, oneKeywordForFilter)}
108 |
109 |
110 |
111 |
112 | )};
113 |
114 | const mapDispatchToProps = dispatch => ({oneKeywordForFilter: x => dispatch(oneKeywordForFilter(x))});
115 | const mapStateToProps = state => ({oneKeywordForFilter: state.oneKeywordForFilter});
116 |
117 | export default connect(mapStateToProps, mapDispatchToProps)(ItemsListGenderHomepage);
118 |
--------------------------------------------------------------------------------
/client/src/containers/Checkout-container.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import axios from 'axios';
3 | import { connect } from 'react-redux';
4 | import { selectorTotalAmountCart } from '../selectors/selector_list_statistics';
5 | import Checkout from '../components/Checkout';
6 | import { addUserAddress } from '../actions/UsersActions';
7 |
8 | class CheckoutContainer extends Component {
9 | constructor(props){
10 | super(props);
11 | this.state={
12 | step1: true,
13 | step2: false,
14 | step2Unlock: false,
15 | step3: false,
16 | step3Unlock: false,
17 | email: '',
18 | emailIsValid: true,
19 | firstName: 'fgsfdsf',
20 | lastName: 'fsdfsd',
21 | country: 'USA',
22 | city: 'fdsfdsfdsf',
23 | province: 'fdsfdsf',
24 | postalCode: 8767671167,
25 | phoneNumber: 17671651,
26 | address1: 'qdfsqfdsqfcfdxsf',
27 | address2: 'xsfdsxfsdfxsdf',
28 | shippingMethod: 2,
29 | formIsValid: false,
30 | totalDelivery: 5
31 | }
32 | };
33 |
34 | onChangeEmail = e => this.setState({ email: e.target.value});
35 | onChangeFirstName = e => this.setState({ firstName: e.target.value });
36 | onChangeLastName = e => this.setState({ lastName: e.target.value });
37 | onChangeCountry = e => this.setState({ country: e.target.value });
38 | onChangeCity = e => this.setState({ city: e.target.value });
39 | onChangeProvince = e => this.setState({ province: e.target.value });
40 | onChangePostalCode = e => this.setState({ postalCode: Number(e.target.value) });
41 | onChangePhoneNumber = e => this.setState({ phoneNumber: Number(e.target.value) });
42 | onChangeAdress1 = e => this.setState({ address1: e.target.value });
43 | onChangeAdress2 = e => this.setState({ address2: e.target.value });
44 | onChangeShipppingMethod = shippingMethod => this.setState({ shippingMethod });
45 | handleEmailValidation = emailIsValid => this.setState({ emailIsValid });
46 | formValidator = formIsValid => this.setState({ formIsValid });
47 |
48 | toggle = step => {
49 | step === 'step1' ? this.setState({
50 | step1: true,
51 | step2: false,
52 | step3: false
53 | }) :
54 | step === 'step2' ? this.setState({
55 | step1: false,
56 | step2: true,
57 | step3: false
58 | }) :
59 | step === 'step3' && this.setState({
60 | step1: false,
61 | step2: false,
62 | step3: true
63 | })
64 | };
65 |
66 | stepsUnlock = step => {
67 | step === 'step2' ? this.setState({ step2Unlock: true, step1: false, step2: true, step3: false }) :
68 | step === 'step3' && this.setState({ step3Unlock: true, step1: false, step2: false, step3: true })
69 | };
70 |
71 | onSubmitOrder = () => {
72 | const { email, firstName, lastName, country, city, province, postalCode, phoneNumber, address1, address2, totalDelivery } = this.state;
73 | const { getCart, selectorTotalAmountCart } = this.props;
74 | const ref = Math.random().toString(36).substring(5)+ Date.now();
75 | axios.post('/api/add/orders', {
76 | ref,
77 | customerinfo: {email, firstName, lastName, country, city, province, postalCode, phoneNumber, address1, address2},
78 | order: getCart.map(x => ({idItem: x._id, titleItem:x.title, selectedSize:x.selectedSize, selectedColor:x.selectedColor, price:x.price, quantity:x.quantity})),
79 | totalDelivery,
80 | totalAmount: selectorTotalAmountCart
81 | })
82 | .catch(error => {
83 | console.log(error);
84 | });
85 | };
86 |
87 | render() {
88 | return (
89 |
90 |
110 |
111 |
112 | )
113 | }
114 | };
115 |
116 |
117 | const mapStateToProps = state => ({
118 | getCart: state.cartReducer,
119 | getUserAddress: state.getUserAddress,
120 | selectorTotalAmountCart: selectorTotalAmountCart(state)
121 | });
122 |
123 | const mapDispatchToProps = dispatch => ({
124 | addUserAddress: x => dispatch(addUserAddress(x))
125 | });
126 |
127 | export default connect(mapStateToProps, mapDispatchToProps)(CheckoutContainer);
128 |
129 |
--------------------------------------------------------------------------------
/client/src/containers/Navbar-container.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Link, NavLink } from 'react-router-dom'
3 | import {
4 | isBrowser
5 | } from "react-device-detect";
6 | import { connect } from 'react-redux'
7 | import { oneKeywordForFilter, resetKeywords} from '../actions/DataFetchingActions';
8 | import { selectorTotalItemsCart } from '../selectors/selector_list_statistics';
9 | import CheckoutMiniSummaryPreview from '../components/Checkout-mini-summary-preview';
10 | import Submenu from '../components/Submenu';
11 | import {
12 | Collapse,
13 | Navbar,
14 | NavbarToggler,
15 | Nav,
16 | NavItem,
17 | Badge
18 | } from 'reactstrap';
19 |
20 | const styles = {
21 | itemMenu: {
22 | padding: '10px',
23 | listStyleType: 'none'
24 | },
25 | arrowDown: {
26 | width: 0,
27 | height: 0,
28 | borderStyle: 'solid',
29 | borderWidth: '30px 18px 0 18px',
30 | borderColor:' #072a48 transparent transparent transparent',
31 | position: 'absolute',
32 | zIndex:'3'
33 | },
34 | navbarBackground: {
35 | backgroundColor: '#072a48',
36 | zIndex: 3
37 | }
38 | }
39 |
40 | const arrowStyleSubmenu = (subMenuCategorySelected, gender, arrowDown) => subMenuCategorySelected === gender &&
41 |
42 | class NavbarContainer extends Component {
43 | constructor(props) {
44 | super(props);
45 |
46 | this.toggle = this.toggle.bind(this);
47 | this.state = {
48 | isOpen: false,
49 | subMenuOpen: false,
50 | subMenuCategorySelected: '',
51 | openCartPreview: false
52 | };
53 | }
54 | toggle() {
55 | this.setState({
56 | isOpen: !this.state.isOpen
57 | });
58 | }
59 |
60 | handleSubMenuEnter = (x) => {
61 | this.setState({
62 | subMenuOpen: true,
63 | subMenuCategorySelected: x
64 | })
65 | }
66 |
67 | handleSubMenuExit = () => {
68 | this.setState({
69 | subMenuOpen: false,
70 | subMenuCategorySelected: ''
71 | })
72 | }
73 |
74 |
75 |
76 | render() {
77 | const { sendOneKeyword, getCart, resetKeywords, totalItemsSelectorStats } = this.props
78 | const { isOpen, subMenuCategorySelected, subMenuOpen, openCartPreview } = this.state
79 | const { men, women } = this.props.categoriesProducts
80 | const { itemMenu, arrowDown, navbarBackground } = styles
81 |
82 | const categoriesNavItems = gender =>
83 | isBrowser ?
84 | (this.handleSubMenuEnter(gender)} >
85 | resetKeywords()}>{gender.charAt(0).toUpperCase() + gender.slice(1)} {arrowStyleSubmenu(subMenuCategorySelected, gender, arrowDown)}
86 | ) :
87 | (
88 | {return (resetKeywords(), this.toggle())}}>{gender}
89 | )
90 |
91 | const cartNavItem =
92 | isBrowser ?
93 | () : cart
107 |
108 | const subMenuHoverBrowser =
109 | subMenuOpen && isBrowser &&
110 |
116 |
117 |
118 | return (
119 |
120 |
121 | Home
122 |
123 |
124 | {categoriesNavItems('men')}
125 | {categoriesNavItems('women')}
126 | {cartNavItem}
127 |
128 |
129 | {subMenuHoverBrowser}
130 |
131 | );
132 | }
133 | };
134 |
135 |
136 | const mapStateToProps = state => ({
137 | categoriesProducts: state.categoriesProducts,
138 | getCart: state.cartReducer,
139 | totalItemsSelectorStats: selectorTotalItemsCart(state)
140 | });
141 |
142 | const mapDispatchToProps = dispatch => ({
143 | sendOneKeyword: x => dispatch(oneKeywordForFilter(x)),
144 | resetKeywords: () => dispatch(resetKeywords())
145 | });
146 |
147 |
148 | export default connect(mapStateToProps, mapDispatchToProps)(NavbarContainer);
149 |
--------------------------------------------------------------------------------
/client/src/components/Admin-modal-update.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Button, Modal, ModalHeader, ModalBody, ModalFooter, ListGroup, InputGroup, InputGroupAddon, InputGroupText, Input } from 'reactstrap';
3 | import { FiEdit } from 'react-icons/fi';
4 | import axios from 'axios';
5 |
6 | class AdminModalUpdate extends Component {
7 | constructor(props) {
8 | super(props);
9 | this.state = {
10 | modalEdit: false,
11 | title: this.props.infos.title,
12 | colors: this.props.infos.color,
13 | sizes: this.props.infos.size,
14 | tags: this.props.infos.tags,
15 | images: this.props.infos.images,
16 | description: this.props.infos.description,
17 | price: this.props.infos.price
18 | };
19 | }
20 |
21 | toggle = () => this.setState({ modalEdit: !this.state.modalEdit });
22 |
23 | onSubmit = (id, title, price, color, size, tags, images, description) => {
24 | axios.post('/api/update/item', {
25 | id,
26 | title,
27 | price,
28 | color: (color.slice(0)+'').replace(/\s/g,'').split(','),
29 | size: (size.slice(0)+'').replace(/\s/g,'').split(','),
30 | tags: (tags.slice(0)+'').replace(/\s/g,'').split(','),
31 | images: (images.slice(0)+'').replace(/\s/g,'').split(','),
32 | description
33 | })
34 | .then(response => {
35 | this.setState({ modalEdit: !this.state.modalEdit });
36 | console.log(response);
37 | })
38 | .then(() => {
39 | window.location.reload(true)
40 | })
41 | .catch(err => {
42 | console.log(err);
43 | });
44 | }
45 |
46 | onChangeTitle = (e) => this.setState({title: e.target.value})
47 | onChangePrice = (e) => this.setState({price: e.target.value})
48 | onChangeColor = (e) => this.setState({colors: e.target.value})
49 | onChangeSizes = (e) => this.setState({sizes: e.target.value})
50 | onChangeTags = (e) => this.setState({tags: e.target.value})
51 | onChangeImages = (e) => this.setState({images: e.target.value})
52 | onChangeDescription = (e) => this.setState({description: e.target.value})
53 |
54 |
55 | render() {
56 | const { title, colors, sizes, tags, images, description, price } = this.state
57 | const { _id } = this.props.infos
58 |
59 | return (
60 |
61 |
62 |
63 | {this.props.infos.title} - (id: {_id})
64 |
65 |
66 |
67 |
68 | Name
69 |
70 |
71 |
72 |
73 |
74 | Price $
75 |
76 |
77 |
78 |
79 |
80 | Colors
81 |
82 | ' '+x)} value={colors} onChange={this.onChangeColor} />
83 |
84 |
85 |
86 | Sizes
87 |
88 | x+' '+x)} value={sizes} onChange={this.onChangeSizes} />
89 |
90 |
91 |
92 | Category
93 |
94 |
95 |
96 |
97 |
98 | Images
99 |
100 | x+' '+x)} value={images} onChange={this.onChangeImages} />
101 |
102 |
103 |
104 | description
105 |
106 |
107 |
108 |
109 |
110 |
111 | {' '}
123 |
124 |
125 |
126 |
127 | );
128 | }
129 | };
130 |
131 | export default AdminModalUpdate;
132 |
--------------------------------------------------------------------------------
/client/src/components/Items-list.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react';
3 | import { isBrowser, isMobile } from "react-device-detect";
4 | import ItemsListBanner from './Items-list-banner';
5 | import Breadcrumbs from './Breadcrumbs';
6 | import ItemsListSidebar from './Items-list-sidebar';
7 | import Paginator from './Paginator';
8 | import EachItemInList from './Each-item-in-list';
9 | import LoadingGif from './Loading-gif';
10 | import ButtonLinkGenderPage from './Button-link-gender-page'
11 | import {
12 | Container,
13 | Row,
14 | Col
15 | } from 'reactstrap';
16 |
17 | const propTypes = {
18 | listIsLoading: PropTypes.bool.isRequired,
19 | FilteredSortedList: PropTypes.array.isRequired,
20 | keywordsForFilter: PropTypes.array.isRequired,
21 | oneKeywordForFilter: PropTypes.func.isRequired,
22 | currentPageHandler: PropTypes.func.isRequired,
23 | currentPage: PropTypes.number.isRequired,
24 | itemsMaxPage: PropTypes.number.isRequired,
25 | dispatchSize: PropTypes.func.isRequired,
26 | sortSizeForFilter: PropTypes.string.isRequired,
27 | sortArgsForFilter: PropTypes.string.isRequired,
28 | dispatchToSortList: PropTypes.func.isRequired,
29 | keywordsSelectAction: PropTypes.func.isRequired,
30 | categoriesProducts: PropTypes.object.isRequired,
31 | actionPriceRangeFilter: PropTypes.func.isRequired,
32 | reducerPriceRangeFilter: PropTypes.number.isRequired,
33 | actionFillFilters: PropTypes.func.isRequired
34 | }
35 |
36 | const styles = {
37 | spaceColumn: {
38 | marginLeft: '25px',
39 | marginRight: '25px',
40 | marginBottom: '50px'
41 | },
42 | fontSize: {
43 | fontSize: '15px'
44 | },
45 | marginLeftBtn: {
46 | marginLeft: '30px'
47 | },
48 | containerPaddingTop: {
49 | paddingTop: '35px'
50 | }
51 | };
52 |
53 | const ItemsList = ({
54 | match,
55 | listIsLoading,
56 | FilteredSortedList,
57 | keywordsForFilter,
58 | oneKeywordForFilter,
59 | currentPageHandler,
60 | currentPage,
61 | itemsMaxPage,
62 | dispatchSize,
63 | sortSizeForFilter,
64 | sortArgsForFilter,
65 | dispatchToSortList,
66 | keywordsSelectAction,
67 | categoriesProducts,
68 | actionPriceRangeFilter,
69 | reducerPriceRangeFilter,
70 | actionFillFilters
71 | }) => {
72 |
73 | const { gender } = match.params;
74 | const listLength = FilteredSortedList.length
75 |
76 | const loading_logic = listIsLoading && ;
77 |
78 | const pagination = Math.ceil(listLength/itemsMaxPage)>1 ?
79 | () :
85 | currentPage > 1 && (()=> currentPageHandler('empty'))()
86 |
87 |
88 |
89 | const itemsListByGender_logic =
90 |
91 |
92 | {listIsLoading === false && Results: {listLength}}
93 | {FilteredSortedList.length === 0 && listIsLoading === false &&
94 |
95 |
Select a category:
96 |
97 |
98 | }
99 |
100 |
101 | {loading_logic} {/* if list is loading show loader */}
102 |
109 |
110 | {pagination}
111 | ;
112 |
113 | const sideBar = isBrowser &&
114 |
115 |
116 |
128 |
129 | ;
130 |
131 |
132 | return (
133 |
134 |
141 | 0}
151 | showFilterBtn={isMobile}
152 | dispatchSize={dispatchSize}
153 | sortSizeForFilter={sortSizeForFilter}
154 | keywordsSelectAction={keywordsSelectAction}
155 | categoriesProducts={categoriesProducts}
156 | actionPriceRangeFilter={actionPriceRangeFilter}
157 | reducerPriceRangeFilter={reducerPriceRangeFilter}
158 | listLength={listLength}
159 | />
160 |
161 |
162 | {itemsListByGender_logic} {/* show list depending on gender */}
163 | {sideBar}
164 |
165 |
166 |
167 | );
168 | };
169 |
170 | ItemsList.propTypes = propTypes;
171 |
172 | export default ItemsList;
173 |
--------------------------------------------------------------------------------
/client/src/components/Checkout.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react';
3 | import CheckoutStepOne from './Checkout-step-one';
4 | import CheckoutStepTwo from './Checkout-step-two';
5 | import CheckoutStepThree from './Checkout-step-three';
6 | import CheckoutMiniSummary from '../components/Checkout-mini-summary';
7 | import {
8 | Row,
9 | Col,
10 | Container,
11 | Progress,
12 | ListGroup
13 | } from 'reactstrap';
14 |
15 | const propTypes = {
16 | getCart: PropTypes.array.isRequired,
17 | addUserAddress: PropTypes.func.isRequired,
18 | getUserAddress: PropTypes.object.isRequired,
19 | step1: PropTypes.bool.isRequired,
20 | step2: PropTypes.bool.isRequired,
21 | step3: PropTypes.bool.isRequired,
22 | step2Unlock: PropTypes.bool.isRequired,
23 | step3Unlock: PropTypes.bool.isRequired,
24 | email: PropTypes.string.isRequired,
25 | firstName: PropTypes.string.isRequired,
26 | lastName: PropTypes.string.isRequired,
27 | country: PropTypes.string.isRequired,
28 | city: PropTypes.string.isRequired,
29 | province: PropTypes.string.isRequired,
30 | postalCode: PropTypes.number.isRequired,
31 | phoneNumber: PropTypes.number.isRequired,
32 | address1: PropTypes.string.isRequired,
33 | address2: PropTypes.string.isRequired,
34 | shippingMethod: PropTypes.number.isRequired,
35 | formIsValid: PropTypes.bool.isRequired,
36 | onChangeFirstName: PropTypes.func.isRequired,
37 | onChangeLastName: PropTypes.func.isRequired,
38 | onChangeCountry: PropTypes.func.isRequired,
39 | onChangeCity: PropTypes.func.isRequired,
40 | onChangeProvince: PropTypes.func.isRequired,
41 | onChangePostalCode: PropTypes.func.isRequired,
42 | onChangePhoneNumber: PropTypes.func.isRequired,
43 | onChangeAdress1: PropTypes.func.isRequired,
44 | onChangeAdress2: PropTypes.func.isRequired,
45 | onChangeShipppingMethod: PropTypes.func.isRequired,
46 | onChangeEmail: PropTypes.func.isRequired,
47 | toggle: PropTypes.func.isRequired,
48 | stepsUnlock: PropTypes.func.isRequired,
49 | formValidator: PropTypes.func.isRequired,
50 | onSubmitOrder: PropTypes.func.isRequired
51 | };
52 |
53 | const styles = {
54 | container: {
55 | paddingTop: '120px',
56 | paddingBottom: '120px'
57 | },
58 | h1: {
59 | paddingBottom: '20px'
60 | },
61 | collapsePannel: {
62 | borderBottom: '1px solid grey'
63 | },
64 | collapasePannelTitle: {
65 | cursor: 'pointer'
66 | },
67 | formFieldsSpace: {
68 | paddingTop: '10px'
69 | }
70 | }
71 |
72 | const CheckoutContainer = ({
73 | getCart,
74 | addUserAddress,
75 | getUserAddress,
76 | step1,
77 | step2,
78 | step3,
79 | step2Unlock,
80 | step3Unlock,
81 | email,
82 | firstName,
83 | lastName,
84 | country,
85 | city,
86 | province,
87 | postalCode,
88 | phoneNumber,
89 | address1,
90 | address2,
91 | shippingMethod,
92 | formIsValid,
93 | onChangeFirstName,
94 | onChangeLastName,
95 | onChangeCountry,
96 | onChangeCity,
97 | onChangeProvince,
98 | onChangePostalCode,
99 | onChangePhoneNumber,
100 | onChangeAdress1,
101 | onChangeAdress2,
102 | onChangeShipppingMethod,
103 | onChangeEmail,
104 | toggle,
105 | stepsUnlock,
106 | formValidator,
107 | emailIsValid,
108 | handleEmailValidation,
109 | onSubmitOrder,
110 | selectorTotalAmountCart,
111 | totalDelivery
112 | }) => (
113 |
114 |
115 |
116 | Check out
117 |
118 |
119 |
120 |
132 |
163 |
173 |
174 |
175 |
176 |
181 |
182 |
183 |
184 |
185 | );
186 |
187 | CheckoutContainer.propTypes = propTypes;
188 |
189 | export default CheckoutContainer;
190 |
--------------------------------------------------------------------------------
/client/src/components/Item.js:
--------------------------------------------------------------------------------
1 | import PropTypes from "prop-types";
2 | import React, { Component } from "react";
3 | import { isBrowser } from "react-device-detect";
4 | import Breadcrumbs from "./Breadcrumbs";
5 | import { Col, Row, Container } from "reactstrap";
6 | import StarRatings from "react-star-ratings";
7 | import ButtonSizeSelect from "./Button-size-select";
8 | import ButtonAddToCart from "./Button-add-to-cart";
9 | import CheckoutModal from "./Checkout-modal";
10 | import ButtonsColorSelect from "./Buttons-color-select";
11 | import CarouselItemPage from "./Carousel-item";
12 |
13 | const propTypes = {
14 | infoItem: PropTypes.array.isRequired,
15 | addToCart: PropTypes.func.isRequired,
16 | selectedSize: PropTypes.string.isRequired,
17 | selectedColor: PropTypes.string.isRequired,
18 | loading: PropTypes.bool.isRequired,
19 | errorFetching: PropTypes.bool,
20 | handleSizeSelection: PropTypes.func.isRequired,
21 | handleColorSelection: PropTypes.func.isRequired,
22 | totalItemsSelectorStats: PropTypes.number.isRequired
23 | };
24 |
25 | const styles = {
26 | marginTop: "20px"
27 | };
28 |
29 | class Item extends Component {
30 | constructor(props) {
31 | super(props);
32 | this.state = {
33 | selectedImage: 0,
34 | openModal: false
35 | };
36 | }
37 |
38 | toggleModal = () => this.setState({ openModal: !this.state.openModal });
39 |
40 | render() {
41 | const {
42 | infoItem,
43 | loading,
44 | errorFetching,
45 | addToCart,
46 | handleSizeSelection,
47 | handleColorSelection,
48 | validateSizeSelection,
49 | validateColorSelection,
50 | selectedSize,
51 | selectedColor,
52 | totalItemsSelectorStats,
53 | colorSelectionMissingRemark,
54 | sizeSelectionMissingRemark
55 | } = this.props;
56 |
57 | const { selectedImage } = this.state;
58 |
59 | if (errorFetching) {
60 | return Sorry! There was an error loading the items
;
61 | }
62 |
63 | const { size, images, color, tags } = infoItem;
64 | if (
65 | loading ||
66 | size === undefined ||
67 | images === undefined ||
68 | color === undefined ||
69 | tags === undefined
70 | ) {
71 | return ;
72 | }
73 | const thumbnailsBrowersView = infoItem.images.map((x, index) => (
74 |
75 |
![]()
this.setState({ selectedImage: index })}
77 | src={infoItem.images[index]}
78 | alt="Smiley face"
79 | width="50px"
80 | height="70px"
81 | style={{cursor: 'pointer'}}
82 | />
83 |
84 | ));
85 | const MainImageBrowserView = (
86 |
91 | );
92 | const MainImageMobileView = (
93 |
94 | );
95 |
96 | return (
97 |
98 |
103 |
104 |
105 | {isBrowser && thumbnailsBrowersView}
106 |
107 | {isBrowser ? MainImageBrowserView : MainImageMobileView}
108 |
109 |
110 | {infoItem.title}
111 | {infoItem.price} $
112 |
121 |
122 |
130 | {sizeSelectionMissingRemark.length > 0 ? (
131 | *{sizeSelectionMissingRemark}
132 | ) : (
133 | ""
134 | )}
135 |
136 |
137 |
138 | Selected color:{" "}
139 | {selectedColor === "" ? Choose below : selectedColor}
140 |
141 |
147 | {colorSelectionMissingRemark.length > 0 ? (
148 |
*{colorSelectionMissingRemark}
149 | ) : (
150 | ""
151 | )}
152 |
153 |
154 |
166 |
167 |
175 | Description:
176 | {infoItem.description}
177 |
178 |
179 |
180 |
181 | );
182 | }
183 | }
184 |
185 | Item.propTypes = propTypes;
186 |
187 | export default Item;
188 |
--------------------------------------------------------------------------------
/client/src/components/Checkout-step-two.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react';
3 | import {
4 | Row,
5 | Col,
6 | Input,
7 | Badge,
8 | Button,
9 | ListGroupItem,
10 | Collapse,
11 | Label,
12 | Alert,
13 | FormGroup
14 | } from 'reactstrap';
15 |
16 | const propTypes = {
17 | addUserAddress: PropTypes.func.isRequired,
18 | step2: PropTypes.bool.isRequired,
19 | step2Unlock: PropTypes.bool.isRequired,
20 | firstName: PropTypes.string.isRequired,
21 | lastName: PropTypes.string.isRequired,
22 | country: PropTypes.string.isRequired,
23 | city: PropTypes.string.isRequired,
24 | province: PropTypes.string.isRequired,
25 | postalCode: PropTypes.number.isRequired,
26 | phoneNumber: PropTypes.number.isRequired,
27 | address1: PropTypes.string.isRequired,
28 | address2: PropTypes.string.isRequired,
29 | shippingMethod: PropTypes.number.isRequired,
30 | formIsValid: PropTypes.bool.isRequired,
31 | onChangeFirstName: PropTypes.func.isRequired,
32 | onChangeLastName: PropTypes.func.isRequired,
33 | onChangeCountry: PropTypes.func.isRequired,
34 | onChangeCity: PropTypes.func.isRequired,
35 | onChangeProvince: PropTypes.func.isRequired,
36 | onChangePostalCode: PropTypes.func.isRequired,
37 | onChangePhoneNumber: PropTypes.func.isRequired,
38 | onChangeAdress1: PropTypes.func.isRequired,
39 | onChangeAdress2: PropTypes.func.isRequired,
40 | onChangeShipppingMethod: PropTypes.func.isRequired,
41 | toggle: PropTypes.func.isRequired,
42 | stepsUnlock: PropTypes.func.isRequired,
43 | formValidator: PropTypes.func.isRequired
44 | };
45 |
46 | const CheckoutStepTwo = ({
47 | styles,
48 | step2,
49 | step2Unlock,
50 | toggle,
51 | stepsUnlock,
52 | onChangeFirstName,
53 | onChangeLastName,
54 | onChangeCountry,
55 | onChangeCity,
56 | onChangeProvince,
57 | onChangePostalCode,
58 | onChangePhoneNumber,
59 | onChangeAdress1,
60 | onChangeAdress2,
61 | onChangeShipppingMethod,
62 | firstName,
63 | lastName,
64 | country,
65 | city,
66 | province,
67 | postalCode,
68 | phoneNumber,
69 | address1,
70 | address2,
71 | shippingMethod,
72 | formValidator,
73 | formIsValid,
74 | addUserAddress
75 | }) => {
76 |
77 | const validatorClient = {
78 | firstName: firstName.length > 2 && typeof firstName === 'string',
79 | lastName: lastName.length > 2 && typeof lastName === 'string',
80 | country: typeof country === 'string',
81 | city: city.length > 2 && typeof city === 'string',
82 | province: province.length > 2 && typeof province === 'string',
83 | postalCode: typeof postalCode === 'number',
84 | phoneNumber: typeof phoneNumber === 'number',
85 | address1: address1.length > 2 && typeof address1 === 'string'
86 | }
87 |
88 | const warningValidator = (x) => {
89 | if(x) {
90 | if([...new Set(Object.entries(validatorClient).map(([k, v]) => (v)))].sort()[0] === false){
91 | return(
92 |
93 |
94 | Please fill these fields correctly:
95 | {!validatorClient.firstName && First Name
}
96 | {!validatorClient.lastName && Last Name
}
97 | {!validatorClient.country && Country
}
98 | {!validatorClient.city && City
}
99 | {!validatorClient.province && Province
}
100 | {!validatorClient.postalCode && Postal Code
}
101 | {!validatorClient.phoneNumber && Phone Number
}
102 | {!validatorClient.address1 && Address1 field
}
103 |
104 |
105 | )
106 | } else {
107 | return(
108 |
109 | Thank you! Click 'continue'
110 |
111 | )
112 | }
113 | }
114 | }
115 |
116 | return (
117 |
118 |
119 | step2Unlock && toggle('step2')}>
120 | 2 Shipping {}
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
174 |
175 |
176 |
180 |
181 |
182 |
183 |
184 |
202 |
203 |
204 | {warningValidator(formIsValid)}
205 |
206 |
207 |
208 | )
209 | }
210 |
211 | CheckoutStepTwo.propTypes = propTypes;
212 |
213 | export default CheckoutStepTwo;
214 |
--------------------------------------------------------------------------------
/client/src/style/checkbox.min.css:
--------------------------------------------------------------------------------
1 | .pretty *{box-sizing:border-box}.pretty input:not([type=checkbox]):not([type=radio]){display:none}.pretty{position:relative;display:inline-block;margin-right:1em;white-space:nowrap;line-height:1}.pretty input{position:absolute;left:0;top:0;min-width:1em;width:100%;height:100%;z-index:2;opacity:0;margin:0;padding:0;cursor:pointer}.pretty .state label{position:initial;display:inline-block;font-weight:400;margin:0;text-indent:1.5em;min-width:calc(1em + 2px)}.pretty .state label:after,.pretty .state label:before{content:'';width:calc(1em + 2px);height:calc(1em + 2px);display:block;box-sizing:border-box;border-radius:0;border:1px solid transparent;z-index:0;position:absolute;left:0;top:calc((0% - (100% - 1em)) - 8%);background-color:transparent}.pretty .state label:before{border-color:#bdc3c7}.pretty .state.p-is-hover,.pretty .state.p-is-indeterminate{display:none}@-webkit-keyframes zoom{0%{opacity:0;-webkit-transform:scale(0);transform:scale(0)}}@keyframes zoom{0%{opacity:0;-webkit-transform:scale(0);transform:scale(0)}}@-webkit-keyframes tada{0%{-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in;opacity:0;-webkit-transform:scale(7);transform:scale(7)}38%{-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out;opacity:1;-webkit-transform:scale(1);transform:scale(1)}55%{-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in;-webkit-transform:scale(1.5);transform:scale(1.5)}72%{-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out;-webkit-transform:scale(1);transform:scale(1)}81%{-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in;-webkit-transform:scale(1.24);transform:scale(1.24)}89%{-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out;-webkit-transform:scale(1);transform:scale(1)}95%{-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in;-webkit-transform:scale(1.04);transform:scale(1.04)}100%{-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out;-webkit-transform:scale(1);transform:scale(1)}}@keyframes tada{0%{-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in;opacity:0;-webkit-transform:scale(7);transform:scale(7)}38%{-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out;opacity:1;-webkit-transform:scale(1);transform:scale(1)}55%{-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in;-webkit-transform:scale(1.5);transform:scale(1.5)}72%{-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out;-webkit-transform:scale(1);transform:scale(1)}81%{-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in;-webkit-transform:scale(1.24);transform:scale(1.24)}89%{-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out;-webkit-transform:scale(1);transform:scale(1)}95%{-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in;-webkit-transform:scale(1.04);transform:scale(1.04)}100%{-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out;-webkit-transform:scale(1);transform:scale(1)}}@-webkit-keyframes jelly{0%{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}30%{-webkit-transform:scale3d(.75,1.25,1);transform:scale3d(.75,1.25,1)}40%{-webkit-transform:scale3d(1.25,.75,1);transform:scale3d(1.25,.75,1)}50%{-webkit-transform:scale3d(.85,1.15,1);transform:scale3d(.85,1.15,1)}65%{-webkit-transform:scale3d(1.05,.95,1);transform:scale3d(1.05,.95,1)}75%{-webkit-transform:scale3d(.95,1.05,1);transform:scale3d(.95,1.05,1)}100%{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}}@keyframes jelly{0%{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}30%{-webkit-transform:scale3d(.75,1.25,1);transform:scale3d(.75,1.25,1)}40%{-webkit-transform:scale3d(1.25,.75,1);transform:scale3d(1.25,.75,1)}50%{-webkit-transform:scale3d(.85,1.15,1);transform:scale3d(.85,1.15,1)}65%{-webkit-transform:scale3d(1.05,.95,1);transform:scale3d(1.05,.95,1)}75%{-webkit-transform:scale3d(.95,1.05,1);transform:scale3d(.95,1.05,1)}100%{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}}@-webkit-keyframes rotate{0%{opacity:0;-webkit-transform:translateZ(-200px) rotate(-45deg);transform:translateZ(-200px) rotate(-45deg)}100%{opacity:1;-webkit-transform:translateZ(0) rotate(0);transform:translateZ(0) rotate(0)}}@keyframes rotate{0%{opacity:0;-webkit-transform:translateZ(-200px) rotate(-45deg);transform:translateZ(-200px) rotate(-45deg)}100%{opacity:1;-webkit-transform:translateZ(0) rotate(0);transform:translateZ(0) rotate(0)}}@-webkit-keyframes pulse{0%{box-shadow:0 0 0 0 #bdc3c7}100%{box-shadow:0 0 0 1.5em rgba(189,195,199,0)}}@keyframes pulse{0%{box-shadow:0 0 0 0 #bdc3c7}100%{box-shadow:0 0 0 1.5em rgba(189,195,199,0)}}.pretty.p-default.p-fill .state label:after{-webkit-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}.pretty.p-default .state label:after{-webkit-transform:scale(.6);-ms-transform:scale(.6);transform:scale(.6)}.pretty.p-default input:checked~.state label:after{background-color:#bdc3c7!important}.pretty.p-default.p-thick .state label:after,.pretty.p-default.p-thick .state label:before{border-width:calc(1em / 7)}.pretty.p-default.p-thick .state label:after{-webkit-transform:scale(.4)!important;-ms-transform:scale(.4)!important;transform:scale(.4)!important}.pretty.p-icon .state .icon{position:absolute;font-size:1em;width:calc(1em + 2px);height:calc(1em + 2px);left:0;z-index:1;text-align:center;line-height:normal;top:calc((0% - (100% - 1em)) - 8%);border:1px solid transparent;opacity:0}.pretty.p-icon .state .icon:before{margin:0;width:100%;height:100%;text-align:center;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-flex:1;-ms-flex:1;flex:1;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;line-height:1}.pretty.p-icon input:checked~.state .icon{opacity:1}.pretty.p-icon input:checked~.state label:before{border-color:#5a656b}.pretty.p-svg .state .svg{position:absolute;font-size:1em;width:calc(1em + 2px);height:calc(1em + 2px);left:0;z-index:1;text-align:center;line-height:normal;top:calc((0% - (100% - 1em)) - 8%);border:1px solid transparent;opacity:0}.pretty.p-svg .state svg{margin:0;width:100%;height:100%;text-align:center;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-flex:1;-ms-flex:1;flex:1;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;line-height:1}.pretty.p-svg input:checked~.state .svg{opacity:1}.pretty.p-image .state img{opacity:0;position:absolute;width:calc(1em + 2px);height:calc(1em + 2px);top:0;top:calc((0% - (100% - 1em)) - 8%);left:0;z-index:0;text-align:center;line-height:normal;-webkit-transform:scale(.8);-ms-transform:scale(.8);transform:scale(.8)}.pretty.p-image input:checked~.state img{opacity:1}.pretty.p-switch input{min-width:2em}.pretty.p-switch .state{position:relative}.pretty.p-switch .state:before{content:'';border:1px solid #bdc3c7;border-radius:60px;width:2em;box-sizing:unset;height:calc(1em + 2px);position:absolute;top:0;top:calc((0% - (100% - 1em)) - 16%);z-index:0;transition:all .5s ease}.pretty.p-switch .state label{text-indent:2.5em}.pretty.p-switch .state label:after,.pretty.p-switch .state label:before{transition:all .5s ease;border-radius:100%;left:0;border-color:transparent;-webkit-transform:scale(.8);-ms-transform:scale(.8);transform:scale(.8)}.pretty.p-switch .state label:after{background-color:#bdc3c7!important}.pretty.p-switch input:checked~.state:before{border-color:#5a656b}.pretty.p-switch input:checked~.state label:before{opacity:0}.pretty.p-switch input:checked~.state label:after{background-color:#5a656b!important;left:1em}.pretty.p-switch.p-fill input:checked~.state:before{border-color:#5a656b;background-color:#5a656b!important}.pretty.p-switch.p-fill input:checked~.state label:before{opacity:0}.pretty.p-switch.p-fill input:checked~.state label:after{background-color:#fff!important;left:1em}.pretty.p-switch.p-slim .state:before{height:.1em;background:#bdc3c7!important;top:calc(50% - .1em)}.pretty.p-switch.p-slim input:checked~.state:before{border-color:#5a656b;background-color:#5a656b!important}.pretty.p-has-hover input:hover~.state:not(.p-is-hover){display:none}.pretty.p-has-hover input:hover~.state.p-is-hover{display:block}.pretty.p-has-hover input:hover~.state.p-is-hover .icon{display:block}.pretty.p-has-focus input:focus~.state label:before{box-shadow:0 0 3px 0 #bdc3c7}.pretty.p-has-indeterminate input[type=checkbox]:indeterminate~.state:not(.p-is-indeterminate){display:none}.pretty.p-has-indeterminate input[type=checkbox]:indeterminate~.state.p-is-indeterminate{display:block}.pretty.p-has-indeterminate input[type=checkbox]:indeterminate~.state.p-is-indeterminate .icon{display:block;opacity:1}.pretty.p-toggle .state.p-on{opacity:0;display:none}.pretty.p-toggle .state .icon,.pretty.p-toggle .state .svg,.pretty.p-toggle .state img,.pretty.p-toggle .state.p-off{opacity:1;display:inherit}.pretty.p-toggle .state.p-off .icon{color:#bdc3c7}.pretty.p-toggle input:checked~.state.p-on{opacity:1;display:inherit}.pretty.p-toggle input:checked~.state.p-off{opacity:0;display:none}.pretty.p-plain input:checked~.state label:before,.pretty.p-plain.p-toggle .state label:before{content:none}.pretty.p-plain.p-plain .icon{-webkit-transform:scale(1.1);-ms-transform:scale(1.1);transform:scale(1.1)}.pretty.p-round .state label:after,.pretty.p-round .state label:before{border-radius:100%}.pretty.p-round.p-icon .state .icon{border-radius:100%;overflow:hidden}.pretty.p-round.p-icon .state .icon:before{-webkit-transform:scale(.8);-ms-transform:scale(.8);transform:scale(.8)}.pretty.p-curve .state label:after,.pretty.p-curve .state label:before{border-radius:20%}.pretty.p-smooth .icon,.pretty.p-smooth .svg,.pretty.p-smooth label:after,.pretty.p-smooth label:before{transition:all .5s ease}.pretty.p-smooth input:checked+.state label:after{transition:all .3s ease}.pretty.p-smooth input:checked+.state .icon,.pretty.p-smooth input:checked+.state .svg,.pretty.p-smooth input:checked+.state img{-webkit-animation:zoom .2s ease;animation:zoom .2s ease}.pretty.p-smooth.p-default input:checked+.state label:after{-webkit-animation:zoom .2s ease;animation:zoom .2s ease}.pretty.p-smooth.p-plain input:checked+.state label:before{content:'';-webkit-transform:scale(0);-ms-transform:scale(0);transform:scale(0);transition:all .5s ease}.pretty.p-tada:not(.p-default) input:checked+.state .icon,.pretty.p-tada:not(.p-default) input:checked+.state .svg,.pretty.p-tada:not(.p-default) input:checked+.state img,.pretty.p-tada:not(.p-default) input:checked+.state label:after,.pretty.p-tada:not(.p-default) input:checked+.state label:before{-webkit-animation:tada .7s cubic-bezier(.25,.46,.45,.94) 1 alternate;animation:tada .7s cubic-bezier(.25,.46,.45,.94) 1 alternate;opacity:1}.pretty.p-jelly:not(.p-default) input:checked+.state .icon,.pretty.p-jelly:not(.p-default) input:checked+.state .svg,.pretty.p-jelly:not(.p-default) input:checked+.state img,.pretty.p-jelly:not(.p-default) input:checked+.state label:after,.pretty.p-jelly:not(.p-default) input:checked+.state label:before{-webkit-animation:jelly .7s cubic-bezier(.25,.46,.45,.94);animation:jelly .7s cubic-bezier(.25,.46,.45,.94);opacity:1}.pretty.p-jelly:not(.p-default) input:checked+.state label:before{border-color:transparent}.pretty.p-rotate:not(.p-default) input:checked~.state .icon,.pretty.p-rotate:not(.p-default) input:checked~.state .svg,.pretty.p-rotate:not(.p-default) input:checked~.state img,.pretty.p-rotate:not(.p-default) input:checked~.state label:after,.pretty.p-rotate:not(.p-default) input:checked~.state label:before{-webkit-animation:rotate .7s cubic-bezier(.25,.46,.45,.94);animation:rotate .7s cubic-bezier(.25,.46,.45,.94);opacity:1}.pretty.p-rotate:not(.p-default) input:checked~.state label:before{border-color:transparent}.pretty.p-pulse:not(.p-switch) input:checked~.state label:before{-webkit-animation:pulse 1s;animation:pulse 1s}.pretty input[disabled]{cursor:not-allowed;display:none}.pretty input[disabled]~*{opacity:.5}.pretty.p-locked input{display:none;cursor:not-allowed}.pretty input:checked~.state.p-primary label:after,.pretty.p-toggle .state.p-primary label:after{background-color:#428bca!important}.pretty input:checked~.state.p-primary .icon,.pretty input:checked~.state.p-primary .svg,.pretty.p-toggle .state.p-primary .icon,.pretty.p-toggle .state.p-primary .svg{color:#fff;stroke:#fff}.pretty input:checked~.state.p-primary-o label:before,.pretty.p-toggle .state.p-primary-o label:before{border-color:#428bca}.pretty input:checked~.state.p-primary-o label:after,.pretty.p-toggle .state.p-primary-o label:after{background-color:transparent}.pretty input:checked~.state.p-primary-o .icon,.pretty input:checked~.state.p-primary-o .svg,.pretty input:checked~.state.p-primary-o svg,.pretty.p-toggle .state.p-primary-o .icon,.pretty.p-toggle .state.p-primary-o .svg,.pretty.p-toggle .state.p-primary-o svg{color:#428bca;stroke:#428bca}.pretty.p-default:not(.p-fill) input:checked~.state.p-primary-o label:after{background-color:#428bca!important}.pretty.p-switch input:checked~.state.p-primary:before{border-color:#428bca}.pretty.p-switch.p-fill input:checked~.state.p-primary:before{background-color:#428bca!important}.pretty.p-switch.p-slim input:checked~.state.p-primary:before{border-color:#245682;background-color:#245682!important}.pretty input:checked~.state.p-info label:after,.pretty.p-toggle .state.p-info label:after{background-color:#5bc0de!important}.pretty input:checked~.state.p-info .icon,.pretty input:checked~.state.p-info .svg,.pretty.p-toggle .state.p-info .icon,.pretty.p-toggle .state.p-info .svg{color:#fff;stroke:#fff}.pretty input:checked~.state.p-info-o label:before,.pretty.p-toggle .state.p-info-o label:before{border-color:#5bc0de}.pretty input:checked~.state.p-info-o label:after,.pretty.p-toggle .state.p-info-o label:after{background-color:transparent}.pretty input:checked~.state.p-info-o .icon,.pretty input:checked~.state.p-info-o .svg,.pretty input:checked~.state.p-info-o svg,.pretty.p-toggle .state.p-info-o .icon,.pretty.p-toggle .state.p-info-o .svg,.pretty.p-toggle .state.p-info-o svg{color:#5bc0de;stroke:#5bc0de}.pretty.p-default:not(.p-fill) input:checked~.state.p-info-o label:after{background-color:#5bc0de!important}.pretty.p-switch input:checked~.state.p-info:before{border-color:#5bc0de}.pretty.p-switch.p-fill input:checked~.state.p-info:before{background-color:#5bc0de!important}.pretty.p-switch.p-slim input:checked~.state.p-info:before{border-color:#2390b0;background-color:#2390b0!important}.pretty input:checked~.state.p-success label:after,.pretty.p-toggle .state.p-success label:after{background-color:#5cb85c!important}.pretty input:checked~.state.p-success .icon,.pretty input:checked~.state.p-success .svg,.pretty.p-toggle .state.p-success .icon,.pretty.p-toggle .state.p-success .svg{color:#fff;stroke:#fff}.pretty input:checked~.state.p-success-o label:before,.pretty.p-toggle .state.p-success-o label:before{border-color:#5cb85c}.pretty input:checked~.state.p-success-o label:after,.pretty.p-toggle .state.p-success-o label:after{background-color:transparent}.pretty input:checked~.state.p-success-o .icon,.pretty input:checked~.state.p-success-o .svg,.pretty input:checked~.state.p-success-o svg,.pretty.p-toggle .state.p-success-o .icon,.pretty.p-toggle .state.p-success-o .svg,.pretty.p-toggle .state.p-success-o svg{color:#5cb85c;stroke:#5cb85c}.pretty.p-default:not(.p-fill) input:checked~.state.p-success-o label:after{background-color:#5cb85c!important}.pretty.p-switch input:checked~.state.p-success:before{border-color:#5cb85c}.pretty.p-switch.p-fill input:checked~.state.p-success:before{background-color:#5cb85c!important}.pretty.p-switch.p-slim input:checked~.state.p-success:before{border-color:#357935;background-color:#357935!important}.pretty input:checked~.state.p-warning label:after,.pretty.p-toggle .state.p-warning label:after{background-color:#f0ad4e!important}.pretty input:checked~.state.p-warning .icon,.pretty input:checked~.state.p-warning .svg,.pretty.p-toggle .state.p-warning .icon,.pretty.p-toggle .state.p-warning .svg{color:#fff;stroke:#fff}.pretty input:checked~.state.p-warning-o label:before,.pretty.p-toggle .state.p-warning-o label:before{border-color:#f0ad4e}.pretty input:checked~.state.p-warning-o label:after,.pretty.p-toggle .state.p-warning-o label:after{background-color:transparent}.pretty input:checked~.state.p-warning-o .icon,.pretty input:checked~.state.p-warning-o .svg,.pretty input:checked~.state.p-warning-o svg,.pretty.p-toggle .state.p-warning-o .icon,.pretty.p-toggle .state.p-warning-o .svg,.pretty.p-toggle .state.p-warning-o svg{color:#f0ad4e;stroke:#f0ad4e}.pretty.p-default:not(.p-fill) input:checked~.state.p-warning-o label:after{background-color:#f0ad4e!important}.pretty.p-switch input:checked~.state.p-warning:before{border-color:#f0ad4e}.pretty.p-switch.p-fill input:checked~.state.p-warning:before{background-color:#f0ad4e!important}.pretty.p-switch.p-slim input:checked~.state.p-warning:before{border-color:#c77c11;background-color:#c77c11!important}.pretty input:checked~.state.p-danger label:after,.pretty.p-toggle .state.p-danger label:after{background-color:#d9534f!important}.pretty input:checked~.state.p-danger .icon,.pretty input:checked~.state.p-danger .svg,.pretty.p-toggle .state.p-danger .icon,.pretty.p-toggle .state.p-danger .svg{color:#fff;stroke:#fff}.pretty input:checked~.state.p-danger-o label:before,.pretty.p-toggle .state.p-danger-o label:before{border-color:#d9534f}.pretty input:checked~.state.p-danger-o label:after,.pretty.p-toggle .state.p-danger-o label:after{background-color:transparent}.pretty input:checked~.state.p-danger-o .icon,.pretty input:checked~.state.p-danger-o .svg,.pretty input:checked~.state.p-danger-o svg,.pretty.p-toggle .state.p-danger-o .icon,.pretty.p-toggle .state.p-danger-o .svg,.pretty.p-toggle .state.p-danger-o svg{color:#d9534f;stroke:#d9534f}.pretty.p-default:not(.p-fill) input:checked~.state.p-danger-o label:after{background-color:#d9534f!important}.pretty.p-switch input:checked~.state.p-danger:before{border-color:#d9534f}.pretty.p-switch.p-fill input:checked~.state.p-danger:before{background-color:#d9534f!important}.pretty.p-switch.p-slim input:checked~.state.p-danger:before{border-color:#a02622;background-color:#a02622!important}.pretty.p-bigger .icon,.pretty.p-bigger .img,.pretty.p-bigger .svg,.pretty.p-bigger label:after,.pretty.p-bigger label:before{font-size:1.2em!important;top:calc((0% - (100% - 1em)) - 35%)!important}.pretty.p-bigger label{text-indent:1.7em}@media print{.pretty .state .icon,.pretty .state label:after,.pretty .state label:before,.pretty .state:before{color-adjust:exact;-webkit-print-color-adjust:exact;print-color-adjust:exact}}
--------------------------------------------------------------------------------