Final functionality development commit
This commit is contained in:
@@ -1,4 +1,7 @@
|
||||
/* PLOP_INJECT_IMPORT */
|
||||
import SortTool from './molecules/SortTool';
|
||||
import FilterTool from './molecules/FilterTool';
|
||||
import ProductContainerWithTools from './molecules/ProductContainerWithTools';
|
||||
import Modal from './molecules/Modal';
|
||||
import QuantityControlWidget from './molecules/QuantityControlWidget';
|
||||
import ItemPrice from './molecules/ItemPrice';
|
||||
@@ -27,6 +30,9 @@ import SelectOption from './atoms/SelectOption';
|
||||
|
||||
export {
|
||||
/* PLOP_INJECT_EXPORT */
|
||||
SortTool,
|
||||
FilterTool,
|
||||
ProductContainerWithTools,
|
||||
Modal,
|
||||
QuantityControlWidget,
|
||||
ItemPrice,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
@import './../../../styles/variables';
|
||||
|
||||
.c-Plp__c-SortAndFilterPanel__c-CartIcon {
|
||||
margin-left: 1rem;
|
||||
.c-Plp__c-Header__c-CartIcon {
|
||||
// margin-left: 1rem;
|
||||
position: relative;
|
||||
|
||||
.c-Plp__c-SortAndFilterPanel__c-CartIcon__badge {
|
||||
.c-Plp__c-Header__c-CartIcon__badge {
|
||||
position: absolute;
|
||||
top: -3px;
|
||||
top: -7px;
|
||||
right: -9px;
|
||||
padding: 0px 5px;
|
||||
border-radius: 50%;
|
||||
@@ -14,4 +14,8 @@
|
||||
color: $neutral-00;
|
||||
font-size: $smaller-font-size;
|
||||
}
|
||||
|
||||
.fa-shopping-cart {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,9 +16,9 @@ const CartIcon = props => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='c-Plp__c-SortAndFilterPanel__c-CartIcon header-icon' onClick={navigateToCart}>
|
||||
<div className='c-Plp__c-Header__c-CartIcon header-icon' onClick={navigateToCart}>
|
||||
<FontAwesomeIcon icon={faShoppingCart} />
|
||||
<span className="c-Plp__c-SortAndFilterPanel__c-CartIcon__badge">{props.cartTotalCount}</span>
|
||||
<span className="c-Plp__c-Header__c-CartIcon__badge">{props.cartTotalCount}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,3 +1,60 @@
|
||||
@import './../../../styles/variables';
|
||||
|
||||
.c-FilterModal {
|
||||
|
||||
&.modal {
|
||||
&.show {
|
||||
display: block;
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
}
|
||||
// &.fade .modal-dialog {
|
||||
// transition: transform .3s ease-out;
|
||||
// }
|
||||
.modal-dialog {
|
||||
pointer-events: auto;
|
||||
.modal-body {
|
||||
color: $neutral-40;
|
||||
padding: 1rem;
|
||||
.error {
|
||||
color: $error-color;
|
||||
font-size: $small-font-size;
|
||||
}
|
||||
.input-range {
|
||||
margin: 25px 0;
|
||||
font-weight: $bold-font-weight;
|
||||
font-size: $small-font-size;
|
||||
.input-range__label-container {
|
||||
font-family: $font-family;
|
||||
}
|
||||
|
||||
.input-range__label {
|
||||
top: 20px;
|
||||
&.input-range__label--value {
|
||||
top: -40px;
|
||||
}
|
||||
&.input-range__label--max {
|
||||
right: 2em;
|
||||
}
|
||||
}
|
||||
}
|
||||
.c-FilterModal__input-range__subtitle {
|
||||
font-size: $font-size;
|
||||
font-weight: $bold-font-weight;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.ReactModal__Overlay {
|
||||
opacity: 0;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
&.ReactModal__Overlay--after-open {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.ReactModal__Overlay--before-close {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,50 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import styles from './FilterModal.component.scss';
|
||||
import Modal from 'react-modal';
|
||||
import FilterTool from './../FilterTool';
|
||||
import './FilterModal.component.scss';
|
||||
import 'react-input-range/lib/css/index.css';
|
||||
|
||||
const customStyles = {
|
||||
content: {
|
||||
top: '20%',
|
||||
width: '93%',
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
background: 'rgb(255, 255, 255)',
|
||||
overflow: 'auto',
|
||||
borderRadius: '4px'
|
||||
}
|
||||
};
|
||||
|
||||
const FilterModal = ({closeModal, modalIsOpen}) => {
|
||||
|
||||
function afterOpenModal() {
|
||||
// subtitle.style.color = '#000';
|
||||
// subtitle.style.fontWeight = '700';
|
||||
}
|
||||
|
||||
|
||||
const FilterModal = props => {
|
||||
return (
|
||||
<div className='c-FilterModal'>
|
||||
</div>
|
||||
<Modal
|
||||
overlayClassName="c-FilterModal modal fade show"
|
||||
closeTimeoutMS={300}
|
||||
className="modal-dialog"
|
||||
isOpen={modalIsOpen}
|
||||
onAfterOpen={afterOpenModal}
|
||||
onRequestClose={closeModal}
|
||||
style={customStyles}
|
||||
contentLabel="Example Modal"
|
||||
>
|
||||
<FilterTool
|
||||
headerClass="modal-header"
|
||||
titleClass="modal-title"
|
||||
bodyClass="modal-body"
|
||||
footerClass="modal-footer"
|
||||
title="Filter Options"
|
||||
closeModal={closeModal}
|
||||
isModal={true} />
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -14,7 +53,9 @@ FilterModal.defaultProps = {
|
||||
};
|
||||
|
||||
FilterModal.propTypes = {
|
||||
|
||||
closeModal: PropTypes.func,
|
||||
dispatchFilterSelection: PropTypes.func,
|
||||
modalIsOpen: PropTypes.bool
|
||||
};
|
||||
|
||||
export default FilterModal;
|
||||
export default FilterModal;
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
.c-FilterTool {
|
||||
|
||||
}
|
||||
54
src/app/components/molecules/FilterTool/FilterTool.jsx
Normal file
54
src/app/components/molecules/FilterTool/FilterTool.jsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import React, {useState} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {connect} from 'react-redux';
|
||||
import {dispatchFilterRange} from './../../../pages/PLP/actions';
|
||||
import InputRange from 'react-input-range';
|
||||
import './FilterTool.component.scss';
|
||||
|
||||
const FilterTool = props => {
|
||||
// var subtitle;
|
||||
const [filterRange, setFilterRange] = useState({min: 0, max: 10000})
|
||||
|
||||
const submitSelection = (e) => {
|
||||
props.dispatchFilterRange(filterRange)
|
||||
props.isModal && props.closeModal(e)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`c-FilterTool${props.isModal ? " modal-content" : " c-FilterTool__content"}`}>
|
||||
<div className={props.headerClass}>
|
||||
<h5 className={props.titleClass}>{props.title}</h5>
|
||||
{props.isModal && <button type="button" className="close" onClick={props.closeModal} aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>}
|
||||
</div>
|
||||
<div className={props.bodyClass}>
|
||||
<InputRange
|
||||
maxValue={10000}
|
||||
minValue={0}
|
||||
formatLabel={value => `₹${value}`}
|
||||
value={filterRange}
|
||||
onChange={value => setFilterRange(value)} />
|
||||
{/* onChangeComplete={(e) =>submitSelection(e)} /> */}
|
||||
<p className="c-FilterModal__input-range__subtitle">Price</p>
|
||||
</div>
|
||||
<div className={props.footerClass}>
|
||||
{props.isModal && <button type="button" className="btn btn-secondary" onClick={props.closeModal}>Cancel</button>}
|
||||
<button type="button" className="btn btn-primary" onClick={submitSelection}>Apply</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
FilterTool.propTypes = {
|
||||
dispatchFilterRange: PropTypes.func
|
||||
};
|
||||
|
||||
const mapDispatchToProps = ({
|
||||
dispatchFilterRange
|
||||
})
|
||||
|
||||
export default connect(
|
||||
null,
|
||||
mapDispatchToProps
|
||||
)(FilterTool);
|
||||
@@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
import FilterTool from './FilterTool';
|
||||
|
||||
describe('FilterTool', () => {
|
||||
it('renders without error', () => {
|
||||
|
||||
});
|
||||
});
|
||||
3
src/app/components/molecules/FilterTool/index.js
Normal file
3
src/app/components/molecules/FilterTool/index.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import FilterTool from './FilterTool.jsx';
|
||||
|
||||
export default FilterTool;
|
||||
@@ -1,7 +1,7 @@
|
||||
@import './../../../styles/variables';
|
||||
|
||||
.c-Header {
|
||||
display: flex;
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
// justify-content: center;
|
||||
position: fixed;
|
||||
@@ -12,18 +12,39 @@
|
||||
width: 100%;
|
||||
background-color: $brand-color;
|
||||
color: $neutral-00;
|
||||
padding: 1em 1.5em;
|
||||
padding: 1em 1em;
|
||||
z-index: 2;
|
||||
transition: transform 0.4s;
|
||||
|
||||
.row {
|
||||
justify-content: space-between;
|
||||
.c-Header__cartIconContainer--aligner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
[class*='col-'] {
|
||||
padding: 0;
|
||||
|
||||
&.c-Header--margin-right {
|
||||
margin-right: 0.8em;
|
||||
}
|
||||
}
|
||||
|
||||
.c-Header__logo-main {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.c-Header__iconClass {
|
||||
transform: rotateX(18deg) rotateZ(10deg) scale(2);
|
||||
color: yellow;
|
||||
margin: 6px 0px 3px 3px;
|
||||
// margin: 6px 0px 3px 3px;
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
@@ -32,4 +53,13 @@
|
||||
color: $light-accent-color;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 768px) {
|
||||
[class*='col-'] {
|
||||
padding: 0 15px;
|
||||
}
|
||||
.c-Header__logo-main {
|
||||
justify-content: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,9 +36,13 @@ const Header = props => {
|
||||
|
||||
return (
|
||||
<header className='c-Header'>
|
||||
<Link to="/view/plp" className="c-Header__logo-main"><FontAwesomeIcon className="c-Header__iconClass" icon={faStar} /></Link>
|
||||
<Search />
|
||||
{!props.inCart && <CartIcon />}
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
<div className="col-1 c-Header--margin-right"><Link to="/view/plp" className="c-Header__logo-main"><FontAwesomeIcon className="c-Header__iconClass" icon={faStar} /></Link></div>
|
||||
<div className={`${props.inCart ? "col-10" : "col-9 c-Header--margin-right"}`}><Search inCart={props.inCart} /></div>
|
||||
<div className="col-1 c-Header__cartIconContainer--aligner">{!props.inCart && <CartIcon />}</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,3 +1,55 @@
|
||||
@import './../../../styles/variables';
|
||||
|
||||
.c-Plp__c-ProductContainer {
|
||||
margin-bottom: 46px;
|
||||
|
||||
.c-Cart__emptyPlp {
|
||||
background: $neutral-00;
|
||||
margin-top: 25%;
|
||||
border: 1px solid black;
|
||||
text-align: center;
|
||||
padding: 1em;
|
||||
|
||||
.c-Cart__emptyPlp__information {
|
||||
color: $neutral-40;
|
||||
}
|
||||
}
|
||||
|
||||
.c-Plp__c-ProductContainer__infoContainer {
|
||||
background: $neutral-00;
|
||||
color: $success-color;
|
||||
padding: 1em;
|
||||
font-weight: $bold-font-weight;
|
||||
|
||||
.c-Plp__c-ProductContainer__information {
|
||||
display: inline-block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.c-Plp__c-ProductContainer__btnClearSearch {
|
||||
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;
|
||||
}
|
||||
|
||||
.row {
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 768px) {
|
||||
.c-Cart__emptyPlp {
|
||||
margin-top: 0;
|
||||
width: 75%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,31 @@
|
||||
import React from 'react';
|
||||
import React, {useState} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {connect} from 'react-redux';
|
||||
import {dispatchProducts} from './../../../pages/PLP/actions'
|
||||
import {createPropsSelector} from 'reselect-immutable-helpers';
|
||||
import {dispatchProducts, dispatchSearchString} from './../../../pages/PLP/actions'
|
||||
import {getFilterRange, getSortSelection, getSearchString} from './../../../pages/PLP/selectors'
|
||||
import ProductTile from './../ProductTile'
|
||||
import PageLoader from '../PageLoader/PageLoader';
|
||||
import './ProductContainer.component.scss'
|
||||
|
||||
const ProductContainer = props => {
|
||||
|
||||
const productsWithDiscountedPrice = props.products
|
||||
const fr = props.filterRange
|
||||
const sb = props.sortBy
|
||||
const ss = props.searchString
|
||||
|
||||
const [filterNoMatch, setFilterNoMatch] = useState(false)
|
||||
|
||||
const getEmptyPlpBlock = () => {
|
||||
return (
|
||||
<div className="c-Cart__emptyPlp">
|
||||
<h3>No Products match the search criteria!</h3>
|
||||
<p className="c-Cart__emptyPlp__information">Please modify Search or filter criteria, and try again.</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
let productsWithDiscountedPrice = props.products
|
||||
&& props.products.length !== 0
|
||||
&& props.products.map(product => {
|
||||
const discount = product.discount && product.price * (product.discount/100)
|
||||
@@ -16,20 +33,73 @@ const ProductContainer = props => {
|
||||
product.discountedPrice = Math.ceil(product.price - product.discountAmount)
|
||||
return product
|
||||
})
|
||||
if (productsWithDiscountedPrice) {
|
||||
if (sb) {
|
||||
switch(sb) {
|
||||
case "priceHtoL":
|
||||
productsWithDiscountedPrice.sort((prod1, prod2) => prod2.discountedPrice - prod1.discountedPrice);
|
||||
break;
|
||||
case "priceLtoH":
|
||||
productsWithDiscountedPrice.sort((prod1, prod2) => prod1.discountedPrice - prod2.discountedPrice);
|
||||
break;
|
||||
case "discount":
|
||||
productsWithDiscountedPrice.sort((prod1, prod2) => prod2.discount - prod1.discount);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let productsWithDiscountedPriceFiltered = [...productsWithDiscountedPrice]
|
||||
const noOfProducts = productsWithDiscountedPrice.length
|
||||
if (fr) {
|
||||
productsWithDiscountedPriceFiltered = productsWithDiscountedPrice.filter(prod => prod.discountedPrice > fr.min && prod.discountedPrice < fr.max);
|
||||
productsWithDiscountedPrice = productsWithDiscountedPriceFiltered
|
||||
}
|
||||
|
||||
if (ss && ss !== "") {
|
||||
productsWithDiscountedPriceFiltered = productsWithDiscountedPrice.filter(prod => {
|
||||
return (prod.name.toLowerCase().includes(ss.toLowerCase())
|
||||
|| prod.category.toLowerCase().includes(ss.toLowerCase())
|
||||
|| prod.img_url.toLowerCase().includes(ss.toLowerCase()));
|
||||
});
|
||||
}
|
||||
if (noOfProducts > 0 && productsWithDiscountedPriceFiltered.length === 0) {
|
||||
!filterNoMatch && setFilterNoMatch(true)
|
||||
} else {
|
||||
filterNoMatch && setFilterNoMatch(false)
|
||||
}
|
||||
productsWithDiscountedPrice = productsWithDiscountedPriceFiltered
|
||||
}
|
||||
|
||||
const productTiles = productsWithDiscountedPrice && productsWithDiscountedPrice.map((product, key) =>{
|
||||
return <ProductTile product={product} key={key} />
|
||||
})
|
||||
props.dispatchProducts(productsWithDiscountedPrice)
|
||||
|
||||
return props.products && props.products.length !== 0 ?
|
||||
<main className='c-Plp__c-ProductContainer'>
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
{productTiles}
|
||||
</div>
|
||||
</div>
|
||||
</main> : <PageLoader />
|
||||
const clearSearch = () => {
|
||||
props.dispatchSearchString("")
|
||||
}
|
||||
|
||||
return <main className='c-Plp__c-ProductContainer'>
|
||||
{
|
||||
filterNoMatch ? getEmptyPlpBlock() : props.products && props.products.length !== 0 ?
|
||||
<div className="container">
|
||||
{
|
||||
ss && ss !== "" &&
|
||||
<div className="row">
|
||||
<div className="col-12 c-Plp__c-ProductContainer__infoContainer">
|
||||
<span className="c-Plp__c-ProductContainer__information">Showing Results for the search - "{ss}"</span>
|
||||
<p><button type="button" className="c-Plp__c-ProductContainer__btnClearSearch" onClick={clearSearch}>Clear Search</button></p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div className="row c-Plp__c-ProductContainer__row">
|
||||
{productTiles}
|
||||
</div>
|
||||
</div> : <PageLoader />
|
||||
}
|
||||
</main>
|
||||
};
|
||||
|
||||
ProductContainer.defaultProps = {
|
||||
@@ -37,14 +107,21 @@ ProductContainer.defaultProps = {
|
||||
};
|
||||
|
||||
ProductContainer.propTypes = {
|
||||
products: PropTypes.array
|
||||
products: PropTypes.array,
|
||||
sortBy: PropTypes.string
|
||||
};
|
||||
|
||||
const mapStateToProps = createPropsSelector({
|
||||
filterRange: getFilterRange,
|
||||
searchString: getSearchString,
|
||||
sortBy: getSortSelection
|
||||
})
|
||||
|
||||
const mapDispatchToProps = ({
|
||||
dispatchProducts
|
||||
dispatchProducts, dispatchSearchString
|
||||
})
|
||||
|
||||
export default connect(
|
||||
null,
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ProductContainer);
|
||||
@@ -0,0 +1,80 @@
|
||||
@import './../../../styles/variables';
|
||||
|
||||
.c-ProductContainerWithTools {
|
||||
|
||||
.c-ProductContainerWithTools__asideCol {
|
||||
border-right: 2px solid #ddd;
|
||||
padding-right: 27px;
|
||||
}
|
||||
.c-SortTool__content {
|
||||
padding: 20px;
|
||||
.c-SortTool__header {
|
||||
margin-right: 3rem;
|
||||
|
||||
.c-SortTool__title {
|
||||
font-weight: $bold-font-weight;
|
||||
font-size: $big-font-size;
|
||||
}
|
||||
}
|
||||
.radio {
|
||||
display: inline;
|
||||
margin-right: 2.5rem;
|
||||
input[type=radio] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
label {
|
||||
color: $neutral-40;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.c-SortTool__body__label {
|
||||
border-bottom: 1px solid $brand-color;
|
||||
color: $brand-color;
|
||||
}
|
||||
}
|
||||
|
||||
.c-SortTool__header, .c-SortTool__title, .c-SortTool__body, .c-SortTool__body>.container, .c-SortTool__body>.container.radio {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.c-FilterTool__content {
|
||||
padding: 1rem;
|
||||
.c-FilterTool__body {
|
||||
color: $neutral-40;
|
||||
margin: 3em 0;
|
||||
.input-range {
|
||||
// margin: 25px 0;
|
||||
font-weight: $bold-font-weight;
|
||||
font-size: $small-font-size;
|
||||
.input-range__label-container {
|
||||
font-family: $font-family;
|
||||
}
|
||||
|
||||
.input-range__label {
|
||||
top: 20px;
|
||||
&.input-range__label--value {
|
||||
top: -40px;
|
||||
}
|
||||
&.input-range__label--max {
|
||||
right: 2em;
|
||||
}
|
||||
}
|
||||
}
|
||||
.c-FilterModal__input-range__subtitle {
|
||||
font-size: $font-size;
|
||||
font-weight: $bold-font-weight;
|
||||
text-align: center;
|
||||
margin-top: 1em;
|
||||
}
|
||||
}
|
||||
.c-FilterTool__footer {
|
||||
text-align: center;
|
||||
button {
|
||||
border-radius: 22px;
|
||||
padding: 9px 29px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import React from 'react';
|
||||
import SortAndFilterPanel from './../SortAndFilterPanel'
|
||||
import ProductContainer from './../ProductContainer'
|
||||
import FilterTool from './../FilterTool'
|
||||
import SortTool from './../SortTool'
|
||||
import './ProductContainerWithTools.component.scss';
|
||||
|
||||
const ProductContainerWithTools = ({isMobile, products}) => {
|
||||
return <section className="c-ProductContainerWithTools">
|
||||
{
|
||||
isMobile ?
|
||||
<React.Fragment>
|
||||
<SortAndFilterPanel />
|
||||
<ProductContainer products={products} />
|
||||
</React.Fragment> :
|
||||
<React.Fragment>
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
<div className="col-2 c-ProductContainerWithTools__asideCol">
|
||||
<aside className="c-ProductContainerWithTools__c-FilterAside">
|
||||
<FilterTool
|
||||
headerClass="c-FilterTool__header"
|
||||
titleClass="c-FilterTool__title"
|
||||
bodyClass="c-FilterTool__body"
|
||||
footerClass="c-FilterTool__footer"
|
||||
title="Filters" />
|
||||
</aside>
|
||||
</div>
|
||||
<div className="col-10">
|
||||
<SortTool
|
||||
headerClass="c-SortTool__header"
|
||||
titleClass="c-SortTool__title"
|
||||
bodyClass="c-SortTool__body"
|
||||
labelClass="c-SortTool__body__label"
|
||||
title="Sort By" />
|
||||
<ProductContainer products={products} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
}
|
||||
</section>
|
||||
};
|
||||
|
||||
ProductContainerWithTools.defaultProps = {
|
||||
|
||||
};
|
||||
|
||||
ProductContainerWithTools.propTypes = {
|
||||
|
||||
};
|
||||
|
||||
export default ProductContainerWithTools;
|
||||
@@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
import ProductContainerWithTools from './ProductContainerWithTools';
|
||||
|
||||
describe('ProductContainerWithTools', () => {
|
||||
it('renders without error', () => {
|
||||
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
import ProductContainerWithTools from './ProductContainerWithTools.jsx';
|
||||
|
||||
export default ProductContainerWithTools;
|
||||
@@ -1,9 +1,6 @@
|
||||
@import './../../../styles/variables';
|
||||
|
||||
.c-Plp__c-ProductContainer__c-ProductTile {
|
||||
// box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
|
||||
max-width: 300px;
|
||||
// margin-bottom: 2rem;
|
||||
padding: 10px 15px;
|
||||
border: 1px solid $neutral-17;
|
||||
|
||||
@@ -31,6 +28,9 @@
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 768px) {
|
||||
|
||||
border: none;
|
||||
&:not(:last-child) {
|
||||
margin-right: 2em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,33 @@
|
||||
@import './../../../styles//variables';
|
||||
|
||||
.c-Plp__c-SortAndFilterPanel__c-Search {
|
||||
.c-Plp__c-Header__c-Search {
|
||||
margin-left: auto;
|
||||
position: relative;
|
||||
|
||||
.c-Plp__c-SortAndFilterPanel__c-Search__input {
|
||||
.c-Plp__c-Header__c-Search__input {
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-bottom: 1px solid $neutral-00;
|
||||
color: $neutral-00;
|
||||
padding-bottom: 0.3rem;
|
||||
float: right;
|
||||
min-height: 0;
|
||||
width: 0;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
font-size: $small-font-size;
|
||||
font-weight: $bold-font-weight;
|
||||
padding: 7px 10px 8px 0;
|
||||
-webkit-transition: all 0.33s ease-in-out;
|
||||
transition: all 0.3s ease-in-out;
|
||||
box-shadow: none;
|
||||
outline: none;
|
||||
|
||||
&.c-Plp__c-Header__c-Search__input--visible {
|
||||
width: 100%;
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: $neutral-00;
|
||||
opacity: 1;
|
||||
@@ -16,10 +35,17 @@
|
||||
|
||||
&:-ms-input-placeholder {
|
||||
color: $neutral-00;
|
||||
font-size: $small-font-size;
|
||||
}
|
||||
|
||||
&::-ms-input-placeholder {
|
||||
color: $neutral-00;
|
||||
}
|
||||
}
|
||||
|
||||
.c-Plp__c-Header__c-Search__searchIcon {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
right: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,50 @@
|
||||
import React, {useState} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {connect} from 'react-redux';
|
||||
import {withRouter} from 'react-router-dom';
|
||||
import {dispatchSearchString} from './../../../pages/PLP/actions'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'
|
||||
import {faSearch} from '@fortawesome/free-solid-svg-icons'
|
||||
import './Search.component.scss';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faSearch } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
const Search = props => {
|
||||
const Search = ({dispatchSearchString, inCart, history}) => {
|
||||
var timerId;
|
||||
const [searchInitiated, setSearchInitiated] = useState(false)
|
||||
|
||||
const startSearch = (searchStringUpdated) => {
|
||||
dispatchSearchString(searchStringUpdated)
|
||||
inCart && history.push('/view/plp')
|
||||
}
|
||||
|
||||
const debouncedStartSearch = (func, delay, searchStringUpdated) => {
|
||||
clearTimeout(timerId)
|
||||
timerId = setTimeout(() => func(searchStringUpdated), delay)
|
||||
}
|
||||
|
||||
const onChangeHandler = (e) => {
|
||||
debouncedStartSearch(startSearch, 500, e.target.value)
|
||||
}
|
||||
|
||||
return (
|
||||
<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 className={`c-Plp__c-Header__c-Search header-icon${searchInitiated ? " c-Plp__c-Header__c-Search--searchInitiated" : ""}`}>
|
||||
<input type="text"
|
||||
className={`c-Plp__c-Header__c-Search__input${searchInitiated ? " c-Plp__c-Header__c-Search__input--visible" : ""}`}
|
||||
placeholder="Search" onChange={onChangeHandler} />
|
||||
<FontAwesomeIcon className="c-Plp__c-Header__c-Search__searchIcon" icon={faSearch} onClick={() => setSearchInitiated(!searchInitiated)} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Search.defaultProps = {
|
||||
|
||||
};
|
||||
|
||||
Search.propTypes = {
|
||||
|
||||
dispatchSearchString: PropTypes.func,
|
||||
inCart: PropTypes.bool
|
||||
};
|
||||
|
||||
export default Search;
|
||||
const mapDispatchToProps = ({
|
||||
dispatchSearchString
|
||||
})
|
||||
|
||||
export default connect(
|
||||
null,
|
||||
mapDispatchToProps
|
||||
)(withRouter(Search));
|
||||
@@ -8,24 +8,25 @@ import FilterModal from '../FilterModal';
|
||||
|
||||
const SortAndFilterPanel = props => {
|
||||
|
||||
const [showSortModal, setShowSortModal] = useState(false)
|
||||
const [showFilterModal, setShowFilterModal] = useState(false)
|
||||
|
||||
const handleSortModalChange = (modalState) => {
|
||||
setShowSortModal(() => modalState)
|
||||
const [sortModalIsOpen,setSortIsOpen] = useState(false);
|
||||
const [filterModalIsOpen,setFilterIsOpen] = useState(false);
|
||||
|
||||
function closeModal(type, e){
|
||||
e.stopPropagation();
|
||||
type === "sort" ? setSortIsOpen(false) : setFilterIsOpen(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='c-Plp__c-SortAndFilterPanel'>
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
<div className="c-Plp__c-SortAndFilterPanel__tool sort col-6" onClick={() => setShowSortModal(true)}>
|
||||
<div className="c-Plp__c-SortAndFilterPanel__tool sort col-6" onClick={() => setSortIsOpen(true)}>
|
||||
<p className="c-Plp__c-SortAndFilterPanel__toolContent"><FontAwesomeIcon icon={faSort} /> Sort</p>
|
||||
<SortModal showModal={showSortModal} setModalState={handleSortModalChange} />
|
||||
<SortModal closeModal={(e) => closeModal("sort", e)} modalIsOpen={sortModalIsOpen} />
|
||||
</div>
|
||||
<div className="c-Plp__c-SortAndFilterPanel__tool filter col-6" onClick={() => setShowFilterModal(true)}>
|
||||
<div className="c-Plp__c-SortAndFilterPanel__tool filter col-6" onClick={() => setFilterIsOpen(true)}>
|
||||
<p className="c-Plp__c-SortAndFilterPanel__toolContent"><FontAwesomeIcon icon={faFilter} /> Filter</p>
|
||||
<FilterModal showModal={showFilterModal} setModalState={setShowFilterModal} />
|
||||
<FilterModal closeModal={(e) => closeModal("filter", e)} modalIsOpen={filterModalIsOpen}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,39 @@
|
||||
@import './../../../styles/variables';
|
||||
|
||||
.c-SortModal {
|
||||
|
||||
// transform: translate(0,0);
|
||||
// transition: transform .3s ease-out,-webkit-transform .3s ease-out;
|
||||
|
||||
&.modal {
|
||||
&.show {
|
||||
display: block;
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
}
|
||||
// &.fade .modal-dialog {
|
||||
// transition: transform .3s ease-out;
|
||||
// }
|
||||
.modal-dialog {
|
||||
pointer-events: auto;
|
||||
.modal-body {
|
||||
color: $neutral-40;
|
||||
.error {
|
||||
color: $error-color;
|
||||
font-size: $small-font-size;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.ReactModal__Overlay {
|
||||
opacity: 0;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
&.ReactModal__Overlay--after-open {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.ReactModal__Overlay--before-close {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,50 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {connect} from 'react-redux';
|
||||
import Modal from 'react-modal';
|
||||
import SortTool from '../SortTool';
|
||||
import './SortModal.component.scss';
|
||||
import Modal from '../common/Modal/Modal';
|
||||
|
||||
const SortModal = ({showModal, setModalState}) => {
|
||||
const customStyles = {
|
||||
content: {
|
||||
top: '20%',
|
||||
width: '93%',
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
background: 'rgb(255, 255, 255)',
|
||||
overflow: 'auto',
|
||||
borderRadius: '4px'
|
||||
}
|
||||
};
|
||||
|
||||
const SortModal = ({closeModal, modalIsOpen}) => {
|
||||
|
||||
|
||||
function afterOpenModal() {
|
||||
// subtitle.style.color = '#000';
|
||||
// subtitle.style.fontWeight = '700';
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='c-SortModal'>
|
||||
<Modal
|
||||
showModal={showModal}
|
||||
setModalStateHandler={setModalState} />
|
||||
</div>
|
||||
<Modal
|
||||
overlayClassName="c-SortModal modal fade show"
|
||||
closeTimeoutMS={300}
|
||||
className="modal-dialog"
|
||||
isOpen={modalIsOpen}
|
||||
onAfterOpen={afterOpenModal}
|
||||
onRequestClose={closeModal}
|
||||
style={customStyles}
|
||||
contentLabel="Example Modal"
|
||||
>
|
||||
<SortTool
|
||||
headerClass="modal-header"
|
||||
titleClass="modal-title"
|
||||
bodyClass="modal-body"
|
||||
footerClass="modal-footer"
|
||||
title="Sort Options"
|
||||
closeModal={closeModal}
|
||||
isModal={true} />
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -18,8 +53,9 @@ SortModal.defaultProps = {
|
||||
};
|
||||
|
||||
SortModal.propTypes = {
|
||||
showModal: PropTypes.bool,
|
||||
setModalState: PropTypes.func
|
||||
closeModal: PropTypes.func,
|
||||
dispatchSortSelection: PropTypes.func,
|
||||
modalIsOpen: PropTypes.bool
|
||||
};
|
||||
|
||||
export default SortModal;
|
||||
@@ -0,0 +1,3 @@
|
||||
.c-SortTool {
|
||||
|
||||
}
|
||||
84
src/app/components/molecules/SortTool/SortTool.jsx
Normal file
84
src/app/components/molecules/SortTool/SortTool.jsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import React, {useState} from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import {dispatchSortSelection} from './../../../pages/PLP/actions'
|
||||
import './SortTool.component.scss';
|
||||
|
||||
const SortTool = props => {
|
||||
|
||||
const [sortType, setSortType] = useState('')
|
||||
const [selectionError, setSelectionError] = useState('')
|
||||
|
||||
const submitSelection = (e) => {
|
||||
if (!sortType) {
|
||||
setSelectionError(true)
|
||||
return
|
||||
}
|
||||
props.dispatchSortSelection(sortType)
|
||||
props.isModal && props.closeModal(e)
|
||||
setSelectionError(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`c-SortTool${props.isModal ? " modal-content" : " c-SortTool__content"}`}>
|
||||
<div className={props.headerClass}>
|
||||
<h5 className={props.titleClass} id="exampleModalLiveLabel">{props.title}</h5>
|
||||
{props.isModal && <button type="button" className="close" onClick={props.closeModal} aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>}
|
||||
</div>
|
||||
<div className={props.bodyClass}>
|
||||
{selectionError && <span className="error">Please Select One of the options below.</span>}
|
||||
<div className="container">
|
||||
<div className="radio">
|
||||
<label className={sortType === "priceHtoL" && props.labelClass}>
|
||||
<input type="radio" name="survey" checked={sortType === "priceHtoL"}
|
||||
onChange={() => {
|
||||
setSortType("priceHtoL");
|
||||
!props.isModal && props.dispatchSortSelection("priceHtoL");
|
||||
setSelectionError(false);
|
||||
}} />Price -- High Low
|
||||
</label>
|
||||
</div>
|
||||
<div className="radio">
|
||||
<label className={sortType === "priceLtoH" && props.labelClass}>
|
||||
<input type="radio" name="survey" checked={sortType === "priceLtoH"}
|
||||
onChange={() => {
|
||||
setSortType("priceLtoH");
|
||||
!props.isModal && props.dispatchSortSelection("priceLtoH");
|
||||
setSelectionError(false);
|
||||
}} />Price -- Low High
|
||||
</label>
|
||||
</div>
|
||||
<div className="radio disabled">
|
||||
<label className={sortType === "discount" && props.labelClass}>
|
||||
<input type="radio" name="survey" checked={sortType === "discount"}
|
||||
onChange={() => {
|
||||
setSortType("discount");
|
||||
!props.isModal && props.dispatchSortSelection("discount");
|
||||
setSelectionError(false);
|
||||
}} />Discount
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{props.isModal && <div className={props.footerClass}>
|
||||
<button type="button" className="btn btn-secondary" onClick={props.closeModal}>Cancel</button>
|
||||
<button type="button" className="btn btn-primary" onClick={submitSelection}>Apply</button>
|
||||
</div>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
SortTool.propTypes = {
|
||||
dispatchSortSelection: PropTypes.func
|
||||
};
|
||||
|
||||
const mapDispatchToProps = ({
|
||||
dispatchSortSelection
|
||||
})
|
||||
|
||||
export default connect(
|
||||
null,
|
||||
mapDispatchToProps
|
||||
)(SortTool);
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import Modal from './Modal';
|
||||
import SortTool from './SortTool';
|
||||
|
||||
describe('Modal', () => {
|
||||
describe('SortTool', () => {
|
||||
it('renders without error', () => {
|
||||
|
||||
});
|
||||
3
src/app/components/molecules/SortTool/index.js
Normal file
3
src/app/components/molecules/SortTool/index.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import SortTool from './SortTool.jsx';
|
||||
|
||||
export default SortTool;
|
||||
@@ -1,3 +0,0 @@
|
||||
.c-Modal {
|
||||
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
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;
|
||||
@@ -1,3 +0,0 @@
|
||||
import Modal from './Modal.jsx';
|
||||
|
||||
export default Modal;
|
||||
@@ -6,6 +6,7 @@ import {createStore, applyMiddleware, compose} from 'redux'
|
||||
// import * as serviceWorker from '../serviceWorker';
|
||||
import Router from './router';
|
||||
import reducer from './reducer'
|
||||
import ReactModal from 'react-modal'
|
||||
import './index.scss'
|
||||
import 'bootstrap/dist/css/bootstrap.min.css'
|
||||
|
||||
@@ -19,6 +20,7 @@ const composeEnhancers =
|
||||
}) : compose;
|
||||
const store = createStore(reducer, composeEnhancers(applyMiddleware(thunk)))
|
||||
|
||||
ReactModal.setAppElement('#root');
|
||||
ReactDOM.render(<Router store={store} />, document.getElementById('root'));
|
||||
|
||||
// serviceWorker.unregister();
|
||||
|
||||
@@ -4,14 +4,19 @@ import {connect} from 'react-redux';
|
||||
import {dispatchProducts} from './actions';
|
||||
|
||||
import Header from './../../components/molecules/Header'
|
||||
import SortAndFilterPanel from './../../components/molecules/SortAndFilterPanel'
|
||||
import ProductContainer from './../../components/molecules/ProductContainer'
|
||||
import Footer from './../../components/molecules/Footer'
|
||||
import ProductContainerWithTools from '../../components/molecules/ProductContainerWithTools/ProductContainerWithTools';
|
||||
|
||||
const Plp = ({dispatchProducts}) => {
|
||||
|
||||
const [products, setProducts] = useState([])
|
||||
|
||||
const isMobile = (() => {
|
||||
var check = false;
|
||||
(function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) check = true;})(navigator.userAgent||navigator.vendor||window.opera);
|
||||
return check;
|
||||
})();
|
||||
|
||||
useEffect(() => {
|
||||
fetch('https://api.myjson.com/bins/qzuzi')
|
||||
.then(res => res.json())
|
||||
@@ -25,8 +30,7 @@ const Plp = ({dispatchProducts}) => {
|
||||
return (
|
||||
<div className="c-Plp">
|
||||
<Header />
|
||||
<SortAndFilterPanel />
|
||||
<ProductContainer products={products} />
|
||||
<ProductContainerWithTools products={products} isMobile={isMobile} />
|
||||
<Footer />
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
export const DISPATCH_PRODUCTS = 'DISPATCH_PRODUCTS'
|
||||
export const DISPATCH_SORT_SELECTION = 'DISPATCH_SORT_SELECTION'
|
||||
export const DISPATCH_FILTER_RANGE = 'DISPATCH_FILTER_RANGE'
|
||||
export const DISPATCH_SEARCH_STRING = 'DISPATCH_SEARCH_STRING'
|
||||
export const UPDATE_FORM_VALUES = 'UPDATE_BILLING_FORM_VALUES'
|
||||
export const UPDATE_FORM_ERRORS = 'UPDATE_BILLING_FORM_ERRORS'
|
||||
|
||||
@@ -17,6 +20,27 @@ export const dispatchProducts = (products) => {
|
||||
}
|
||||
}
|
||||
|
||||
export const dispatchSortSelection = (sortSelection) => {
|
||||
return {
|
||||
type: DISPATCH_SORT_SELECTION,
|
||||
payload: {sortBy: sortSelection}
|
||||
}
|
||||
}
|
||||
|
||||
export const dispatchFilterRange = (filterRange) => {
|
||||
return {
|
||||
type: DISPATCH_FILTER_RANGE,
|
||||
payload: {filterRange}
|
||||
}
|
||||
}
|
||||
|
||||
export const dispatchSearchString = (searchString) => {
|
||||
return {
|
||||
type: DISPATCH_SEARCH_STRING,
|
||||
payload: {searchString}
|
||||
}
|
||||
}
|
||||
|
||||
export const updateFormValues = (formValues) => {
|
||||
return {
|
||||
type: UPDATE_FORM_VALUES,
|
||||
|
||||
@@ -2,6 +2,9 @@ import Immutable from 'immutable'
|
||||
|
||||
import {
|
||||
DISPATCH_PRODUCTS,
|
||||
DISPATCH_SORT_SELECTION,
|
||||
DISPATCH_FILTER_RANGE,
|
||||
DISPATCH_SEARCH_STRING,
|
||||
UPDATE_FORM_ERRORS,
|
||||
UPDATE_FORM_VALUES
|
||||
} from './actions'
|
||||
@@ -13,6 +16,9 @@ const initialState = Immutable.Map({
|
||||
const reducer = (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case DISPATCH_PRODUCTS:
|
||||
case DISPATCH_SORT_SELECTION:
|
||||
case DISPATCH_FILTER_RANGE:
|
||||
case DISPATCH_SEARCH_STRING:
|
||||
case UPDATE_FORM_ERRORS:
|
||||
case UPDATE_FORM_VALUES:
|
||||
return state.mergeDeep(action.payload)
|
||||
|
||||
@@ -12,4 +12,7 @@ export const getPlp = createSelector(
|
||||
|
||||
export const getFormValues = createGetSelector(getPlp, 'formValues')
|
||||
export const getFormErrors = createGetSelector(getPlp, 'formErrors')
|
||||
export const getProducts = createGetSelector(getPlp, 'products')
|
||||
export const getProducts = createGetSelector(getPlp, 'products')
|
||||
export const getFilterRange = createGetSelector(getPlp, 'filterRange')
|
||||
export const getSortSelection = createGetSelector(getPlp, 'sortBy')
|
||||
export const getSearchString = createGetSelector(getPlp, 'searchString')
|
||||
@@ -1,6 +1,5 @@
|
||||
// Components
|
||||
// ===
|
||||
// @import '../components/atoms/InputField/InputField.component';
|
||||
@import '../components/atoms/InputField/InputField.component';
|
||||
@import '../components/atoms/SelectOption/SelectOption.component';
|
||||
@import '../components/molecules/common/Form/Form.component';
|
||||
@@ -10,5 +9,3 @@
|
||||
@import '../components/molecules/LoginForm/LoginForm.component';
|
||||
@import '../components/molecules/PageLoader/PageLoader.component';
|
||||
@import '../components/molecules/SocialLogin/SocialLogin.component';
|
||||
// @import '../components/molecules/SortAndFilterPanel/SortAndFilterPanel.component';
|
||||
// @import '../preloader/preload';
|
||||
|
||||
@@ -127,4 +127,10 @@ sub {
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 768px) {
|
||||
.c-Plp .container {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user