Merge pull request #27 from sharetribe/searchpage-wires

Searchpage wires
This commit is contained in:
Vesa Luusua 2017-01-26 17:24:42 +02:00 committed by GitHub
commit dbda88f364
31 changed files with 1295 additions and 30 deletions

View file

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

View file

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

View file

@ -0,0 +1,13 @@
import React from 'react';
import { NamedLink } from '../../components';
import css from './FilterPanel.css';
const FilterPanel = () => (
<div>
<h1 className={css.filterTitle}>Filters</h1>
<NamedLink className={css.toListingsButton} name="SearchListingsPage">See studios</NamedLink>
<NamedLink className={css.close} name="SearchListingsPage">X</NamedLink>
</div>
);
export default FilterPanel;

View file

@ -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(
(
<TestProvider>
<RoutesProvider routes={routesConfiguration}>
<FilterPanel />
</RoutesProvider>
</TestProvider>
),
);
const tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
});

View file

@ -0,0 +1,22 @@
exports[`FilterPanel matches snapshot 1`] = `
<div>
<h1
className={undefined}>
Filters
</h1>
<a
className=""
href="/s/listings"
onClick={[Function]}
style={Object {}}>
See studios
</a>
<a
className=""
href="/s/listings"
onClick={[Function]}
style={Object {}}>
X
</a>
</div>
`;

View file

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

View file

@ -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 (
<div className={css.listing}>
<div className={css.squareWrapper}>
<div className={css.aspectWrapper}>
<img className={css.thumbnail} src="http://placehold.it/400x300" alt="Listing Title" />
</div>
</div>
<div className={css.info}>
<div className={css.mainInfo}>
<NamedLink className={css.title} name="ListingPage" params={{ id, slug }}>
{title}
</NamedLink>
<div className={css.price}>
{price}
</div>
</div>
<div className={css.description}>
{description}
</div>
<div className={css.details}>
<div className={css.location}>
{location}
</div>
<div className={css.reviews}>
(<span>{review.rating}</span><span>/5</span>){' '}
<span>{review.count}</span>
</div>
</div>
<hr />
<div className={css.authorInfo}>
<div className={css.avatarWrapper}>
<img className={css.avatar} src={author.avatar} alt={author.name} />
</div>
<div className={css.authorDetails}>
<span className={css.authorName}>{author.name}</span>
<div className={css.authorReview}>
review: <span>{author.review.rating}</span><span>/5</span>
</div>
</div>
</div>
</div>
</div>
);
};
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;

View file

@ -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(
(
<TestProvider>
<RoutesProvider routes={routesConfiguration}>
<ListingCard {...listing} />
</RoutesProvider>
</TestProvider>
),
);
const tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
});

View file

@ -0,0 +1,86 @@
exports[`ListingCard matches snapshot 1`] = `
<div
className={undefined}>
<div
className={undefined}>
<div
className={undefined}>
<img
alt="Listing Title"
className={undefined}
src="http://placehold.it/400x300" />
</div>
</div>
<div
className={undefined}>
<div
className={undefined}>
<a
className=""
href="/l/Banyan-Studios/123"
onClick={[Function]}
style={Object {}}>
Banyan Studios
</a>
<div
className={undefined}>
55€ / day
</div>
</div>
<div
className={undefined}>
Organic Music Production in a Sustainable, Ethical and Professional Studio.
</div>
<div
className={undefined}>
<div
className={undefined}>
New York, NY • 40mi away
</div>
<div
className={undefined}>
(
<span>
4
</span>
<span>
/5
</span>
)
<span>
8 reviews
</span>
</div>
</div>
<hr />
<div
className={undefined}>
<div
className={undefined}>
<img
alt="The Stardust Collective"
className={undefined}
src="http://placehold.it/44x44" />
</div>
<div
className={undefined}>
<span
className={undefined}>
The Stardust Collective
</span>
<div
className={undefined}>
review:
<span>
4
</span>
<span>
/5
</span>
</div>
</div>
</div>
</div>
</div>
`;

