From f6507657d4fbc0b218b8396fe7a99afa1c543d1b Mon Sep 17 00:00:00 2001 From: Kimmo Puputti Date: Tue, 10 Apr 2018 13:54:56 +0300 Subject: [PATCH 01/11] Extract images section into a separate file --- src/containers/ListingPage/ActionBarMaybe.js | 61 ++++++++ src/containers/ListingPage/ListingPage.css | 3 + src/containers/ListingPage/ListingPage.js | 119 ++------------ .../ListingPage/ListingPage.test.js | 3 +- src/containers/ListingPage/SectionImages.js | 75 +++++++++ .../__snapshots__/ListingPage.test.js.snap | 146 ++++++------------ 6 files changed, 203 insertions(+), 204 deletions(-) create mode 100644 src/containers/ListingPage/ActionBarMaybe.js create mode 100644 src/containers/ListingPage/SectionImages.js diff --git a/src/containers/ListingPage/ActionBarMaybe.js b/src/containers/ListingPage/ActionBarMaybe.js new file mode 100644 index 00000000..823bd665 --- /dev/null +++ b/src/containers/ListingPage/ActionBarMaybe.js @@ -0,0 +1,61 @@ +import React from 'react'; +import { bool, oneOfType, object } from 'prop-types'; +import { FormattedMessage } from 'react-intl'; +import classNames from 'classnames'; +import { LISTING_STATE_PENDING_APPROVAL, LISTING_STATE_CLOSED, propTypes } from '../../util/types'; +import { NamedLink } from '../../components'; +import EditIcon from './EditIcon'; + +import css from './ListingPage.css'; + +export const ActionBarMaybe = props => { + const { isOwnListing, listing, editParams } = props; + const state = listing.attributes.state; + const isPendingApproval = state === LISTING_STATE_PENDING_APPROVAL; + const isClosed = state === LISTING_STATE_CLOSED; + + if (isOwnListing) { + let ownListingTextTranslationId = 'ListingPage.ownListing'; + + if (isPendingApproval) { + ownListingTextTranslationId = 'ListingPage.ownListingPendingApproval'; + } else if (isClosed) { + ownListingTextTranslationId = 'ListingPage.ownClosedListing'; + } + + const ownListingTextClasses = classNames(css.ownListingText, { + [css.ownListingTextPendingApproval]: isPendingApproval, + }); + + return ( +
+

+ +

+ + + + +
+ ); + } else if (isClosed) { + return ( +
+

+ +

+
+ ); + } + return null; +}; + +ActionBarMaybe.propTypes = { + isOwnListing: bool.isRequired, + listing: oneOfType([propTypes.listing, propTypes.ownListing]).isRequired, + editParams: object.isRequired, +}; + +ActionBarMaybe.displayName = 'ActionBarMaybe'; + +export default ActionBarMaybe; diff --git a/src/containers/ListingPage/ListingPage.css b/src/containers/ListingPage/ListingPage.css index 9e043d21..c54fd964 100644 --- a/src/containers/ListingPage/ListingPage.css +++ b/src/containers/ListingPage/ListingPage.css @@ -17,6 +17,9 @@ margin: 24px; } +.sectionImages { +} + .threeToTwoWrapper { /* Layout */ display: block; diff --git a/src/containers/ListingPage/ListingPage.js b/src/containers/ListingPage/ListingPage.js index 6caff5e2..f084ba99 100644 --- a/src/containers/ListingPage/ListingPage.js +++ b/src/containers/ListingPage/ListingPage.js @@ -1,11 +1,10 @@ /* eslint-disable jsx-a11y/no-static-element-interactions */ import React, { Component } from 'react'; -import { arrayOf, bool, func, object, shape, string, oneOf, oneOfType } from 'prop-types'; +import { arrayOf, bool, func, shape, string, oneOf } from 'prop-types'; import { FormattedMessage, intlShape, injectIntl } from 'react-intl'; import { compose } from 'redux'; import { connect } from 'react-redux'; import { withRouter } from 'react-router-dom'; -import classNames from 'classnames'; import config from '../../config'; import routeConfiguration from '../../routeConfiguration'; import { LISTING_STATE_PENDING_APPROVAL, LISTING_STATE_CLOSED, propTypes } from '../../util/types'; @@ -23,11 +22,9 @@ import { Button, ModalInMobile, Page, - ResponsiveImage, NamedLink, NamedRedirect, Modal, - ImageCarousel, InlineTextButton, LayoutSingleColumn, LayoutWrapperTopbar, @@ -41,7 +38,7 @@ import { import { BookingDatesForm, TopbarContainer, EnquiryForm, NotFoundPage } from '../../containers'; import { sendEnquiry, loadData, setInitialValues } from './ListingPage.duck'; -import EditIcon from './EditIcon'; +import SectionImages from './SectionImages'; import SectionRulesMaybe from './SectionRulesMaybe'; import SectionMapMaybe from './SectionMapMaybe'; import css from './ListingPage.css'; @@ -66,56 +63,6 @@ const priceData = (price, intl) => { return {}; }; -export const ActionBarMaybe = props => { - const { isOwnListing, listing, editParams } = props; - const state = listing.attributes.state; - const isPendingApproval = state === LISTING_STATE_PENDING_APPROVAL; - const isClosed = state === LISTING_STATE_CLOSED; - - if (isOwnListing) { - let ownListingTextTranslationId = 'ListingPage.ownListing'; - - if (isPendingApproval) { - ownListingTextTranslationId = 'ListingPage.ownListingPendingApproval'; - } else if (isClosed) { - ownListingTextTranslationId = 'ListingPage.ownClosedListing'; - } - - const ownListingTextClasses = classNames(css.ownListingText, { - [css.ownListingTextPendingApproval]: isPendingApproval, - }); - - return ( -
-

- -

- - - - -
- ); - } else if (isClosed) { - return ( -
-

- -

-
- ); - } - return null; -}; - -ActionBarMaybe.propTypes = { - isOwnListing: bool.isRequired, - listing: oneOfType([propTypes.listing, propTypes.ownListing]).isRequired, - editParams: object.isRequired, -}; - -ActionBarMaybe.displayName = 'ActionBarMaybe'; - const openBookModal = (history, listing) => { if (!listing.id) { // Listing not fully loaded yet @@ -363,9 +310,6 @@ export class ListingPageComponent extends Component { ); } - const hasImages = currentListing.images && currentListing.images.length > 0; - const firstImage = hasImages ? currentListing.images[0] : null; - const handleViewPhotosClick = e => { // Stop event from bubbling up to prevent image click handler // trying to open the carousel as well. @@ -374,15 +318,6 @@ export class ListingPageComponent extends Component { imageCarouselOpen: true, }); }; - const viewPhotosButton = hasImages ? ( - - ) : null; - const authorAvailable = currentListing && currentListing.author; const userAndListingAuthorAvailable = !!(currentUser && authorAvailable); const isOwnListing = @@ -443,18 +378,6 @@ export class ListingPageComponent extends Component { } }; - // Action bar is wrapped with a div that prevents the click events - // to the parent that would otherwise open the image carousel - const actionBar = currentListing.id ? ( -
e.stopPropagation()}> - -
- ) : null; - const listingImages = (listing, variantName) => (listing.images || []) .map(image => { @@ -517,36 +440,16 @@ export class ListingPageComponent extends Component { {topbar}
-
-
- {actionBar} - - {viewPhotosButton} -
-
- this.setState({ imageCarouselOpen: false })} + this.setState({ imageCarouselOpen: false })} + handleViewPhotosClick={handleViewPhotosClick} onManageDisableScrolling={onManageDisableScrolling} - > - - - + />
diff --git a/src/containers/ListingPage/ListingPage.test.js b/src/containers/ListingPage/ListingPage.test.js index 2820c972..5fa87e54 100644 --- a/src/containers/ListingPage/ListingPage.test.js +++ b/src/containers/ListingPage/ListingPage.test.js @@ -24,7 +24,8 @@ import { showListingRequest, showListingError, showListing } from './ListingPage // Otherwise, ListingPage itself is not initialized correctly when routeConfiguration is imported // (loadData call fails). import routeConfiguration from '../../routeConfiguration'; -import { ListingPageComponent, ActionBarMaybe } from './ListingPage'; +import { ListingPageComponent } from './ListingPage'; +import ActionBarMaybe from './ActionBarMaybe'; const { UUID } = sdkTypes; const noop = () => null; diff --git a/src/containers/ListingPage/SectionImages.js b/src/containers/ListingPage/SectionImages.js new file mode 100644 index 00000000..2bfcc2f9 --- /dev/null +++ b/src/containers/ListingPage/SectionImages.js @@ -0,0 +1,75 @@ +import React from 'react'; +import { FormattedMessage } from 'react-intl'; +import { ResponsiveImage, Modal, ImageCarousel } from '../../components'; +import ActionBarMaybe from './ActionBarMaybe'; + +import css from './ListingPage.css'; + +const SectionImages = props => { + const { + title, + listing, + isOwnListing, + editParams, + handleViewPhotosClick, + imageCarouselOpen, + onImageCarouselClose, + onManageDisableScrolling, + } = props; + + const hasImages = listing.images && listing.images.length > 0; + const firstImage = hasImages ? listing.images[0] : null; + + // Action bar is wrapped with a div that prevents the click events + // to the parent that would otherwise open the image carousel + const actionBar = listing.id ? ( +
e.stopPropagation()}> + +
+ ) : null; + + const viewPhotosButton = hasImages ? ( + + ) : null; + + return ( +
+
+
+ {actionBar} + + {viewPhotosButton} +
+
+ + + +
+ ); +}; + +export default SectionImages; diff --git a/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap b/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap index 64fac301..9b170ea1 100644 --- a/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap +++ b/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap @@ -34,103 +34,59 @@ exports[`ListingPage matches snapshot 1`] = ` rootClassName={null} >
-
-
-
- -
- -
-
- - - + title="listing1 title" + />
Date: Tue, 10 Apr 2018 14:32:43 +0300 Subject: [PATCH 02/11] Extract heading section into a separate file --- src/containers/ListingPage/ListingPage.css | 2 +- src/containers/ListingPage/ListingPage.js | 40 ++------ src/containers/ListingPage/SectionHeading.js | 46 +++++++++ .../__snapshots__/ListingPage.test.js.snap | 96 +++++++------------ 4 files changed, 91 insertions(+), 93 deletions(-) create mode 100644 src/containers/ListingPage/SectionHeading.js diff --git a/src/containers/ListingPage/ListingPage.css b/src/containers/ListingPage/ListingPage.css index c54fd964..301e0ac7 100644 --- a/src/containers/ListingPage/ListingPage.css +++ b/src/containers/ListingPage/ListingPage.css @@ -255,7 +255,7 @@ } } -.headingContainer { +.sectionHeading { margin-top: 22px; margin-bottom: 34px; diff --git a/src/containers/ListingPage/ListingPage.js b/src/containers/ListingPage/ListingPage.js index f084ba99..49d133d2 100644 --- a/src/containers/ListingPage/ListingPage.js +++ b/src/containers/ListingPage/ListingPage.js @@ -25,7 +25,6 @@ import { NamedLink, NamedRedirect, Modal, - InlineTextButton, LayoutSingleColumn, LayoutWrapperTopbar, LayoutWrapperMain, @@ -39,6 +38,7 @@ import { BookingDatesForm, TopbarContainer, EnquiryForm, NotFoundPage } from '.. import { sendEnquiry, loadData, setInitialValues } from './ListingPage.duck'; import SectionImages from './SectionImages'; +import SectionHeading from './SectionHeading'; import SectionRulesMaybe from './SectionRulesMaybe'; import SectionMapMaybe from './SectionMapMaybe'; import css from './ListingPage.css'; @@ -469,35 +469,15 @@ export class ListingPageComponent extends Component {
-
-
-
- {formattedPrice} -
-
- -
-
-
-

{richTitle}

-
- {category} - - {showContactUser ? ( - - - - - - - ) : null} -
-
-
- +

diff --git a/src/containers/ListingPage/SectionHeading.js b/src/containers/ListingPage/SectionHeading.js new file mode 100644 index 00000000..71761fc0 --- /dev/null +++ b/src/containers/ListingPage/SectionHeading.js @@ -0,0 +1,46 @@ +import React from 'react'; +import { FormattedMessage } from 'react-intl'; +import { InlineTextButton } from '../../components'; + +import css from './ListingPage.css'; + +const SectionHeading = props => { + const { + priceTitle, + formattedPrice, + richTitle, + category, + hostLink, + showContactUser, + onContactUser, + } = props; + return ( +
+
+
+ {formattedPrice} +
+
+ +
+
+
+

{richTitle}

+
+ {category} + + {showContactUser ? ( + + + + + + + ) : null} +
+
+
+ ); +}; + +export default SectionHeading; diff --git a/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap b/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap index 9b170ea1..959a7c27 100644 --- a/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap +++ b/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap @@ -157,69 +157,41 @@ exports[`ListingPage matches snapshot 1`] = `

-
-
-
- 55 -
-
- -
-
-
-

- - listing1 - - title - -

-
- - user-1 display name - , - } + - - - • - - - - - -
-
-
+ } + to={ + Object { + "hash": "#host", + } + } + > + user-1 display name + + } + onContactUser={[Function]} + priceTitle={55} + richTitle={ + + + listing1 + + title + + + } + showContactUser={true} + />

