diff --git a/src/components/SearchFiltersPanel/SearchFiltersPanel.js b/src/components/SearchFiltersPanel/SearchFiltersPanel.js
index 103df60c..2d6f019c 100644
--- a/src/components/SearchFiltersPanel/SearchFiltersPanel.js
+++ b/src/components/SearchFiltersPanel/SearchFiltersPanel.js
@@ -41,10 +41,6 @@ import { createResourceLocatorString } from '../../util/routes';
import { InlineTextButton } from '../../components';
import css from './SearchFiltersPanel.css';
-// Create constants from url params and uset them in FILTERS array and while adding actual filters
-// e.g. const MULTI_SELECT_URL_PARAM = 'pub_filterX';
-const FILTERS = [];
-
class SearchFiltersPanelComponent extends Component {
constructor(props) {
super(props);
@@ -82,9 +78,9 @@ class SearchFiltersPanelComponent extends Component {
// Reset all filter query parameters
resetAll(e) {
- const { urlQueryParams, history, onClosePanel } = this.props;
+ const { urlQueryParams, customURLParamToConfig, history, onClosePanel } = this.props;
- const queryParams = omit(urlQueryParams, FILTERS);
+ const queryParams = omit(urlQueryParams, Object.keys(customURLParamToConfig));
history.push(createResourceLocatorString('SearchPage', routeConfiguration(), {}, queryParams));
// Ensure that panel closes (if now changes have been made)
@@ -161,6 +157,7 @@ SearchFiltersPanelComponent.propTypes = {
rootClassName: string,
className: string,
urlQueryParams: object.isRequired,
+ customURLParamToConfig: object.isRequired,
onClosePanel: func.isRequired,
// from injectIntl
diff --git a/src/containers/SearchPage/MainPanel.js b/src/containers/SearchPage/MainPanel.js
new file mode 100644
index 00000000..c13fc815
--- /dev/null
+++ b/src/containers/SearchPage/MainPanel.js
@@ -0,0 +1,157 @@
+import React, { Component } from 'react';
+import { array, bool, func, object, number, string } from 'prop-types';
+import { FormattedMessage } from 'react-intl';
+import classNames from 'classnames';
+import { propTypes } from '../../util/types';
+import {
+ SearchResultsPanel,
+ SearchFilters,
+ SearchFiltersMobile,
+ SearchFiltersPanel,
+} from '../../components';
+
+import css from './SearchPage.css';
+
+class MainPanel extends Component {
+ constructor(props) {
+ super(props);
+ this.state = { isSearchFiltersPanelOpen: false };
+ }
+
+ render() {
+ const {
+ className,
+ rootClassName,
+ urlQueryParams,
+ listings,
+ searchInProgress,
+ searchListingsError,
+ searchParamsAreInSync,
+ onActivateListing,
+ onManageDisableScrolling,
+ onMapIconClick,
+ pagination,
+ searchParamsForPagination,
+ showAsModalMaxWidth,
+ customURLParamToConfig,
+ primaryFilters,
+ secondaryFilters,
+ } = this.props;
+
+ const isSearchFiltersPanelOpen = !!secondaryFilters && this.state.isSearchFiltersPanelOpen;
+ const searchFiltersPanelSelectedCount = !secondaryFilters
+ ? 0
+ : Object.keys(customURLParamToConfig)
+ .map(key => urlQueryParams[key])
+ .filter(param => !!param).length;
+
+ const searchFiltersPanelProps = !!secondaryFilters
+ ? {
+ isSearchFiltersPanelOpen: this.state.isSearchFiltersPanelOpen,
+ toggleSearchFiltersPanel: isOpen => {
+ this.setState({ isSearchFiltersPanelOpen: isOpen });
+ },
+ searchFiltersPanelSelectedCount,
+ }
+ : {};
+
+ const hasPaginationInfo = !!pagination && pagination.totalItems != null;
+ const totalItems = searchParamsAreInSync && hasPaginationInfo ? pagination.totalItems : 0;
+ const listingsAreLoaded = !searchInProgress && searchParamsAreInSync && hasPaginationInfo;
+
+ const classes = classNames(rootClassName || css.searchResultContainer, className);
+
+ return (
+
+
+
+ {isSearchFiltersPanelOpen ? (
+
+ this.setState({ isSearchFiltersPanelOpen: false })}
+ {...secondaryFilters}
+ />
+
+ ) : (
+
+ {searchListingsError ? (
+
+
+
+ ) : null}
+
+
+ )}
+
+ );
+ }
+}
+
+MainPanel.defaultProps = {
+ className: null,
+ rootClassName: null,
+ listings: [],
+ resultsCount: 0,
+ pagination: null,
+ searchParamsForPagination: {},
+ primaryFilters: null,
+ secondaryFilters: null,
+};
+
+MainPanel.propTypes = {
+ className: string,
+ rootClassName: string,
+
+ urlQueryParams: object.isRequired,
+ listings: array,
+ searchInProgress: bool.isRequired,
+ searchListingsError: propTypes.error,
+ searchParamsAreInSync: bool.isRequired,
+ onActivateListing: func.isRequired,
+ onManageDisableScrolling: func.isRequired,
+ onMapIconClick: func.isRequired,
+ pagination: propTypes.pagination,
+ searchParamsForPagination: object,
+ showAsModalMaxWidth: number.isRequired,
+ customURLParamToConfig: object.isRequired,
+ primaryFilters: object,
+ secondaryFilters: object,
+};
+
+export default MainPanel;
diff --git a/src/containers/SearchPage/README.md b/src/containers/SearchPage/README.md
new file mode 100644
index 00000000..1c358224
--- /dev/null
+++ b/src/containers/SearchPage/README.md
@@ -0,0 +1,71 @@
+# SearchPage
+
+SearchPage component has roughly 3 sections inside its layout:
+Topbar, MainPanel (for results and filters), and Map
+
+So, rough JSX presentation is something like:
+```jsx
+
+
+
+
+
+```
+
+Searches can be made by each of these components.
+
+* Topbar contains location search (LocationAutocompleteInput)
+* MainPanel has Filters that can fine-tune current location search
+* SearchMap can create new location searches when the map's bounding box changes
+ (i.e. moving, zooming, etc.)
+
+## Topbar
+
+In contrast to other pages, Topbar gets `currentSearchParams` among other props. This makes it
+possible for Topbar to take current filters into account.
+
+## MainPanel
+
+MainPanel has two functions: showing searchResults and showing filters. There are two sets of
+filters that can be passed to it: `primaryFilters`, `secondaryFilters`.
+
+Primary filters are filters that are shown always on top of SearchResults as dropdown-selections.
+We recommend that only 1 - 3 primary filters are passed in since they start to take too much space
+on narrow screens.
+
+Secondary filters create one more button to the space containing primary filters: *More filters*.
+This more-filters button opens up a SearchFiltersPanel component that can be changed to show those
+extra filters passed to it.
+**Note:** Currently, it doesn't contain any filter components by default, even if you pass in some
+filter data. Creating those filter components is part of the customization process.
+
+On the mobile layout, all the filters are shown in separate mobile filters panel.
+
+## SearchMap
+
+SearchMap listens to 'idle' event and SearchPage function `onIndle` can create a new location search if
+SearchMap's bounds have changed enough.
+
+## Other things to consider
+
+### URL Params vs marketplace-custom-config.js
+
+Filter handling needs some mapping between configured extended data. For example,
+*marketplace-custom-config.js* defines all the categories used by the app, but that data needs to be in
+format `pub_category=selectedCategory` when we talk with the API. (We also use this format in URL.)
+
+This mapping is done at the beginning of the file:
+```
+const CATEGORY_URL_PARAM = 'pub_category';
+const AMENITIES_URL_PARAM = 'pub_amenities';
+
+const customURLParamToConfig = {
+ [CATEGORY_URL_PARAM]: 'categories',
+ [AMENITIES_URL_PARAM]: 'amenities',
+};
+```
+
+### SeachPage schema / SEO
+
+Schema is created inside `createSearchResultSchema` in *SearchPage.helpers.js*. It needs listings
+and address to make meaningful JSON-LD presentation for search engines.
diff --git a/src/containers/SearchPage/SearchPage.helpers.js b/src/containers/SearchPage/SearchPage.helpers.js
new file mode 100644
index 00000000..d42d2487
--- /dev/null
+++ b/src/containers/SearchPage/SearchPage.helpers.js
@@ -0,0 +1,98 @@
+import { intersection } from 'lodash';
+import config from '../../config';
+import { createResourceLocatorString } from '../../util/routes';
+import { createSlug } from '../../util/urlHelpers';
+import routeConfiguration from '../../routeConfiguration';
+
+// customURLParams
+export const validURLParamForExtendedData = (paramKey, urlParams, customConfigKeys) => {
+ const configKey = customConfigKeys[paramKey];
+ const value = urlParams[paramKey];
+ const valueArray = value ? value.split(',') : [];
+
+ if (configKey && valueArray.length > 0) {
+ const allowedValues = config.custom[configKey].map(a => a.key);
+ const validValues = intersection(valueArray, allowedValues).join(',');
+ return validValues.length > 0 ? { [paramKey]: validValues } : {};
+ }
+ return {};
+};
+
+// validate filter params
+export const validURLParamsForExtendedData = (params, customURLParams, customURLParamToConfig) => {
+ const paramKeys = Object.keys(params);
+ return paramKeys.reduce((validParams, paramKey) => {
+ return customURLParams.includes(paramKey)
+ ? {
+ ...validParams,
+ ...validURLParamForExtendedData(paramKey, params, customURLParamToConfig),
+ }
+ : { ...validParams, [paramKey]: params[paramKey] };
+ }, {});
+};
+
+// extract search parameters, including a custom URL params
+// which are validated by mapping the values to marketplace custom config.
+export const pickSearchParamsOnly = (params, customURLParams, customURLParamToConfig) => {
+ const { address, origin, bounds, country, ...rest } = params || {};
+ const boundsMaybe = bounds ? { bounds } : {};
+ const originMaybe = config.sortSearchByDistance && origin ? { origin } : {};
+
+ const customSearchParamKeys = Object.keys(rest);
+ const customSearchParams = customSearchParamKeys.reduce((validParams, paramKey) => {
+ return customURLParams.includes(paramKey)
+ ? { ...validParams, ...validURLParamForExtendedData(paramKey, rest, customURLParamToConfig) }
+ : { ...validParams };
+ }, {});
+
+ return {
+ ...boundsMaybe,
+ ...originMaybe,
+ ...customSearchParams,
+ };
+};
+
+export const createSearchResultSchema = (listings, address, intl) => {
+ // Schema for search engines (helps them to understand what this page is about)
+ // http://schema.org
+ // We are using JSON-LD format
+ const siteTitle = config.siteTitle;
+ const searchAddress = address || intl.formatMessage({ id: 'SearchPage.schemaMapSearch' });
+ const schemaDescription = intl.formatMessage({ id: 'SearchPage.schemaDescription' });
+ const schemaTitle = intl.formatMessage(
+ { id: 'SearchPage.schemaTitle' },
+ { searchAddress, siteTitle }
+ );
+
+ const schemaListings = listings.map((l, i) => {
+ const title = l.attributes.title;
+ const pathToItem = createResourceLocatorString('ListingPage', routeConfiguration(), {
+ id: l.id.uuid,
+ slug: createSlug(title),
+ });
+ return {
+ '@type': 'ListItem',
+ position: i,
+ url: `${config.canonicalRootURL}${pathToItem}`,
+ name: title,
+ };
+ });
+
+ const schemaMainEntity = JSON.stringify({
+ '@type': 'ItemList',
+ name: searchAddress,
+ itemListOrder: 'http://schema.org/ItemListOrderAscending',
+ itemListElement: schemaListings,
+ });
+ return {
+ title: schemaTitle,
+ description: schemaDescription,
+ schema: {
+ '@context': 'http://schema.org',
+ '@type': 'SearchResultsPage',
+ description: schemaDescription,
+ name: schemaTitle,
+ mainEntity: [schemaMainEntity],
+ },
+ };
+};
diff --git a/src/containers/SearchPage/SearchPage.js b/src/containers/SearchPage/SearchPage.js
index 6d1bacf1..ccd92167 100644
--- a/src/containers/SearchPage/SearchPage.js
+++ b/src/containers/SearchPage/SearchPage.js
@@ -1,10 +1,10 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
-import { FormattedMessage, injectIntl, intlShape } from 'react-intl';
+import { injectIntl, intlShape } from 'react-intl';
import { connect } from 'react-redux';
import { compose } from 'redux';
import { withRouter } from 'react-router-dom';
-import { debounce, intersection, isEqual, unionWith } from 'lodash';
+import { debounce, isEqual, unionWith } from 'lodash';
import classNames from 'classnames';
import config from '../../config';
import routeConfiguration from '../../routeConfiguration';
@@ -15,85 +15,37 @@ import {
hasSameSDKBounds,
} from '../../util/googleMaps';
import { createResourceLocatorString } from '../../util/routes';
-import { createSlug, parse, stringify } from '../../util/urlHelpers';
+import { parse, stringify } from '../../util/urlHelpers';
import { propTypes } from '../../util/types';
import { getListingsById } from '../../ducks/marketplaceData.duck';
import { manageDisableScrolling, isScrollingDisabled } from '../../ducks/UI.duck';
-import {
- SearchMap,
- ModalInMobile,
- Page,
- SearchResultsPanel,
- SearchFilters,
- SearchFiltersMobile,
- SearchFiltersPanel,
-} from '../../components';
+import { SearchMap, ModalInMobile, Page } from '../../components';
import { TopbarContainer } from '../../containers';
-import { searchListings, searchMapListings, setActiveListing } from './SearchPage.duck';
+import { searchListings, searchMapListings, setActiveListing } from './SearchPage.duck';
+import {
+ pickSearchParamsOnly,
+ validURLParamForExtendedData,
+ validURLParamsForExtendedData,
+ createSearchResultSchema,
+} from './SearchPage.helpers';
+import MainPanel from './MainPanel';
import css from './SearchPage.css';
// Pagination page size might need to be dynamic on responsive page layouts
// Current design has max 3 columns 12 is divisible by 2 and 3
// So, there's enough cards to fill all columns on full pagination pages
const RESULT_PAGE_SIZE = 24;
-const MAX_SEARCH_RESULT_PAGE_SIZE_ON_MAP = 80; // max page size is 100 in API
-const MAX_SEARCH_RESULT_PAGES_ON_MAP = 1; // page size * n pages = number of listings shown on a map.
const MODAL_BREAKPOINT = 768; // Search is in modal on mobile layout
const SEARCH_WITH_MAP_DEBOUNCE = 300; // Little bit of debounce before search is initiated.
const BOUNDS_FIXED_PRECISION = 8;
const CATEGORY_URL_PARAM = 'pub_category';
const AMENITIES_URL_PARAM = 'pub_amenities';
-const USE_SEARCH_FILTER_PANEL = false;
-// Find correct extended data key from config.custom
-// e.g. 'pub_category' -> 'categories'.
-const customConfigKey = paramKey => {
- switch (paramKey) {
- case CATEGORY_URL_PARAM:
- return 'categories';
- case AMENITIES_URL_PARAM:
- return 'amenities';
- default:
- return null;
- }
-};
-
-const validURLParamForExtendedData = (paramKey, urlParams) => {
- const configKey = customConfigKey(paramKey);
- const value = urlParams[paramKey];
- const valueArray = value ? value.split(',') : [];
-
- if (configKey && valueArray.length > 0) {
- const allowedValues = config.custom[configKey].map(a => a.key);
- const validValues = intersection(valueArray, allowedValues).join(',');
- return validValues.length > 0 ? { [paramKey]: validValues } : {};
- }
- return {};
-};
-
-// validate filter params
-const validURLParamsForExtendedData = params => {
- const { [CATEGORY_URL_PARAM]: category, [AMENITIES_URL_PARAM]: amenities, ...rest } = params;
- return {
- ...rest,
- ...validURLParamForExtendedData(CATEGORY_URL_PARAM, params),
- ...validURLParamForExtendedData(AMENITIES_URL_PARAM, params),
- };
-};
-
-// extract search parameters, including a custom attribute named category
-const pickSearchParamsOnly = params => {
- const { address, origin, bounds, country, ...rest } = params || {};
- const boundsMaybe = bounds ? { bounds } : {};
- const originMaybe = config.sortSearchByDistance && origin ? { origin } : {};
- return {
- ...boundsMaybe,
- ...originMaybe,
- ...validURLParamForExtendedData(CATEGORY_URL_PARAM, rest),
- ...validURLParamForExtendedData(AMENITIES_URL_PARAM, rest),
- };
+const customURLParamToConfig = {
+ [CATEGORY_URL_PARAM]: 'categories',
+ [AMENITIES_URL_PARAM]: 'amenities',
};
export class SearchPageComponent extends Component {
@@ -103,7 +55,6 @@ export class SearchPageComponent extends Component {
this.state = {
isSearchMapOpenOnMobile: props.tab === 'map',
isMobileModalOpen: false,
- isSearchFiltersPanelOpen: false,
};
// Initiating map creates 'bounds_changes' event
@@ -116,19 +67,12 @@ export class SearchPageComponent extends Component {
this.searchMapListingsInProgress = false;
this.onIdle = debounce(this.onIdle.bind(this), SEARCH_WITH_MAP_DEBOUNCE);
- this.fetchMoreListingsToMap = this.fetchMoreListingsToMap.bind(this);
this.onOpenMobileModal = this.onOpenMobileModal.bind(this);
this.onCloseMobileModal = this.onCloseMobileModal.bind(this);
}
- componentDidMount() {
- this.fetchMoreListingsToMap(this.props.location);
- }
-
componentWillReceiveProps(nextProps) {
if (!isEqual(this.props.location, nextProps.location)) {
- this.fetchMoreListingsToMap(nextProps.location);
-
// If no mapSearch url parameter is given, this is original location search
const { mapSearch } = parse(nextProps.location.search, {
latlng: ['origin'],
@@ -175,8 +119,8 @@ export class SearchPageComponent extends Component {
bounds: viewportBounds,
country,
mapSearch: true,
- ...validURLParamForExtendedData(CATEGORY_URL_PARAM, rest),
- ...validURLParamForExtendedData(AMENITIES_URL_PARAM, rest),
+ ...validURLParamForExtendedData(CATEGORY_URL_PARAM, rest, customURLParamToConfig),
+ ...validURLParamForExtendedData(AMENITIES_URL_PARAM, rest, customURLParamToConfig),
};
this.viewportBounds = viewportBounds;
history.push(
@@ -188,52 +132,6 @@ export class SearchPageComponent extends Component {
}
}
- fetchMoreListingsToMap(location) {
- // TODO Remove this function.
- // Temporarily just return immediately to avoid merge conflicts.
- return;
-
- // eslint-disable-next-line no-unreachable
- const { onSearchMapListings } = this.props;
- const searchInURL = parse(location.search, {
- latlng: ['origin'],
- latlngBounds: ['bounds'],
- });
-
- const perPage = MAX_SEARCH_RESULT_PAGE_SIZE_ON_MAP;
- const page = 1;
- const { address, country, origin, ...rest } = searchInURL;
- const originMaybe = config.sortSearchByDistance ? { origin } : {};
- const searchParamsForMapResults = {
- ...rest,
- ...originMaybe,
- include: ['images'],
- page,
- perPage,
- 'fields.image': ['variants.landscape-crop', 'variants.landscape-crop2x'],
- 'limit.images': 1,
- };
- this.searchMapListingsInProgress = true;
-
- // Search more listings for map
- onSearchMapListings(searchParamsForMapResults)
- .then(response => {
- const hasNextPage =
- page < response.data.meta.totalPages && page < MAX_SEARCH_RESULT_PAGES_ON_MAP;
- if (hasNextPage) {
- onSearchMapListings({ ...searchParamsForMapResults, page: page + 1 });
- } else {
- this.searchMapListingsInProgress = false;
- }
- })
- .catch(error => {
- // In case of error, stop recursive loop and report error.
- // TODO: Show and error in the listings column
- // eslint-disable-next-line no-console
- console.error(`An error (${error} occured while trying to retrieve map listings`);
- });
- }
-
// Invoked when a modal is opened from a child component,
// for example when a filter modal is opened in mobile view
onOpenMobileModal() {
@@ -271,77 +169,33 @@ export class SearchPageComponent extends Component {
// urlQueryParams doesn't contain page specific url params
// like mapSearch, page or origin (origin depends on config.sortSearchByDistance)
- const urlQueryParams = pickSearchParamsOnly(searchInURL);
+ const urlQueryParams = pickSearchParamsOnly(
+ searchInURL,
+ [AMENITIES_URL_PARAM, CATEGORY_URL_PARAM],
+ customURLParamToConfig
+ );
// Page transition might initially use values from previous search
const urlQueryString = stringify(urlQueryParams);
- const paramsQueryString = stringify(pickSearchParamsOnly(searchParams));
- const searchParamsMatch = urlQueryString === paramsQueryString;
+ const paramsQueryString = stringify(
+ pickSearchParamsOnly(
+ searchParams,
+ [AMENITIES_URL_PARAM, CATEGORY_URL_PARAM],
+ customURLParamToConfig
+ )
+ );
+ const searchParamsAreInSync = urlQueryString === paramsQueryString;
- const { address, bounds, origin } = searchInURL || {};
-
- const hasPaginationInfo = !!pagination && pagination.totalItems != null;
- const totalItems = searchParamsMatch && hasPaginationInfo ? pagination.totalItems : 0;
- const listingsAreLoaded = !searchInProgress && searchParamsMatch && hasPaginationInfo;
-
- const validQueryParams = validURLParamsForExtendedData(searchInURL);
-
- const searchError = (
-
-
-
+ const validQueryParams = validURLParamsForExtendedData(
+ searchInURL,
+ [AMENITIES_URL_PARAM, CATEGORY_URL_PARAM],
+ customURLParamToConfig
);
- const searchMap = (
- {
- onManageDisableScrolling('SearchPage.map', false);
- }}
- useLocationSearchBounds={!this.viewportBounds}
- />
- );
- const showSearchMapInMobile = this.state.isSearchMapOpenOnMobile ? searchMap : null;
const isWindowDefined = typeof window !== 'undefined';
- const searchMapMaybe =
- isWindowDefined && window.innerWidth < MODAL_BREAKPOINT ? showSearchMapInMobile : searchMap;
-
- const searchParamsForPagination = parse(location.search);
-
- // Schema for search engines (helps them to understand what this page is about)
- // http://schema.org
- // We are using JSON-LD format
- const siteTitle = config.siteTitle;
- const searchAddress = address || intl.formatMessage({ id: 'SearchPage.schemaMapSearch' });
- const schemaTitle = intl.formatMessage(
- { id: 'SearchPage.schemaTitle' },
- { searchAddress, siteTitle }
- );
- const schemaDescription = intl.formatMessage({ id: 'SearchPage.schemaDescription' });
- const schemaListings = listings.map((l, i) => {
- const title = l.attributes.title;
- const pathToItem = createResourceLocatorString('ListingPage', routeConfiguration(), {
- id: l.id.uuid,
- slug: createSlug(title),
- });
- return {
- '@type': 'ListItem',
- position: i,
- url: `${config.canonicalRootURL}${pathToItem}`,
- name: title,
- };
- });
- const schemaMainEntity = JSON.stringify({
- '@type': 'ItemList',
- name: searchAddress,
- itemListOrder: 'http://schema.org/ItemListOrderAscending',
- itemListElement: schemaListings,
- });
+ const isMobileLayout = isWindowDefined && window.innerWidth < MODAL_BREAKPOINT;
+ const shouldShowSearchMap =
+ !isMobileLayout || (isMobileLayout && this.state.isSearchMapOpenOnMobile);
const onMapIconClick = () => {
this.useLocationSearchBounds = true;
@@ -349,51 +203,8 @@ export class SearchPageComponent extends Component {
this.setState({ isSearchMapOpenOnMobile: true });
};
- const extraSearchFiltersPanelOrListings =
- USE_SEARCH_FILTER_PANEL && this.state.isSearchFiltersPanelOpen ? (
-
- this.setState({ isSearchFiltersPanelOpen: false })}
- />
-
- ) : (
-
- {searchListingsError ? searchError : null}
-
-
- );
-
- // An example how to check how many filters are selected on SearchFilterPanel
- // if it is in use.
- //
- // const searchFiltersPanelSelectedCount = [
- // validQueryParams[FILTER_1_URL_PARAM],
- // validQueryParams[FILTER_2_URL_PARAM],
- // ].filter(param => !!param).length
- const searchFiltersPanelSelectedCount = 0;
- const searchFiltersPanelProps = USE_SEARCH_FILTER_PANEL
- ? {
- isSearchFiltersPanelOpen: this.state.isSearchFiltersPanelOpen,
- toggleSearchFiltersPanel: isOpen => {
- this.setState({ isSearchFiltersPanelOpen: isOpen });
- },
- searchFiltersPanelSelectedCount,
- }
- : {};
+ const { address, bounds, origin } = searchInURL || {};
+ const { title, description, schema } = createSearchResultSchema(listings, address, intl);
// Set topbar class based on if a modal is open in
// a child component
@@ -407,15 +218,9 @@ export class SearchPageComponent extends Component {
return (
-
-
-
- {extraSearchFiltersPanelOrListings}
-
+
- {searchMapMaybe}
+
+ {shouldShowSearchMap ? (
+ {
+ onManageDisableScrolling('SearchPage.map', false);
+ }}
+ useLocationSearchBounds={!this.viewportBounds}
+ />
+ ) : null}
+
@@ -487,6 +293,7 @@ const { array, bool, func, oneOf, object, shape, string } = PropTypes;
SearchPageComponent.propTypes = {
listings: array,
mapListings: array,
+ onActivateListing: func.isRequired,
onManageDisableScrolling: func.isRequired,
onSearchMapListings: func.isRequired,
pagination: propTypes.pagination,
diff --git a/src/containers/SearchPage/SearchPage.test.js b/src/containers/SearchPage/SearchPage.test.js
index aa38bfc9..9b7a35e4 100644
--- a/src/containers/SearchPage/SearchPage.test.js
+++ b/src/containers/SearchPage/SearchPage.test.js
@@ -36,6 +36,7 @@ describe('SearchPageComponent', () => {
currentUserHasListings: false,
intl: fakeIntl,
isAuthenticated: false,
+ onActivateListing: noop,
onLogout: noop,
onManageDisableScrolling: noop,
onSearchMapListings: noop,
diff --git a/src/containers/SearchPage/__snapshots__/SearchPage.test.js.snap b/src/containers/SearchPage/__snapshots__/SearchPage.test.js.snap
index 2c6d9c8f..df9d474c 100644
--- a/src/containers/SearchPage/__snapshots__/SearchPage.test.js.snap
+++ b/src/containers/SearchPage/__snapshots__/SearchPage.test.js.snap
@@ -22,10 +22,29 @@ exports[`SearchPageComponent matches snapshot 1`] = `
currentSearchParams={Object {}}
/>
-
+ }
+ showAsModalMaxWidth={768}
+ urlQueryParams={Object {}}
+ />