View file

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

View file

@ -0,0 +1,43 @@
import React, { PropTypes } from 'react';
import { NamedLink } from '../../components';
import css from './ListingCardSmall.css';
// <NamedLink name="SearchListingsPage">X</NamedLink>
const ListingCardSmall = props => {
const { id, title, price, review } = props;
const slug = encodeURIComponent(title.split(' ').join('-'));
return (
<div className={css.listing}>
<div className={css.squareWrapper}>
<div className={css.aspectWrapper}>
<img className={css.thumbnail} src="http://placehold.it/200x150" alt="Listing Title" />
</div>
</div>
<div className={css.info}>
<NamedLink className={css.title} name="ListingPage" params={{ id, slug }}>
{title}
</NamedLink>
<div className={css.reviews}>
(<span>{review.rating}</span><span>/5</span>){' '}
<span>{review.count}</span>
</div>
<div className={css.price}>
{price}
</div>
</div>
</div>
);
};
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;

View file

@ -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(
(
<TestProvider>
<RoutesProvider routes={routesConfiguration}>
<ListingCardSmall {...listing} />
</RoutesProvider>
</TestProvider>
),
);
const tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
});

View file

@ -0,0 +1,44 @@
exports[`ListingCardSmall matches snapshot 1`] = `
<div
className={undefined}>
<div
className={undefined}>
<div
className={undefined}>
<img
alt="Listing Title"
className={undefined}
src="http://placehold.it/200x150" />
</div>
</div>
<div
className={undefined}>
<a
className=""
href="/l/Banyan-Studios/123"
onClick={[Function]}
style={Object {}}>
Banyan Studios
</a>
<div
className={undefined}>
(
<span>
4
</span>
<span>
/5
</span>
)
<span>
8 reviews
</span>
</div>
<div
className={undefined}>
55€ / day
</div>
</div>
</div>
`;

View file

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

View file

@ -0,0 +1,22 @@
import React, { PropTypes } from 'react';
import { NamedLink } from '../../components';
import css from './MapPanel.css';
const MapPanel = props => (
<div>
<div className={css.mapContainer}>Map</div>
<div className={css.mapListingsContainer}>
{props.children}
</div>
<NamedLink className={css.toFiltersButton} name="SearchFiltersPage">Filters</NamedLink>
<NamedLink className={css.close} name="SearchListingsPage">X</NamedLink>
</div>
);
MapPanel.defaultProps = { children: null };
const { any } = PropTypes;
MapPanel.propTypes = { children: any };
export default MapPanel;

View file

@ -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(
(
<TestProvider>
<RoutesProvider routes={routesConfiguration}>
<MapPanel />
</RoutesProvider>
</TestProvider>
),
);
const tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
});

View file

@ -0,0 +1,24 @@
exports[`MapPanel matches snapshot 1`] = `
<div>
<div
className={undefined}>
Map
</div>
<div
className={undefined} />
<a
className=""
href="/s/filters"
onClick={[Function]}
style={Object {}}>
Filters
</a>
<a
className=""
href="/s/listings"
onClick={[Function]}
style={Object {}}>
X
</a>
</div>
`;

View file

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

View file

@ -0,0 +1,10 @@
import React from 'react';
import css from './Menu.css';
const Menu = () => (
<div className={css.container}>
New York, Jan 2nd Jan 4th <span className={css.openIndicator}></span>
</div>
);
export default Menu;

View file

@ -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(<Menu />);
const tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
});

View file

@ -0,0 +1,10 @@
exports[`Menu matches snapshot 1`] = `
<div
className={undefined}>
New York, Jan 2nd Jan 4th
<span
className={undefined}>
</span>
</div>
`;

View file

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

View file

