From 2563bfeb2d7a1bf3735c6258c076cc02a391c5dd Mon Sep 17 00:00:00 2001
From: Hannu Lyytikainen
Date: Wed, 8 Aug 2018 18:01:05 +0300
Subject: [PATCH 01/11] Default time slots to null
---
src/containers/ListingPage/ListingPage.duck.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/containers/ListingPage/ListingPage.duck.js b/src/containers/ListingPage/ListingPage.duck.js
index 2dafe664..9ada66a1 100644
--- a/src/containers/ListingPage/ListingPage.duck.js
+++ b/src/containers/ListingPage/ListingPage.duck.js
@@ -37,7 +37,7 @@ const initialState = {
showListingError: null,
reviews: [],
fetchReviewsError: null,
- timeSlots: [],
+ timeSlots: null,
fetchTimesLotsError: null,
sendEnquiryInProgress: false,
sendEnquiryError: null,
From bcbeec8aefdb47b5d3c6e11b9c6cada3870d7370 Mon Sep 17 00:00:00 2001
From: Hannu Lyytikainen
Date: Wed, 8 Aug 2018 18:03:51 +0300
Subject: [PATCH 02/11] Pass time slots to BookingDatesForm
---
src/containers/ListingPage/ListingPage.js | 2 ++
src/containers/ListingPage/SectionBooking.js | 2 ++
.../ListingPage/__snapshots__/ListingPage.test.js.snap | 1 +
src/forms/BookingDatesForm/BookingDatesForm.js | 6 +++++-
.../__snapshots__/BookingDatesForm.test.js.snap | 1 +
5 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/src/containers/ListingPage/ListingPage.js b/src/containers/ListingPage/ListingPage.js
index 80af60e5..4d6eacc1 100644
--- a/src/containers/ListingPage/ListingPage.js
+++ b/src/containers/ListingPage/ListingPage.js
@@ -197,6 +197,7 @@ export class ListingPageComponent extends Component {
fetchReviewsError,
sendEnquiryInProgress,
sendEnquiryError,
+ timeSlots,
categoriesConfig,
amenitiesConfig,
} = this.props;
@@ -477,6 +478,7 @@ export class ListingPageComponent extends Component {
handleBookButtonClick={handleBookButtonClick}
handleMobileBookModalClose={handleMobileBookModalClose}
onManageDisableScrolling={onManageDisableScrolling}
+ timeSlots={timeSlots}
/>
diff --git a/src/containers/ListingPage/SectionBooking.js b/src/containers/ListingPage/SectionBooking.js
index 5efa683d..08cb4a6f 100644
--- a/src/containers/ListingPage/SectionBooking.js
+++ b/src/containers/ListingPage/SectionBooking.js
@@ -24,6 +24,7 @@ const SectionBooking = props => {
handleBookButtonClick,
handleMobileBookModalClose,
onManageDisableScrolling,
+ timeSlots,
} = props;
const showClosedListingHelpText = listing.id && isClosed;
return (
@@ -68,6 +69,7 @@ const SectionBooking = props => {
onSubmit={handleBookingSubmit}
price={price}
isOwnListing={isOwnListing}
+ timeSlots={timeSlots}
/>
) : null}
diff --git a/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap b/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap
index ef469cdf..4ec39bef 100644
--- a/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap
+++ b/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap
@@ -325,6 +325,7 @@ exports[`ListingPage matches snapshot 1`] = `
}
+ timeSlots={null}
unitType="line-item/night"
/>
diff --git a/src/forms/BookingDatesForm/BookingDatesForm.js b/src/forms/BookingDatesForm/BookingDatesForm.js
index 285ccf92..a127a951 100644
--- a/src/forms/BookingDatesForm/BookingDatesForm.js
+++ b/src/forms/BookingDatesForm/BookingDatesForm.js
@@ -1,5 +1,5 @@
import React, { Component } from 'react';
-import { string, bool } from 'prop-types';
+import { string, bool, arrayOf } from 'prop-types';
import { compose } from 'redux';
import { Form as FinalForm } from 'react-final-form';
import { FormattedMessage, intlShape, injectIntl } from 'react-intl';
@@ -85,6 +85,7 @@ export class BookingDatesFormComponent extends Component {
unitPrice,
unitType,
values,
+ timeSlots,
} = fieldRenderProps;
const { startDate, endDate } = values && values.bookingDates ? values.bookingDates : {};
@@ -160,6 +161,7 @@ export class BookingDatesFormComponent extends Component {
focusedInput={this.state.focusedInput}
onFocusedInputChange={this.onFocusedInputChange}
format={null}
+ timeSlots={timeSlots}
useMobileMargins
validate={composeValidators(
required(requiredMessage),
@@ -197,6 +199,7 @@ BookingDatesFormComponent.defaultProps = {
isOwnListing: false,
startDatePlaceholder: null,
endDatePlaceholder: null,
+ timeSlots: null,
};
BookingDatesFormComponent.propTypes = {
@@ -207,6 +210,7 @@ BookingDatesFormComponent.propTypes = {
unitType: propTypes.bookingUnitType.isRequired,
price: propTypes.money,
isOwnListing: bool,
+ timeSlots: arrayOf(propTypes.timeSlot),
// from injectIntl
intl: intlShape.isRequired,
diff --git a/src/forms/BookingDatesForm/__snapshots__/BookingDatesForm.test.js.snap b/src/forms/BookingDatesForm/__snapshots__/BookingDatesForm.test.js.snap
index 7a310e10..e9b67861 100644
--- a/src/forms/BookingDatesForm/__snapshots__/BookingDatesForm.test.js.snap
+++ b/src/forms/BookingDatesForm/__snapshots__/BookingDatesForm.test.js.snap
@@ -22,6 +22,7 @@ exports[`BookingDatesForm matches snapshot without selected dates 1`] = `
render={[Function]}
startDatePlaceholder="today"
submitButtonWrapperClassName={null}
+ timeSlots={null}
unitPrice={
Money {
"amount": 1099,
From 1706f22e52e338303eeb3645b373cb0a7c798883 Mon Sep 17 00:00:00 2001
From: Hannu Lyytikainen
Date: Wed, 8 Aug 2018 18:41:38 +0300
Subject: [PATCH 03/11] Add error handling to checkout page
Add handling for the tx time already booked error to checkout page.
---
src/containers/CheckoutPage/CheckoutPage.js | 7 +++++++
src/translations/en.json | 1 +
src/util/errors.js | 9 +++++++++
src/util/types.js | 2 ++
4 files changed, 19 insertions(+)
diff --git a/src/containers/CheckoutPage/CheckoutPage.js b/src/containers/CheckoutPage/CheckoutPage.js
index 7581ff5c..7a3f91e6 100644
--- a/src/containers/CheckoutPage/CheckoutPage.js
+++ b/src/containers/CheckoutPage/CheckoutPage.js
@@ -15,6 +15,7 @@ import {
isTransactionInitiateAmountTooLowError,
isTransactionInitiateListingNotFoundError,
isTransactionInitiateMissingStripeAccountError,
+ isTransactionInitiateBookingTimeNotAvailableError,
} from '../../util/errors';
import {
AvatarMedium,
@@ -298,6 +299,12 @@ export class CheckoutPageComponent extends Component {
);
+ } else if (isTransactionInitiateBookingTimeNotAvailableError(speculateTransactionError)) {
+ speculateErrorMessage = (
+
+
+
+ );
} else if (speculateTransactionError) {
speculateErrorMessage = (
diff --git a/src/translations/en.json b/src/translations/en.json
index 06b662aa..15cacf57 100644
--- a/src/translations/en.json
+++ b/src/translations/en.json
@@ -66,6 +66,7 @@
"BookingDatesForm.requestToBook": "Request to book",
"BookingDatesForm.requiredDate": "Oops, make sure your date is correct!",
"BookingDatesForm.youWontBeChargedInfo": "You won't be charged yet",
+ "CheckoutPage.bookingTimeNotAvailableMessage": "Unfortunately, the requested time is already booked.",
"CheckoutPage.errorlistingLinkText": "the sauna page",
"CheckoutPage.goToLandingPage": "Go to homepage",
"CheckoutPage.hostedBy": "Hosted by {name}",
diff --git a/src/util/errors.js b/src/util/errors.js
index 3fd07d8f..732d52bd 100644
--- a/src/util/errors.js
+++ b/src/util/errors.js
@@ -21,6 +21,7 @@ import {
ERROR_CODE_TOO_MANY_VERIFICATION_REQUESTS,
ERROR_CODE_UPLOAD_OVER_LIMIT,
ERROR_CODE_MISSING_STRIPE_ACCOUNT,
+ ERROR_CODE_TRANSACTION_BOOKING_TIME_NOT_AVAILABLE,
} from './types';
const errorAPIErrors = error => {
@@ -102,6 +103,14 @@ export const isTransactionInitiateListingNotFoundError = error =>
export const isTransactionInitiateMissingStripeAccountError = error =>
hasErrorWithCode(error, ERROR_CODE_MISSING_STRIPE_ACCOUNT);
+/**
+ * Check if the given API error (from `sdk.transaction.initiate()` or
+ * `sdk.transaction.initiateSpeculative()`) is due to selected booking
+ * time already being booked.
+ */
+export const isTransactionInitiateBookingTimeNotAvailableError = error =>
+ hasErrorWithCode(error, ERROR_CODE_TRANSACTION_BOOKING_TIME_NOT_AVAILABLE);
+
/**
* Check if the given API error (from `sdk.transaction.initiate()`) is
* due to the transaction total amount being too low for Stripe.
diff --git a/src/util/types.js b/src/util/types.js
index b06a3962..cd4ca53f 100644
--- a/src/util/types.js
+++ b/src/util/types.js
@@ -432,6 +432,8 @@ export const ERROR_CODE_TRANSACTION_ALREADY_REVIEWED_BY_CUSTOMER =
'transaction-already-reviewed-by-customer';
export const ERROR_CODE_TRANSACTION_ALREADY_REVIEWED_BY_PROVIDER =
'transaction-already-reviewed-by-provider';
+export const ERROR_CODE_TRANSACTION_BOOKING_TIME_NOT_AVAILABLE =
+ 'transaction-booking-time-not-available';
export const ERROR_CODE_PAYMENT_FAILED = 'transaction-payment-failed';
export const ERROR_CODE_EMAIL_TAKEN = 'email-taken';
export const ERROR_CODE_EMAIL_NOT_FOUND = 'email-not-found';
From 4449a071ecec0a5de8e7a142af1ba0dadd209115 Mon Sep 17 00:00:00 2001
From: Hannu Lyytikainen
Date: Wed, 8 Aug 2018 18:52:51 +0300
Subject: [PATCH 04/11] Update changelog
---
CHANGELOG.md | 3 +++
1 file changed, 3 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6865f1ac..d324eebc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,9 @@ way to update this template, but currently, we follow a pattern:
---
## Upcoming version 2018-08-XX
+* [add] Listing availability
+ [#868](https://github.com/sharetribe/flex-template-web/pull/868), [#873](https://github.com/sharetribe/flex-template-web/pull/873), [#891](https://github.com/sharetribe/flex-template-web/pull/891) & [#892](https://github.com/sharetribe/flex-template-web/pull/892)
+
## v1.3.2 2018-08-07
* [change] Update the Sharetribe Flex SDK to the 1.0.0 version in NPM.
From 5e1aa7b244893795dde771675496f37e04606ef7 Mon Sep 17 00:00:00 2001
From: Hannu Lyytikainen
Date: Fri, 10 Aug 2018 08:37:21 +0300
Subject: [PATCH 05/11] Add error handling for initiate request
---
src/containers/CheckoutPage/CheckoutPage.js | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/src/containers/CheckoutPage/CheckoutPage.js b/src/containers/CheckoutPage/CheckoutPage.js
index 7a3f91e6..a1d2597d 100644
--- a/src/containers/CheckoutPage/CheckoutPage.js
+++ b/src/containers/CheckoutPage/CheckoutPage.js
@@ -270,6 +270,10 @@ export class CheckoutPageComponent extends Component {
);
const isAmountTooLowError = isTransactionInitiateAmountTooLowError(initiateOrderError);
+ const isBookingTimeNotAvailableError = isTransactionInitiateBookingTimeNotAvailableError(
+ initiateOrderError
+ );
+
let initiateOrderErrorMessage = null;
if (!listingNotFound && isAmountTooLowError) {
@@ -278,6 +282,12 @@ export class CheckoutPageComponent extends Component {
);
+ } else if (!listingNotFound && isBookingTimeNotAvailableError) {
+ initiateOrderErrorMessage = (
+
+
+
+ );
} else if (!listingNotFound && initiateOrderError) {
initiateOrderErrorMessage = (
From 19dd70c4334a065d4d7f16f0ac1e1a7fe0dcd278 Mon Sep 17 00:00:00 2001
From: Hannu Lyytikainen
Date: Fri, 10 Aug 2018 08:55:47 +0300
Subject: [PATCH 06/11] Enable availability by default
---
src/config.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/config.js b/src/config.js
index 3b94037a..7b219c62 100644
--- a/src/config.js
+++ b/src/config.js
@@ -38,7 +38,7 @@ const bookingUnitType = 'line-item/night';
// Should the application fetch available time slots (currently defined as
// start and end dates) to be shown on listing page.
-const fetchAvailableTimeSlots = false;
+const fetchAvailableTimeSlots = true;
// A maximum number of days forwards during which a booking can be made.
// This is limited due to Stripe holding funds up to 90 days from the
From b43a97f0db96f9f8ef041dbf7e23b5cef5ad6dca Mon Sep 17 00:00:00 2001
From: Hannu Lyytikainen
Date: Fri, 10 Aug 2018 09:34:11 +0300
Subject: [PATCH 07/11] Remove an unused function
---
.../FieldDateRangeInput/DateRangeInput.helpers.js | 10 ----------
1 file changed, 10 deletions(-)
diff --git a/src/components/FieldDateRangeInput/DateRangeInput.helpers.js b/src/components/FieldDateRangeInput/DateRangeInput.helpers.js
index 8d2d5f13..413ed0fd 100644
--- a/src/components/FieldDateRangeInput/DateRangeInput.helpers.js
+++ b/src/components/FieldDateRangeInput/DateRangeInput.helpers.js
@@ -28,16 +28,6 @@ const timeSlotsContain = (timeSlots, date) => {
return timeSlots.findIndex(slot => timeSlotEqualsDay(slot, date)) > -1;
};
-const lastBlockedBetweenExclusive = (timeSlots, startDate, endDate) => {
- if (startDate.isSame(endDate, 'date')) {
- return null;
- }
-
- return timeSlotsContain(timeSlots, endDate)
- ? lastBlockedBetweenExclusive(timeSlots, startDate, moment(endDate).subtract(1, 'days'))
- : endDate;
-};
-
/**
* Find first blocked date between two dates.
* If none is found, null is returned.
From 2c0cb83b5b6145a97d78441c9f626c1770634943 Mon Sep 17 00:00:00 2001
From: Hannu Lyytikainen
Date: Fri, 10 Aug 2018 14:52:08 +0300
Subject: [PATCH 08/11] Add an example for day based bookings
---
.../FieldDateRangeInput.example.js | 45 +++++++++++++++++--
1 file changed, 41 insertions(+), 4 deletions(-)
diff --git a/src/components/FieldDateRangeInput/FieldDateRangeInput.example.js b/src/components/FieldDateRangeInput/FieldDateRangeInput.example.js
index 699c0669..28a1b194 100644
--- a/src/components/FieldDateRangeInput/FieldDateRangeInput.example.js
+++ b/src/components/FieldDateRangeInput/FieldDateRangeInput.example.js
@@ -4,7 +4,7 @@ import { Form as FinalForm, FormSpy } from 'react-final-form';
import moment from 'moment';
import { Button } from '../../components';
import { required, bookingDatesRequired, composeValidators } from '../../util/validators';
-import { LINE_ITEM_NIGHT } from '../../util/types';
+import { LINE_ITEM_NIGHT, LINE_ITEM_DAY } from '../../util/types';
import { createTimeSlots } from '../../util/test-data';
import FieldDateRangeInput from './FieldDateRangeInput';
@@ -62,7 +62,7 @@ export const Empty = {
dateInputProps: {
name: 'bookingDates',
unitType: LINE_ITEM_NIGHT,
- startDateId: 'EmptyDateRangeInputForm.bookingStartDate',
+ startDateId: 'EmptyDateRange.bookingStartDate',
startDateLabel: 'Start date',
startDatePlaceholderText: moment().format('ddd, MMMM D'),
endDateId: 'EmptyDateRangeInputForm.bookingEndDate',
@@ -91,13 +91,50 @@ export const Empty = {
group: 'custom inputs',
};
-export const WithAvailableTimeSlots = {
+export const WithAvailableTimeSlotsNighlyBooking = {
component: FormComponent,
props: {
+ style: { marginBottom: '140px' },
dateInputProps: {
name: 'bookingDates',
unitType: LINE_ITEM_NIGHT,
- startDateId: 'WithAvailableTimeSlotsDateRangeInputForm.bookingStartDate',
+ startDateId: 'WithAvailableTimeSlotsDateRangeNightly.bookingStartDate',
+ startDateLabel: 'Start date',
+ startDatePlaceholderText: moment().format('ddd, MMMM D'),
+ endDateId: 'WithAvailableTimeSlotsDateRangeInputForm.bookingEndDate',
+ endDateLabel: 'End date',
+ endDatePlaceholderText: moment()
+ .add(1, 'days')
+ .format('ddd, MMMM D'),
+ format: null,
+ timeSlots: createAvailableTimeSlots(90, 60),
+ validate: composeValidators(
+ required('Required'),
+ bookingDatesRequired('Start date is not valid', 'End date is not valid')
+ ),
+ onBlur: () => console.log('onBlur called from DateRangeInput props.'),
+ onFocus: () => console.log('onFocus called from DateRangeInput props.'),
+ },
+ onChange: formState => {
+ const { startDate, endDate } = formState.values;
+ if (startDate || endDate) {
+ console.log('Changed to', moment(startDate).format('L'), moment(endDate).format('L'));
+ }
+ },
+ onSubmit: values => {
+ console.log('Submitting a form with values:', values);
+ },
+ },
+ group: 'custom inputs',
+};
+
+export const WithAvailableTimeSlotsDailyBooking = {
+ component: FormComponent,
+ props: {
+ dateInputProps: {
+ name: 'bookingDates',
+ unitType: LINE_ITEM_DAY,
+ startDateId: 'WithAvailableTimeSlotsDateRangeDaily.bookingStartDate',
startDateLabel: 'Start date',
startDatePlaceholderText: moment().format('ddd, MMMM D'),
endDateId: 'WithAvailableTimeSlotsDateRangeInputForm.bookingEndDate',
From e659ac480987affd55ffc43d82678ff4ecac1588 Mon Sep 17 00:00:00 2001
From: Hannu Lyytikainen
Date: Mon, 13 Aug 2018 13:00:57 +0300
Subject: [PATCH 09/11] Fix a bug in daily booking
Fixes a bug in daily booking where selected end date styles are not
cleared when updating start date with a date that is before the old
start date and a booked date can be found between those old and new
start dates.
---
.../DateRangeInput.helpers.js | 9 +---
.../FieldDateRangeInput/DateRangeInput.js | 48 +++++++++----------
2 files changed, 23 insertions(+), 34 deletions(-)
diff --git a/src/components/FieldDateRangeInput/DateRangeInput.helpers.js b/src/components/FieldDateRangeInput/DateRangeInput.helpers.js
index 413ed0fd..bfd4c4d3 100644
--- a/src/components/FieldDateRangeInput/DateRangeInput.helpers.js
+++ b/src/components/FieldDateRangeInput/DateRangeInput.helpers.js
@@ -132,14 +132,7 @@ export const isDayBlockedFn = (timeSlots, startDate, endDate, focusedInput) => {
* Returns an isOutsideRange function that can be passed to
* a react-dates DateRangePicker component.
*/
-export const isOutsideRangeFn = (
- timeSlots,
- startDate,
- endDate,
- previousStartDate,
- focusedInput,
- unitType
-) => {
+export const isOutsideRangeFn = (timeSlots, startDate, endDate, focusedInput, unitType) => {
const endOfRange = config.dayCountAvailableForBooking - 1;
const lastBookableDate = moment().add(endOfRange, 'days');
diff --git a/src/components/FieldDateRangeInput/DateRangeInput.js b/src/components/FieldDateRangeInput/DateRangeInput.js
index 27485f1f..666aaec7 100644
--- a/src/components/FieldDateRangeInput/DateRangeInput.js
+++ b/src/components/FieldDateRangeInput/DateRangeInput.js
@@ -123,7 +123,6 @@ class DateRangeInputComponent extends Component {
this.state = {
focusedInput: null,
currentStartDate: null,
- previousStartDate: null,
};
this.blurTimeoutId = null;
@@ -145,14 +144,29 @@ class DateRangeInputComponent extends Component {
}
onDatesChange(dates) {
- const { unitType } = this.props;
+ const { unitType, timeSlots } = this.props;
const { startDate, endDate } = dates;
- const startDateAsDate = startDate instanceof moment ? startDate.toDate() : null;
- const endDateAsDate = pickerEndDateToApiDate(unitType, endDate);
- this.setState(prevState => ({
+ // both dates are selected, a new start date before the previous start
+ // date is selected
+ const startDateUpdated =
+ timeSlots &&
+ startDate &&
+ endDate &&
+ this.state.currentStartDate &&
+ startDate.isBefore(this.state.currentStartDate);
+
+ // clear the end date in case a blocked date can be found
+ // between previous start date and new start date
+ const clearEndDate = startDateUpdated
+ ? isBlockedBetween(timeSlots, startDate, moment(this.state.currentStartDate).add(1, 'days'))
+ : false;
+
+ const startDateAsDate = startDate instanceof moment ? startDate.toDate() : null;
+ const endDateAsDate = clearEndDate ? null : pickerEndDateToApiDate(unitType, endDate);
+
+ this.setState(() => ({
currentStartDate: startDateAsDate,
- previousStartDate: prevState.currentStartDate,
}));
this.props.onChange({ startDate: startDateAsDate, endDate: endDateAsDate });
@@ -206,30 +220,12 @@ class DateRangeInputComponent extends Component {
const endDate =
apiEndDateToPickerDate(unitType, value ? value.endDate : null) || initialEndMoment;
- // both dates are selected, a new start date before the previous start
- // date is selected
- const startDateUpdated =
- timeSlots &&
- startDate &&
- endDate &&
- this.state.previousStartDate &&
- startDate.isBefore(this.state.previousStartDate);
-
- // clear the end date in case a blocked date can be found
- // between previous start date and new start date
- const clearEndDate = startDateUpdated
- ? isBlockedBetween(timeSlots, startDate, moment(this.state.previousStartDate).add(1, 'days'))
- : false;
-
- const endDateMaybe = clearEndDate ? null : endDate;
-
let isDayBlocked = isDayBlockedFn(timeSlots, startDate, endDate, this.state.focusedInput);
let isOutsideRange = isOutsideRangeFn(
timeSlots,
startDate,
- endDateMaybe,
- this.state.previousStartDate,
+ endDate,
this.state.focusedInput,
unitType
);
@@ -261,7 +257,7 @@ class DateRangeInputComponent extends Component {
focusedInput={this.state.focusedInput}
onFocusChange={this.onFocusChange}
startDate={startDate}
- endDate={endDateMaybe}
+ endDate={endDate}
minimumNights={isDaily ? 0 : 1}
onDatesChange={this.onDatesChange}
startDatePlaceholderText={startDatePlaceholderTxt}
From c282dc513d9959be6d00aca98c610819bd2d7f4d Mon Sep 17 00:00:00 2001
From: Hannu Lyytikainen
Date: Mon, 13 Aug 2018 14:43:17 +0300
Subject: [PATCH 10/11] Fix selecting end date first
Enable ending booking on the day another booking starts.
---
.../DateRangeInput.helpers.js | 33 ++++++++++++++++---
.../FieldDateRangeInput/DateRangeInput.js | 8 ++++-
2 files changed, 35 insertions(+), 6 deletions(-)
diff --git a/src/components/FieldDateRangeInput/DateRangeInput.helpers.js b/src/components/FieldDateRangeInput/DateRangeInput.helpers.js
index bfd4c4d3..c2fbc745 100644
--- a/src/components/FieldDateRangeInput/DateRangeInput.helpers.js
+++ b/src/components/FieldDateRangeInput/DateRangeInput.helpers.js
@@ -3,7 +3,7 @@ import { isSameDay, isInclusivelyAfterDay, isInclusivelyBeforeDay } from 'react-
import { ensureTimeSlot } from '../../util/data';
import { START_DATE, END_DATE, dateFromAPIToLocalNoon } from '../../util/dates';
-import { LINE_ITEM_DAY, TIME_SLOT_DAY } from '../../util/types';
+import { LINE_ITEM_DAY, LINE_ITEM_NIGHT, TIME_SLOT_DAY } from '../../util/types';
import config from '../../config';
// Checks if time slot (propTypes.timeSlot) start time equals a day (moment)
@@ -79,6 +79,9 @@ export const isBlockedBetween = (timeSlots, startDate, endDate) =>
export const isStartDateSelected = (timeSlots, startDate, endDate, focusedInput) =>
timeSlots && startDate && (!endDate || focusedInput === END_DATE) && focusedInput !== START_DATE;
+export const isSelectingEndDateNightly = (timeSlots, startDate, endDate, focusedInput, unitType) =>
+ timeSlots && !startDate && !endDate && focusedInput === END_DATE && unitType === LINE_ITEM_NIGHT;
+
export const apiEndDateToPickerDate = (unitType, endDate) => {
const isValid = endDate instanceof Date;
const isDaily = unitType === LINE_ITEM_DAY;
@@ -112,20 +115,40 @@ export const pickerEndDateToApiDate = (unitType, endDate) => {
* Returns an isDayBlocked function that can be passed to
* a react-dates DateRangePicker component.
*/
-export const isDayBlockedFn = (timeSlots, startDate, endDate, focusedInput) => {
+export const isDayBlockedFn = (timeSlots, startDate, endDate, focusedInput, unitType) => {
const endOfRange = config.dayCountAvailableForBooking - 1;
const lastBookableDate = moment().add(endOfRange, 'days');
// start date selected, end date missing
const startDateSelected = isStartDateSelected(timeSlots, startDate, endDate, focusedInput);
+ // find the next booking after a start date
const nextBookingStarts = startDateSelected
? firstBlockedBetween(timeSlots, startDate, moment(lastBookableDate).add(1, 'days'))
: null;
- return nextBookingStarts || !timeSlots
- ? () => false
- : day => !timeSlots.find(timeSlot => timeSlotEqualsDay(timeSlot, day));
+ // end date is focused but no dates are selected
+ const selectingEndDate = isSelectingEndDateNightly(
+ timeSlots,
+ startDate,
+ endDate,
+ focusedInput,
+ unitType
+ );
+
+ if (selectingEndDate) {
+ // if end date is being selected first, block the day after a
+ // booked date as a booking can end on the day the following booking starts
+ return day =>
+ !timeSlots.find(timeSlot => timeSlotEqualsDay(timeSlot, moment(day).subtract(1, 'days')));
+ } else if (nextBookingStarts || !timeSlots) {
+ // a next booking is found or time slots are not provided
+ // -> booking range handles blocking dates
+ return () => false;
+ } else {
+ // otherwise return standard timeslots check
+ return day => !timeSlots.find(timeSlot => timeSlotEqualsDay(timeSlot, day));
+ }
};
/**
diff --git a/src/components/FieldDateRangeInput/DateRangeInput.js b/src/components/FieldDateRangeInput/DateRangeInput.js
index 666aaec7..c3541e32 100644
--- a/src/components/FieldDateRangeInput/DateRangeInput.js
+++ b/src/components/FieldDateRangeInput/DateRangeInput.js
@@ -220,7 +220,13 @@ class DateRangeInputComponent extends Component {
const endDate =
apiEndDateToPickerDate(unitType, value ? value.endDate : null) || initialEndMoment;
- let isDayBlocked = isDayBlockedFn(timeSlots, startDate, endDate, this.state.focusedInput);
+ let isDayBlocked = isDayBlockedFn(
+ timeSlots,
+ startDate,
+ endDate,
+ this.state.focusedInput,
+ unitType
+ );
let isOutsideRange = isOutsideRangeFn(
timeSlots,
From cfd7494e2564c6a9a3d6a80fd6186db70b371218 Mon Sep 17 00:00:00 2001
From: Hannu Lyytikainen
Date: Mon, 13 Aug 2018 15:16:31 +0300
Subject: [PATCH 11/11] Improve comments
---
src/components/FieldDateRangeInput/DateRangeInput.helpers.js | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/components/FieldDateRangeInput/DateRangeInput.helpers.js b/src/components/FieldDateRangeInput/DateRangeInput.helpers.js
index c2fbc745..1c540192 100644
--- a/src/components/FieldDateRangeInput/DateRangeInput.helpers.js
+++ b/src/components/FieldDateRangeInput/DateRangeInput.helpers.js
@@ -111,6 +111,7 @@ export const pickerEndDateToApiDate = (unitType, endDate) => {
return endDate.toDate();
}
};
+
/**
* Returns an isDayBlocked function that can be passed to
* a react-dates DateRangePicker component.
@@ -137,8 +138,8 @@ export const isDayBlockedFn = (timeSlots, startDate, endDate, focusedInput, unit
);
if (selectingEndDate) {
- // if end date is being selected first, block the day after a
- // booked date as a booking can end on the day the following booking starts
+ // if end date is being selected first, block the day after a booked date
+ // (as a booking can end on the day the following booking starts)
return day =>
!timeSlots.find(timeSlot => timeSlotEqualsDay(timeSlot, moment(day).subtract(1, 'days')));
} else if (nextBookingStarts || !timeSlots) {