diff --git a/CHANGELOG.md b/CHANGELOG.md
index d35aec20..f03d7a79 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,6 +18,8 @@ way to update this template, but currently, we follow a pattern:
[#985](https://github.com/sharetribe/flex-template-web/pull/985)
- [remove] Remove the default built-in email templates. Built-in email templates can be edited in
Console. [#983](https://github.com/sharetribe/flex-template-web/pull/983)
+- [change] Extract SectionBooking to a distinct component from ListingPage.
+ [#969](https://github.com/sharetribe/flex-template-web/pull/969)
## [v2.5.0] 2018-12-17
diff --git a/src/components/BookingPanel/BookingPanel.css b/src/components/BookingPanel/BookingPanel.css
new file mode 100644
index 00000000..de2a7210
--- /dev/null
+++ b/src/components/BookingPanel/BookingPanel.css
@@ -0,0 +1,207 @@
+@import '../../marketplace.css';
+
+/* Booking form inside modalContainer needs special handling */
+/* since "inMobile" breakpoint is actually --viewportLarge */
+.modalContainer {
+ @apply --marketplaceModalInMobileBaseStyles;
+
+ height: 100vh;
+
+ @media (--viewportMedium) {
+ flex-basis: 576px;
+ height: 100%;
+ padding: var(--modalPaddingMedium);
+ background-color: var(--matterColorLight);
+ margin-top: 12.5vh;
+ margin-bottom: 12.5vh;
+ }
+
+ @media (--viewportLarge) {
+ padding: 0;
+ background-color: transparent;
+ margin-top: 0;
+ margin-bottom: 0;
+ }
+}
+
+.modalHeading {
+ margin-top: 89px;
+ margin-bottom: 36px;
+ padding: 0 24px;
+
+ @media (--viewportMedium) {
+ padding: 0;
+ }
+
+ @media (--viewportLarge) {
+ display: none;
+ }
+}
+
+.title {
+ /* Font */
+ @apply --marketplaceH1FontStyles;
+ color: var(--matterColor);
+
+ /* Layout */
+ width: 100%;
+ margin-top: 0;
+ margin-bottom: 0;
+ @media (--viewportMedium) {
+ margin-top: 0;
+ margin-bottom: 5px;
+ }
+}
+
+.author {
+ width: 100%;
+ @apply --marketplaceH4FontStyles;
+ margin-top: 7px;
+ margin-bottom: 0;
+
+ @media (--viewportMedium) {
+ margin-top: 0;
+ margin-bottom: 0;
+ color: var(--matterColor);
+ }
+}
+
+.bookingHeading {
+ display: none;
+
+ @media (--viewportLarge) {
+ display: block;
+ margin-top: -2px;
+ margin-bottom: 49px;
+ }
+}
+
+.bookingTitle {
+ @media (--viewportMedium) {
+ /* Font */
+ color: var(--matterColor);
+
+ margin-top: 0;
+ margin-bottom: 9px;
+ }
+}
+
+.bookingHelp {
+ display: none;
+ @apply --marketplaceH5FontStyles;
+
+ @media (--viewportMedium) {
+ color: var(--matterColor);
+ display: block;
+ margin-top: 0;
+ }
+}
+
+.bookingForm {
+ flex-grow: 1;
+ display: flex;
+ flex-direction: column;
+ margin: 0 0 84px 0;
+
+ @media (--viewportMedium) {
+ margin: 0;
+ min-height: 400px;
+ }
+
+ @media (--viewportLarge) {
+ min-width: 312px;
+ min-height: auto;
+ }
+}
+
+.bookingDatesSubmitButtonWrapper {
+ flex-shrink: 0;
+ padding: 0 24px 24px 24px;
+ width: 100%;
+ position: fixed;
+ bottom: 0;
+ background-color: var(--matterColorLight);
+
+ @media (--viewportMedium) {
+ padding: 0;
+ width: 100%;
+ position: static;
+ bottom: unset;
+ background-color: transparent;
+ }
+}
+
+.openBookingForm {
+ /* Ensure that mobile button is over Footer too */
+ z-index: 9;
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ width: 100vw;
+ padding: 18px 24px 18px 16px;
+ background-color: var(--matterColorLight);
+ box-shadow: var(--boxShadowTop);
+ display: flex;
+
+ /* Contain repainting to this component only */
+ /* 3D painting container helps scrolling */
+ transform: translate3d(0, 0, 0);
+
+ @media (--viewportMedium) {
+ padding: 18px 60px 18px 60px;
+ }
+
+ @media (--viewportLarge) {
+ display: none;
+ }
+}
+
+.priceContainer {
+ /* Layout */
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ flex-shrink: 0;
+ margin-right: 22px;
+ padding: 5px 12px;
+}
+
+.priceValue {
+ /* Font */
+ @apply --marketplaceH2FontStyles;
+ color: var(--matterColor);
+
+ margin-top: 0;
+ margin-bottom: 0px;
+ @media (--viewportMedium) {
+ margin-top: 0;
+ margin-bottom: 0;
+ }
+}
+
+.perUnit {
+ /* Font */
+ @apply --marketplaceTinyFontStyles;
+ color: var(--matterColor);
+
+ margin-top: 0;
+ margin-bottom: 0px;
+ @media (--viewportMedium) {
+ margin-top: 0;
+ margin-bottom: 0;
+ }
+}
+
+.bookButton {
+ @apply --marketplaceButtonStylesPrimary;
+
+ /* Clear padding that is set for link elements looking like buttons */
+ padding: 0;
+}
+
+.closedListingButton {
+ border-left: 1px solid var(--matterColorNegative);
+ width: 100%;
+ padding: 15px 24px 15px 24px;
+ text-align: center;
+}
diff --git a/src/components/BookingPanel/BookingPanel.example.css b/src/components/BookingPanel/BookingPanel.example.css
new file mode 100644
index 00000000..7349578d
--- /dev/null
+++ b/src/components/BookingPanel/BookingPanel.example.css
@@ -0,0 +1,3 @@
+.example {
+ width: 312px;
+}
diff --git a/src/components/BookingPanel/BookingPanel.example.js b/src/components/BookingPanel/BookingPanel.example.js
new file mode 100644
index 00000000..589027c1
--- /dev/null
+++ b/src/components/BookingPanel/BookingPanel.example.js
@@ -0,0 +1,31 @@
+import React from 'react';
+import { createListing } from '../../util/test-data';
+import { LISTING_STATE_CLOSED } from '../../util/types';
+import BookingPanel from './BookingPanel';
+import css from './BookingPanel.example.css';
+
+export const Default = {
+ component: BookingPanel,
+ props: {
+ className: css.example,
+ listing: createListing('listing_1'),
+ handleBookingSubmit: values => console.log('Submit:', values),
+ title: Booking title ,
+ subTitle: 'Hosted by Author N',
+ authorDisplayName: 'Author Name',
+ onManageDisableScrolling: () => null,
+ },
+};
+
+export const WithClosedListing = {
+ component: BookingPanel,
+ props: {
+ className: css.example,
+ listing: createListing('listing_1', { state: LISTING_STATE_CLOSED }),
+ handleBookingSubmit: values => console.log('Submit:', values),
+ title: Booking title ,
+ subTitle: 'Hosted by Author N',
+ authorDisplayName: 'Author Name',
+ onManageDisableScrolling: () => null,
+ },
+};
diff --git a/src/components/BookingPanel/BookingPanel.js b/src/components/BookingPanel/BookingPanel.js
new file mode 100644
index 00000000..87ebd94a
--- /dev/null
+++ b/src/components/BookingPanel/BookingPanel.js
@@ -0,0 +1,193 @@
+import React from 'react';
+import { compose } from 'redux';
+import { withRouter } from 'react-router-dom';
+import { intlShape, injectIntl } from 'react-intl';
+import { arrayOf, bool, func, node, oneOfType, shape, string } from 'prop-types';
+import { FormattedMessage } from 'react-intl';
+import classNames from 'classnames';
+import omit from 'lodash/omit';
+import { propTypes, LISTING_STATE_CLOSED, LINE_ITEM_NIGHT, LINE_ITEM_DAY } from '../../util/types';
+import { formatMoney } from '../../util/currency';
+import { parse, stringify } from '../../util/urlHelpers';
+import config from '../../config';
+import { ModalInMobile, Button } from '../../components';
+import { BookingDatesForm } from '../../forms';
+
+import css from './BookingPanel.css';
+
+// This defines when ModalInMobile shows content as Modal
+const MODAL_BREAKPOINT = 1023;
+
+const priceData = (price, intl) => {
+ if (price && price.currency === config.currency) {
+ const formattedPrice = formatMoney(intl, price);
+ return { formattedPrice, priceTitle: formattedPrice };
+ } else if (price) {
+ return {
+ formattedPrice: `(${price.currency})`,
+ priceTitle: `Unsupported currency (${price.currency})`,
+ };
+ }
+ return {};
+};
+
+const openBookModal = (isOwnListing, isClosed, history, location) => {
+ if (isOwnListing || isClosed) {
+ window.scrollTo(0, 0);
+ } else {
+ const { pathname, search, state } = location;
+ const searchString = `?${stringify({ ...parse(search), book: true })}`;
+ history.push(`${pathname}${searchString}`, state);
+ }
+};
+
+const closeBookModal = (history, location) => {
+ const { pathname, search, state } = location;
+ const searchParams = omit(parse(search), 'book');
+ const searchString = `?${stringify(searchParams)}`;
+ history.push(`${pathname}${searchString}`, state);
+};
+
+const BookingPanel = props => {
+ const {
+ rootClassName,
+ className,
+ listing,
+ isOwnListing,
+ unitType,
+ handleBookingSubmit,
+ title,
+ subTitle,
+ authorDisplayName,
+ onManageDisableScrolling,
+ timeSlots,
+ fetchTimeSlotsError,
+ history,
+ location,
+ intl,
+ } = props;
+
+ const price = listing.attributes.price;
+ const isClosed = listing.attributes.state === LISTING_STATE_CLOSED;
+ const showClosedListingHelpText = listing.id && isClosed;
+ const { formattedPrice, priceTitle } = priceData(price, intl);
+ const isBook = !!parse(location.search).book;
+
+ const subTitleText = !!subTitle
+ ? subTitle
+ : showClosedListingHelpText
+ ? intl.formatMessage({ id: 'BookingPanel.subTitleClosedListing' })
+ : null;
+
+ const isNightly = unitType === LINE_ITEM_NIGHT;
+ const isDaily = unitType === LINE_ITEM_DAY;
+
+ const unitTranslationKey = isNightly
+ ? 'BookingPanel.perNight'
+ : isDaily
+ ? 'BookingPanel.perDay'
+ : 'BookingPanel.perUnit';
+
+ const classes = classNames(rootClassName || css.root, className);
+
+ return (
+
+
closeBookModal(history, location)}
+ showAsModalMaxWidth={MODAL_BREAKPOINT}
+ onManageDisableScrolling={onManageDisableScrolling}
+ >
+
+
+
+
{title}
+ {subTitleText ?
{subTitleText}
: null}
+
+ {!isClosed ? (
+
+ ) : null}
+
+
+
+
+ {formattedPrice}
+
+
+
+
+
+
+ {!isClosed ? (
+
openBookModal(isOwnListing, isClosed, history, location)}
+ >
+
+
+ ) : (
+
+
+
+ )}
+
+
+ );
+};
+
+BookingPanel.defaultProps = {
+ rootClassName: null,
+ className: null,
+ isOwnListing: false,
+ subTitle: null,
+ unitType: config.bookingUnitType,
+ timeSlots: null,
+ fetchTimeSlotsError: null,
+};
+
+BookingPanel.propTypes = {
+ rootClassName: string,
+ className: string,
+ listing: propTypes.listing.isRequired,
+ isOwnListing: bool,
+ unitType: propTypes.bookingUnitType,
+ handleBookingSubmit: func.isRequired,
+ title: oneOfType([node, string]).isRequired,
+ subTitle: oneOfType([node, string]),
+ authorDisplayName: string.isRequired,
+ onManageDisableScrolling: func.isRequired,
+ timeSlots: arrayOf(propTypes.timeSlot),
+ fetchTimeSlotsError: propTypes.error,
+
+ // from withRouter
+ history: shape({
+ push: func.isRequired,
+ }).isRequired,
+ location: shape({
+ search: string,
+ }).isRequired,
+
+ // from injectIntl
+ intl: intlShape.isRequired,
+};
+
+export default compose(
+ withRouter,
+ injectIntl
+)(BookingPanel);
diff --git a/src/components/FieldDateRangeInput/DateRangeInput.css b/src/components/FieldDateRangeInput/DateRangeInput.css
index 8e084d0c..d3807836 100644
--- a/src/components/FieldDateRangeInput/DateRangeInput.css
+++ b/src/components/FieldDateRangeInput/DateRangeInput.css
@@ -110,6 +110,10 @@
font-weight: 400;
}
+ & :global(.DayPicker__withBorder) {
+ border-radius: 0;
+ }
+
& :global(.CalendarMonth_caption) {
color: var(--matterColorLight);
@apply --marketplaceH2FontStyles;
diff --git a/src/components/FieldDateRangeInput/FieldDateRangeInput.css b/src/components/FieldDateRangeInput/FieldDateRangeInput.css
index 1aaf063c..c7352b16 100644
--- a/src/components/FieldDateRangeInput/FieldDateRangeInput.css
+++ b/src/components/FieldDateRangeInput/FieldDateRangeInput.css
@@ -23,6 +23,10 @@
margin-bottom: 0;
font-weight: 600;
}
+
+ @media (--viewportMedium) {
+ padding-bottom: 1px;
+ }
}
.labelSuccess {
diff --git a/src/components/ListingCard/ListingCard.js b/src/components/ListingCard/ListingCard.js
index f5b0f001..d8fdb0f0 100644
--- a/src/components/ListingCard/ListingCard.js
+++ b/src/components/ListingCard/ListingCard.js
@@ -99,11 +99,7 @@ export const ListingCardComponent = props => {
})}
-
+
diff --git a/src/components/index.js b/src/components/index.js
index a59280c9..3a3e195f 100644
--- a/src/components/index.js
+++ b/src/components/index.js
@@ -3,6 +3,7 @@ export { default as AddImages } from './AddImages/AddImages';
export { default as Avatar, AvatarMedium, AvatarLarge } from './Avatar/Avatar';
export { default as BookingBreakdown } from './BookingBreakdown/BookingBreakdown';
export { default as Button, PrimaryButton, SecondaryButton, InlineTextButton } from './Button/Button';
+export { default as BookingPanel } from './BookingPanel/BookingPanel';
export { default as CookieConsent } from './CookieConsent/CookieConsent';
export { default as Discussion } from './Discussion/Discussion';
export { default as EditListingDescriptionPanel } from './EditListingDescriptionPanel/EditListingDescriptionPanel';
diff --git a/src/containers/CheckoutPage/CheckoutPage.js b/src/containers/CheckoutPage/CheckoutPage.js
index 5a526339..4b3d8d63 100644
--- a/src/containers/CheckoutPage/CheckoutPage.js
+++ b/src/containers/CheckoutPage/CheckoutPage.js
@@ -393,12 +393,10 @@ export class CheckoutPageComponent extends Component {
diff --git a/src/containers/CheckoutPage/__snapshots__/CheckoutPage.test.js.snap b/src/containers/CheckoutPage/__snapshots__/CheckoutPage.test.js.snap
index 7a8adecb..0e3759ce 100644
--- a/src/containers/CheckoutPage/__snapshots__/CheckoutPage.test.js.snap
+++ b/src/containers/CheckoutPage/__snapshots__/CheckoutPage.test.js.snap
@@ -65,16 +65,14 @@ exports[`CheckoutPage matches snapshot 1`] = `
CheckoutPage.title
-
-
-
+ }
+ />
diff --git a/src/containers/ListingPage/ListingPage.css b/src/containers/ListingPage/ListingPage.css
index 62410f22..f0700cb8 100644
--- a/src/containers/ListingPage/ListingPage.css
+++ b/src/containers/ListingPage/ListingPage.css
@@ -1,5 +1,8 @@
@import '../../marketplace.css';
+.root {
+}
+
.pageRoot {
display: flex;
padding-bottom: 90px;
@@ -213,8 +216,26 @@
@media (--viewportLarge) {
margin-top: 50px;
margin-bottom: 0px;
- /* contentContainer's full width - (bookingColumn + gutters) */
- flex-basis: calc(100% - 491px);
+ /* contentContainer's full width - (bookingColumn + two paddings + margin + border) */
+ flex-basis: calc(100% - 493px);
+ flex-shrink: 0;
+ }
+}
+
+.bookingPanel {
+ @media (--viewportLarge) {
+ display: block;
+ margin-top: 87px;
+ margin-left: 60px;
+ border-left-style: solid;
+ border-left-width: 1px;
+ border-left-color: var(--matterColorNegative);
+
+ padding-left: 60px;
+ padding-right: 60px;
+
+ /* The calendar needs 312px */
+ flex-basis: 312px;
flex-shrink: 0;
}
}
@@ -548,200 +569,6 @@
}
}
-.openBookingForm {
- /* Ensure that mobile button is over Footer too */
- z-index: 9;
- position: fixed;
- bottom: 0;
- left: 0;
- width: 100vw;
- padding: 18px 24px 18px 16px;
- background-color: var(--matterColorLight);
- box-shadow: var(--boxShadowTop);
- display: flex;
-
- /* Contain repainting to this component only */
- /* 3D painting container helps scrolling */
- transform: translate3d(0, 0, 0);
-
- @media (--viewportMedium) {
- padding: 18px 60px 18px 60px;
- }
-
- @media (--viewportLarge) {
- display: none;
- }
-}
-
-.priceContainer {
- /* Layout */
- display: flex;
- flex-direction: column;
- align-items: center;
- flex-shrink: 0;
- margin-right: 22px;
- padding: 5px 12px;
-}
-
-.priceValue {
- /* Font */
- @apply --marketplaceH2FontStyles;
- color: var(--matterColor);
-
- margin-top: 0;
- margin-bottom: 0px;
- @media (--viewportMedium) {
- margin-top: 0;
- margin-bottom: 0;
- }
-}
-
-.perUnit {
- /* Font */
- @apply --marketplaceTinyFontStyles;
- color: var(--matterColor);
-
- margin-top: 0;
- margin-bottom: 0px;
- @media (--viewportMedium) {
- margin-top: 0;
- margin-bottom: 0;
- }
-}
-
-.modalInMobile {
- @media (--viewportLarge) {
- display: block;
- margin-top: 87px;
- margin-left: 60px;
- border-left-style: solid;
- border-left-width: 1px;
- border-left-color: var(--matterColorNegative);
-
- /* Calendar needs 311px width and therefore padding can't be wider than 58px */
- padding-left: 60px;
- padding-right: 60px;
- flex-basis: 311px;
- flex-shrink: 0;
- }
-}
-
-/* Booking form inside modalContainer needs special handling */
-/* since "inMobile" breakpoint is actually --viewportLarge */
-.modalContainer {
- @apply --marketplaceModalInMobileBaseStyles;
-
- height: 100vh;
-
- @media (--viewportMedium) {
- flex-basis: 576px;
- height: 100%;
- padding: var(--modalPaddingMedium);
- background-color: var(--matterColorLight);
- margin-top: 12.5vh;
- margin-bottom: 12.5vh;
- }
-
- @media (--viewportLarge) {
- padding: 0;
- background-color: transparent;
- margin-top: 0;
- margin-bottom: 0;
- }
-}
-
-.modalHeading {
- margin-top: 89px;
- margin-bottom: 36px;
- padding: 0 24px;
-
- @media (--viewportMedium) {
- padding: 0;
- }
-
- @media (--viewportLarge) {
- display: none;
- }
-}
-
-.bookingHeading {
- display: none;
-
- @media (--viewportLarge) {
- display: block;
- margin-top: 6px;
- margin-bottom: 47px;
- }
-}
-
-.bookingTitle {
- @media (--viewportMedium) {
- /* Font */
- color: var(--matterColor);
-
- margin-top: 0;
- margin-bottom: 9px;
- }
-}
-
-.bookingHelp {
- display: none;
- @apply --marketplaceH5FontStyles;
-
- @media (--viewportMedium) {
- color: var(--matterColor);
- display: block;
- margin-top: 0;
- }
-}
-
-.bookingForm {
- flex-grow: 1;
- display: flex;
- flex-direction: column;
- margin: 0 0 84px 0;
-
- @media (--viewportMedium) {
- margin: 0;
- min-height: 400px;
- }
-
- @media (--viewportLarge) {
- min-width: 311px;
- min-height: auto;
- }
-}
-.bookingDatesSubmitButtonWrapper {
- flex-shrink: 0;
- padding: 0 24px 24px 24px;
- width: 100%;
- position: fixed;
- bottom: 0;
- background-color: var(--matterColorLight);
-
- @media (--viewportMedium) {
- padding: 0;
- width: 100%;
- position: static;
- bottom: unset;
- background-color: transparent;
- }
-}
-
-.bookButton {
- @apply --marketplaceButtonStylesPrimary;
-
- /* Clear padding that is set for link elements looking like buttons */
- padding: 0;
-}
-
-.closedListingButton {
- border-left: 1px solid var(--matterColorNegative);
- width: 100%;
- padding: 15px 24px 15px 24px;
- text-align: center;
-}
-
.longWord {
/* fallback option */
word-break: break-all;
diff --git a/src/containers/ListingPage/ListingPage.js b/src/containers/ListingPage/ListingPage.js
index 6d8cf841..b5d5b5cb 100644
--- a/src/containers/ListingPage/ListingPage.js
+++ b/src/containers/ListingPage/ListingPage.js
@@ -15,7 +15,6 @@ import {
LISTING_PAGE_PARAM_TYPE_DRAFT,
LISTING_PAGE_PARAM_TYPE_EDIT,
createSlug,
- parse,
} from '../../util/urlHelpers';
import { formatMoney } from '../../util/currency';
import { createResourceLocatorString, findRouteByRouteName } from '../../util/routes';
@@ -32,6 +31,7 @@ import {
LayoutWrapperMain,
LayoutWrapperFooter,
Footer,
+ BookingPanel,
} from '../../components';
import { TopbarContainer, NotFoundPage } from '../../containers';
@@ -45,7 +45,6 @@ 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';
const MIN_LENGTH_FOR_LONG_WORDS_IN_TITLE = 16;
@@ -65,38 +64,6 @@ const priceData = (price, intl) => {
return {};
};
-const openBookModal = (history, listing) => {
- if (!listing.id) {
- // Listing not fully loaded yet
- return;
- }
- const routes = routeConfiguration();
- history.push(
- createResourceLocatorString(
- 'ListingPage',
- routes,
- { id: listing.id.uuid, slug: createSlug(listing.attributes.title) },
- { book: true }
- )
- );
-};
-
-const closeBookModal = (history, listing) => {
- if (!listing.id) {
- // Listing not fully loaded yet
- return;
- }
- const routes = routeConfiguration();
- history.push(
- createResourceLocatorString(
- 'ListingPage',
- routes,
- { id: listing.id.uuid, slug: createSlug(listing.attributes.title) },
- {}
- )
- );
-};
-
const categoryLabel = (categories, key) => {
const cat = categories.find(c => c.key === key);
return cat ? cat.label : key;
@@ -199,7 +166,6 @@ export class ListingPageComponent extends Component {
location,
scrollingDisabled,
showListingError,
- history,
reviews,
fetchReviewsError,
sendEnquiryInProgress,
@@ -210,7 +176,6 @@ export class ListingPageComponent extends Component {
amenitiesConfig,
} = this.props;
- const isBook = !!parse(location.search).book;
const listingId = new UUID(rawParams.id);
const isPendingApprovalVariant = rawParams.variant === LISTING_PAGE_PENDING_APPROVAL_VARIANT;
const isDraftVariant = rawParams.variant === LISTING_PAGE_DRAFT_VARIANT;
@@ -264,6 +229,12 @@ export class ListingPageComponent extends Component {
);
+ const bookingTitle = intl.formatMessage(
+ { id: 'ListingPage.bookingSubTitle' },
+ { title: richTitle }
+ );
+ const bookingSubTitle = intl.formatMessage({ id: 'ListingPage.bookingSubTitle' });
+
const topbar = ;
if (showListingError && showListingError.status === 404) {
@@ -328,7 +299,6 @@ export class ListingPageComponent extends Component {
const userAndListingAuthorAvailable = !!(currentUser && authorAvailable);
const isOwnListing =
userAndListingAuthorAvailable && currentListing.author.id.uuid === currentUser.id.uuid;
- const isClosed = currentListing.attributes.state === LISTING_STATE_CLOSED;
const showContactUser = !currentUser || (currentUser && !isOwnListing);
const currentAuthor = authorAvailable ? currentListing.author : null;
@@ -341,10 +311,6 @@ export class ListingPageComponent extends Component {
const { formattedPrice, priceTitle } = priceData(price, intl);
- const handleMobileBookModalClose = () => {
- closeBookModal(history, currentListing);
- };
-
const handleBookingSubmit = values => {
const isCurrentlyClosed = currentListing.attributes.state === LISTING_STATE_CLOSED;
if (isOwnListing || isCurrentlyClosed) {
@@ -354,15 +320,6 @@ export class ListingPageComponent extends Component {
}
};
- const handleBookButtonClick = () => {
- const isCurrentlyClosed = currentListing.attributes.state === LISTING_STATE_CLOSED;
- if (isOwnListing || isCurrentlyClosed) {
- window.scrollTo(0, 0);
- } else {
- openBookModal(history, currentListing);
- }
- };
-
const listingImages = (listing, variantName) =>
(listing.images || [])
.map(image => {
@@ -480,20 +437,15 @@ export class ListingPageComponent extends Component {
onManageDisableScrolling={onManageDisableScrolling}
/>
- {
- const {
- listing,
- isOwnListing,
- isClosed,
- isBook,
- unitType,
- price,
- formattedPrice,
- priceTitle,
- handleBookingSubmit,
- richTitle,
- authorDisplayName,
- handleBookButtonClick,
- handleMobileBookModalClose,
- onManageDisableScrolling,
- timeSlots,
- fetchTimeSlotsError,
- } = props;
- const showClosedListingHelpText = listing.id && isClosed;
-
- const isNightly = unitType === LINE_ITEM_NIGHT;
- const isDaily = unitType === LINE_ITEM_DAY;
-
- const unitTranslationKey = isNightly
- ? 'ListingPage.perNight'
- : isDaily
- ? 'ListingPage.perDay'
- : 'ListingPage.perUnit';
-
- 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 072c0e2a..e8df2d93 100644
--- a/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap
+++ b/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap
@@ -262,15 +262,10 @@ exports[`ListingPage matches snapshot 1`] = `
title="listing1 title"
/>
-
-
- listing1
-
- title
-
-
- }
+ subTitle="ListingPage.bookingSubTitle"
timeSlots={null}
+ title="ListingPage.bookingSubTitle"
unitType="line-item/night"
/>
diff --git a/src/examples.js b/src/examples.js
index 0c75352a..ea21892f 100644
--- a/src/examples.js
+++ b/src/examples.js
@@ -3,6 +3,7 @@ import * as ActivityFeed from './components/ActivityFeed/ActivityFeed.example';
import * as AddImages from './components/AddImages/AddImages.example';
import * as Avatar from './components/Avatar/Avatar.example';
import * as BookingBreakdown from './components/BookingBreakdown/BookingBreakdown.example';
+import * as BookingPanel from './components/BookingPanel/BookingPanel.example';
import * as Button from './components/Button/Button.example';
import * as ExpandingTextarea from './components/ExpandingTextarea/ExpandingTextarea.example';
import * as FieldBirthdayInput from './components/FieldBirthdayInput/FieldBirthdayInput.example';
@@ -89,6 +90,7 @@ export {
Avatar,
BookingBreakdown,
BookingDatesForm,
+ BookingPanel,
Button,
Colors,
EditListingDescriptionForm,
diff --git a/src/forms/BookingDatesForm/BookingDatesForm.css b/src/forms/BookingDatesForm/BookingDatesForm.css
index e30d160f..a54aa6e8 100644
--- a/src/forms/BookingDatesForm/BookingDatesForm.css
+++ b/src/forms/BookingDatesForm/BookingDatesForm.css
@@ -71,7 +71,7 @@
@media (--viewportLarge) {
margin-top: 4px;
- margin-bottom: 22px;
+ margin-bottom: 21px;
}
}
diff --git a/src/translations/en.json b/src/translations/en.json
index 5c915ebf..2f31d57a 100644
--- a/src/translations/en.json
+++ b/src/translations/en.json
@@ -67,6 +67,13 @@
"BookingDatesForm.requiredDate": "Oops, make sure your date is correct!",
"BookingDatesForm.timeSlotsError": "Loading listing availability failed. Please refresh the page.",
"BookingDatesForm.youWontBeChargedInfo": "You won't be charged yet",
+ "BookingPanel.closedListingButtonText": "Sorry, this listing has been closed.",
+ "BookingPanel.ctaButtonMessage": "Request to book",
+ "BookingPanel.hostedBy": "Hosted by {name}",
+ "BookingPanel.perDay": "per day",
+ "BookingPanel.perNight": "per night",
+ "BookingPanel.perUnit": "per unit",
+ "BookingPanel.subTitleClosedListing": "Sorry, this listing has been closed.",
"CheckoutPage.bookingTimeNotAvailableMessage": "Unfortunately, the requested time is already booked.",
"CheckoutPage.errorlistingLinkText": "the sauna page",
"CheckoutPage.goToLandingPage": "Go to homepage",
@@ -287,13 +294,10 @@
"ListingCard.unsupportedPrice": "({currency})",
"ListingCard.unsupportedPriceTitle": "Unsupported currency ({currency})",
"ListingPage.bannedUserDisplayName": "Banned user",
- "ListingPage.bookingHelp": "Start by choosing your dates.",
- "ListingPage.bookingHelpClosedListing": "Sorry, this listing has been closed.",
+ "ListingPage.bookingSubTitle": "Start by choosing your dates.",
"ListingPage.bookingTitle": "Book {title}",
"ListingPage.closedListing": "This listing has been closed and can't be booked.",
- "ListingPage.closedListingButtonText": "Sorry, this listing has been closed.",
"ListingPage.contactUser": "Contact",
- "ListingPage.ctaButtonMessage": "Request to book",
"ListingPage.descriptionTitle": "About this sauna",
"ListingPage.editListing": "Edit listing",
"ListingPage.editProfileLink": "Edit profile",
diff --git a/src/translations/fr.json b/src/translations/fr.json
index 8f26df4a..1b8efe51 100644
--- a/src/translations/fr.json
+++ b/src/translations/fr.json
@@ -67,6 +67,13 @@
"BookingDatesForm.requiredDate": "Oups, vérifiez que la date est correcte",
"BookingDatesForm.timeSlotsError": "Le chargement des disponibilités pour cette annonce a échoué. Veuillez rafraîchir la page.",
"BookingDatesForm.youWontBeChargedInfo": "Vous ne serez pas facturé immédiatement",
+ "BookingPanel.closedListingButtonText": "Navré, cette annonce est close.",
+ "BookingPanel.ctaButtonMessage": "Réserver",
+ "BookingPanel.hostedBy": "Proposé par {name}",
+ "BookingPanel.perDay": "par jour",
+ "BookingPanel.perNight": "par nuit",
+ "BookingPanel.perUnit": "par unité",
+ "BookingPanel.subTitleClosedListing": "Navré, cette annonce est close.",
"CheckoutPage.bookingTimeNotAvailableMessage": "Hélas, le créneau souhaité est déjà réservé.",
"CheckoutPage.errorlistingLinkText": "la page de l'annonce",
"CheckoutPage.goToLandingPage": "Aller sur la page d'accueil",
@@ -287,13 +294,10 @@
"ListingCard.unsupportedPrice": "({currency})",
"ListingCard.unsupportedPriceTitle": "Devise non supportée ({currency})",
"ListingPage.bannedUserDisplayName": "Utilisateur banni",
- "ListingPage.bookingHelp": "À quelle date souhaiteriez-vous réserver ?",
- "ListingPage.bookingHelpClosedListing": "Navré, cette annonce est close.",
+ "ListingPage.bookingSubTitle": "À quelle date souhaiteriez-vous réserver ?",
"ListingPage.bookingTitle": "Réserver {title}",
"ListingPage.closedListing": "Cette annonce est close et ne peut être réservée.",
- "ListingPage.closedListingButtonText": "Navré, cette annonce est close.",
"ListingPage.contactUser": "Envoyer un message",
- "ListingPage.ctaButtonMessage": "Réserver",
"ListingPage.descriptionTitle": "À propos de ce sauna",
"ListingPage.editListing": "Modifier l'annonce",
"ListingPage.editProfileLink": "Modifier le profil",
@@ -796,4 +800,4 @@
"UserCard.heading": "Bonjour, je suis {name}.",
"UserCard.showFullBioLink": "plus",
"UserCard.viewProfileLink": "Voir le profil"
-}
\ No newline at end of file
+}