diff --git a/package.json b/package.json
index 9b7c33b5..fbe221ae 100644
--- a/package.json
+++ b/package.json
@@ -4,6 +4,7 @@
"private": true,
"dependencies": {
"basic-auth": "^1.1.0",
+ "classnames": "^2.2.5",
"compression": "^1.6.2",
"express": "^4.14.0",
"helmet": "^3.3.0",
diff --git a/src/components/FilterPanel/FilterPanel.css b/src/components/FilterPanel/FilterPanel.css
new file mode 100644
index 00000000..00539109
--- /dev/null
+++ b/src/components/FilterPanel/FilterPanel.css
@@ -0,0 +1,37 @@
+.filterTitle {
+ text-align: center;
+}
+
+.toListingsButton {
+ position: fixed;
+ bottom: 1rem;
+ left: 50%;
+ width: 200px;
+ margin-left: -100px;
+ display: block;
+ text-align: center;
+ text-decoration: none;
+ font-size: 1.4rem;
+ padding: 0.5rem;
+ color: #fff;
+ background-color: #9B9B9B;
+ cursor: pointer;
+
+ &:hover {
+ background-color: #888;
+ }
+}
+
+.close {
+ position: absolute;
+ top: 0;
+ left: 0;
+ height: 3em;
+ padding: 1em;
+ color: #999;
+ text-decoration: none;
+
+ &:hover {
+ color: #000;
+ }
+}
diff --git a/src/components/FilterPanel/FilterPanel.js b/src/components/FilterPanel/FilterPanel.js
new file mode 100644
index 00000000..28c798c8
--- /dev/null
+++ b/src/components/FilterPanel/FilterPanel.js
@@ -0,0 +1,13 @@
+import React from 'react';
+import { NamedLink } from '../../components';
+import css from './FilterPanel.css';
+
+const FilterPanel = () => (
+
+
Filters
+ See studios
+ X
+
+);
+
+export default FilterPanel;
diff --git a/src/components/FilterPanel/FilterPanel.test.js b/src/components/FilterPanel/FilterPanel.test.js
new file mode 100644
index 00000000..86338357
--- /dev/null
+++ b/src/components/FilterPanel/FilterPanel.test.js
@@ -0,0 +1,22 @@
+import React from 'react';
+import renderer from 'react-test-renderer';
+import { TestProvider } from '../../util/test-helpers';
+import { RoutesProvider } from '../../components';
+import routesConfiguration from '../../routesConfiguration';
+import FilterPanel from './FilterPanel';
+
+describe('FilterPanel', () => {
+ it('matches snapshot', () => {
+ const component = renderer.create(
+ (
+
+
+
+
+
+ ),
+ );
+ const tree = component.toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+});
diff --git a/src/components/FilterPanel/__snapshots__/FilterPanel.test.js.snap b/src/components/FilterPanel/__snapshots__/FilterPanel.test.js.snap
new file mode 100644
index 00000000..d941b9f6
--- /dev/null
+++ b/src/components/FilterPanel/__snapshots__/FilterPanel.test.js.snap
@@ -0,0 +1,22 @@
+exports[`FilterPanel matches snapshot 1`] = `
+
+`;
diff --git a/src/components/ListingCard/ListingCard.css b/src/components/ListingCard/ListingCard.css
new file mode 100644
index 00000000..bafa1e92
--- /dev/null
+++ b/src/components/ListingCard/ListingCard.css
@@ -0,0 +1,67 @@
+.listing {
+ display: flex;
+ flex-direction: column;
+ border-radius: 4px;
+}
+
+.squareWrapper {
+ display: block;
+ position: relative;
+ width: 100%;
+}
+
+/* Firefox doesn't support image aspect ratio inside flexbox */
+.aspectWrapper {
+ padding-bottom: 75%; /* 4:3 Aspect Ratio */
+}
+
+.thumbnail {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ width: 100%;
+ height: 100%;
+}
+
+.info {
+ padding: 1rem;
+
+}
+
+.mainInfo {
+ width: 100%;
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+}
+
+.details {
+ width: 100%;
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+}
+
+.authorInfo {
+ width: 100%;
+ display: flex;
+ flex-direction: row;
+}
+
+.authorDetails {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+}
+.avatarWrapper {
+ display: block;
+ flex-basis: 44px;
+ width: 44px;
+ height: 44px;
+ margin-right: 1rem;
+}
+.avatar {
+ border-radius: 22px;
+}
diff --git a/src/components/ListingCard/ListingCard.js b/src/components/ListingCard/ListingCard.js
new file mode 100644
index 00000000..a868367d
--- /dev/null
+++ b/src/components/ListingCard/ListingCard.js
@@ -0,0 +1,71 @@
+import React, { PropTypes } from 'react';
+import { NamedLink } from '../../components';
+import css from './ListingCard.css';
+
+const ListingCard = props => {
+ const { id, title, price, description, location, review, author } = props;
+ const slug = encodeURIComponent(title.split(' ').join('-'));
+ return (
+
+
+
+

+
+
+
+
+
+ {title}
+
+
+ {price}
+
+
+
+ {description}
+
+
+
+ {location}
+
+
+ ({review.rating}/5){' '}
+ {review.count}
+
+
+
+
+
+

+
+
+
{author.name}
+
+ review: {author.review.rating}/5
+
+
+
+
+
+ );
+};
+
+ListingCard.defaultProps = { location: null, review: {}, author: {} };
+
+const { number, shape, string } = PropTypes;
+
+ListingCard.propTypes = {
+ id: number.isRequired,
+ title: string.isRequired,
+ price: string.isRequired,
+ description: string.isRequired,
+ location: string,
+ review: shape({ rating: string.isRequired, count: string.isRequired }),
+ author: shape({
+ name: string.isRequired,
+ avatar: string.isRequired,
+ review: shape({ rating: string.isRequired }),
+ }),
+};
+
+export default ListingCard;
diff --git a/src/components/ListingCard/ListingCard.test.js b/src/components/ListingCard/ListingCard.test.js
new file mode 100644
index 00000000..c016fffe
--- /dev/null
+++ b/src/components/ListingCard/ListingCard.test.js
@@ -0,0 +1,35 @@
+import React from 'react';
+import renderer from 'react-test-renderer';
+import { TestProvider } from '../../util/test-helpers';
+import { RoutesProvider } from '../../components';
+import routesConfiguration from '../../routesConfiguration';
+import ListingCard from './ListingCard';
+
+describe('ListingCard', () => {
+ it('matches snapshot', () => {
+ const listing = {
+ id: 123,
+ title: 'Banyan Studios',
+ price: '55\u20AC / day',
+ description: 'Organic Music Production in a Sustainable, Ethical and Professional Studio.',
+ location: 'New York, NY \u2022 40mi away',
+ review: { count: '8 reviews', rating: '4' },
+ author: {
+ name: 'The Stardust Collective',
+ avatar: 'http://placehold.it/44x44',
+ review: { rating: '4' },
+ },
+ };
+ const component = renderer.create(
+ (
+
+
+
+
+
+ ),
+ );
+ const tree = component.toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+});
diff --git a/src/components/ListingCard/__snapshots__/ListingCard.test.js.snap b/src/components/ListingCard/__snapshots__/ListingCard.test.js.snap
new file mode 100644
index 00000000..2c06adbf
--- /dev/null
+++ b/src/components/ListingCard/__snapshots__/ListingCard.test.js.snap
@@ -0,0 +1,86 @@
+exports[`ListingCard matches snapshot 1`] = `
+
+
+
+

+
+
+
+
+
+ Organic Music Production in a Sustainable, Ethical and Professional Studio.
+
+
+
+ New York, NY • 40mi away
+
+
+ (
+
+ 4
+
+
+ /5
+
+ )
+
+
+ 8 reviews
+
+
+
+
+
+
+

+
+
+
+ The Stardust Collective
+
+
+ review:
+
+ 4
+
+
+ /5
+
+
+
+
+
+
+`;
diff --git a/src/components/ListingCardSmall/ListingCardSmall.css b/src/components/ListingCardSmall/ListingCardSmall.css
new file mode 100644
index 00000000..4ac667ed
--- /dev/null
+++ b/src/components/ListingCardSmall/ListingCardSmall.css
@@ -0,0 +1,38 @@
+.listing {
+ width: 130px;
+ display: flex;
+ flex-direction: column;
+ border-radius: 4px;
+ margin-right: 1rem;
+}
+
+.squareWrapper {
+ display: block;
+ position: relative;
+ width: 100%;
+}
+
+/* Firefox doesn't support image aspect ratio inside flexbox */
+.aspectWrapper {
+ padding-bottom: 75%; /* 4:3 Aspect Ratio */
+}
+
+.thumbnail {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ width: 100%;
+ height: 100%;
+}
+
+.info {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ padding: 0.5rem;
+ font-size: 0.8rem;
+ border: solid 1px #ccc;
+}
diff --git a/src/components/ListingCardSmall/ListingCardSmall.js b/src/components/ListingCardSmall/ListingCardSmall.js
new file mode 100644
index 00000000..c4578dd0
--- /dev/null
+++ b/src/components/ListingCardSmall/ListingCardSmall.js
@@ -0,0 +1,43 @@
+import React, { PropTypes } from 'react';
+import { NamedLink } from '../../components';
+import css from './ListingCardSmall.css';
+
+// X
+const ListingCardSmall = props => {
+ const { id, title, price, review } = props;
+ const slug = encodeURIComponent(title.split(' ').join('-'));
+ return (
+
+
+
+

+
+
+
+
+ {title}
+
+
+ ({review.rating}/5){' '}
+ {review.count}
+
+
+ {price}
+
+
+
+ );
+};
+
+ListingCardSmall.defaultProps = { review: {} };
+
+const { number, shape, string } = PropTypes;
+
+ListingCardSmall.propTypes = {
+ id: number.isRequired,
+ title: string.isRequired,
+ price: string.isRequired,
+ review: shape({ rating: string.isRequired, count: string.isRequired }),
+};
+
+export default ListingCardSmall;
diff --git a/src/components/ListingCardSmall/ListingCardSmall.test.js b/src/components/ListingCardSmall/ListingCardSmall.test.js
new file mode 100644
index 00000000..2f5e2eb1
--- /dev/null
+++ b/src/components/ListingCardSmall/ListingCardSmall.test.js
@@ -0,0 +1,35 @@
+import React from 'react';
+import renderer from 'react-test-renderer';
+import { TestProvider } from '../../util/test-helpers';
+import { RoutesProvider } from '../../components';
+import routesConfiguration from '../../routesConfiguration';
+import ListingCardSmall from './ListingCardSmall';
+
+describe('ListingCardSmall', () => {
+ it('matches snapshot', () => {
+ const listing = {
+ id: 123,
+ title: 'Banyan Studios',
+ price: '55\u20AC / day',
+ description: 'Organic Music Production in a Sustainable, Ethical and Professional Studio.',
+ location: 'New York, NY \u2022 40mi away',
+ review: { count: '8 reviews', rating: '4' },
+ author: {
+ name: 'The Stardust Collective',
+ avatar: 'http://placehold.it/44x44',
+ review: { rating: '4' },
+ },
+ };
+ const component = renderer.create(
+ (
+
+
+
+
+
+ ),
+ );
+ const tree = component.toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+});
diff --git a/src/components/ListingCardSmall/__snapshots__/ListingCardSmall.test.js.snap b/src/components/ListingCardSmall/__snapshots__/ListingCardSmall.test.js.snap
new file mode 100644
index 00000000..e6a52348
--- /dev/null
+++ b/src/components/ListingCardSmall/__snapshots__/ListingCardSmall.test.js.snap
@@ -0,0 +1,44 @@
+exports[`ListingCardSmall matches snapshot 1`] = `
+
+
+
+

+
+
+
+
+`;
diff --git a/src/components/MapPanel/MapPanel.css b/src/components/MapPanel/MapPanel.css
new file mode 100644
index 00000000..3ff8f8e6
--- /dev/null
+++ b/src/components/MapPanel/MapPanel.css
@@ -0,0 +1,53 @@
+.mapContainer {
+ height: 70vh;
+ width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background-color: #ddd;
+ color: #fff;
+ font-size: 2rem;
+}
+.mapListingsContainer {
+ height: 30vh;
+ width: 100%;
+ background-color: #fff;
+ display: flex;
+ flex-direction: row;
+ padding: 1rem;
+}
+
+
+.toFiltersButton {
+ position: fixed;
+ top: calc(70vh - 80px);
+ left: 50%;
+ width: 200px;
+ margin-left: -100px;
+ display: block;
+ text-align: center;
+ text-decoration: none;
+ font-size: 1.4rem;
+ padding: 0.5rem;
+ color: #fff;
+ background-color: #9B9B9B;
+ cursor: pointer;
+
+ &:hover {
+ background-color: #888;
+ }
+}
+
+.close {
+ position: absolute;
+ top: 0;
+ left: 0;
+ height: 3em;
+ padding: 1em;
+ color: #999;
+ text-decoration: none;
+
+ &:hover {
+ color: #000;
+ }
+}
diff --git a/src/components/MapPanel/MapPanel.js b/src/components/MapPanel/MapPanel.js
new file mode 100644
index 00000000..a0383b4d
--- /dev/null
+++ b/src/components/MapPanel/MapPanel.js
@@ -0,0 +1,22 @@
+import React, { PropTypes } from 'react';
+import { NamedLink } from '../../components';
+import css from './MapPanel.css';
+
+const MapPanel = props => (
+
+
Map
+
+ {props.children}
+
+
Filters
+
X
+
+);
+
+MapPanel.defaultProps = { children: null };
+
+const { any } = PropTypes;
+
+MapPanel.propTypes = { children: any };
+
+export default MapPanel;
diff --git a/src/components/MapPanel/MapPanel.test.js b/src/components/MapPanel/MapPanel.test.js
new file mode 100644
index 00000000..562860b6
--- /dev/null
+++ b/src/components/MapPanel/MapPanel.test.js
@@ -0,0 +1,22 @@
+import React from 'react';
+import renderer from 'react-test-renderer';
+import { TestProvider } from '../../util/test-helpers';
+import { RoutesProvider } from '../../components';
+import routesConfiguration from '../../routesConfiguration';
+import MapPanel from './MapPanel';
+
+describe('MapPanel', () => {
+ it('matches snapshot', () => {
+ const component = renderer.create(
+ (
+
+
+
+
+
+ ),
+ );
+ const tree = component.toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+});
diff --git a/src/components/MapPanel/__snapshots__/MapPanel.test.js.snap b/src/components/MapPanel/__snapshots__/MapPanel.test.js.snap
new file mode 100644
index 00000000..8b5b9f82
--- /dev/null
+++ b/src/components/MapPanel/__snapshots__/MapPanel.test.js.snap
@@ -0,0 +1,24 @@
+exports[`MapPanel matches snapshot 1`] = `
+
+`;
diff --git a/src/components/Menu/Menu.css b/src/components/Menu/Menu.css
new file mode 100644
index 00000000..b3e300b7
--- /dev/null
+++ b/src/components/Menu/Menu.css
@@ -0,0 +1,17 @@
+.container {
+ width: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 50px;
+ border-top: solid 1px #f7f7f7;
+}
+.container:hover {
+ background-color: #f7f7f7;
+ cursor: pointer;
+}
+
+.openIndicator {
+ margin-left: auto;
+ margin-right: 1em;
+}
diff --git a/src/components/Menu/Menu.js b/src/components/Menu/Menu.js
new file mode 100644
index 00000000..96c3a38d
--- /dev/null
+++ b/src/components/Menu/Menu.js
@@ -0,0 +1,10 @@
+import React from 'react';
+import css from './Menu.css';
+
+const Menu = () => (
+
+ New York, Jan 2nd – Jan 4th ▼
+
+);
+
+export default Menu;
diff --git a/src/components/Menu/Menu.test.js b/src/components/Menu/Menu.test.js
new file mode 100644
index 00000000..a23c1acd
--- /dev/null
+++ b/src/components/Menu/Menu.test.js
@@ -0,0 +1,14 @@
+import React from 'react';
+import renderer from 'react-test-renderer';
+import { TestProvider } from '../../util/test-helpers';
+import { RoutesProvider } from '../../components';
+import routesConfiguration from '../../routesConfiguration';
+import Menu from './Menu';
+
+describe('Menu', () => {
+ it('matches snapshot', () => {
+ const component = renderer.create();
+ const tree = component.toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+});
diff --git a/src/components/Menu/__snapshots__/Menu.test.js.snap b/src/components/Menu/__snapshots__/Menu.test.js.snap
new file mode 100644
index 00000000..3361773b
--- /dev/null
+++ b/src/components/Menu/__snapshots__/Menu.test.js.snap
@@ -0,0 +1,10 @@
+exports[`Menu matches snapshot 1`] = `
+
+ New York, Jan 2nd – Jan 4th
+
+ ▼
+
+
+`;
diff --git a/src/components/SearchResultsPanel/SearchResultsPanel.css b/src/components/SearchResultsPanel/SearchResultsPanel.css
new file mode 100644
index 00000000..d811c984
--- /dev/null
+++ b/src/components/SearchResultsPanel/SearchResultsPanel.css
@@ -0,0 +1,24 @@
+.navigation {
+ display: flex;
+ position: fixed;
+ bottom: 15px;
+ left: 50%;
+ margin-left: -100px;
+}
+
+.button {
+ display: block;
+ width: 100px;
+ text-align: center;
+ text-decoration: none;
+ font-size: 1.4rem;
+ padding: 0.5rem;
+ margin: 1rem 0.1rem;
+ color: #fff;
+ background-color: #9B9B9B;
+ cursor: pointer;
+
+ &:hover {
+ background-color: #888;
+ }
+}
diff --git a/src/components/SearchResultsPanel/SearchResultsPanel.js b/src/components/SearchResultsPanel/SearchResultsPanel.js
new file mode 100644
index 00000000..4c57f621
--- /dev/null
+++ b/src/components/SearchResultsPanel/SearchResultsPanel.js
@@ -0,0 +1,22 @@
+import React, { PropTypes } from 'react';
+import { Menu, NamedLink } from '../../components';
+import css from './SearchResultsPanel.css';
+
+const SearchResultsPanel = props => (
+
+
+ {props.children}
+
+ Filters
+ Map
+
+
+);
+
+SearchResultsPanel.defaultProps = { children: null };
+
+const { any } = PropTypes;
+
+SearchResultsPanel.propTypes = { children: any };
+
+export default SearchResultsPanel;
diff --git a/src/components/SearchResultsPanel/SearchResultsPanel.test.js b/src/components/SearchResultsPanel/SearchResultsPanel.test.js
new file mode 100644
index 00000000..0cb99f5e
--- /dev/null
+++ b/src/components/SearchResultsPanel/SearchResultsPanel.test.js
@@ -0,0 +1,22 @@
+import React from 'react';
+import renderer from 'react-test-renderer';
+import { TestProvider } from '../../util/test-helpers';
+import { RoutesProvider } from '../../components';
+import routesConfiguration from '../../routesConfiguration';
+import SearchResultsPanel from './SearchResultsPanel';
+
+describe('SearchResultsPanel', () => {
+ it('matches snapshot', () => {
+ const component = renderer.create(
+ (
+
+
+
+
+
+ ),
+ );
+ const tree = component.toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+});
diff --git a/src/components/SearchResultsPanel/__snapshots__/SearchResultsPanel.test.js.snap b/src/components/SearchResultsPanel/__snapshots__/SearchResultsPanel.test.js.snap
new file mode 100644
index 00000000..a93c2baa
--- /dev/null
+++ b/src/components/SearchResultsPanel/__snapshots__/SearchResultsPanel.test.js.snap
@@ -0,0 +1,29 @@
+exports[`SearchResultsPanel matches snapshot 1`] = `
+
+
+ New York, Jan 2nd – Jan 4th
+
+ ▼
+
+
+
+
+`;
diff --git a/src/components/index.js b/src/components/index.js
index 082c9dc5..da8b45ee 100644
--- a/src/components/index.js
+++ b/src/components/index.js
@@ -1,8 +1,27 @@
+import FilterPanel from './FilterPanel/FilterPanel';
import HeroSection from './HeroSection/HeroSection';
+import ListingCard from './ListingCard/ListingCard';
+import ListingCardSmall from './ListingCardSmall/ListingCardSmall';
+import MapPanel from './MapPanel/MapPanel';
+import Menu from './Menu/Menu';
import NamedLink from './NamedLink/NamedLink';
import NamedRedirect from './NamedRedirect/NamedRedirect';
import PageLayout from './PageLayout/PageLayout';
import RouterProvider from './RouterProvider/RouterProvider';
import RoutesProvider from './RoutesProvider/RoutesProvider';
+import SearchResultsPanel from './SearchResultsPanel/SearchResultsPanel';
-export { HeroSection, NamedLink, NamedRedirect, PageLayout, RouterProvider, RoutesProvider };
+export {
+ FilterPanel,
+ HeroSection,
+ ListingCard,
+ ListingCardSmall,
+ MapPanel,
+ Menu,
+ NamedLink,
+ NamedRedirect,
+ PageLayout,
+ RouterProvider,
+ RoutesProvider,
+ SearchResultsPanel,
+};
diff --git a/src/containers/SearchPage/SearchPage.css b/src/containers/SearchPage/SearchPage.css
new file mode 100644
index 00000000..690c08d9
--- /dev/null
+++ b/src/containers/SearchPage/SearchPage.css
@@ -0,0 +1,36 @@
+.container {
+ width: 100%;
+}
+
+.tab {
+ display: none;
+}
+
+.filters {
+ composes: tab;
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100vw;
+ height: 100vh;
+ background-color: #fff;
+}
+
+.listings{
+ composes: tab;
+}
+
+.map {
+ composes: tab;
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100vw;
+ height: 100vh;
+}
+
+
+
+.open {
+ display: block;
+}
diff --git a/src/containers/SearchPage/SearchPage.js b/src/containers/SearchPage/SearchPage.js
index 73a305d8..6a476b9f 100644
--- a/src/containers/SearchPage/SearchPage.js
+++ b/src/containers/SearchPage/SearchPage.js
@@ -1,18 +1,97 @@
-import React from 'react';
+import React, { PropTypes } from 'react';
+import classNames from 'classnames';
import { connect } from 'react-redux';
-import { NamedLink, PageLayout } from '../../components';
+import { find } from 'lodash';
import { addFlashNotification } from '../../ducks/FlashNotification.ducks';
import { addFilter } from './SearchPage.ducks';
+import css from './SearchPage.css';
+import {
+ FilterPanel,
+ ListingCard,
+ ListingCardSmall,
+ MapPanel,
+ PageLayout,
+ SearchResultsPanel,
+} from '../../components';
-export const SearchPageComponent = () => (
-
-
- Nice studio in Helsinki
-
-
- LandingPage
-
-);
+const tabClasses = [
+ { name: 'filters', css: css.filters },
+ { name: 'listings', css: css.listings },
+ { name: 'map', css: css.map },
+];
+
+const findTab = forTabType => {
+ const foundTab = find(tabClasses, c => c.name === forTabType);
+ if (!foundTab) {
+ return find(tabClasses, c => c.name === 'listings');
+ }
+ return foundTab;
+};
+
+const combinedClasses = (forTabType, currentTab) => {
+ const foundTab = findTab(forTabType);
+ const shouldOpenDefault = !currentTab && foundTab.name === 'listings';
+
+ if (foundTab.name === currentTab || shouldOpenDefault) {
+ return classNames(foundTab.css, css.open);
+ }
+
+ return foundTab.css;
+};
+
+const fakeListings = [
+ {
+ id: 123,
+ title: 'Banyan Studios',
+ price: '55\u20AC / day',
+ description: 'Organic Music Production in a Sustainable, Ethical and Professional Studio.',
+ location: 'New York, NY \u2022 40mi away',
+ review: { count: '8 reviews', rating: '4' },
+ author: {
+ name: 'The Stardust Collective',
+ avatar: 'http://placehold.it/44x44',
+ review: { rating: '4' },
+ },
+ },
+ {
+ id: 1234,
+ title: 'Pienix Studio',
+ price: '80\u20AC day',
+ description: 'Pienix Studio specializes in music mixing and mastering production.',
+ location: 'New York, NY \u2022 6mi away',
+ review: { count: '7 reviews', rating: '4' },
+ author: { name: 'Juhan', avatar: 'http://placehold.it/44x44', review: { rating: '4' } },
+ },
+];
+
+export const SearchPageComponent = props => {
+ const { tab } = props;
+ return (
+
+
+
+
+
+
+
+ {fakeListings.map(l => )}
+
+
+
+
+ {fakeListings.map(l => )}
+
+
+
+
+ );
+};
+
+SearchPageComponent.defaultProps = { tab: 'listings' };
+
+const { string } = PropTypes;
+
+SearchPageComponent.propTypes = { tab: string };
/**
* Container functions.
@@ -25,8 +104,8 @@ const mapStateToProps = function mapStateToProps(state) {
const mapDispatchToProps = function mapDispatchToProps(dispatch) {
return {
- addNotice: msg => dispatch(addFlashNotification('notice', msg)),
- addFilter: (k, v) => dispatch(addFilter(k, v)),
+ onAddNotice: msg => dispatch(addFlashNotification('notice', msg)),
+ onAddFilter: (k, v) => dispatch(addFilter(k, v)),
};
};
diff --git a/src/containers/SearchPage/__snapshots__/SearchPage.test.js.snap b/src/containers/SearchPage/__snapshots__/SearchPage.test.js.snap
index f3eb1c46..1d1ee0ee 100644
--- a/src/containers/SearchPage/__snapshots__/SearchPage.test.js.snap
+++ b/src/containers/SearchPage/__snapshots__/SearchPage.test.js.snap
@@ -119,20 +119,339 @@ exports[`SearchPageComponent matches snapshot 1`] = `
Search page
-
- Nice studio in Helsinki
-
-
-
- LandingPage
-
+
+
+
+
+
+ New York, Jan 2nd – Jan 4th
+
+ ▼
+
+
+
+
+
+

+
+
+
+
+
+ Organic Music Production in a Sustainable, Ethical and Professional Studio.
+
+
+
+ New York, NY • 40mi away
+
+
+ (
+
+ 4
+
+
+ /5
+
+ )
+
+
+ 8 reviews
+
+
+
+
+
+
+

+
+
+
+ The Stardust Collective
+
+
+ review:
+
+ 4
+
+
+ /5
+
+
+
+
+
+
+
+
+
+

+
+
+
+
+
+ Pienix Studio specializes in music mixing and mastering production.
+
+
+
+ New York, NY • 6mi away
+
+
+ (
+
+ 4
+
+
+ /5
+
+ )
+
+
+ 7 reviews
+
+
+
+
+
+
+

+
+
+
+ Juhan
+
+
+ review:
+
+ 4
+
+
+ /5
+
+
+
+
+
+
+
+
+
+
+
`;
diff --git a/src/routesConfiguration.js b/src/routesConfiguration.js
index 34ea3f9a..6f20a8f7 100644
--- a/src/routesConfiguration.js
+++ b/src/routesConfiguration.js
@@ -28,7 +28,32 @@ const RedirectLandingPage = () => ;
const routesConfiguration = [
{ pattern: '/', exactly: true, name: 'LandingPage', component: LandingPage },
- { pattern: '/s', exactly: true, name: 'SearchPage', component: SearchPage },
+ {
+ pattern: '/s',
+ exactly: true,
+ name: 'SearchPage',
+ component: SearchPage,
+ routes: [
+ {
+ pattern: '/s/filters',
+ exactly: true,
+ name: 'SearchFiltersPage',
+ component: props => ,
+ },
+ {
+ pattern: '/s/listings',
+ exactly: true,
+ name: 'SearchListingsPage',
+ component: props => ,
+ },
+ {
+ pattern: '/s/map',
+ exactly: true,
+ name: 'SearchMapPage',
+ component: props => ,
+ },
+ ],
+ },
{
pattern: '/l',
exactly: true,
diff --git a/yarn.lock b/yarn.lock
index 1ca4fa97..61929537 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1205,6 +1205,10 @@ clap@^1.0.9:
dependencies:
chalk "^1.1.3"
+classnames@^2.2.5:
+ version "2.2.5"
+ resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.5.tgz#fb3801d453467649ef3603c7d61a02bd129bde6d"
+
clean-css@3.4.x:
version "3.4.24"
resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-3.4.24.tgz#89f5a5e9da37ae02394fe049a41388abbe72c3b5"