From 5c073d75baf57dd3ce4eeef66194fe6f53acec43 Mon Sep 17 00:00:00 2001 From: Hannu Lyytikainen Date: Thu, 8 Feb 2018 13:10:49 +0200 Subject: [PATCH 1/8] Remove options shape validation The shape of filtering options is validated in a child component. --- .../SelectMultipleFilterMobile.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/components/SelectMultipleFilterMobile/SelectMultipleFilterMobile.js b/src/components/SelectMultipleFilterMobile/SelectMultipleFilterMobile.js index 79d9826d..efc2db6d 100644 --- a/src/components/SelectMultipleFilterMobile/SelectMultipleFilterMobile.js +++ b/src/components/SelectMultipleFilterMobile/SelectMultipleFilterMobile.js @@ -1,5 +1,5 @@ import React from 'react'; -import { array, string, arrayOf, func, shape } from 'prop-types'; +import { array, string, func } from 'prop-types'; import classNames from 'classnames'; import SelectMultipleFilterMobileForm from './SelectMultipleFilterMobileForm'; @@ -61,12 +61,7 @@ SelectMultipleFilterMobile.propTypes = { urlParam: string.isRequired, label: string.isRequired, onSelect: func.isRequired, - options: arrayOf( - shape({ - key: string.isRequired, - label: string.isRequired, - }) - ).isRequired, + options: array.isRequired, initialValues: array, }; From 8bf94da03ee9084a87cd68ab901d8d8c251e295f Mon Sep 17 00:00:00 2001 From: Hannu Lyytikainen Date: Thu, 8 Feb 2018 13:31:27 +0200 Subject: [PATCH 2/8] Rename features url param const --- src/components/SearchFilters/SearchFilters.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/SearchFilters/SearchFilters.js b/src/components/SearchFilters/SearchFilters.js index ccfbe41e..548340fc 100644 --- a/src/components/SearchFilters/SearchFilters.js +++ b/src/components/SearchFilters/SearchFilters.js @@ -12,7 +12,7 @@ import { createResourceLocatorString } from '../../util/routes'; import css from './SearchFilters.css'; const CATEGORY_URL_PARAM = 'pub_category'; -const AMENITIES_URL_PARAM = 'pub_amenities'; +const FEATURES_URL_PARAM = 'pub_amenities'; // Dropdown container can have a positional offset (in pixels) const FILTER_DROPDOWN_OFFSET = -14; @@ -49,8 +49,8 @@ const SearchFiltersComponent = props => { id: 'SearchFilters.featuresLabel', }); - const initialFeatures = !!urlQueryParams[AMENITIES_URL_PARAM] - ? urlQueryParams[AMENITIES_URL_PARAM].split(',') + const initialFeatures = !!urlQueryParams[FEATURES_URL_PARAM] + ? urlQueryParams[FEATURES_URL_PARAM].split(',') : []; const initialCategory = urlQueryParams[CATEGORY_URL_PARAM]; @@ -58,8 +58,8 @@ const SearchFiltersComponent = props => { const handleSelectOptions = (urlParam, options) => { const queryParams = options && options.length > 0 - ? { ...urlQueryParams, [AMENITIES_URL_PARAM]: options.join(',') } - : omit(urlQueryParams, AMENITIES_URL_PARAM); + ? { ...urlQueryParams, [FEATURES_URL_PARAM]: options.join(',') } + : omit(urlQueryParams, FEATURES_URL_PARAM); history.push(createResourceLocatorString('SearchPage', routeConfiguration(), {}, queryParams)); }; @@ -88,7 +88,7 @@ const SearchFiltersComponent = props => { const featuresFilter = features ? ( Date: Thu, 8 Feb 2018 13:49:14 +0200 Subject: [PATCH 3/8] Add features filtering to mobile search --- .../SearchFiltersMobile.js | 51 +++++++++++++++++-- src/containers/SearchPage/SearchPage.js | 1 + .../__snapshots__/SearchPage.test.js.snap | 36 +++++++++++++ src/translations/en.json | 1 + 4 files changed, 84 insertions(+), 5 deletions(-) diff --git a/src/components/SearchFiltersMobile/SearchFiltersMobile.js b/src/components/SearchFiltersMobile/SearchFiltersMobile.js index 7e2d3cca..d224302a 100644 --- a/src/components/SearchFiltersMobile/SearchFiltersMobile.js +++ b/src/components/SearchFiltersMobile/SearchFiltersMobile.js @@ -7,11 +7,18 @@ import { omit, toPairs } from 'lodash'; import routeConfiguration from '../../routeConfiguration'; import { createResourceLocatorString } from '../../util/routes'; -import { SecondaryButton, ModalInMobile, Button, SelectSingleFilterMobile } from '../../components'; +import { + SecondaryButton, + ModalInMobile, + Button, + SelectSingleFilterMobile, + SelectMultipleFilterMobile, +} from '../../components'; import css from './SearchFiltersMobile.css'; const CATEGORY_URL_PARAM = 'pub_category'; -const allowedCategories = [CATEGORY_URL_PARAM]; +const FEATURES_URL_PARAM = 'pub_amenities'; +const allowedCategories = [CATEGORY_URL_PARAM, FEATURES_URL_PARAM]; const validateParamValue = value => value !== null && value !== undefined && value.length > 0; const validateParamKey = key => allowedCategories.includes(key); @@ -33,7 +40,8 @@ class SearchFiltersMobileComponent extends Component { this.cancelFilters = this.cancelFilters.bind(this); this.closeFilters = this.closeFilters.bind(this); this.resetAll = this.resetAll.bind(this); - this.onSelectSingle = this.onSelectSingle.bind(this); + this.handleSelectSingle = this.handleSelectSingle.bind(this); + this.handleSelectMultiple = this.handleSelectMultiple.bind(this); } // Open filters modal, set the initial parameters to current ones @@ -65,7 +73,7 @@ class SearchFiltersMobileComponent extends Component { this.setState({ isFiltersOpenOnMobile: false }); } - onSelectSingle(urlParam, option) { + handleSelectSingle(urlParam, option) { const { urlQueryParams, history } = this.props; // query parameters after selecting the option @@ -77,6 +85,17 @@ class SearchFiltersMobileComponent extends Component { history.push(createResourceLocatorString('SearchPage', routeConfiguration(), {}, queryParams)); } + handleSelectMultiple(urlParam, options) { + const { urlQueryParams, history } = this.props; + + const queryParams = + options && options.length > 0 + ? { ...urlQueryParams, [FEATURES_URL_PARAM]: options.join(',') } + : omit(urlQueryParams, FEATURES_URL_PARAM); + + history.push(createResourceLocatorString('SearchPage', routeConfiguration(), {}, queryParams)); + } + // Reset all filter query parameters resetAll(e) { const { urlQueryParams, history } = this.props; @@ -102,6 +121,7 @@ class SearchFiltersMobileComponent extends Component { onMapIconClick, onManageDisableScrolling, categories, + features, intl, } = this.props; @@ -139,13 +159,30 @@ class SearchFiltersMobileComponent extends Component { ) : null; + const featuresLabel = intl.formatMessage({ id: 'SearchFiltersMobile.featuresLabel' }); + + const initialFeatures = !!urlQueryParams[FEATURES_URL_PARAM] + ? urlQueryParams[FEATURES_URL_PARAM].split(',') + : []; + + const featuresFilter = features ? ( + + ) : null; + return (
@@ -175,6 +212,7 @@ class SearchFiltersMobileComponent extends Component {
{categoryFilter} + {featuresFilter}
+ +
+
+ +
+
+ ); + } +} + +SelectMultipleFilterMobileComponent.defaultProps = { rootClassName: null, className: null, initialValues: [], }; -SelectMultipleFilterMobile.propTypes = { +SelectMultipleFilterMobileComponent.propTypes = { rootClassName: string, className: string, name: string.isRequired, @@ -65,6 +95,11 @@ SelectMultipleFilterMobile.propTypes = { onSelect: func.isRequired, options: array.isRequired, initialValues: array, + + // from injectIntl + intl: intlShape.isRequired, }; +const SelectMultipleFilterMobile = injectIntl(SelectMultipleFilterMobileComponent); + export default SelectMultipleFilterMobile; diff --git a/src/components/SelectMultipleFilterMobile/SelectMultipleFilterMobileForm.css b/src/components/SelectMultipleFilterMobile/SelectMultipleFilterMobileForm.css index 65477bf5..93adba15 100644 --- a/src/components/SelectMultipleFilterMobile/SelectMultipleFilterMobileForm.css +++ b/src/components/SelectMultipleFilterMobile/SelectMultipleFilterMobileForm.css @@ -2,57 +2,3 @@ .root { } - -.filterLabel, -.filterLabelSelected { - @apply --marketplaceH3FontStyles; - margin-bottom: 16px; -} - -.filterLabel { - color: var(--matterColorDark); -} - -.filterLabelSelected { - color: var(--marketplaceColor); -} - -.labelButton { - /* Override button styles */ - outline: none; - text-align: left; - border: none; - padding: 0; -} - -.optionsContainerOpen { - padding-left: 20px; - height: auto; -} - -.optionsContainerClosed { - height: 0; - overflow: hidden; -} - -.clearButton { - @apply --marketplaceH5FontStyles; - font-weight: var(--fontWeightMedium); - color: var(--matterColorAnti); - - /* Layout */ - display: inline; - float: right; - margin-top: 6px; - padding: 0; - - /* Override button styles */ - outline: none; - text-align: left; - border: none; - - &:focus, - &:hover { - color: var(--matterColor); - } -} diff --git a/src/components/SelectMultipleFilterMobile/SelectMultipleFilterMobileForm.js b/src/components/SelectMultipleFilterMobile/SelectMultipleFilterMobileForm.js index 315f6497..d8ea3d66 100644 --- a/src/components/SelectMultipleFilterMobile/SelectMultipleFilterMobileForm.js +++ b/src/components/SelectMultipleFilterMobile/SelectMultipleFilterMobileForm.js @@ -1,88 +1,32 @@ -import React, { Component } from 'react'; -import { arrayOf, func, node, number, shape, string } from 'prop-types'; -import { compose } from 'redux'; +import React from 'react'; +import { arrayOf, shape, string, node } from 'prop-types'; import { reduxForm, propTypes as formPropTypes } from 'redux-form'; -import { FormattedMessage, injectIntl, intlShape } from 'react-intl'; import { FieldGroupCheckbox, Form } from '../../components'; -import css from './SelectMultipleFilterMobileForm.css'; -class SelectMultipleFilterMobileFormComponent extends Component { - constructor(props) { - super(props); - this.state = { isOpen: true }; +const SelectMultipleFilterMobileFormComponent = props => { + const { form, name, options } = props; - this.handleClear = this.handleClear.bind(this); - this.toggleIsOpen = this.toggleIsOpen.bind(this); - } - - handleClear() { - this.props.onClear(); - } - - toggleIsOpen() { - this.setState(prevState => ({ isOpen: !prevState.isOpen })); - } - - render() { - const { form, name, label, options, initialValuesCount, intl } = this.props; - - const hasInitialValues = initialValuesCount > 0; - const labelClass = hasInitialValues ? css.filterLabelSelected : css.filterLabel; - - const labelText = hasInitialValues - ? intl.formatMessage( - { id: 'SelectMultipleFilterMobileForm.labelSelected' }, - { labelText: label, count: initialValuesCount } - ) - : label; - - const optionsContainerClass = this.state.isOpen - ? css.optionsContainerOpen - : css.optionsContainerClosed; - - return ( -
-
- - -
- -
- -
-
- ); - } -} - -SelectMultipleFilterMobileFormComponent.defaultProps = { - initialValuesCount: 0, + return ( +
+ + + ); }; +SelectMultipleFilterMobileFormComponent.defaultProps = {}; + SelectMultipleFilterMobileFormComponent.propTypes = { ...formPropTypes, name: string.isRequired, - label: string.isRequired, - onClear: func.isRequired, options: arrayOf( shape({ key: string.isRequired, label: node.isRequired, }) ).isRequired, - initialValuesCount: number, - - // form injectIntl - intl: intlShape.isRequired, }; const defaultFormName = 'SelectMultipleFilterMobileForm'; -export default compose(reduxForm({ form: defaultFormName }), injectIntl)( - SelectMultipleFilterMobileFormComponent -); +export default reduxForm({ form: defaultFormName })(SelectMultipleFilterMobileFormComponent); From b4739567059419c8eb9f673aaac374dedf7182e2 Mon Sep 17 00:00:00 2001 From: Hannu Lyytikainen Date: Thu, 8 Feb 2018 17:29:42 +0200 Subject: [PATCH 6/8] Fix layout and dom structure --- .../SearchFiltersMobile.css | 7 ++++++- .../SearchFiltersMobile.js | 6 ++++-- .../SelectMultipleFilterMobile.css | 1 + .../SelectMultipleFilterMobile.js | 19 +++++++++---------- .../SelectMultipleFilterMobileForm.css | 4 ---- .../SelectMultipleFilterMobileForm.js | 9 ++++++--- .../SelectSingleFilterMobile.css | 1 + 7 files changed, 27 insertions(+), 20 deletions(-) delete mode 100644 src/components/SelectMultipleFilterMobile/SelectMultipleFilterMobileForm.css diff --git a/src/components/SearchFiltersMobile/SearchFiltersMobile.css b/src/components/SearchFiltersMobile/SearchFiltersMobile.css index d5ec8ccf..06621cfa 100644 --- a/src/components/SearchFiltersMobile/SearchFiltersMobile.css +++ b/src/components/SearchFiltersMobile/SearchFiltersMobile.css @@ -65,7 +65,6 @@ .modalHeadingWrapper { padding-bottom: 31px; border-bottom: 1px solid var(--matterColorNegative); - margin-bottom: 27px; } .modalHeading { @@ -95,6 +94,12 @@ } } +.filtersWrapper { + /* add bottom margin so that the last filter won't be hidden + * under the bottom bar */ + margin-bottom: 160px; +} + .showListingsContainer { position: fixed; bottom: 0; diff --git a/src/components/SearchFiltersMobile/SearchFiltersMobile.js b/src/components/SearchFiltersMobile/SearchFiltersMobile.js index 54c5f6b6..18a93f7e 100644 --- a/src/components/SearchFiltersMobile/SearchFiltersMobile.js +++ b/src/components/SearchFiltersMobile/SearchFiltersMobile.js @@ -211,8 +211,10 @@ class SearchFiltersMobileComponent extends Component { - {categoryFilter} - {featuresFilter} +
+ {categoryFilter} + {featuresFilter} +
-
- -
+ ); } diff --git a/src/components/SelectMultipleFilterMobile/SelectMultipleFilterMobileForm.css b/src/components/SelectMultipleFilterMobile/SelectMultipleFilterMobileForm.css deleted file mode 100644 index 93adba15..00000000 --- a/src/components/SelectMultipleFilterMobile/SelectMultipleFilterMobileForm.css +++ /dev/null @@ -1,4 +0,0 @@ -@import '../../marketplace.css'; - -.root { -} diff --git a/src/components/SelectMultipleFilterMobile/SelectMultipleFilterMobileForm.js b/src/components/SelectMultipleFilterMobile/SelectMultipleFilterMobileForm.js index d8ea3d66..dd0015a3 100644 --- a/src/components/SelectMultipleFilterMobile/SelectMultipleFilterMobileForm.js +++ b/src/components/SelectMultipleFilterMobile/SelectMultipleFilterMobileForm.js @@ -5,19 +5,22 @@ import { reduxForm, propTypes as formPropTypes } from 'redux-form'; import { FieldGroupCheckbox, Form } from '../../components'; const SelectMultipleFilterMobileFormComponent = props => { - const { form, name, options } = props; + const { form, className, name, options } = props; return ( -
+ ); }; -SelectMultipleFilterMobileFormComponent.defaultProps = {}; +SelectMultipleFilterMobileFormComponent.defaultProps = { + className: null, +}; SelectMultipleFilterMobileFormComponent.propTypes = { ...formPropTypes, + className: string, name: string.isRequired, options: arrayOf( shape({ diff --git a/src/components/SelectSingleFilterMobile/SelectSingleFilterMobile.css b/src/components/SelectSingleFilterMobile/SelectSingleFilterMobile.css index 7cc9f1a1..e2f281d2 100644 --- a/src/components/SelectSingleFilterMobile/SelectSingleFilterMobile.css +++ b/src/components/SelectSingleFilterMobile/SelectSingleFilterMobile.css @@ -2,6 +2,7 @@ .root { padding-bottom: 16px; + margin-top: 30px; border-bottom: 1px solid var(--matterColorNegative); } From 03924667e0cf5c81b148a1b06efdb0a250c47327 Mon Sep 17 00:00:00 2001 From: Hannu Lyytikainen Date: Thu, 8 Feb 2018 17:51:36 +0200 Subject: [PATCH 7/8] Pass redux form name from parent component --- src/components/SelectMultipleFilter/SelectMultipleFilter.js | 1 + .../SelectMultipleFilter/SelectMultipleFilterForm.js | 6 +++--- .../SelectMultipleFilterMobile.js | 1 + .../SelectMultipleFilterMobileForm.js | 4 ++-- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/components/SelectMultipleFilter/SelectMultipleFilter.js b/src/components/SelectMultipleFilter/SelectMultipleFilter.js index 37206a3e..5ae4f700 100644 --- a/src/components/SelectMultipleFilter/SelectMultipleFilter.js +++ b/src/components/SelectMultipleFilter/SelectMultipleFilter.js @@ -127,6 +127,7 @@ class SelectMultipleFilter extends Component { {buttonLabel} Date: Fri, 9 Feb 2018 10:19:46 +0200 Subject: [PATCH 8/8] Pass test features in SearchPage.test.js --- src/containers/SearchPage/SearchPage.test.js | 1 + .../__snapshots__/SearchPage.test.js.snap | 64 +++---------------- 2 files changed, 9 insertions(+), 56 deletions(-) diff --git a/src/containers/SearchPage/SearchPage.test.js b/src/containers/SearchPage/SearchPage.test.js index 8c6d7922..99fe898a 100644 --- a/src/containers/SearchPage/SearchPage.test.js +++ b/src/containers/SearchPage/SearchPage.test.js @@ -42,6 +42,7 @@ describe('SearchPageComponent', () => { sendVerificationEmailInProgress: false, onResendVerificationEmail: noop, categories: [{ key: 'cat1', label: 'Cat 1' }, { key: 'cat2', label: 'Cat 2' }], + features: [{ key: 'dog1', label: 'Dog 1' }, { key: 'dog2', label: 'Dog 2' }], }; const tree = renderShallow(); expect(tree).toMatchSnapshot(); diff --git a/src/containers/SearchPage/__snapshots__/SearchPage.test.js.snap b/src/containers/SearchPage/__snapshots__/SearchPage.test.js.snap index 30b3b75e..ad0b1614 100644 --- a/src/containers/SearchPage/__snapshots__/SearchPage.test.js.snap +++ b/src/containers/SearchPage/__snapshots__/SearchPage.test.js.snap @@ -36,36 +36,12 @@ exports[`SearchPageComponent matches snapshot 1`] = ` features={ Array [ Object { - "key": "towels", - "label": "Towels", + "key": "dog1", + "label": "Dog 1", }, Object { - "key": "bathroom", - "label": "Bathroom", - }, - Object { - "key": "swimming_pool", - "label": "Swimming pool", - }, - Object { - "key": "own_drinks", - "label": "Own drinks allowed", - }, - Object { - "key": "jacuzzi", - "label": "Jacuzzi", - }, - Object { - "key": "audiovisual_entertainment", - "label": "Audiovisual entertainment", - }, - Object { - "key": "barbeque", - "label": "Barbeque", - }, - Object { - "key": "own_food_allowed", - "label": "Own food allowed", + "key": "dog2", + "label": "Dog 2", }, ] } @@ -101,36 +77,12 @@ exports[`SearchPageComponent matches snapshot 1`] = ` features={ Array [ Object { - "key": "towels", - "label": "Towels", + "key": "dog1", + "label": "Dog 1", }, Object { - "key": "bathroom", - "label": "Bathroom", - }, - Object { - "key": "swimming_pool", - "label": "Swimming pool", - }, - Object { - "key": "own_drinks", - "label": "Own drinks allowed", - }, - Object { - "key": "jacuzzi", - "label": "Jacuzzi", - }, - Object { - "key": "audiovisual_entertainment", - "label": "Audiovisual entertainment", - }, - Object { - "key": "barbeque", - "label": "Barbeque", - }, - Object { - "key": "own_food_allowed", - "label": "Own food allowed", + "key": "dog2", + "label": "Dog 2", }, ] }