PLP and cart functionality added, partial filter and sort modals
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
/* PLOP_INJECT_IMPORT */
|
||||
import Modal from './molecules/Modal';
|
||||
import QuantityControlWidget from './molecules/QuantityControlWidget';
|
||||
import ItemPrice from './molecules/ItemPrice';
|
||||
import SectionLoader from './molecules/SectionLoader';
|
||||
import CartSummary from './molecules/CartSummary';
|
||||
@@ -25,6 +27,8 @@ import SelectOption from './atoms/SelectOption';
|
||||
|
||||
export {
|
||||
/* PLOP_INJECT_EXPORT */
|
||||
Modal,
|
||||
QuantityControlWidget,
|
||||
ItemPrice,
|
||||
SectionLoader,
|
||||
CartSummary,
|
||||
|
||||
@@ -1,22 +1,32 @@
|
||||
@import './../../../styles//variables';
|
||||
|
||||
.c-Cart__c-CartList__c-CartItem {
|
||||
padding: 15px 0;
|
||||
margin-bottom: 13px;
|
||||
padding: 1rem 0;
|
||||
background: $neutral-00;
|
||||
border: 1px solid $neutral-40;
|
||||
line-height: 1;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 13px;
|
||||
}
|
||||
|
||||
[class*="col-"] {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.c-Cart__c-CartList__c-CartItem__image {
|
||||
margin-bottom: 0.7rem;
|
||||
// margin-bottom: 0.7rem;
|
||||
}
|
||||
|
||||
.c-Cart__c-CartList__c-CartItem__name {
|
||||
font-size: $small-font-size;
|
||||
}
|
||||
|
||||
.c-Cart__c-CartList__c-CartItem__buttonItemRemove {
|
||||
text-transform: capitalize;
|
||||
font-size: $font-size;
|
||||
font-weight: $bold-font-weight;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,9 @@ import PropTypes from 'prop-types';
|
||||
import './CartItem.component.scss';
|
||||
import SectionLoader from '../SectionLoader/SectionLoader';
|
||||
import ItemPrice from '../ItemPrice/ItemPrice';
|
||||
import QuantityControlWidget from '../QuantityControlWidget/QuantityControlWidget';
|
||||
|
||||
const CartItem = ({product}) => {
|
||||
const CartItem = ({count, cartTotalCount, cartItems, product, removeItem, updateCart}) => {
|
||||
|
||||
return product ?
|
||||
<article className='c-Cart__c-CartList__c-CartItem'>
|
||||
@@ -16,6 +17,9 @@ const CartItem = ({product}) => {
|
||||
<div className="c-Cart__c-CartList__c-CartItem__inner col-8 col-md-9 col-lg-9">
|
||||
{product.name && <p className="c-Cart__c-CartList__c-CartItem__name">{product.name}</p>}
|
||||
<ItemPrice product={product} />
|
||||
<QuantityControlWidget count={count} productId={product.id} cartTotalCount={cartTotalCount}
|
||||
cartItems={cartItems} updateCart={updateCart} removeItem={removeItem} />
|
||||
<p className="c-Cart__c-CartList__c-CartItem__buttonItemRemove" onClick={() => removeItem(product.id)}>REMOVE</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -27,7 +31,12 @@ CartItem.defaultProps = {
|
||||
};
|
||||
|
||||
CartItem.propTypes = {
|
||||
product: PropTypes.object
|
||||
count: PropTypes.number,
|
||||
cartTotalCount: PropTypes.number,
|
||||
cartItems: PropTypes.object,
|
||||
product: PropTypes.object,
|
||||
removeItem: PropTypes.func,
|
||||
updateCart: PropTypes.func
|
||||
};
|
||||
|
||||
export default CartItem;
|
||||
@@ -1,16 +1,5 @@
|
||||
@import './../../../styles/variables';
|
||||
|
||||
.c-CartList {
|
||||
padding: 0.8em 0;
|
||||
.c-CartList__emptyCart {
|
||||
background: $neutral-00;
|
||||
margin-top: 25%;
|
||||
border: 1px solid black;
|
||||
text-align: center;
|
||||
padding: 1em;
|
||||
|
||||
.c-CartList__emptyCart__information {
|
||||
color: $neutral-40;
|
||||
}
|
||||
}
|
||||
padding: 0.8em 0 0;
|
||||
}
|
||||
|
||||
@@ -3,31 +3,38 @@ import PropTypes from 'prop-types';
|
||||
import {connect} from 'react-redux';
|
||||
import {createPropsSelector} from 'reselect-immutable-helpers';
|
||||
import {getProducts} from './../../../pages/PLP/selectors'
|
||||
import {getCartItems} from './../../../pages/Cart/selectors'
|
||||
import {getCartItems, getCartTotalCount} from './../../../pages/Cart/selectors'
|
||||
import {updateCart} from './../../../pages/Cart/actions'
|
||||
import CartItem from './../CartItem';
|
||||
import './CartList.component.scss';
|
||||
|
||||
const CartList = props => {
|
||||
const CartList = ({cartItems, products, isCartEmpty, cartTotalCount, updateCart}) => {
|
||||
|
||||
const products = props.products
|
||||
const isCartEmpty = !props.cartItems || Object.keys(props.cartItems).length === 0
|
||||
const cartItemTiles = !isCartEmpty && Object.keys(props.cartItems).map((itemId, key) => {
|
||||
const product = products && products.length > 0 && products.filter(product => product.id === parseInt(itemId))[0]
|
||||
return <CartItem product={product} key={key} />
|
||||
})
|
||||
|
||||
const getEmptyCartBlock = () => {
|
||||
return (
|
||||
<div className="c-CartList__emptyCart">
|
||||
<h3>Your cart is Empty!</h3>
|
||||
<p className="c-CartList__emptyCart__information">Please add some items from Available Products (use start button on top left)</p>
|
||||
</div>
|
||||
)
|
||||
const removeItem = (productId) => {
|
||||
const quantity = cartItems && cartItems[productId]
|
||||
cartItems && delete cartItems[productId]
|
||||
let updatedTotalCount = cartTotalCount - quantity
|
||||
updateCart(updatedTotalCount, cartItems)
|
||||
}
|
||||
|
||||
const cartItemTiles = !isCartEmpty && Object.keys(cartItems).map((itemId, key) => {
|
||||
const product = products && products.length > 0 && products.filter(product => product.id === parseInt(itemId))[0]
|
||||
return (
|
||||
<CartItem
|
||||
product={product}
|
||||
count={cartItems[itemId]}
|
||||
cartItems={cartItems}
|
||||
cartTotalCount={cartTotalCount}
|
||||
updateCart={updateCart}
|
||||
removeItem={removeItem}
|
||||
key={key}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<section className='c-CartList'>
|
||||
{isCartEmpty ? getEmptyCartBlock() : cartItemTiles}
|
||||
{cartItemTiles}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
@@ -38,12 +45,23 @@ CartList.defaultProps = {
|
||||
|
||||
CartList.propTypes = {
|
||||
cartItems: PropTypes.object,
|
||||
products: PropTypes.array
|
||||
cartTotalCount: PropTypes.number,
|
||||
isCartEmpty: PropTypes.bool,
|
||||
products: PropTypes.array,
|
||||
updateCart: PropTypes.func
|
||||
};
|
||||
|
||||
const mapStateToProps = createPropsSelector({
|
||||
cartItems: getCartItems,
|
||||
cartTotalCount: getCartTotalCount,
|
||||
products: getProducts
|
||||
})
|
||||
|
||||
export default connect(mapStateToProps)(CartList);
|
||||
const mapDispatchToProps = ({
|
||||
updateCart
|
||||
})
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(CartList);
|
||||
@@ -1,3 +1,54 @@
|
||||
@import './../../../styles/variables';
|
||||
|
||||
.c-CartSummary {
|
||||
|
||||
padding: 0.8em 0;
|
||||
.c-CartSummary__inner {
|
||||
border: 1px solid $neutral-40;
|
||||
background: $neutral-00;
|
||||
padding: 0.4rem 0;
|
||||
font-size: $font-size;
|
||||
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
[class*="col-"] {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
|
||||
}
|
||||
|
||||
.c-CartSummary__headerRow, .c-CartSummary__price, .c-CartSummary__discount, .c-CartSummary__total {
|
||||
padding: 0.5em 0;
|
||||
}
|
||||
.c-CartSummary__headerRow {
|
||||
border-bottom: 1px solid $neutral-20;
|
||||
font-size: $big-font-size;
|
||||
.c-CartSummary__headerContent {
|
||||
text-transform: capitalize;
|
||||
font-weight: $bold-font-weight;
|
||||
color: $neutral-40;
|
||||
}
|
||||
}
|
||||
|
||||
.c-CartSummary__details {
|
||||
padding: 1em 0;
|
||||
border-bottom: 2px solid $neutral-40;
|
||||
margin-left: -15px;
|
||||
margin-right: -15px;
|
||||
|
||||
.row {
|
||||
margin-right: 0;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.float-right {
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.c-CartSummary__total {
|
||||
font-weight: $bold-font-weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,46 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import styles from './CartSummary.component.scss';
|
||||
import './CartSummary.component.scss';
|
||||
|
||||
const CartSummary = ({cartItems, products}) => {
|
||||
|
||||
const totalData = cartItems && Object.keys(cartItems).reduce((priceAccumulator, productId) => {
|
||||
const matchedProducts = products.filter(product => product.id === parseInt(productId))
|
||||
const price = matchedProducts && matchedProducts.length > 0 && matchedProducts[0].price
|
||||
const discount = matchedProducts && matchedProducts.length > 0 && matchedProducts[0].discountAmount
|
||||
priceAccumulator.totalPrice = priceAccumulator.totalPrice + (price * cartItems[productId])
|
||||
priceAccumulator.discount = priceAccumulator.discount + (discount * cartItems[productId])
|
||||
return priceAccumulator
|
||||
}, {totalPrice: 0, discount: 0})
|
||||
|
||||
const CartSummary = props => {
|
||||
return (
|
||||
<div className='c-CartSummary'>
|
||||
</div>
|
||||
<section className='c-CartSummary'>
|
||||
<div className="c-CartSummary__inner">
|
||||
<div className="container">
|
||||
<header className="c-CartSummary__headerRow row">
|
||||
<div className="col-12">
|
||||
<p className="c-CartSummary__headerContent">PRICE DETAILS</p>
|
||||
</div>
|
||||
</header>
|
||||
<section className="c-CartSummary__details">
|
||||
<div className="c-CartSummary__price row">
|
||||
<div className="col-6"><p>Total Price</p></div>
|
||||
<div className="col-1"><p>:</p></div>
|
||||
<div className="col-5"><p className="float-right">₹{totalData.totalPrice}</p></div>
|
||||
</div>
|
||||
<div className="c-CartSummary__discount row">
|
||||
<div className="col-6"><p>Discount</p></div>
|
||||
<div className="col-1"><p>:</p></div>
|
||||
<div className="col-5"><p className="float-right">₹{totalData.discount}</p></div>
|
||||
</div>
|
||||
</section>
|
||||
<div className="c-CartSummary__total row">
|
||||
<div className="col-7">Total Payable</div>
|
||||
<div className="col-5"><p className="float-right">₹{totalData.totalPrice - totalData.discount}</p></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -14,7 +49,8 @@ CartSummary.defaultProps = {
|
||||
};
|
||||
|
||||
CartSummary.propTypes = {
|
||||
|
||||
cartItems: PropTypes.object,
|
||||
products: PropTypes.array
|
||||
};
|
||||
|
||||
export default CartSummary;
|
||||
@@ -4,8 +4,9 @@
|
||||
line-height: $smaller-font-size;
|
||||
margin-bottom: 0.9rem;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
.c-ItemPrice__price {
|
||||
font-size: $smaller-font-size;
|
||||
font-size: $small-font-size;
|
||||
font-weight: $bold-font-weight;
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
@@ -13,15 +14,15 @@
|
||||
.c-ItemPrice__price--strikethrough {
|
||||
color: $neutral-40;
|
||||
font-weight: $bold-font-weight;
|
||||
font-size: $tiny-font-size;
|
||||
font-size: $smaller-font-size;
|
||||
text-decoration: line-through;
|
||||
margin-left: $unit * 0.8;
|
||||
// margin-left: $unit * 0.8;
|
||||
}
|
||||
|
||||
.c-ItemPrice__discount {
|
||||
color: #4aa219;
|
||||
font-weight: $bold-font-weight;
|
||||
font-size: $tiny-font-size;
|
||||
font-size: $smaller-font-size;
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ const ItemPrice = ({product}) => {
|
||||
|
||||
return (
|
||||
<div className='c-ItemPrice'>
|
||||
{product.discountedPrice && <span className="c-Plp__c-ProductContainer__c-ProductTile__price">₹{product.discountedPrice}</span>}
|
||||
{product.discountedPrice && <span className="c-ItemPrice__price">₹{product.discountedPrice}</span>}
|
||||
{
|
||||
hasDiscount &&
|
||||
(
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
.c-Plp__c-ProductContainer {
|
||||
|
||||
margin-bottom: 46px;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import {connect} from 'react-redux';
|
||||
import {dispatchProducts} from './../../../pages/PLP/actions'
|
||||
import ProductTile from './../ProductTile'
|
||||
import PageLoader from '../PageLoader/PageLoader';
|
||||
import './ProductContainer.component.scss'
|
||||
|
||||
const ProductContainer = props => {
|
||||
|
||||
@@ -11,7 +12,8 @@ const ProductContainer = props => {
|
||||
&& props.products.length !== 0
|
||||
&& props.products.map(product => {
|
||||
const discount = product.discount && product.price * (product.discount/100)
|
||||
product.discountedPrice = Math.ceil(product.price - discount)
|
||||
product.discountAmount = Math.ceil(discount)
|
||||
product.discountedPrice = Math.ceil(product.price - product.discountAmount)
|
||||
return product
|
||||
})
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
font-size: $small-font-size;
|
||||
}
|
||||
|
||||
button {
|
||||
.btn__addToCart {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
border-radius: 20px;
|
||||
@@ -26,7 +26,7 @@
|
||||
font-weight: $bold-font-weight;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
.btn__addToCart:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {connect} from 'react-redux';
|
||||
import {createPropsSelector} from 'reselect-immutable-helpers';
|
||||
import {addToCart} from './../../../pages/Cart/actions'
|
||||
import {updateCart} from './../../../pages/Cart/actions'
|
||||
import {getCartTotalCount, getCartItems} from './../../../pages/Cart/selectors'
|
||||
|
||||
import Button from './../../atoms/Button'
|
||||
@@ -15,12 +15,12 @@ const ProductTile = props => {
|
||||
const product = props.product
|
||||
const addToCartButtonClass = "btn btn__addToCart"
|
||||
|
||||
const addToCart = (productId) => {
|
||||
const updateCart = (productId) => {
|
||||
let cartTotalCount = props.cartTotalCount
|
||||
const cartItems = props.cartItems
|
||||
let count = cartItems && cartItems[productId] ? cartItems[productId] : 0
|
||||
cartItems[productId] = ++count
|
||||
props.addToCart(++cartTotalCount, cartItems)
|
||||
props.updateCart(++cartTotalCount, cartItems)
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -50,13 +50,13 @@ const ProductTile = props => {
|
||||
buttonType="button"
|
||||
buttonName="addToCartButton"
|
||||
buttonText="Add To Cart"
|
||||
onClickHandler={() => addToCart(product.id)} />
|
||||
onClickHandler={() => updateCart(product.id)} />
|
||||
</article>
|
||||
);
|
||||
};
|
||||
|
||||
ProductTile.propTypes = {
|
||||
addToCart: PropTypes.func,
|
||||
updateCart: PropTypes.func,
|
||||
cartTotalCount: PropTypes.number,
|
||||
cartItems: PropTypes.object
|
||||
};
|
||||
@@ -67,7 +67,7 @@ const mapStateToProps = createPropsSelector({
|
||||
})
|
||||
|
||||
const mapDispatchToProps = ({
|
||||
addToCart
|
||||
updateCart
|
||||
})
|
||||
|
||||
export default connect(
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
@import './../../../styles/variables';
|
||||
|
||||
.c-QuantityControlWidget {
|
||||
display: flex;
|
||||
margin-bottom: 1rem;
|
||||
.c-QuantityControlWidget__part {
|
||||
border: 1.5px solid $neutral-30;;
|
||||
border-radius: 50%;
|
||||
font-size: $big-font-size;
|
||||
// {props.product && props.product.count}
|
||||
display: flex;
|
||||
align-items: center;
|
||||
&:not(:last-child) {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
&.c-QuantityControlWidget__part--circled {
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
color: $neutral-50;
|
||||
}
|
||||
&.c-QuantityControlWidget__part--minus {
|
||||
padding: 2px 8px 3px;
|
||||
}
|
||||
&.c-QuantityControlWidget__part--plus {
|
||||
padding: 1px 4px;
|
||||
}
|
||||
&.c-QuantityControlWidget__part--squared {
|
||||
border-radius: 2px;
|
||||
font-size: $smaller-font-size;
|
||||
font-weight: $bold-font-weight;
|
||||
color: $neutral-60;
|
||||
padding: 0 13px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import './QuantityControlWidget.component.scss';
|
||||
|
||||
const QuantityControlWidget = ({cartTotalCount, cartItems, count, productId, removeItem, updateCart}) => {
|
||||
|
||||
const updateCartHandler = (operationType) => {
|
||||
let countUpdated = count
|
||||
if (operationType === "remove") {
|
||||
countUpdated = countUpdated ? --countUpdated : 0
|
||||
countUpdated === 0 ? removeItem(productId) : cartItems[productId] = countUpdated
|
||||
cartTotalCount = --cartTotalCount
|
||||
} else {
|
||||
countUpdated = countUpdated ? ++countUpdated : 1
|
||||
cartItems[productId] = countUpdated
|
||||
cartTotalCount = ++cartTotalCount
|
||||
}
|
||||
updateCart(cartTotalCount, cartItems)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='c-QuantityControlWidget'>
|
||||
<div
|
||||
className="c-QuantityControlWidget__part c-QuantityControlWidget__part--minus c-QuantityControlWidget__part--circled"
|
||||
onClick={() => updateCartHandler("remove")}
|
||||
>
|
||||
<span>-</span>
|
||||
</div>
|
||||
<div
|
||||
className="c-QuantityControlWidget__part c-QuantityControlWidget__part--squared"
|
||||
>
|
||||
<span>{count}</span>
|
||||
</div>
|
||||
<div
|
||||
className="c-QuantityControlWidget__part c-QuantityControlWidget__part--plus c-QuantityControlWidget__part--circled"
|
||||
onClick={() => updateCartHandler("add")}
|
||||
>
|
||||
<span>+</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
QuantityControlWidget.defaultProps = {
|
||||
|
||||
};
|
||||
|
||||
QuantityControlWidget.propTypes = {
|
||||
cartTotalCount: PropTypes.number,
|
||||
cartItems: PropTypes.object,
|
||||
count: PropTypes.number,
|
||||
productId: PropTypes.number,
|
||||
removeItem: PropTypes.func,
|
||||
updateCart: PropTypes.func
|
||||
};
|
||||
|
||||
export default QuantityControlWidget;
|
||||
@@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
import QuantityControlWidget from './QuantityControlWidget';
|
||||
|
||||
describe('QuantityControlWidget', () => {
|
||||
it('renders without error', () => {
|
||||
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
import QuantityControlWidget from './QuantityControlWidget.jsx';
|
||||
|
||||
export default QuantityControlWidget;
|
||||
@@ -1,3 +1,25 @@
|
||||
@import './../../../styles//variables';
|
||||
|
||||
.c-Plp__c-SortAndFilterPanel__c-Search {
|
||||
margin-left: auto;
|
||||
|
||||
.c-Plp__c-SortAndFilterPanel__c-Search__input {
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-bottom: 1px solid $neutral-00;
|
||||
color: $neutral-00;
|
||||
padding-bottom: 0.3rem;
|
||||
&::placeholder {
|
||||
color: $neutral-00;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&:-ms-input-placeholder {
|
||||
color: $neutral-00;
|
||||
}
|
||||
|
||||
&::-ms-input-placeholder {
|
||||
color: $neutral-00;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,12 @@ import React, {useState} from 'react';
|
||||
import './Search.component.scss';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faSearch } from '@fortawesome/free-solid-svg-icons'
|
||||
import InputField from './../../atoms/InputField'
|
||||
|
||||
const Search = props => {
|
||||
const [iconClicked, setIconClicked] = useState(false)
|
||||
const [searchInitiated, setSearchInitiated] = useState(false)
|
||||
return (
|
||||
<div className='c-Plp__c-SortAndFilterPanel__c-Search header-icon' onClick={() => setIconClicked(!iconClicked)}>
|
||||
{iconClicked && <InputField /> }
|
||||
<div className='c-Plp__c-SortAndFilterPanel__c-Search header-icon' onClick={() => setSearchInitiated(true)}>
|
||||
{searchInitiated && <input type="text" className="c-Plp__c-SortAndFilterPanel__c-Search__input" placeholder="Search..." /> }
|
||||
<FontAwesomeIcon icon={faSearch} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,19 +1,31 @@
|
||||
import React from 'react';
|
||||
import React, {useState} from 'react';
|
||||
import './SortAndFilterPanel.component.scss'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faSort } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faFilter } from '@fortawesome/free-solid-svg-icons'
|
||||
import SortModal from '../SortModal';
|
||||
import FilterModal from '../FilterModal';
|
||||
|
||||
const SortAndFilterPanel = props => {
|
||||
|
||||
const [showSortModal, setShowSortModal] = useState(false)
|
||||
const [showFilterModal, setShowFilterModal] = useState(false)
|
||||
|
||||
const handleSortModalChange = (modalState) => {
|
||||
setShowSortModal(() => modalState)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='c-Plp__c-SortAndFilterPanel'>
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
<div className="c-Plp__c-SortAndFilterPanel__tool sort col-6">
|
||||
<div className="c-Plp__c-SortAndFilterPanel__tool sort col-6" onClick={() => setShowSortModal(true)}>
|
||||
<p className="c-Plp__c-SortAndFilterPanel__toolContent"><FontAwesomeIcon icon={faSort} /> Sort</p>
|
||||
<SortModal showModal={showSortModal} setModalState={handleSortModalChange} />
|
||||
</div>
|
||||
<div className="c-Plp__c-SortAndFilterPanel__tool filter col-6">
|
||||
<div className="c-Plp__c-SortAndFilterPanel__tool filter col-6" onClick={() => setShowFilterModal(true)}>
|
||||
<p className="c-Plp__c-SortAndFilterPanel__toolContent"><FontAwesomeIcon icon={faFilter} /> Filter</p>
|
||||
<FilterModal showModal={showFilterModal} setModalState={setShowFilterModal} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import styles from './SortModal.component.scss';
|
||||
import './SortModal.component.scss';
|
||||
import Modal from '../common/Modal/Modal';
|
||||
|
||||
const SortModal = props => {
|
||||
const SortModal = ({showModal, setModalState}) => {
|
||||
return (
|
||||
<div className='c-SortModal'>
|
||||
<Modal
|
||||
showModal={showModal}
|
||||
setModalStateHandler={setModalState} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -14,7 +18,8 @@ SortModal.defaultProps = {
|
||||
};
|
||||
|
||||
SortModal.propTypes = {
|
||||
|
||||
showModal: PropTypes.bool,
|
||||
setModalState: PropTypes.func
|
||||
};
|
||||
|
||||
export default SortModal;
|
||||
@@ -29,9 +29,6 @@ class FormFieldContainer extends React.Component {
|
||||
[componentKey]: ''
|
||||
}
|
||||
})
|
||||
this.customExecutes = this.customExecutes.bind(this)
|
||||
this.prepareCVN = this.prepareCVN.bind(this)
|
||||
this.executeDateValidations = this.executeDateValidations.bind(this)
|
||||
this.onChangeHandler = this.onChangeHandler.bind(this)
|
||||
this.onBlurHandler = this.onBlurHandler.bind(this)
|
||||
this.onFocusHandler = this.onFocusHandler.bind(this)
|
||||
@@ -83,13 +80,6 @@ class FormFieldContainer extends React.Component {
|
||||
errorMessage = null
|
||||
}
|
||||
|
||||
if (!errorMessage && propsData.id === 'expiration_month') {
|
||||
errorMessage = this.executeDateValidations()
|
||||
if (errorMessage) {
|
||||
error = true
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({
|
||||
error
|
||||
})
|
||||
@@ -116,7 +106,6 @@ class FormFieldContainer extends React.Component {
|
||||
this.setState({
|
||||
value
|
||||
})
|
||||
this.executeDateValidations(event)
|
||||
}
|
||||
|
||||
onFocusHandler(event) {
|
||||
@@ -139,70 +128,10 @@ class FormFieldContainer extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
prepareCVN() {
|
||||
const {propsData, selectedCreditCard} = this.props
|
||||
if (propsData && propsData.ccNumberUpdated !== undefined && propsData.ccNumberUpdated) {
|
||||
this.setState({
|
||||
value: ''
|
||||
})
|
||||
this.props.updateFormValues({
|
||||
formValues: {
|
||||
...this.props.formValues,
|
||||
security_code: ''
|
||||
}
|
||||
})
|
||||
propsData.ccNumberUpdated = false
|
||||
}
|
||||
if (selectedCreditCard) {
|
||||
if (selectedCreditCard.payment_card.card_type === 'Amex') {
|
||||
propsData.validation.dataRuleRegex.regex = /^[0-9'\s]{4}$/
|
||||
propsData.validation.rules.maxlength = 4
|
||||
} else {
|
||||
propsData.validation.dataRuleRegex.regex = /^[0-9'\s]{3}$/
|
||||
propsData.validation.rules.maxlength = 3
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
executeDateValidations(event) {
|
||||
const {propsData, formValues, formErrors, updateFormErrors} = this.props
|
||||
let errorMessage = ''
|
||||
if (propsData.id === 'expiration_month' || propsData.id === 'expiration_year') {
|
||||
let month = ''
|
||||
let year = ''
|
||||
if (propsData.id === 'expiration_month') {
|
||||
month = +event.target.value
|
||||
year = +formValues.expiration_year
|
||||
} else if (propsData.id === 'expiration_year') {
|
||||
month = +formValues.expiration_month
|
||||
year = +event.target.value
|
||||
}
|
||||
const currentYear = new Date().getFullYear()
|
||||
const currentMonth = new Date().getMonth()
|
||||
if (year === currentYear && month < currentMonth + 1) {
|
||||
errorMessage = 'This Credit Card is expired'
|
||||
} else {
|
||||
errorMessage = ''
|
||||
}
|
||||
updateFormErrors({
|
||||
formErrors: {
|
||||
...formErrors,
|
||||
expiration_month: errorMessage
|
||||
}
|
||||
})
|
||||
}
|
||||
return errorMessage
|
||||
}
|
||||
|
||||
customExecutes() {
|
||||
this.prepareCVN()
|
||||
}
|
||||
|
||||
render() {
|
||||
const {customBlurHandler, formErrors, propsData} = this.props
|
||||
const {elementType} = propsData
|
||||
const errorMessage = formErrors && formErrors[propsData.id]
|
||||
this.customExecutes()
|
||||
const meta = {
|
||||
...this.props.propsData,
|
||||
className: this.state.error ? 'error' : '',
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
.c-Modal {
|
||||
|
||||
}
|
||||
57
src/app/components/molecules/common/Modal/Modal.jsx
Normal file
57
src/app/components/molecules/common/Modal/Modal.jsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import './Modal.component.scss';
|
||||
import Button from './../../../atoms/Button'
|
||||
|
||||
const Modal = ({modalBody, modalButtons, modalTitle, showModal, setModalStateHandler}) => {
|
||||
|
||||
const modalButtonRenders = modalButtons && modalButtons.map(modalButton => {
|
||||
return (
|
||||
<Button
|
||||
buttonType="button"
|
||||
classes={`btn ${modalButton.classes}`}
|
||||
buttonText={modalButton.text}
|
||||
onClickHandler={modalButton.onClickHandler}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
const closeModal = () => {
|
||||
setModalStateHandler(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`c-Modal modal fade ${showModal && 'show'}`} style={{"display": `${showModal ? "block" : "none"}`}} tabIndex="-1" role="dialog">
|
||||
<div className="modal-dialog" role="document">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h5 className="modal-title">{modalTitle}</h5>
|
||||
<button type="button" className="close" data-dismiss="modal" aria-label="Close" onClick={closeModal}>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<p>Modal body text goes here.</p>
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
{modalButtonRenders}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Modal.defaultProps = {
|
||||
|
||||
};
|
||||
|
||||
Modal.propTypes = {
|
||||
modalBody: PropTypes.object,
|
||||
modalContent: PropTypes.array,
|
||||
modalTitle: PropTypes.string,
|
||||
showModal: PropTypes.bool,
|
||||
setModalStateHandler: PropTypes.func
|
||||
};
|
||||
|
||||
export default Modal;
|
||||
8
src/app/components/molecules/common/Modal/Modal.test.js
Normal file
8
src/app/components/molecules/common/Modal/Modal.test.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
import Modal from './Modal';
|
||||
|
||||
describe('Modal', () => {
|
||||
it('renders without error', () => {
|
||||
|
||||
});
|
||||
});
|
||||
3
src/app/components/molecules/common/Modal/index.js
Normal file
3
src/app/components/molecules/common/Modal/index.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import Modal from './Modal.jsx';
|
||||
|
||||
export default Modal;
|
||||
@@ -30,6 +30,22 @@
|
||||
},
|
||||
"rules": {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
"search": {
|
||||
"placeholder": "Search..",
|
||||
"type": "text",
|
||||
"id": "search",
|
||||
"elementType": "input",
|
||||
"validation": {
|
||||
"required": {
|
||||
"isRequired": "false"
|
||||
},
|
||||
"rules": {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,64 @@
|
||||
import React from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
import {createPropsSelector} from 'reselect-immutable-helpers';
|
||||
import {withRouter} from 'react-router-dom';
|
||||
import {getProducts} from './../PLP/selectors';
|
||||
import {getCartItems, getCartTotalCount} from './selectors'
|
||||
import {updateCart} from './actions'
|
||||
import './Cart.module.scss';
|
||||
|
||||
import Button from './../../components/atoms/Button'
|
||||
import Header from './../../components/molecules/Header'
|
||||
import CartList from './../../components/molecules/CartList'
|
||||
import CartSummary from './../../components/molecules/CartSummary'
|
||||
import Footer from './../../components/molecules/Footer'
|
||||
|
||||
const Cart = props => {
|
||||
const Cart = ({cartItems, cartTotalCount, history, products, updateCart}) => {
|
||||
|
||||
const isCartEmpty = !cartItems || Object.keys(cartItems).length === 0
|
||||
|
||||
const navigateToPlp = () => {
|
||||
history.push('/view/plp')
|
||||
}
|
||||
const getEmptyCartBlock = () => {
|
||||
return (
|
||||
<div className="c-Cart__emptyCart">
|
||||
<h3>Your cart is Empty!</h3>
|
||||
<p className="c-Cart__emptyCart__information">Please add some items from Available Products.</p>
|
||||
<Button
|
||||
buttonType="button"
|
||||
classes="btn btn__continueShopping"
|
||||
onClickHandler={navigateToPlp}
|
||||
buttonText="Continue Shopping"
|
||||
/>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="c-Cart">
|
||||
<Header inCart={true} />
|
||||
<main className="container c-Cart__mainContent">
|
||||
<div className="row">
|
||||
<div className="col-12 col-md-8 col-lg-8">
|
||||
<CartList />
|
||||
</div>
|
||||
<div className="col-12 col-md-4 col-lg-4">
|
||||
<CartSummary />
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
isCartEmpty ? getEmptyCartBlock() :
|
||||
(
|
||||
<div className="row">
|
||||
<div className="col-12 col-md-8 col-lg-8">
|
||||
<CartList
|
||||
cartItems={cartItems}
|
||||
cartTotalCount={cartTotalCount}
|
||||
products={products}
|
||||
updateCart={updateCart} />
|
||||
</div>
|
||||
<div className="col-12 col-md-4 col-lg-4">
|
||||
<CartSummary
|
||||
cartItems={cartItems}
|
||||
products={products} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
@@ -33,4 +73,17 @@ Cart.propTypes = {
|
||||
|
||||
};
|
||||
|
||||
export default Cart;
|
||||
const mapStateToProps = createPropsSelector({
|
||||
cartItems: getCartItems,
|
||||
cartTotalCount: getCartTotalCount,
|
||||
products: getProducts
|
||||
})
|
||||
|
||||
const mapDispatchToProps = ({
|
||||
updateCart
|
||||
})
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(withRouter(Cart));
|
||||
@@ -7,6 +7,34 @@
|
||||
margin-bottom: 46px;
|
||||
// padding: 15px 10px;
|
||||
|
||||
.c-Cart__emptyCart {
|
||||
background: $neutral-00;
|
||||
margin-top: 25%;
|
||||
border: 1px solid black;
|
||||
text-align: center;
|
||||
padding: 1em;
|
||||
|
||||
.c-Cart__emptyCart__information {
|
||||
color: $neutral-40;
|
||||
}
|
||||
}
|
||||
|
||||
.btn__continueShopping {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
border-radius: 20px;
|
||||
padding: 7px 20px;
|
||||
background-color: $brand-color;
|
||||
color: $neutral-12;
|
||||
cursor: pointer;
|
||||
font-size: $smaller-font-size;
|
||||
font-weight: $bold-font-weight;
|
||||
}
|
||||
|
||||
.btn__continueShopping:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.c-Cart__mainContent {
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export const ADD_TO_CART = 'ADD_TO_CART'
|
||||
export const UPDATE_CART = 'UPDATE_CART'
|
||||
export const UPDATE_FORM_VALUES = 'UPDATE_BILLING_FORM_VALUES'
|
||||
export const UPDATE_FORM_ERRORS = 'UPDATE_BILLING_FORM_ERRORS'
|
||||
|
||||
@@ -10,9 +10,9 @@ export const initializeLogin = () => (dispatch) => {
|
||||
// .catch((err) => ({statusCode: err.statusCode || 500}))
|
||||
}
|
||||
|
||||
export const addToCart = (cartTotalCount, cartItems) => {
|
||||
export const updateCart = (cartTotalCount, cartItems) => {
|
||||
return {
|
||||
type: ADD_TO_CART,
|
||||
type: UPDATE_CART,
|
||||
payload: {cartTotalCount, cartItems}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Immutable from 'immutable'
|
||||
|
||||
import {
|
||||
ADD_TO_CART,
|
||||
UPDATE_CART,
|
||||
UPDATE_FORM_ERRORS,
|
||||
UPDATE_FORM_VALUES
|
||||
} from './actions'
|
||||
@@ -12,7 +12,10 @@ const initialState = Immutable.Map({
|
||||
|
||||
const reducer = (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case ADD_TO_CART:
|
||||
case UPDATE_CART:
|
||||
return state
|
||||
.set('cartItems', action.payload.cartItems)
|
||||
.set('cartTotalCount', action.payload.cartTotalCount)
|
||||
case UPDATE_FORM_ERRORS:
|
||||
case UPDATE_FORM_VALUES:
|
||||
return state.mergeDeep(action.payload)
|
||||
|
||||
@@ -46,7 +46,8 @@ body {
|
||||
// ([Source](http://en.wikipedia.org/wiki/User:Davidgothberg/Test59)).
|
||||
|
||||
figure {
|
||||
margin: 1em 40px; // 1
|
||||
// margin: 1em 40px; // 1
|
||||
margin: 0 0 1rem; // 1
|
||||
}
|
||||
|
||||
pre {
|
||||
|
||||
Reference in New Issue
Block a user