Date: Tue, 10 Apr 2018 14:39:21 +0300 Subject: [PATCH 03/11] Extract description section into a separate file --- src/containers/ListingPage/ListingPage.css | 2 +- src/containers/ListingPage/ListingPage.js | 14 ++-------- .../ListingPage/SectionDescription.js | 26 +++++++++++++++++++ .../__snapshots__/ListingPage.test.js.snap | 16 +++--------- 4 files changed, 32 insertions(+), 26 deletions(-) create mode 100644 src/containers/ListingPage/SectionDescription.js diff --git a/src/containers/ListingPage/ListingPage.css b/src/containers/ListingPage/ListingPage.css index 301e0ac7..6a322850 100644 --- a/src/containers/ListingPage/ListingPage.css +++ b/src/containers/ListingPage/ListingPage.css @@ -359,7 +359,7 @@ margin: 0; } -.descriptionContainer { +.sectionDescription { padding: 0 24px; margin-bottom: 35px; diff --git a/src/containers/ListingPage/ListingPage.js b/src/containers/ListingPage/ListingPage.js index 49d133d2..336a5c6e 100644 --- a/src/containers/ListingPage/ListingPage.js +++ b/src/containers/ListingPage/ListingPage.js @@ -39,6 +39,7 @@ import { BookingDatesForm, TopbarContainer, EnquiryForm, NotFoundPage } from '.. import { sendEnquiry, loadData, setInitialValues } from './ListingPage.duck'; import SectionImages from './SectionImages'; import SectionHeading from './SectionHeading'; +import SectionDescription from './SectionDescription'; import SectionRulesMaybe from './SectionRulesMaybe'; import SectionMapMaybe from './SectionMapMaybe'; import css from './ListingPage.css'; @@ -46,7 +47,6 @@ import css from './ListingPage.css'; // This defines when ModalInMobile shows content as Modal const MODAL_BREAKPOINT = 1023; const MIN_LENGTH_FOR_LONG_WORDS_IN_TITLE = 16; -const MIN_LENGTH_FOR_LONG_WORDS_IN_DESCRIPTION = 20; const { UUID } = sdkTypes; @@ -478,17 +478,7 @@ export class ListingPageComponent extends Component { showContactUser={showContactUser} onContactUser={this.onContactUser} /> -
-

- -

-

- {richText(description, { - longWordMinLength: MIN_LENGTH_FOR_LONG_WORDS_IN_DESCRIPTION, - longWordClass: css.longWord, - })} -

-
+

diff --git a/src/containers/ListingPage/SectionDescription.js b/src/containers/ListingPage/SectionDescription.js new file mode 100644 index 00000000..5e6213b6 --- /dev/null +++ b/src/containers/ListingPage/SectionDescription.js @@ -0,0 +1,26 @@ +import React from 'react'; +import { FormattedMessage } from 'react-intl'; +import { richText } from '../../util/richText'; + +import css from './ListingPage.css'; + +const MIN_LENGTH_FOR_LONG_WORDS_IN_DESCRIPTION = 20; + +const SectionDescription = props => { + const { description } = props; + return ( +
+

+ +

+

+ {richText(description, { + longWordMinLength: MIN_LENGTH_FOR_LONG_WORDS_IN_DESCRIPTION, + longWordClass: css.longWord, + })} +

+
+ ); +}; + +export default SectionDescription; diff --git a/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap b/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap index 959a7c27..2e3fe888 100644 --- a/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap +++ b/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap @@ -192,19 +192,9 @@ exports[`ListingPage matches snapshot 1`] = ` } showContactUser={true} /> -
-

- -

-

- listing1 - - description -

-
+

Date: Tue, 10 Apr 2018 14:48:59 +0300 Subject: [PATCH 04/11] Extract features section into separate file --- src/containers/ListingPage/ListingPage.css | 2 +- src/containers/ListingPage/ListingPage.js | 19 ++-- src/containers/ListingPage/SectionFeatures.js | 24 +++++ .../__snapshots__/ListingPage.test.js.snap | 89 ++++++++----------- 4 files changed, 68 insertions(+), 66 deletions(-) create mode 100644 src/containers/ListingPage/SectionFeatures.js diff --git a/src/containers/ListingPage/ListingPage.css b/src/containers/ListingPage/ListingPage.css index 6a322850..8f723cdd 100644 --- a/src/containers/ListingPage/ListingPage.css +++ b/src/containers/ListingPage/ListingPage.css @@ -382,7 +382,7 @@ } } -.featuresContainer { +.sectionFeatures { padding: 0 24px; margin-bottom: 32px; diff --git a/src/containers/ListingPage/ListingPage.js b/src/containers/ListingPage/ListingPage.js index 336a5c6e..c4b7595a 100644 --- a/src/containers/ListingPage/ListingPage.js +++ b/src/containers/ListingPage/ListingPage.js @@ -32,7 +32,6 @@ import { Footer, UserCard, Reviews, - PropertyGroup, } from '../../components'; import { BookingDatesForm, TopbarContainer, EnquiryForm, NotFoundPage } from '../../containers'; @@ -40,6 +39,7 @@ import { sendEnquiry, loadData, setInitialValues } from './ListingPage.duck'; import SectionImages from './SectionImages'; import SectionHeading from './SectionHeading'; import SectionDescription from './SectionDescription'; +import SectionFeatures from './SectionFeatures'; import SectionRulesMaybe from './SectionRulesMaybe'; import SectionMapMaybe from './SectionMapMaybe'; import css from './ListingPage.css'; @@ -479,19 +479,10 @@ export class ListingPageComponent extends Component { onContactUser={this.onContactUser} /> - -
-

- -

- -
- + { + const { options, selectedOptions } = props; + return ( +
+

+ +

+ +
+ ); +}; + +export default SectionFeatures; diff --git a/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap b/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap index 2e3fe888..58316698 100644 --- a/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap +++ b/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap @@ -195,57 +195,44 @@ exports[`ListingPage matches snapshot 1`] = ` -
-

- -

- -
+ Date: Tue, 10 Apr 2018 14:56:41 +0300 Subject: [PATCH 05/11] Extract reviews section into separate file --- src/containers/ListingPage/ListingPage.css | 2 +- src/containers/ListingPage/ListingPage.js | 20 ++------------ src/containers/ListingPage/SectionReviews.js | 27 +++++++++++++++++++ .../__snapshots__/ListingPage.test.js.snap | 19 +++---------- 4 files changed, 34 insertions(+), 34 deletions(-) create mode 100644 src/containers/ListingPage/SectionReviews.js diff --git a/src/containers/ListingPage/ListingPage.css b/src/containers/ListingPage/ListingPage.css index 8f723cdd..a71d8e13 100644 --- a/src/containers/ListingPage/ListingPage.css +++ b/src/containers/ListingPage/ListingPage.css @@ -441,7 +441,7 @@ } } -.reviewsContainer { +.sectionReviews { padding: 0 24px; margin-bottom: 5px; diff --git a/src/containers/ListingPage/ListingPage.js b/src/containers/ListingPage/ListingPage.js index c4b7595a..430d7b76 100644 --- a/src/containers/ListingPage/ListingPage.js +++ b/src/containers/ListingPage/ListingPage.js @@ -31,7 +31,6 @@ import { LayoutWrapperFooter, Footer, UserCard, - Reviews, } from '../../components'; import { BookingDatesForm, TopbarContainer, EnquiryForm, NotFoundPage } from '../../containers'; @@ -40,6 +39,7 @@ import SectionImages from './SectionImages'; import SectionHeading from './SectionHeading'; import SectionDescription from './SectionDescription'; import SectionFeatures from './SectionFeatures'; +import SectionReviews from './SectionReviews'; import SectionRulesMaybe from './SectionRulesMaybe'; import SectionMapMaybe from './SectionMapMaybe'; import css from './ListingPage.css'; @@ -413,12 +413,6 @@ export class ListingPageComponent extends Component { ); - const reviewsError = ( -

- -

- ); - return ( - -
-

- -

- {fetchReviewsError ? reviewsError : null} - -
+

diff --git a/src/containers/ListingPage/SectionReviews.js b/src/containers/ListingPage/SectionReviews.js new file mode 100644 index 00000000..29d6fc42 --- /dev/null +++ b/src/containers/ListingPage/SectionReviews.js @@ -0,0 +1,27 @@ +import React from 'react'; +import { FormattedMessage } from 'react-intl'; +import { Reviews } from '../../components'; + +import css from './ListingPage.css'; + +const SectionReviews = props => { + const { reviews, fetchReviewsError } = props; + + const reviewsError = ( +

+ +

+ ); + + return ( +
+

+ +

+ {fetchReviewsError ? reviewsError : null} + +
+ ); +}; + +export default SectionReviews; diff --git a/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap b/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap index 58316698..561732ba 100644 --- a/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap +++ b/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap @@ -254,21 +254,10 @@ exports[`ListingPage matches snapshot 1`] = ` publicData={Object {}} rootClassName={null} /> -
-

- -

- -
+
From ee8f25c1626afa24dc335c2eb7be406250a6ab94 Mon Sep 17 00:00:00 2001 From: Kimmo Puputti Date: Tue, 10 Apr 2018 15:19:11 +0300 Subject: [PATCH 06/11] Extract host section into separate file --- src/containers/ListingPage/ListingPage.css | 2 +- src/containers/ListingPage/ListingPage.js | 51 +++------ src/containers/ListingPage/SectionHost.js | 55 +++++++++ .../__snapshots__/ListingPage.test.js.snap | 105 +++++++++--------- 4 files changed, 127 insertions(+), 86 deletions(-) create mode 100644 src/containers/ListingPage/SectionHost.js diff --git a/src/containers/ListingPage/ListingPage.css b/src/containers/ListingPage/ListingPage.css index a71d8e13..8219bf00 100644 --- a/src/containers/ListingPage/ListingPage.css +++ b/src/containers/ListingPage/ListingPage.css @@ -467,7 +467,7 @@ } } -.yourHostContainer { +.sectionHost { position: relative; padding: 0 24px; margin-bottom: 5px; diff --git a/src/containers/ListingPage/ListingPage.js b/src/containers/ListingPage/ListingPage.js index 430d7b76..79713184 100644 --- a/src/containers/ListingPage/ListingPage.js +++ b/src/containers/ListingPage/ListingPage.js @@ -24,15 +24,13 @@ import { Page, NamedLink, NamedRedirect, - Modal, LayoutSingleColumn, LayoutWrapperTopbar, LayoutWrapperMain, LayoutWrapperFooter, Footer, - UserCard, } from '../../components'; -import { BookingDatesForm, TopbarContainer, EnquiryForm, NotFoundPage } from '../../containers'; +import { BookingDatesForm, TopbarContainer, NotFoundPage } from '../../containers'; import { sendEnquiry, loadData, setInitialValues } from './ListingPage.duck'; import SectionImages from './SectionImages'; @@ -40,6 +38,7 @@ import SectionHeading from './SectionHeading'; import SectionDescription from './SectionDescription'; import SectionFeatures from './SectionFeatures'; import SectionReviews from './SectionReviews'; +import SectionHost from './SectionHost'; import SectionRulesMaybe from './SectionRulesMaybe'; import SectionMapMaybe from './SectionMapMaybe'; import css from './ListingPage.css'; @@ -484,38 +483,20 @@ export class ListingPageComponent extends Component { listingId={currentListing.id} /> -
-

- -

- {isOwnListing ? ( - - - - ) : null} - - this.setState({ enquiryModalOpen: false })} - onManageDisableScrolling={onManageDisableScrolling} - > - - -
+ this.setState({ enquiryModalOpen: false })} + sendEnquiryError={sendEnquiryError} + sendEnquiryInProgress={sendEnquiryInProgress} + onSubmitEnquiry={this.onSubmitEnquiry} + currentUser={currentUser} + onManageDisableScrolling={onManageDisableScrolling} + />
{ + const { + title, + listing, + isOwnListing, + authorDisplayName, + onContactUser, + isEnquiryModalOpen, + onCloseEnquiryModal, + sendEnquiryError, + sendEnquiryInProgress, + onSubmitEnquiry, + currentUser, + onManageDisableScrolling, + } = props; + return ( +
+

+ +

+ {isOwnListing ? ( + + + + ) : null} + + + + +
+ ); +}; + +export default SectionHost; diff --git a/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap b/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap index 561732ba..8e51d88c 100644 --- a/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap +++ b/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap @@ -258,41 +258,48 @@ exports[`ListingPage matches snapshot 1`] = ` fetchReviewsError={null} reviews={Array []} /> -
-

- -

- - - - -
+ } + onCloseEnquiryModal={[Function]} + onContactUser={[Function]} + onManageDisableScrolling={[Function]} + onSubmitEnquiry={[Function]} + sendEnquiryError={null} + sendEnquiryInProgress={false} + title="listing1 title" + />

Date: Tue, 10 Apr 2018 15:40:13 +0300 Subject: [PATCH 07/11] Extract booking section into separate file --- src/containers/ListingPage/ListingPage.js | 94 ++--------- src/containers/ListingPage/SectionBooking.js | 98 +++++++++++ .../__snapshots__/ListingPage.test.js.snap | 152 ++++++++---------- 3 files changed, 179 insertions(+), 165 deletions(-) create mode 100644 src/containers/ListingPage/SectionBooking.js diff --git a/src/containers/ListingPage/ListingPage.js b/src/containers/ListingPage/ListingPage.js index 79713184..29ad247d 100644 --- a/src/containers/ListingPage/ListingPage.js +++ b/src/containers/ListingPage/ListingPage.js @@ -19,8 +19,6 @@ import { manageDisableScrolling, isScrollingDisabled } from '../../ducks/UI.duck import { AvatarLarge, AvatarMedium, - Button, - ModalInMobile, Page, NamedLink, NamedRedirect, @@ -30,7 +28,7 @@ import { LayoutWrapperFooter, Footer, } from '../../components'; -import { BookingDatesForm, TopbarContainer, NotFoundPage } from '../../containers'; +import { TopbarContainer, NotFoundPage } from '../../containers'; import { sendEnquiry, loadData, setInitialValues } from './ListingPage.duck'; import SectionImages from './SectionImages'; @@ -41,10 +39,9 @@ import SectionReviews from './SectionReviews'; import SectionHost from './SectionHost'; import SectionRulesMaybe from './SectionRulesMaybe'; import SectionMapMaybe from './SectionMapMaybe'; +import SectionBooking from './SectionBooking'; import css from './ListingPage.css'; -// This defines when ModalInMobile shows content as Modal -const MODAL_BREAKPOINT = 1023; const MIN_LENGTH_FOR_LONG_WORDS_IN_TITLE = 16; const { UUID } = sdkTypes; @@ -332,27 +329,8 @@ export class ListingPageComponent extends Component { }); const authorDisplayName = userDisplayName(ensuredAuthor, bannedUserDisplayName); - const bookBtnMessage = intl.formatMessage({ id: 'ListingPage.ctaButtonMessage' }); const { formattedPrice, priceTitle } = priceData(price, intl); - const showClosedListingHelpText = currentListing.id && isClosed; - const bookingHeading = ( -
-

- -

-
- -
-
- ); - const handleMobileBookModalClose = () => { closeBookModal(history, currentListing); }; @@ -498,60 +476,22 @@ export class ListingPageComponent extends Component { onManageDisableScrolling={onManageDisableScrolling} />

- - -
-

{richTitle}

-
- - - -
-
- - {bookingHeading} - {!isClosed ? ( - - ) : null} -
-
-
-
- {formattedPrice} -
-
- -
-
- - {!isClosed ? ( - - ) : ( -
- -
- )} -
+ />

diff --git a/src/containers/ListingPage/SectionBooking.js b/src/containers/ListingPage/SectionBooking.js new file mode 100644 index 00000000..c568689b --- /dev/null +++ b/src/containers/ListingPage/SectionBooking.js @@ -0,0 +1,98 @@ +import React from 'react'; +import { FormattedMessage } from 'react-intl'; +import { ModalInMobile, Button } from '../../components'; +import { BookingDatesForm } from '../../containers'; + +import css from './ListingPage.css'; + +// This defines when ModalInMobile shows content as Modal +const MODAL_BREAKPOINT = 1023; + +const SectionBooking = props => { + const { + listing, + isOwnListing, + isClosed, + isBook, + unitType, + price, + formattedPrice, + priceTitle, + handleBookingSubmit, + richTitle, + authorDisplayName, + handleBookButtonClick, + handleMobileBookModalClose, + onManageDisableScrolling, + } = props; + const showClosedListingHelpText = listing.id && isClosed; + return ( +
+ +
+

{richTitle}

+
+ + + +
+
+ +
+

+ +

+
+ +
+
+ {!isClosed ? ( + + ) : null} +
+
+
+
+ {formattedPrice} +
+
+ +
+
+ + {!isClosed ? ( + + ) : ( +
+ +
+ )} +
+
+ ); +}; + +export default SectionBooking; diff --git a/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap b/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap index 8e51d88c..8183998b 100644 --- a/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap +++ b/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap @@ -328,95 +328,71 @@ exports[`ListingPage matches snapshot 1`] = ` title="listing1 title" />
- -
-

- - listing1 - - title - -

-
- - - -
-
-
-

- - - listing1 - - title - - , - } - } - /> -

-
- -
-
- -
-
-
-
- 55 -
-
- -
-
- -
+ } + onManageDisableScrolling={[Function]} + price={ + Money { + "amount": 5500, + "currency": "USD", + } + } + priceTitle={55} + richTitle={ + + + listing1 + + title + + + } + unitType="line-item/night" + />
From 09adef66651cf6ee248f33ab07c011ad96f70f46 Mon Sep 17 00:00:00 2001 From: Kimmo Puputti Date: Tue, 10 Apr 2018 15:40:27 +0300 Subject: [PATCH 08/11] Unify class name usage --- src/containers/ListingPage/ListingPage.css | 2 +- src/containers/ListingPage/SectionMapMaybe.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/containers/ListingPage/ListingPage.css b/src/containers/ListingPage/ListingPage.css index 8219bf00..39e3e1c7 100644 --- a/src/containers/ListingPage/ListingPage.css +++ b/src/containers/ListingPage/ListingPage.css @@ -418,7 +418,7 @@ } } -.locationContainer { +.sectionMap { padding: 0 24px; margin-bottom: 35px; diff --git a/src/containers/ListingPage/SectionMapMaybe.js b/src/containers/ListingPage/SectionMapMaybe.js index f5499cd8..ac6e015c 100644 --- a/src/containers/ListingPage/SectionMapMaybe.js +++ b/src/containers/ListingPage/SectionMapMaybe.js @@ -17,7 +17,7 @@ const SectionMapMaybe = props => { } const address = publicData.location ? publicData.location.address : ''; - const classes = classNames(rootClassName || css.locationContainer, className); + const classes = classNames(rootClassName || css.sectionMap, className); const mapProps = config.coordinates.fuzzy ? { obfuscatedCenter: obfuscatedCoordinates(geolocation, listingId ? listingId.uuid : null) } From 80edfd843fe5ab82030091048045d381630c1e76 Mon Sep 17 00:00:00 2001 From: Kimmo Puputti Date: Tue, 10 Apr 2018 15:45:32 +0300 Subject: [PATCH 09/11] Extract avatar section into separate file --- src/containers/ListingPage/ListingPage.css | 2 +- src/containers/ListingPage/ListingPage.js | 21 +---- src/containers/ListingPage/SectionAvatar.js | 20 +++++ .../__snapshots__/ListingPage.test.js.snap | 87 +++++-------------- 4 files changed, 44 insertions(+), 86 deletions(-) create mode 100644 src/containers/ListingPage/SectionAvatar.js diff --git a/src/containers/ListingPage/ListingPage.css b/src/containers/ListingPage/ListingPage.css index 39e3e1c7..8b8b76e4 100644 --- a/src/containers/ListingPage/ListingPage.css +++ b/src/containers/ListingPage/ListingPage.css @@ -219,7 +219,7 @@ } } -.avatarWrapper { +.sectionAvatar { /* Position (over the listing image)*/ margin-left: 24px; margin-top: -31px; diff --git a/src/containers/ListingPage/ListingPage.js b/src/containers/ListingPage/ListingPage.js index 29ad247d..735758ee 100644 --- a/src/containers/ListingPage/ListingPage.js +++ b/src/containers/ListingPage/ListingPage.js @@ -17,8 +17,6 @@ import { richText } from '../../util/richText'; import { getMarketplaceEntities } from '../../ducks/marketplaceData.duck'; import { manageDisableScrolling, isScrollingDisabled } from '../../ducks/UI.duck'; import { - AvatarLarge, - AvatarMedium, Page, NamedLink, NamedRedirect, @@ -32,6 +30,7 @@ import { TopbarContainer, NotFoundPage } from '../../containers'; import { sendEnquiry, loadData, setInitialValues } from './ListingPage.duck'; import SectionImages from './SectionImages'; +import SectionAvatar from './SectionAvatar'; import SectionHeading from './SectionHeading'; import SectionDescription from './SectionDescription'; import SectionFeatures from './SectionFeatures'; @@ -422,23 +421,7 @@ export class ListingPageComponent extends Component { onManageDisableScrolling={onManageDisableScrolling} />
-
- - - - - - -
- +
{ + const { user, params } = props; + return ( +
+ + + + + + +
+ ); +}; + +export default SectionAvatar; diff --git a/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap b/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap index 8183998b..7bb7e6f4 100644 --- a/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap +++ b/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap @@ -88,74 +88,29 @@ exports[`ListingPage matches snapshot 1`] = ` title="listing1 title" />
-
- - - - - - -
+ } + />
Date: Tue, 10 Apr 2018 15:52:30 +0300 Subject: [PATCH 10/11] Make custom config stable in tests --- src/containers/ListingPage/ListingPage.js | 10 +++--- .../ListingPage/ListingPage.test.js | 10 ++++++ .../__snapshots__/ListingPage.test.js.snap | 32 ++++--------------- 3 files changed, 22 insertions(+), 30 deletions(-) diff --git a/src/containers/ListingPage/ListingPage.js b/src/containers/ListingPage/ListingPage.js index 735758ee..6428fbbf 100644 --- a/src/containers/ListingPage/ListingPage.js +++ b/src/containers/ListingPage/ListingPage.js @@ -95,8 +95,6 @@ const categoryLabel = (categories, key) => { return cat ? cat.label : key; }; -// TODO: price unit (per x), custom fields, contact, reviews -// N.B. All the presentational content needs to be extracted to their own components export class ListingPageComponent extends Component { constructor(props) { super(props); @@ -199,6 +197,8 @@ export class ListingPageComponent extends Component { fetchReviewsError, sendEnquiryInProgress, sendEnquiryError, + categoriesConfig, + amenitiesConfig, } = this.props; const isBook = !!parse(location.search).book; @@ -249,7 +249,7 @@ export class ListingPageComponent extends Component { const category = publicData && publicData.category ? ( - {categoryLabel(config.custom.categories, publicData.category)} + {categoryLabel(categoriesConfig, publicData.category)} ) : null; @@ -434,7 +434,7 @@ export class ListingPageComponent extends Component { /> @@ -495,6 +495,8 @@ ListingPageComponent.defaultProps = { reviews: [], fetchReviewsError: null, sendEnquiryError: null, + categoriesConfig: config.custom.categories, + amenitiesConfig: config.custom.amenities, }; ListingPageComponent.propTypes = { diff --git a/src/containers/ListingPage/ListingPage.test.js b/src/containers/ListingPage/ListingPage.test.js index 5fa87e54..7013c4d7 100644 --- a/src/containers/ListingPage/ListingPage.test.js +++ b/src/containers/ListingPage/ListingPage.test.js @@ -30,6 +30,14 @@ import ActionBarMaybe from './ActionBarMaybe'; const { UUID } = sdkTypes; const noop = () => null; +const categoriesConfig = [{ key: 'cat1', label: 'Cat 1' }, { key: 'cat2', label: 'Cat 2' }]; + +const amenitiesConfig = [ + { key: 'feat1', label: 'Feat 1' }, + { key: 'feat2', label: 'Feat 2' }, + { key: 'feat3', label: 'Feat 3' }, +]; + describe('ListingPage', () => { it('matches snapshot', () => { const currentUser = createCurrentUser('user-2'); @@ -67,6 +75,8 @@ describe('ListingPage', () => { onResendVerificationEmail: noop, sendEnquiryInProgress: false, onSendEnquiry: noop, + categoriesConfig, + amenitiesConfig, }; const tree = renderShallow(); diff --git a/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap b/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap index 7bb7e6f4..bfaabcfa 100644 --- a/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap +++ b/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap @@ -154,36 +154,16 @@ exports[`ListingPage matches snapshot 1`] = ` options={ Array [ Object { - "key": "towels", - "label": "Towels", + "key": "feat1", + "label": "Feat 1", }, Object { - "key": "bathroom", - "label": "Bathroom", + "key": "feat2", + "label": "Feat 2", }, 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": "feat3", + "label": "Feat 3", }, ] } From 4d76e97c6a6a785e09ae224f8aceeca7fdcbbf40 Mon Sep 17 00:00:00 2001 From: Kimmo Puputti Date: Tue, 10 Apr 2018 16:35:05 +0300 Subject: [PATCH 11/11] Cleanup listing page internals based on review comments --- src/containers/ListingPage/ListingPage.js | 45 +++++++++++++---------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/src/containers/ListingPage/ListingPage.js b/src/containers/ListingPage/ListingPage.js index 6428fbbf..84199c73 100644 --- a/src/containers/ListingPage/ListingPage.js +++ b/src/containers/ListingPage/ListingPage.js @@ -1,6 +1,6 @@ /* eslint-disable jsx-a11y/no-static-element-interactions */ import React, { Component } from 'react'; -import { arrayOf, bool, func, shape, string, oneOf } from 'prop-types'; +import { array, arrayOf, bool, func, shape, string, oneOf } from 'prop-types'; import { FormattedMessage, intlShape, injectIntl } from 'react-intl'; import { compose } from 'redux'; import { connect } from 'react-redux'; @@ -246,23 +246,8 @@ export class ListingPageComponent extends Component { ); - const category = - publicData && publicData.category ? ( - - {categoryLabel(categoriesConfig, publicData.category)} - - - ) : null; - const topbar = ; - const loadingTitle = intl.formatMessage({ - id: 'ListingPage.loadingListingTitle', - }); - const errorTitle = intl.formatMessage({ - id: 'ListingPage.errorLoadingListingTitle', - }); - if (showListingError && showListingError.status === 404) { // 404 listing not found @@ -270,6 +255,10 @@ export class ListingPageComponent extends Component { } else if (showListingError) { // Other error in fetching listing + const errorTitle = intl.formatMessage({ + id: 'ListingPage.errorLoadingListingTitle', + }); + return ( @@ -288,6 +277,10 @@ export class ListingPageComponent extends Component { } else if (!currentListing.id) { // Still loading the listing + const loadingTitle = intl.formatMessage({ + id: 'ListingPage.loadingListingTitle', + }); + return ( @@ -343,8 +336,6 @@ export class ListingPageComponent extends Component { } }; - const editParams = { id: listingId.uuid, slug: listingSlug, type: 'edit', tab: 'description' }; - const handleBookButtonClick = () => { const isCurrentlyClosed = currentListing.attributes.state === LISTING_STATE_CLOSED; if (isOwnListing || isCurrentlyClosed) { @@ -389,6 +380,14 @@ export class ListingPageComponent extends Component { ); + const category = + publicData && publicData.category ? ( + + {categoryLabel(categoriesConfig, publicData.category)} + + + ) : null; + return ( this.setState({ imageCarouselOpen: false })} handleViewPhotosClick={handleViewPhotosClick} @@ -532,6 +536,9 @@ ListingPageComponent.propTypes = { sendEnquiryInProgress: bool.isRequired, sendEnquiryError: propTypes.error, onSendEnquiry: func.isRequired, + + categoriesConfig: array, + amenitiesConfig: array, }; const mapStateToProps = state => {