@ -0,0 +1,22 @@
import React, { PropTypes } from 'react';
import { Menu, NamedLink } from '../../components';
import css from './SearchResultsPanel.css';
const SearchResultsPanel = props => (
<div>
<Menu />
{props.children}
<div className={css.navigation}>
<NamedLink className={css.button} name="SearchFiltersPage">Filters</NamedLink>
<NamedLink className={css.button} name="SearchMapPage">Map</NamedLink>
</div>
</div>
);
SearchResultsPanel.defaultProps = { children: null };
const { any } = PropTypes;
SearchResultsPanel.propTypes = { children: any };
export default SearchResultsPanel;

View file

@ -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(
(
<TestProvider>
<RoutesProvider routes={routesConfiguration}>
<SearchResultsPanel />
</RoutesProvider>
</TestProvider>
),
);
const tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
});

View file

@ -0,0 +1,29 @@
exports[`SearchResultsPanel matches snapshot 1`] = `
<div>
<div
className={undefined}>
New York, Jan 2nd Jan 4th
<span
className={undefined}>
</span>
</div>
<div
className={undefined}>
<a
className=""
href="/s/filters"
onClick={[Function]}
style={Object {}}>
Filters
</a>
<a
className=""
href="/s/map"
onClick={[Function]}
style={Object {}}>
Map
</a>
</div>
</div>
`;

View file

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

View file

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

View file

@ -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 = () => (
<PageLayout title="Search page">
<NamedLink name="ListingPage" params={{ slug: 'Nice-studio-in-Helsinki', id: 345 }}>
Nice studio in Helsinki
</NamedLink>
<br />
<NamedLink name="LandingPage">LandingPage</NamedLink>
</PageLayout>
);
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 (
<PageLayout title="Search page">
<div className={css.container}>
<div className={combinedClasses('filters', tab)}>
<FilterPanel />
</div>
<div className={combinedClasses('listings', tab)}>
<SearchResultsPanel>
{fakeListings.map(l => <ListingCard key={l.id} {...l} />)}
</SearchResultsPanel>
</div>
<div className={combinedClasses('map', tab)}>
<MapPanel>
{fakeListings.map(l => <ListingCardSmall key={l.id} {...l} />)}
</MapPanel>
</div>
</div>
</PageLayout>
);
};
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)),
};
};

View file

