Final functionality development commit

This commit is contained in:
2020-03-30 00:52:13 +05:30
parent 21b5a39d27
commit db4c7612a4
93 changed files with 1004 additions and 274 deletions

View File

@@ -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,

View File

@@ -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;
}
}

View File

@@ -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>
);
};

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -0,0 +1,3 @@
.c-FilterTool {
}

View 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);

View File

@@ -0,0 +1,8 @@
import React from 'react';
import FilterTool from './FilterTool';
describe('FilterTool', () => {
it('renders without error', () => {
});
});

View File

@@ -0,0 +1,3 @@
import FilterTool from './FilterTool.jsx';
export default FilterTool;

View File

@@ -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;
}
}
}

View File

@@ -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>
);
};

View File

@@ -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;
}
}
}

View File

@@ -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);

View File

@@ -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;
}
}
}
}

View File

@@ -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;

View File

@@ -0,0 +1,8 @@
import React from 'react';
import ProductContainerWithTools from './ProductContainerWithTools';
describe('ProductContainerWithTools', () => {
it('renders without error', () => {
});
});

View File

@@ -0,0 +1,3 @@
import ProductContainerWithTools from './ProductContainerWithTools.jsx';
export default ProductContainerWithTools;

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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));

View File

@@ -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>

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -0,0 +1,3 @@
.c-SortTool {
}

View 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);

View File

@@ -1,7 +1,7 @@
import React from 'react';
import Modal from './Modal';
import SortTool from './SortTool';
describe('Modal', () => {
describe('SortTool', () => {
it('renders without error', () => {
});

View File

@@ -0,0 +1,3 @@
import SortTool from './SortTool.jsx';
export default SortTool;

View File

@@ -1,3 +0,0 @@
.c-Modal {
}

View File

@@ -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">&times;</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;

View File

@@ -1,3 +0,0 @@
import Modal from './Modal.jsx';
export default Modal;

View File

@@ -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();

View File

@@ -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>
)

View File

@@ -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,

View File

@@ -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)

View File

@@ -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')

View File

@@ -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';

View File

@@ -127,4 +127,10 @@ sub {
[hidden] {
display: none;
}
@media only screen and (min-width: 768px) {
.c-Plp .container {
max-width: 100%;
}
}