Skip to content

Commit

Permalink
Merge pull request #868 from sharetribe/fetch-availability-data
Browse files Browse the repository at this point in the history
Fetch availability data
  • Loading branch information
lyyder authored Jul 23, 2018
2 parents e619ea2 + 8c2033c commit 0d14e44
Show file tree
Hide file tree
Showing 8 changed files with 96 additions and 12 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
"redux-thunk": "^2.2.0",
"sanitize.css": "^5.0.0",
"sharetribe-scripts": "1.1.2",
"sharetribe-sdk": "https://github.com/sharetribe/sharetribe-sdk-js.git#b438be11d0f40f62e1a0388da745f6792420ddb1",
"sharetribe-sdk": "https://github.com/sharetribe/sharetribe-sdk-js.git#b7ffe0fe0a5bdb0a1471e2ff36cdd25b997fb39b",
"smoothscroll-polyfill": "^0.4.0",
"source-map-support": "^0.5.4",
"url": "^0.11.0"
Expand Down
6 changes: 2 additions & 4 deletions src/components/FieldDateInput/DateInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,9 @@ const defaultProps = {
enableOutsideDays: false,
isDayBlocked: () => false,

// Stripe holds funds in a reserve for up to 90 days from charge creation.
// outside range -><- today ... today+89 days -><- outside range
// outside range -><- today ... today+available days -1 -><- outside range
isOutsideRange: day => {
const daysCountAvailableToBook = 90;
const endOfRange = daysCountAvailableToBook - 1;
const endOfRange = config.dayCountAvailableForBooking - 1;
return (
!isInclusivelyAfterDay(day, moment()) ||
!isInclusivelyBeforeDay(day, moment().add(endOfRange, 'days'))
Expand Down
6 changes: 2 additions & 4 deletions src/components/FieldDateRangeInput/DateRangeInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,11 +117,9 @@ const defaultProps = {
enableOutsideDays: false,
isDayBlocked: () => false,

// Stripe holds funds in a reserve for up to 90 days from charge creation.
// outside range -><- today ... today+89 days -><- outside range
// outside range -><- today ... today+available days -1 -><- outside range
isOutsideRange: day => {
const daysCountAvailableToBook = 90;
const endOfRange = daysCountAvailableToBook - 1;
const endOfRange = config.dayCountAvailableForBooking - 1;
return (
!isInclusivelyAfterDay(day, moment()) ||
!isInclusivelyBeforeDay(day, moment().add(endOfRange, 'days'))
Expand Down
11 changes: 11 additions & 0 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ const bookingProcessAlias = 'preauth-with-nightly-booking/release-1';
// translations when the unit is changed.
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;

// 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
// moment they are charged.
const dayCountAvailableForBooking = 90;

// To pass environment variables to the client app in the build
// script, react-scripts (and the sharetribe-scripts fork of
// react-scripts) require using the REACT_APP_ prefix to avoid
Expand Down Expand Up @@ -313,6 +322,8 @@ const config = {
locale,
bookingProcessAlias,
bookingUnitType,
fetchAvailableTimeSlots,
dayCountAvailableForBooking,
i18n,
sdk: {
clientId: sdkClientId,
Expand Down
59 changes: 58 additions & 1 deletion src/containers/ListingPage/ListingPage.duck.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import pick from 'lodash/pick';
import moment from 'moment';
import config from '../../config';
import { types as sdkTypes } from '../../util/sdkLoader';
import { storableError } from '../../util/errors';
Expand All @@ -21,6 +22,10 @@ export const FETCH_REVIEWS_REQUEST = 'app/ListingPage/FETCH_REVIEWS_REQUEST';
export const FETCH_REVIEWS_SUCCESS = 'app/ListingPage/FETCH_REVIEWS_SUCCESS';
export const FETCH_REVIEWS_ERROR = 'app/ListingPage/FETCH_REVIEWS_ERROR';

export const FETCH_TIME_SLOTS_REQUEST = 'app/ListingPage/FETCH_TIME_SLOTS_REQUEST';
export const FETCH_TIME_SLOTS_SUCCESS = 'app/ListingPage/FETCH_TIME_SLOTS_SUCCESS';
export const FETCH_TIME_SLOTS_ERROR = 'app/ListingPage/FETCH_TIME_SLOTS_ERROR';

export const SEND_ENQUIRY_REQUEST = 'app/ListingPage/SEND_ENQUIRY_REQUEST';
export const SEND_ENQUIRY_SUCCESS = 'app/ListingPage/SEND_ENQUIRY_SUCCESS';
export const SEND_ENQUIRY_ERROR = 'app/ListingPage/SEND_ENQUIRY_ERROR';
Expand All @@ -32,6 +37,8 @@ const initialState = {
showListingError: null,
reviews: [],
fetchReviewsError: null,
timeSlots: [],
fetchTimesLotsError: null,
sendEnquiryInProgress: false,
sendEnquiryError: null,
enquiryModalOpenForListingId: null,
Expand All @@ -55,6 +62,13 @@ const listingPageReducer = (state = initialState, action = {}) => {
case FETCH_REVIEWS_ERROR:
return { ...state, fetchReviewsError: payload };

case FETCH_TIME_SLOTS_REQUEST:
return { ...state, fetchTimeSlotsError: null };
case FETCH_TIME_SLOTS_SUCCESS:
return { ...state, timeSlots: payload };
case FETCH_TIME_SLOTS_ERROR:
return { ...state, fetchTimeSlotsError: payload };

case SEND_ENQUIRY_REQUEST:
return { ...state, sendEnquiryInProgress: true, sendEnquiryError: null };
case SEND_ENQUIRY_SUCCESS:
Expand Down Expand Up @@ -95,6 +109,17 @@ export const fetchReviewsError = error => ({
payload: error,
});

export const fetchTimeSlotsRequest = () => ({ type: FETCH_TIME_SLOTS_REQUEST });
export const fetchTimeSlotsSuccess = timeSlots => ({
type: FETCH_TIME_SLOTS_SUCCESS,
payload: timeSlots,
});
export const fetchTimeSlotsError = error => ({
type: FETCH_TIME_SLOTS_ERROR,
error: true,
payload: error,
});

export const sendEnquiryRequest = () => ({ type: SEND_ENQUIRY_REQUEST });
export const sendEnquirySuccess = () => ({ type: SEND_ENQUIRY_SUCCESS });
export const sendEnquiryError = e => ({ type: SEND_ENQUIRY_ERROR, error: true, payload: e });
Expand Down Expand Up @@ -143,6 +168,7 @@ export const showListing = (listingId, isOwn = false) => (dispatch, getState, sd
};

export const fetchReviews = listingId => (dispatch, getState, sdk) => {
dispatch(fetchReviewsRequest);
return sdk.reviews
.query({
listing_id: listingId,
Expand All @@ -159,6 +185,19 @@ export const fetchReviews = listingId => (dispatch, getState, sdk) => {
});
};

export const fetchTimeSlots = params => (dispatch, getState, sdk) => {
dispatch(fetchTimeSlotsRequest);
return sdk.timeslots
.query(params)
.then(response => {
const timeSlots = denormalisedResponseEntities(response);
dispatch(fetchTimeSlotsSuccess(timeSlots));
})
.catch(e => {
dispatch(fetchTimeSlotsError(storableError(e)));
});
};

export const sendEnquiry = (listingId, message) => (dispatch, getState, sdk) => {
dispatch(sendEnquiryRequest());
const bodyParams = {
Expand Down Expand Up @@ -190,5 +229,23 @@ export const loadData = (params, search) => dispatch => {
if (params.variant === LISTING_PAGE_PENDING_APPROVAL_VARIANT) {
return dispatch(showListing(listingId, true));
}
return Promise.all([dispatch(showListing(listingId)), dispatch(fetchReviews(listingId))]);

if (config.fetchAvailableTimeSlots) {
// fetch time slots for 90 days starting today
// as the booking can only be done for 90 days
// in the future due to Stripe limitations
const start = moment().toDate();
const end = moment()
.add(config.dayCountAvailableForBooking, 'days')
.toDate();
const timeSlotsParams = { listingId, start, end };

return Promise.all([
dispatch(showListing(listingId)),
dispatch(fetchTimeSlots(timeSlotsParams)),
dispatch(fetchReviews(listingId)),
]);
} else {
return Promise.all([dispatch(showListing(listingId)), dispatch(fetchReviews(listingId))]);
}
};
8 changes: 8 additions & 0 deletions src/containers/ListingPage/ListingPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,8 @@ ListingPageComponent.defaultProps = {
showListingError: null,
reviews: [],
fetchReviewsError: null,
timeSlots: [],
fetchTimeSlotsError: null,
sendEnquiryError: null,
categoriesConfig: config.custom.categories,
amenitiesConfig: config.custom.amenities,
Expand Down Expand Up @@ -532,6 +534,8 @@ ListingPageComponent.propTypes = {
useInitialValues: func.isRequired,
reviews: arrayOf(propTypes.review),
fetchReviewsError: propTypes.error,
timeSlots: arrayOf(propTypes.timeSlot),
fetchTimeSlotsError: propTypes.error,
sendEnquiryInProgress: bool.isRequired,
sendEnquiryError: propTypes.error,
onSendEnquiry: func.isRequired,
Expand All @@ -546,6 +550,8 @@ const mapStateToProps = state => {
showListingError,
reviews,
fetchReviewsError,
timeSlots,
fetchTimeSlotsError,
sendEnquiryInProgress,
sendEnquiryError,
enquiryModalOpenForListingId,
Expand Down Expand Up @@ -574,6 +580,8 @@ const mapStateToProps = state => {
showListingError,
reviews,
fetchReviewsError,
timeSlots,
fetchTimeSlotsError,
sendEnquiryInProgress,
sendEnquiryError,
};
Expand Down
12 changes: 12 additions & 0 deletions src/util/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,16 @@ propTypes.booking = shape({
}),
});

// Denormalised time slot object
propTypes.timeSlot = shape({
id: propTypes.uuid.isRequired,
type: propTypes.value('timeSlot').isRequired,
attributes: shape({
end: instanceOf(Date).isRequired,
start: instanceOf(Date).isRequired,
}),
});

// When a customer makes a booking to a listing, a transaction is
// created with the initial request transition.
export const TRANSITION_REQUEST = 'transition/request';
Expand Down Expand Up @@ -425,6 +435,7 @@ export const ERROR_CODE_EMAIL_NOT_VERIFIED = 'email-unverified';
export const ERROR_CODE_TOO_MANY_VERIFICATION_REQUESTS = 'email-too-many-verification-requests';
export const ERROR_CODE_UPLOAD_OVER_LIMIT = 'request-upload-over-limit';
export const ERROR_CODE_VALIDATION_INVALID_PARAMS = 'validation-invalid-params';
export const ERROR_CODE_VALIDATION_INVALID_VALUE = 'validation-invalid-value';
export const ERROR_CODE_NOT_FOUND = 'not-found';
export const ERROR_CODE_FORBIDDEN = 'forbidden';
export const ERROR_CODE_MISSING_STRIPE_ACCOUNT = 'transaction-missing-stripe-account';
Expand All @@ -441,6 +452,7 @@ const ERROR_CODES = [
ERROR_CODE_TOO_MANY_VERIFICATION_REQUESTS,
ERROR_CODE_UPLOAD_OVER_LIMIT,
ERROR_CODE_VALIDATION_INVALID_PARAMS,
ERROR_CODE_VALIDATION_INVALID_VALUE,
ERROR_CODE_NOT_FOUND,
ERROR_CODE_FORBIDDEN,
ERROR_CODE_MISSING_STRIPE_ACCOUNT,
Expand Down
4 changes: 2 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7574,9 +7574,9 @@ [email protected]:
optionalDependencies:
fsevents "^1.1.3"

"sharetribe-sdk@https://github.com/sharetribe/sharetribe-sdk-js.git#b438be11d0f40f62e1a0388da745f6792420ddb1":
"sharetribe-sdk@https://github.com/sharetribe/sharetribe-sdk-js.git#b7ffe0fe0a5bdb0a1471e2ff36cdd25b997fb39b":
version "0.0.1"
resolved "https://github.com/sharetribe/sharetribe-sdk-js.git#b438be11d0f40f62e1a0388da745f6792420ddb1"
resolved "https://github.com/sharetribe/sharetribe-sdk-js.git#b7ffe0fe0a5bdb0a1471e2ff36cdd25b997fb39b"
dependencies:
axios "^0.15.3"
js-cookie "^2.1.3"
Expand Down

0 comments on commit 0d14e44

Please sign in to comment.