@ -119,20 +119,339 @@ exports[`SearchPageComponent matches snapshot 1`] = `
<h1>
Search page
</h1>
<a
className=""
href="/l/Nice-studio-in-Helsinki/345"
onClick={[Function]}
style={Object {}}>
Nice studio in Helsinki
</a>
<br />
<a
className=""
href="/"
onClick={[Function]}
style={Object {}}>
LandingPage
</a>
<div
className={undefined}>
<div
className={undefined}>
<div>
<h1
className={undefined}>
Filters
</h1>
<a
className=""
href="/s/listings"
onClick={[Function]}
style={Object {}}>
See studios
</a>
<a
className=""
href="/s/listings"
onClick={[Function]}
style={Object {}}>
X
</a>
</div>
</div>
<div
className="">
<div>
<div
className={undefined}>
New York, Jan 2nd Jan 4th
<span
className={undefined}>
</span>
</div>
<div
className={undefined}>
<div
className={undefined}>
<div
className={undefined}>
<img
alt="Listing Title"
className={undefined}
src="http://placehold.it/400x300" />
</div>
</div>
<div
className={undefined}>
<div
className={undefined}>
<a
className=""
href="/l/Banyan-Studios/123"
onClick={[Function]}
style={Object {}}>
Banyan Studios
</a>
<div
className={undefined}>
55€ / day
</div>
</div>
<div
className={undefined}>
Organic Music Production in a Sustainable, Ethical and Professional Studio.
</div>
<div
className={undefined}>
<div
className={undefined}>
New York, NY • 40mi away
</div>
<div
className={undefined}>
(
<span>
4
</span>
<span>
/5
</span>
)
<span>
8 reviews
</span>
</div>
</div>
<hr />
<div
className={undefined}>
<div
className={undefined}>
<img
alt="The Stardust Collective"
className={undefined}
src="http://placehold.it/44x44" />
</div>
<div
className={undefined}>
<span
className={undefined}>
The Stardust Collective
</span>
<div
className={undefined}>
review:
<span>
4
</span>
<span>
/5
</span>
</div>
</div>
</div>
</div>
</div>
<div
className={undefined}>
<div
className={undefined}>
<div
className={undefined}>
<img
alt="Listing Title"
className={undefined}
src="http://placehold.it/400x300" />
</div>
</div>
<div
className={undefined}>
<div
className={undefined}>
<a
className=""
href="/l/Pienix-Studio/1234"
onClick={[Function]}
style={Object {}}>
Pienix Studio
</a>
<div
className={undefined}>
80€ day
</div>
</div>
<div
className={undefined}>
Pienix Studio specializes in music mixing and mastering production.
</div>
<div
className={undefined}>
<div
className={undefined}>
New York, NY • 6mi away
</div>
<div
className={undefined}>
(
<span>
4
</span>
<span>
/5
</span>
)
<span>
7 reviews
</span>
</div>
</div>
<hr />
<div
className={undefined}>
<div
className={undefined}>
<img
alt="Juhan"
className={undefined}
src="http://placehold.it/44x44" />
</div>
<div
className={undefined}>
<span
className={undefined}>
Juhan
</span>
<div
className={undefined}>
review:
<span>
4
</span>
<span>
/5
</span>
</div>
</div>
</div>
</div>
</div>
<div
className={undefined}>
<a
className=""
href="/s/filters"
onClick={[Function]}
style={Object {}}>
Filters
</a>
<a
className=""
href="/s/map"
onClick={[Function]}
style={Object {}}>
Map
</a>
</div>
</div>
</div>
<div
className={undefined}>
<div>
<div
className={undefined}>
Map
</div>
<div
className={undefined}>
<div
className={undefined}>
<div
className={undefined}>
<div
className={undefined}>
<img
alt="Listing Title"
className={undefined}
src="http://placehold.it/200x150" />
</div>
</div>
<div
className={undefined}>
<a
className=""
href="/l/Banyan-Studios/123"
onClick={[Function]}
style={Object {}}>
Banyan Studios
</a>
<div
className={undefined}>
(
<span>
4
</span>
<span>
/5
</span>
)
<span>
8 reviews
</span>
</div>
<div
className={undefined}>
55€ / day
</div>
</div>
</div>
<div
className={undefined}>
<div
className={undefined}>
<div
className={undefined}>
<img
alt="Listing Title"
className={undefined}
src="http://placehold.it/200x150" />
</div>
</div>
<div
className={undefined}>
<a
className=""
href="/l/Pienix-Studio/1234"
onClick={[Function]}
style={Object {}}>
Pienix Studio
</a>
<div
className={undefined}>
(
<span>
4
</span>
<span>
/5
</span>
)
<span>
7 reviews
</span>
</div>
<div
className={undefined}>
80€ day
</div>
</div>
</div>
</div>
<a
className=""
href="/s/filters"
onClick={[Function]}
style={Object {}}>
Filters
</a>
<a
className=""
href="/s/listings"
onClick={[Function]}
style={Object {}}>
X
</a>
</div>
</div>
</div>
</div>
`;

View file

@ -28,7 +28,32 @@ const RedirectLandingPage = () => <Redirect to="/" />;
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 => <SearchPage {...props} tab="filters" />,
},
{
pattern: '/s/listings',
exactly: true,
name: 'SearchListingsPage',
component: props => <SearchPage {...props} tab="listings" />,
},
{
pattern: '/s/map',
exactly: true,
name: 'SearchMapPage',
component: props => <SearchPage {...props} tab="map" />,
},
],
},
{
pattern: '/l',
exactly: true,

View file

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