Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fetch availability data #868

Merged
merged 4 commits into from
Jul 23, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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