diff --git a/src/app.test.js b/src/app.test.js
index 4e67a4ac..95cc1e08 100644
--- a/src/app.test.js
+++ b/src/app.test.js
@@ -25,7 +25,7 @@ describe('Application', () => {
const urlTitles = {
'/': 'Landing page',
'/s': 'Search page',
- '/l/listing-title-slug/1234': 'Banyan Studios 55€ / day',
+ '/l/listing-title-slug/1234': 'Loading listing data',
'/u/1234': 'Profile page with display name: 1234',
'/checkout/1234': 'Book Banyan Studios (1234)',
'/login': 'Authentication page: login tab',
diff --git a/src/containers/ListingPage/ListingPage.js b/src/containers/ListingPage/ListingPage.js
index 4fdfcb5b..32955485 100644
--- a/src/containers/ListingPage/ListingPage.js
+++ b/src/containers/ListingPage/ListingPage.js
@@ -1,9 +1,14 @@
-import React from 'react';
+import React, { Component, PropTypes } from 'react';
+import { intlShape, injectIntl } from 'react-intl';
+import { connect } from 'react-redux';
+import { types } from 'sharetribe-sdk';
import { NamedLink, PageLayout } from '../../components';
+import { showListings, getListingsById } from '../../ducks/sdk.duck';
import css from './ListingPage.css';
+// TODO this hard coded info needs to be removed when API supports these features
const info = {
- title: 'Banyan Studios',
+ //title: 'Banyan Studios',
price: '55\u20AC / day',
images: [
{ id: 1, title: 'img1', imageUrl: 'http://placehold.it/750x470' },
@@ -12,18 +17,18 @@ const info = {
{ id: 4, title: 'img4', imageUrl: 'http://placehold.it/750x470' },
{ id: 5, title: 'img5', imageUrl: 'http://placehold.it/750x470' },
],
- description: `
-
- Organic Music Production in a Sustainable, Ethical and Professional Studio.
-
-
- Social Permaculture focuses on nourishing our environment with abundance instead of depleting it.
-
-
- Banyan Studios is a comfortable, conscious, inspiring and creative retreat for musicians trying to convey their message through state of the art Audio & Video.
-
- https://vimeo.com/168106603
- `,
+ // description: `
+ //
+ // Organic Music Production in a Sustainable, Ethical and Professional Studio.
+ //
+ //
+ // Social Permaculture focuses on nourishing our environment with abundance instead of depleting it.
+ //
+ //
+ // Banyan Studios is a comfortable, conscious, inspiring and creative retreat for musicians trying to convey their message through state of the art Audio & Video.
+ //
+ // https://vimeo.com/168106603
+ // `,
reviews: [
{
id: 1,
@@ -39,67 +44,122 @@ const info = {
};
// N.B. All the presentational content needs to be extracted to their own components
-const ListingPage = () => (
-
-
-
![{info.images[0].title}]({info.images[0].imageUrl})
-
- {info.images.slice(1).map(image => (
-
-
-

-
-
- ))}
-
-
- {/* eslint-disable react/no-danger */}
-
- {/* eslint-enable react/no-danger */}
-
-
Here will be filters (or dragons)
-
Studio type
-
Amenities
-
Additional Services Available
-
Studio hours: 10am - 6pm
-
-
- Contact studio
-
-
-
Studio reviews (1)
-
- {info.reviews.map(review => (
-
-
{review.review}
-
-
-

-
-
- {review.reviewer.name}
- {review.reviewer.date}
-
-
-
- review: {review.rating}/5
-
-
-
- ))}
-
-
- Map
-
- {`Book ${info.title}`}
-
-
-);
+export class ListingPageComponent extends Component {
+ componentDidMount() {
+ ListingPageComponent.loadData(this.props.params.id, this.props.onLoadListing);
+ }
-export default ListingPage;
+ render() {
+ const { entitiesData, intl, params } = this.props;
+ const id = new types.UUID(params.id);
+ const listingsById = getListingsById(entitiesData, [id]);
+ const currentListing = listingsById.length > 0 ? listingsById[0] : null;
+
+ const title = currentListing ? currentListing.attributes.title : '';
+ const description = currentListing ? currentListing.attributes.description : '';
+
+ const pageContent = (
+
+
+
![{info.images[0].title}]({info.images[0].imageUrl})
+
+ {info.images.slice(1).map(image => (
+
+
+

+
+
+ ))}
+
+
+ {/* eslint-disable react/no-danger */}
+
+ {/* eslint-enable react/no-danger */}
+
+
Here will be filters (or dragons)
+
Studio type
+
Amenities
+
Additional Services Available
+
Studio hours: 10am - 6pm
+
+
+ Contact studio
+
+
+
Studio reviews (1)
+
+ {info.reviews.map(review => (
+
+
{review.review}
+
+
+

+
+
+ {review.reviewer.name}
+ {review.reviewer.date}
+
+
+
+ review: {review.rating}/5
+
+
+
+ ))}
+
+
+ Map
+
+ {`Book ${title}`}
+
+
+ );
+
+ const loadingPageMsg = {
+ id: 'ListingPage.loadingListingData',
+ defaultMessage: 'Loading listing data',
+ };
+
+ const loadingContent = ;
+
+ return currentListing ? pageContent : loadingContent;
+ }
+}
+
+ListingPageComponent.loadData = (id, onLoadListing) => {
+ onLoadListing(id);
+};
+
+ListingPageComponent.defaultProps = { listing: null };
+
+const { func, object, shape, string } = PropTypes;
+
+ListingPageComponent.propTypes = {
+ entitiesData: object.isRequired,
+ intl: intlShape.isRequired,
+ onLoadListing: func.isRequired,
+ params: shape({
+ id: string.isRequired,
+ slug: string.isRequired,
+ }).isRequired,
+};
+
+const mapStateToProps = state => {
+ const { data, ListingPage } = state;
+ const entitiesData = data || {};
+ return { ListingPage, entitiesData };
+};
+
+const mapDispatchToProps = dispatch => {
+ return {
+ onLoadListing: id => dispatch(showListings({ id, include: ['author'] })),
+ };
+};
+
+export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(ListingPageComponent));
diff --git a/src/containers/ListingPage/ListingPage.test.js b/src/containers/ListingPage/ListingPage.test.js
index 9b7c1e52..e9b334b3 100644
--- a/src/containers/ListingPage/ListingPage.test.js
+++ b/src/containers/ListingPage/ListingPage.test.js
@@ -1,10 +1,19 @@
import React from 'react';
-import { renderShallow } from '../../util/test-helpers';
-import ListingPage from './ListingPage';
+import { createListing } from '../../util/test-data';
+import { fakeIntl, renderShallow } from '../../util/test-helpers';
+import { ListingPageComponent } from './ListingPage';
describe('ListingPage', () => {
it('matches snapshot', () => {
- const tree = renderShallow();
+ const entitiesData = { entities: { listing: { listing1: createListing('listing1') } } };
+ const tree = renderShallow(
+ l}
+ />,
+ );
expect(tree).toMatchSnapshot();
});
});
diff --git a/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap b/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap
index f70e5731..8601c5e5 100644
--- a/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap
+++ b/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap
@@ -1,6 +1,6 @@
exports[`ListingPage matches snapshot 1`] = `
+ title="listing1 title 55€ / day">
![img1]()
- Organic Music Production in a Sustainable, Ethical and Professional Studio.
-
-
- Social Permaculture focuses on nourishing our environment with abundance instead of depleting it.
-
-
- Banyan Studios is a comfortable, conscious, inspiring and creative retreat for musicians trying to convey their message through state of the art Audio & Video.
-
-
https://vimeo.com/168106603
- ",
+ "__html": "listing1 description",
}
} />
@@ -129,7 +118,7 @@ exports[`ListingPage matches snapshot 1`] = `
"id": 12345,
}
}>
- Book Banyan Studios
+ Book listing1 title
`;
diff --git a/src/ducks/sdk.duck.js b/src/ducks/sdk.duck.js
index 89adb04c..491fa213 100644
--- a/src/ducks/sdk.duck.js
+++ b/src/ducks/sdk.duck.js
@@ -107,8 +107,14 @@ export default function sdkReducer(state = initialState, action = {}) {
* @param {Object} data the state part of the Redux store for this SDK reducer
* @param {Array} listingIds listing IDs to select from the store
*/
-export const getListingsById = (data, listingIds) =>
- denormalisedEntities(data.entities, 'listing', listingIds);
+export const getListingsById = (data, listingIds) => {
+ try {
+ return denormalisedEntities(data.entities, 'listing', listingIds);
+ } catch (e) {
+ console.error(`Could not denormalise entities with given ids (${listingIds.map(id => id.uuid)}). Error: ${e}`);
+ return [];
+ }
+};
// ================ Action creators ================ //
diff --git a/src/translations/en.json b/src/translations/en.json
index 5ce5a195..858a8517 100644
--- a/src/translations/en.json
+++ b/src/translations/en.json
@@ -3,5 +3,6 @@
"HeroSearchForm.placeholder": "Location search (soon)",
"HeroSearchForm.search": "Search",
"HeroSection.title": "Book Studiotime anywhere",
- "HeroSection.subTitle": "The largest online community to rent music studios"
+ "HeroSection.subTitle": "The largest online community to rent music studios",
+ "ListingPage.loadingListingData": "Loading listing data"
}
diff --git a/src/util/test-helpers.js b/src/util/test-helpers.js
index e2021dac..ef40478e 100644
--- a/src/util/test-helpers.js
+++ b/src/util/test-helpers.js
@@ -46,3 +46,15 @@ export const renderDeep = component => {
);
return comp.toJSON();
};
+
+// Create fake Internalization object to help with shallow rendering.
+export const fakeIntl = {
+ formatDate: d => d,
+ formatHTMLMessage: d => d,
+ formatMessage: msg => msg.defaultMessage,
+ formatNumber: d => d,
+ formatPlural: d => d,
+ formatRelative: d => d,
+ formatTime: d => d,
+ now: d => d,
+};