`;
-exports[`TransactionPanel - Order enquired matches snapshot 1`] = `
+exports[`TransactionPanel - Sale accepted matches snapshot 1`] = `
@@ -5805,8 +9308,8 @@ exports[`TransactionPanel - Order enquired matches snapshot 1`] = `
Object {
"attributes": Object {
"createdAt": 2017-05-01T00:00:00.000Z,
- "lastTransition": "transition/enquire",
- "lastTransitionedAt": 2017-06-01T00:00:00.000Z,
+ "lastTransition": "transition/accept",
+ "lastTransitionedAt": 2017-06-10T00:00:00.000Z,
"lineItems": Array [
Object {
"code": "line-item/night",
@@ -5831,12 +9334,12 @@ exports[`TransactionPanel - Order enquired matches snapshot 1`] = `
"provider",
],
"lineTotal": Money {
- "amount": -100,
+ "amount": -1000,
"currency": "USD",
},
"reversal": false,
"unitPrice": Money {
- "amount": -100,
+ "amount": -1000,
"currency": "USD",
},
},
@@ -5846,7 +9349,7 @@ exports[`TransactionPanel - Order enquired matches snapshot 1`] = `
"currency": "USD",
},
"payoutTotal": Money {
- "amount": 16400,
+ "amount": 15500,
"currency": "USD",
},
"transitions": Array [
@@ -5886,7 +9389,7 @@ exports[`TransactionPanel - Order enquired matches snapshot 1`] = `
"type": "user",
},
"id": UUID {
- "uuid": "order-enquired",
+ "uuid": "sale-accepted",
},
"listing": Object {
"attributes": Object {
@@ -5936,8 +9439,8 @@ exports[`TransactionPanel - Order enquired matches snapshot 1`] = `
Object {
"attributes": Object {
"createdAt": 2017-05-01T00:00:00.000Z,
- "lastTransition": "transition/enquire",
- "lastTransitionedAt": 2017-06-01T00:00:00.000Z,
+ "lastTransition": "transition/accept",
+ "lastTransitionedAt": 2017-06-10T00:00:00.000Z,
"lineItems": Array [
Object {
"code": "line-item/night",
@@ -5962,12 +9465,12 @@ exports[`TransactionPanel - Order enquired matches snapshot 1`] = `
"provider",
],
"lineTotal": Money {
- "amount": -100,
+ "amount": -1000,
"currency": "USD",
},
"reversal": false,
"unitPrice": Money {
- "amount": -100,
+ "amount": -1000,
"currency": "USD",
},
},
@@ -5977,7 +9480,7 @@ exports[`TransactionPanel - Order enquired matches snapshot 1`] = `
"currency": "USD",
},
"payoutTotal": Money {
- "amount": 16400,
+ "amount": 15500,
"currency": "USD",
},
"transitions": Array [
@@ -6017,7 +9520,7 @@ exports[`TransactionPanel - Order enquired matches snapshot 1`] = `
"type": "user",
},
"id": UUID {
- "uuid": "order-enquired",
+ "uuid": "sale-accepted",
},
"listing": Object {
"attributes": Object {
@@ -6089,8 +9592,8 @@ exports[`TransactionPanel - Order enquired matches snapshot 1`] = `
Object {
"attributes": Object {
"createdAt": 2017-05-01T00:00:00.000Z,
- "lastTransition": "transition/enquire",
- "lastTransitionedAt": 2017-06-01T00:00:00.000Z,
+ "lastTransition": "transition/accept",
+ "lastTransitionedAt": 2017-06-10T00:00:00.000Z,
"lineItems": Array [
Object {
"code": "line-item/night",
@@ -6115,12 +9618,12 @@ exports[`TransactionPanel - Order enquired matches snapshot 1`] = `
"provider",
],
"lineTotal": Money {
- "amount": -100,
+ "amount": -1000,
"currency": "USD",
},
"reversal": false,
"unitPrice": Money {
- "amount": -100,
+ "amount": -1000,
"currency": "USD",
},
},
@@ -6130,7 +9633,7 @@ exports[`TransactionPanel - Order enquired matches snapshot 1`] = `
"currency": "USD",
},
"payoutTotal": Money {
- "amount": 16400,
+ "amount": 15500,
"currency": "USD",
},
"transitions": Array [
@@ -6170,7 +9673,7 @@ exports[`TransactionPanel - Order enquired matches snapshot 1`] = `
"type": "user",
},
"id": UUID {
- "uuid": "order-enquired",
+ "uuid": "sale-accepted",
},
"listing": Object {
"attributes": Object {
@@ -6217,8 +9720,8 @@ exports[`TransactionPanel - Order enquired matches snapshot 1`] = `
Object {
"attributes": Object {
"createdAt": 2017-05-01T00:00:00.000Z,
- "lastTransition": "transition/enquire",
- "lastTransitionedAt": 2017-06-01T00:00:00.000Z,
+ "lastTransition": "transition/accept",
+ "lastTransitionedAt": 2017-06-10T00:00:00.000Z,
"lineItems": Array [
Object {
"code": "line-item/night",
@@ -6243,12 +9746,12 @@ exports[`TransactionPanel - Order enquired matches snapshot 1`] = `
"provider",
],
"lineTotal": Money {
- "amount": -100,
+ "amount": -1000,
"currency": "USD",
},
"reversal": false,
"unitPrice": Money {
- "amount": -100,
+ "amount": -1000,
"currency": "USD",
},
},
@@ -6258,7 +9761,7 @@ exports[`TransactionPanel - Order enquired matches snapshot 1`] = `
"currency": "USD",
},
"payoutTotal": Money {
- "amount": 16400,
+ "amount": 15500,
"currency": "USD",
},
"transitions": Array [
@@ -6298,7 +9801,7 @@ exports[`TransactionPanel - Order enquired matches snapshot 1`] = `
"type": "user",
},
"id": UUID {
- "uuid": "order-enquired",
+ "uuid": "sale-accepted",
},
"listing": Object {
"attributes": Object {
@@ -6345,8 +9848,8 @@ exports[`TransactionPanel - Order enquired matches snapshot 1`] = `
Object {
"attributes": Object {
"createdAt": 2017-05-01T00:00:00.000Z,
- "lastTransition": "transition/enquire",
- "lastTransitionedAt": 2017-06-01T00:00:00.000Z,
+ "lastTransition": "transition/accept",
+ "lastTransitionedAt": 2017-06-10T00:00:00.000Z,
"lineItems": Array [
Object {
"code": "line-item/night",
@@ -6371,12 +9874,12 @@ exports[`TransactionPanel - Order enquired matches snapshot 1`] = `
"provider",
],
"lineTotal": Money {
- "amount": -100,
+ "amount": -1000,
"currency": "USD",
},
"reversal": false,
"unitPrice": Money {
- "amount": -100,
+ "amount": -1000,
"currency": "USD",
},
},
@@ -6386,7 +9889,7 @@ exports[`TransactionPanel - Order enquired matches snapshot 1`] = `
"currency": "USD",
},
"payoutTotal": Money {
- "amount": 16400,
+ "amount": 15500,
"currency": "USD",
},
"transitions": Array [
@@ -6426,7 +9929,7 @@ exports[`TransactionPanel - Order enquired matches snapshot 1`] = `
"type": "user",
},
"id": UUID {
- "uuid": "order-enquired",
+ "uuid": "sale-accepted",
},
"listing": Object {
"attributes": Object {
@@ -6470,18 +9973,18 @@ exports[`TransactionPanel - Order enquired matches snapshot 1`] = `
Object {
"attributes": Object {
"banned": false,
- "email": "customer@example.com",
+ "email": "provider@example.com",
"emailVerified": true,
"profile": Object {
- "abbreviatedName": "customer abbreviated name",
- "displayName": "customer display name",
- "firstName": "customer first name",
- "lastName": "customer last name",
+ "abbreviatedName": "provider abbreviated name",
+ "displayName": "provider display name",
+ "firstName": "provider first name",
+ "lastName": "provider last name",
},
"stripeConnected": true,
},
"id": UUID {
- "uuid": "customer",
+ "uuid": "provider",
},
"type": "currentUser",
}
@@ -6545,200 +10048,73 @@ exports[`TransactionPanel - Order enquired matches snapshot 1`] = `
}
oldestMessagePageFetched={1}
onOpenReviewModal={[Function]}
- onShowMoreMessages={[Function]}
- rootClassName=""
- totalMessagePages={1}
- />
-
-
-
-`;
-
-exports[`TransactionPanel - Order preauthorized matches snapshot 1`] = `
-
+`;
+
+exports[`TransactionPanel - Sale autodeclined matches snapshot 1`] = `
+
+
-`;
-
-exports[`TransactionPanel - Sale accepted matches snapshot 1`] = `
-
-
+`;
+
+exports[`TransactionPanel - Sale canceled matches snapshot 1`] = `
+
+
-`;
-
-exports[`TransactionPanel - Sale autodeclined matches snapshot 1`] = `
-
-
+`;
+
+exports[`TransactionPanel - Sale declined matches snapshot 1`] = `
+
+
-`;
-
-exports[`TransactionPanel - Sale canceled matches snapshot 1`] = `
-
-
-
-
-
-
+
+
+
-
-
-
-
-
-
+ }
+ />
`;
-exports[`TransactionPanel - Sale declined matches snapshot 1`] = `
+exports[`TransactionPanel - Sale delivered matches snapshot 1`] = `
@@ -11535,7 +14584,7 @@ exports[`TransactionPanel - Sale declined matches snapshot 1`] = `
Object {
"attributes": Object {
"createdAt": 2017-05-01T00:00:00.000Z,
- "lastTransition": "transition/decline",
+ "lastTransition": "transition/complete",
"lastTransitionedAt": 2017-06-10T00:00:00.000Z,
"lineItems": Array [
Object {
@@ -11616,7 +14665,7 @@ exports[`TransactionPanel - Sale declined matches snapshot 1`] = `
"type": "user",
},
"id": UUID {
- "uuid": "sale-declined",
+ "uuid": "sale-delivered",
},
"listing": Object {
"attributes": Object {
@@ -11666,7 +14715,7 @@ exports[`TransactionPanel - Sale declined matches snapshot 1`] = `
Object {
"attributes": Object {
"createdAt": 2017-05-01T00:00:00.000Z,
- "lastTransition": "transition/decline",
+ "lastTransition": "transition/complete",
"lastTransitionedAt": 2017-06-10T00:00:00.000Z,
"lineItems": Array [
Object {
@@ -11747,7 +14796,7 @@ exports[`TransactionPanel - Sale declined matches snapshot 1`] = `
"type": "user",
},
"id": UUID {
- "uuid": "sale-declined",
+ "uuid": "sale-delivered",
},
"listing": Object {
"attributes": Object {
@@ -11819,7 +14868,7 @@ exports[`TransactionPanel - Sale declined matches snapshot 1`] = `
Object {
"attributes": Object {
"createdAt": 2017-05-01T00:00:00.000Z,
- "lastTransition": "transition/decline",
+ "lastTransition": "transition/complete",
"lastTransitionedAt": 2017-06-10T00:00:00.000Z,
"lineItems": Array [
Object {
@@ -11900,7 +14949,7 @@ exports[`TransactionPanel - Sale declined matches snapshot 1`] = `
"type": "user",
},
"id": UUID {
- "uuid": "sale-declined",
+ "uuid": "sale-delivered",
},
"listing": Object {
"attributes": Object {
@@ -11947,7 +14996,7 @@ exports[`TransactionPanel - Sale declined matches snapshot 1`] = `
Object {
"attributes": Object {
"createdAt": 2017-05-01T00:00:00.000Z,
- "lastTransition": "transition/decline",
+ "lastTransition": "transition/complete",
"lastTransitionedAt": 2017-06-10T00:00:00.000Z,
"lineItems": Array [
Object {
@@ -12028,7 +15077,7 @@ exports[`TransactionPanel - Sale declined matches snapshot 1`] = `
"type": "user",
},
"id": UUID {
- "uuid": "sale-declined",
+ "uuid": "sale-delivered",
},
"listing": Object {
"attributes": Object {
@@ -12075,7 +15124,7 @@ exports[`TransactionPanel - Sale declined matches snapshot 1`] = `
Object {
"attributes": Object {
"createdAt": 2017-05-01T00:00:00.000Z,
- "lastTransition": "transition/decline",
+ "lastTransition": "transition/complete",
"lastTransitionedAt": 2017-06-10T00:00:00.000Z,
"lineItems": Array [
Object {
@@ -12156,7 +15205,7 @@ exports[`TransactionPanel - Sale declined matches snapshot 1`] = `
"type": "user",
},
"id": UUID {
- "uuid": "sale-declined",
+ "uuid": "sale-delivered",
},
"listing": Object {
"attributes": Object {
@@ -12309,10 +15358,121 @@ exports[`TransactionPanel - Sale declined matches snapshot 1`] = `
/>
-
-
+
-
+ }
+ />
`;
-exports[`TransactionPanel - Sale delivered matches snapshot 1`] = `
+exports[`TransactionPanel - Sale enquired matches snapshot 1`] = `
@@ -12681,7 +15903,7 @@ exports[`TransactionPanel - Sale delivered matches snapshot 1`] = `
Object {
"attributes": Object {
"createdAt": 2017-05-01T00:00:00.000Z,
- "lastTransition": "transition/complete",
+ "lastTransition": "transition/enquire",
"lastTransitionedAt": 2017-06-10T00:00:00.000Z,
"lineItems": Array [
Object {
@@ -12762,7 +15984,7 @@ exports[`TransactionPanel - Sale delivered matches snapshot 1`] = `
"type": "user",
},
"id": UUID {
- "uuid": "sale-delivered",
+ "uuid": "sale-enquired",
},
"listing": Object {
"attributes": Object {
@@ -12812,7 +16034,7 @@ exports[`TransactionPanel - Sale delivered matches snapshot 1`] = `
Object {
"attributes": Object {
"createdAt": 2017-05-01T00:00:00.000Z,
- "lastTransition": "transition/complete",
+ "lastTransition": "transition/enquire",
"lastTransitionedAt": 2017-06-10T00:00:00.000Z,
"lineItems": Array [
Object {
@@ -12893,7 +16115,7 @@ exports[`TransactionPanel - Sale delivered matches snapshot 1`] = `
"type": "user",
},
"id": UUID {
- "uuid": "sale-delivered",
+ "uuid": "sale-enquired",
},
"listing": Object {
"attributes": Object {
@@ -12965,7 +16187,7 @@ exports[`TransactionPanel - Sale delivered matches snapshot 1`] = `
Object {
"attributes": Object {
"createdAt": 2017-05-01T00:00:00.000Z,
- "lastTransition": "transition/complete",
+ "lastTransition": "transition/enquire",
"lastTransitionedAt": 2017-06-10T00:00:00.000Z,
"lineItems": Array [
Object {
@@ -13046,7 +16268,7 @@ exports[`TransactionPanel - Sale delivered matches snapshot 1`] = `
"type": "user",
},
"id": UUID {
- "uuid": "sale-delivered",
+ "uuid": "sale-enquired",
},
"listing": Object {
"attributes": Object {
@@ -13093,7 +16315,7 @@ exports[`TransactionPanel - Sale delivered matches snapshot 1`] = `
Object {
"attributes": Object {
"createdAt": 2017-05-01T00:00:00.000Z,
- "lastTransition": "transition/complete",
+ "lastTransition": "transition/enquire",
"lastTransitionedAt": 2017-06-10T00:00:00.000Z,
"lineItems": Array [
Object {
@@ -13174,7 +16396,7 @@ exports[`TransactionPanel - Sale delivered matches snapshot 1`] = `
"type": "user",
},
"id": UUID {
- "uuid": "sale-delivered",
+ "uuid": "sale-enquired",
},
"listing": Object {
"attributes": Object {
@@ -13221,7 +16443,7 @@ exports[`TransactionPanel - Sale delivered matches snapshot 1`] = `
Object {
"attributes": Object {
"createdAt": 2017-05-01T00:00:00.000Z,
- "lastTransition": "transition/complete",
+ "lastTransition": "transition/enquire",
"lastTransitionedAt": 2017-06-10T00:00:00.000Z,
"lineItems": Array [
Object {
@@ -13302,7 +16524,7 @@ exports[`TransactionPanel - Sale delivered matches snapshot 1`] = `
"type": "user",
},
"id": UUID {
- "uuid": "sale-delivered",
+ "uuid": "sale-enquired",
},
"listing": Object {
"attributes": Object {
@@ -13416,204 +16638,77 @@ exports[`TransactionPanel - Sale delivered matches snapshot 1`] = `
"type": "user",
},
"type": "message",
- },
- ]
- }
- oldestMessagePageFetched={1}
- onOpenReviewModal={[Function]}
- onShowMoreMessages={[Function]}
- rootClassName=""
- totalMessagePages={1}
- />
-
-
-
-`;
-
-exports[`TransactionPanel - Sale enquired matches snapshot 1`] = `
-
+`;
+
+exports[`TransactionPanel - Sale preauthorized matches snapshot 1`] = `
+
+
-`;
-
-exports[`TransactionPanel - Sale preauthorized matches snapshot 1`] = `
-
-
-
-
-
-
+
+
+
-
-
-
-
-
-
+ }
+ />
(dispatch, getStat
});
};
+/**
+ * Initiate an order after an enquiry. Transitions previously created transaction.
+ */
+export const initiateOrderAfterEnquiry = (transactionId, orderParams) => (
+ dispatch,
+ getState,
+ sdk
+) => {
+ dispatch(initiateOrderRequest());
+
+ const bodyParams = {
+ id: transactionId,
+ transition: TRANSITION_REQUEST_AFTER_ENQUIRY,
+ params: orderParams,
+ };
+
+ return sdk.transactions
+ .transition(bodyParams)
+ .then(response => {
+ const orderId = response.data.data.id;
+ dispatch(initiateOrderSuccess(orderId));
+ dispatch(fetchCurrentUserHasOrdersSuccess(true));
+ // set initialMessageSuccess to true to unify promise handling with initiateOrder
+ return Promise.resolve({ orderId, initialMessageSuccess: true });
+ })
+ .catch(e => {
+ dispatch(initiateOrderError(storableError(e)));
+ log.error(e, 'initiate-order-failed', {
+ transactionId: transactionId.uuid,
+ listingId: orderParams.listingId.uuid,
+ bookingStart: orderParams.bookingStart,
+ bookingEnd: orderParams.bookingEnd,
+ });
+ throw e;
+ });
+};
+
/**
* Initiate the speculative transaction with the given booking details
*
diff --git a/src/containers/CheckoutPage/CheckoutPage.js b/src/containers/CheckoutPage/CheckoutPage.js
index 4b3d8d63c5..030f949c3c 100644
--- a/src/containers/CheckoutPage/CheckoutPage.js
+++ b/src/containers/CheckoutPage/CheckoutPage.js
@@ -7,7 +7,7 @@ import { withRouter } from 'react-router-dom';
import classNames from 'classnames';
import routeConfiguration from '../../routeConfiguration';
import { pathByRouteName, findRouteByRouteName } from '../../util/routes';
-import { propTypes } from '../../util/types';
+import { propTypes, LINE_ITEM_NIGHT, LINE_ITEM_DAY } from '../../util/types';
import { ensureListing, ensureUser, ensureTransaction, ensureBooking } from '../../util/data';
import { dateFromLocalToAPI } from '../../util/dates';
import { createSlug } from '../../util/urlHelpers';
@@ -19,6 +19,7 @@ import {
isTransactionZeroPaymentError,
transactionInitiateOrderStripeErrors,
} from '../../util/errors';
+import { formatMoney } from '../../util/currency';
import {
AvatarMedium,
BookingBreakdown,
@@ -30,7 +31,12 @@ import {
} from '../../components';
import { StripePaymentForm } from '../../forms';
import { isScrollingDisabled } from '../../ducks/UI.duck';
-import { initiateOrder, setInitialValues, speculateTransaction } from './CheckoutPage.duck';
+import {
+ initiateOrder,
+ initiateOrderAfterEnquiry,
+ setInitialValues,
+ speculateTransaction,
+} from './CheckoutPage.duck';
import config from '../../config';
import { storeData, storedData, clearData } from './CheckoutPageSessionHelpers';
@@ -75,7 +81,14 @@ export class CheckoutPageComponent extends Component {
* based on this initial data.
*/
loadInitialData() {
- const { bookingData, bookingDates, listing, fetchSpeculatedTransaction, history } = this.props;
+ const {
+ bookingData,
+ bookingDates,
+ listing,
+ enquiredTransaction,
+ fetchSpeculatedTransaction,
+ history,
+ } = this.props;
// Browser's back navigation should not rewrite data in session store.
// Action is 'POP' on both history.back() and page refresh cases.
// Action is 'PUSH' when user has directed through a link
@@ -85,12 +98,12 @@ export class CheckoutPageComponent extends Component {
const hasDataInProps = !!(bookingData && bookingDates && listing) && hasNavigatedThroughLink;
if (hasDataInProps) {
// Store data only if data is passed through props and user has navigated through a link.
- storeData(bookingData, bookingDates, listing, STORAGE_KEY);
+ storeData(bookingData, bookingDates, listing, enquiredTransaction, STORAGE_KEY);
}
// NOTE: stored data can be empty if user has already successfully completed transaction.
const pageData = hasDataInProps
- ? { bookingData, bookingDates, listing }
+ ? { bookingData, bookingDates, listing, enquiredTransaction }
: storedData(STORAGE_KEY);
const hasData =
@@ -132,7 +145,13 @@ export class CheckoutPageComponent extends Component {
const cardToken = values.token;
const initialMessage = values.message;
- const { history, sendOrderRequest, speculatedTransaction, dispatch } = this.props;
+ const {
+ history,
+ sendOrderRequest,
+ sendOrderRequestAfterEnquiry,
+ speculatedTransaction,
+ dispatch,
+ } = this.props;
// Create order aka transaction
// NOTE: if unit type is line-item/units, quantity needs to be added.
@@ -144,7 +163,15 @@ export class CheckoutPageComponent extends Component {
bookingEnd: speculatedTransaction.booking.attributes.end,
};
- sendOrderRequest(requestParams, initialMessage)
+ const enquiredTransaction = this.state.pageData.enquiredTransaction;
+
+ // if an enquired transaction is available, use that as basis
+ // otherwise initiate a new transaction
+ const initiateRequest = enquiredTransaction
+ ? sendOrderRequestAfterEnquiry(enquiredTransaction.id, requestParams)
+ : sendOrderRequest(requestParams, initialMessage);
+
+ initiateRequest
.then(values => {
const { orderId, initialMessageSuccess } = values;
this.setState({ submitting: false });
@@ -193,7 +220,7 @@ export class CheckoutPageComponent extends Component {
const isLoading = !this.state.dataLoaded || speculateTransactionInProgress;
- const { listing, bookingDates } = this.state.pageData;
+ const { listing, bookingDates, enquiredTransaction } = this.state.pageData;
const currentTransaction = ensureTransaction(speculatedTransaction, {}, null);
const currentBooking = ensureBooking(currentTransaction.booking);
const currentListing = ensureListing(listing);
@@ -361,6 +388,22 @@ export class CheckoutPageComponent extends Component {
);
+ const unitType = config.bookingUnitType;
+ const isNightly = unitType === LINE_ITEM_NIGHT;
+ const isDaily = unitType === LINE_ITEM_DAY;
+
+ const unitTranslationKey = isNightly
+ ? 'CheckoutPage.perNight'
+ : isDaily
+ ? 'CheckoutPage.perDay'
+ : 'CheckoutPage.perUnit';
+
+ const price = currentListing.attributes.price;
+ const formattedPrice = formatMoney(intl, price);
+ const detailsSubTitle = `${formattedPrice} ${intl.formatMessage({ id: unitTranslationKey })}`;
+
+ const showInitialMessageInput = !enquiredTransaction;
+
const pageProps = { title, scrollingDisabled };
if (isLoading) {
@@ -420,6 +463,7 @@ export class CheckoutPageComponent extends Component {
formId="CheckoutPagePaymentForm"
paymentInfo={intl.formatMessage({ id: 'CheckoutPage.paymentInfo' })}
authorDisplayName={currentAuthor.attributes.profile.displayName}
+ showInitialMessageInput={showInitialMessageInput}
/>
) : null}
@@ -439,12 +483,7 @@ export class CheckoutPageComponent extends Component {
{listingTitle}
-
-
-
+
{detailsSubTitle}
@@ -465,6 +504,7 @@ CheckoutPageComponent.defaultProps = {
bookingDates: null,
speculateTransactionError: null,
speculatedTransaction: null,
+ enquiredTransaction: null,
currentUser: null,
};
@@ -480,6 +520,7 @@ CheckoutPageComponent.propTypes = {
speculateTransactionInProgress: bool.isRequired,
speculateTransactionError: propTypes.error,
speculatedTransaction: propTypes.transaction,
+ enquiredTransaction: propTypes.transaction,
initiateOrderError: propTypes.error,
currentUser: propTypes.currentUser,
params: shape({
@@ -508,6 +549,7 @@ const mapStateToProps = state => {
speculateTransactionInProgress,
speculateTransactionError,
speculatedTransaction,
+ enquiredTransaction,
initiateOrderError,
} = state.CheckoutPage;
const { currentUser } = state.user;
@@ -519,6 +561,7 @@ const mapStateToProps = state => {
speculateTransactionInProgress,
speculateTransactionError,
speculatedTransaction,
+ enquiredTransaction,
listing,
initiateOrderError,
};
@@ -527,6 +570,8 @@ const mapStateToProps = state => {
const mapDispatchToProps = dispatch => ({
dispatch,
sendOrderRequest: (params, initialMessage) => dispatch(initiateOrder(params, initialMessage)),
+ sendOrderRequestAfterEnquiry: (transactionId, params) =>
+ dispatch(initiateOrderAfterEnquiry(transactionId, params)),
fetchSpeculatedTransaction: params => dispatch(speculateTransaction(params)),
});
diff --git a/src/containers/CheckoutPage/CheckoutPage.test.js b/src/containers/CheckoutPage/CheckoutPage.test.js
index a04fddb8af..5cfab905cf 100644
--- a/src/containers/CheckoutPage/CheckoutPage.test.js
+++ b/src/containers/CheckoutPage/CheckoutPage.test.js
@@ -59,6 +59,7 @@ describe('CheckoutPage', () => {
speculateTransactionError: null,
speculateTransactionInProgress: false,
speculatedTransaction: null,
+ enquiredTransaction: null,
};
it('should return the initial state', () => {
diff --git a/src/containers/CheckoutPage/CheckoutPageSessionHelpers.js b/src/containers/CheckoutPage/CheckoutPageSessionHelpers.js
index 7195cb39f4..b74910087d 100644
--- a/src/containers/CheckoutPage/CheckoutPageSessionHelpers.js
+++ b/src/containers/CheckoutPage/CheckoutPageSessionHelpers.js
@@ -7,6 +7,7 @@
import moment from 'moment';
import reduce from 'lodash/reduce';
import { types as sdkTypes } from '../../util/sdkLoader';
+import { TRANSITION_ENQUIRE } from '../../util/types';
const { UUID, Money } = sdkTypes;
@@ -46,8 +47,20 @@ export const isValidListing = listing => {
return validateProperties(listing, props);
};
+// Validate content of an enquired transaction received from SessionStore.
+// An id is required and the last transition needs to be the enquire transition.
+export const isValidEnquiredTransaction = transaction => {
+ const props = {
+ id: id => id instanceof UUID,
+ attributes: v => {
+ return typeof v === 'object' && v.lastTransition === TRANSITION_ENQUIRE;
+ },
+ };
+ return validateProperties(transaction, props);
+};
+
// Stores given bookingDates and listing to sessionStorage
-export const storeData = (bookingData, bookingDates, listing, storageKey) => {
+export const storeData = (bookingData, bookingDates, listing, enquiredTransaction, storageKey) => {
if (window && window.sessionStorage && listing && bookingDates && bookingData) {
// TODO: How should we deal with Dates when data is serialized?
// Hard coded serializable date objects atm.
@@ -59,6 +72,7 @@ export const storeData = (bookingData, bookingDates, listing, storageKey) => {
bookingEnd: { date: bookingDates.bookingEnd, _serializedType: 'SerializableDate' },
},
listing,
+ enquiredTransaction,
storedAt: { date: new Date(), _serializedType: 'SerializableDate' },
};
/* eslint-enable no-underscore-dangle */
@@ -83,7 +97,7 @@ export const storedData = storageKey => {
return sdkTypes.reviver(k, v);
};
- const { bookingData, bookingDates, listing, storedAt } = checkoutPageData
+ const { bookingData, bookingDates, listing, enquiredTransaction, storedAt } = checkoutPageData
? JSON.parse(checkoutPageData, reviver)
: {};
@@ -92,8 +106,19 @@ export const storedData = storageKey => {
? moment(storedAt).isAfter(moment().subtract(1, 'days'))
: false;
- if (isFreshlySaved && isValidBookingDates(bookingDates) && isValidListing(listing)) {
- return { bookingData, bookingDates, listing };
+ // resolve enquired transaction as valid if it is missing
+ const isEnquiredTransactionValid = !!enquiredTransaction
+ ? isValidEnquiredTransaction(enquiredTransaction)
+ : true;
+
+ const isStoredDataValid =
+ isFreshlySaved &&
+ isValidBookingDates(bookingDates) &&
+ isValidListing(listing) &&
+ isEnquiredTransactionValid;
+
+ if (isStoredDataValid) {
+ return { bookingData, bookingDates, listing, enquiredTransaction };
}
}
return {};
diff --git a/src/containers/CheckoutPage/__snapshots__/CheckoutPage.test.js.snap b/src/containers/CheckoutPage/__snapshots__/CheckoutPage.test.js.snap
index 0e3759cee6..0002043f12 100644
--- a/src/containers/CheckoutPage/__snapshots__/CheckoutPage.test.js.snap
+++ b/src/containers/CheckoutPage/__snapshots__/CheckoutPage.test.js.snap
@@ -90,6 +90,7 @@ exports[`CheckoutPage matches snapshot 1`] = `
inProgress={false}
onSubmit={[Function]}
paymentInfo="CheckoutPage.paymentInfo"
+ showInitialMessageInput={true}
/>
@@ -134,14 +135,7 @@ exports[`CheckoutPage matches snapshot 1`] = `
listing1 title
-
+ 55 CheckoutPage.perNight
diff --git a/src/containers/ListingPage/ListingPage.js b/src/containers/ListingPage/ListingPage.js
index b5d5b5cb08..304549a889 100644
--- a/src/containers/ListingPage/ListingPage.js
+++ b/src/containers/ListingPage/ListingPage.js
@@ -442,7 +442,7 @@ export class ListingPageComponent extends Component {
listing={currentListing}
isOwnListing={isOwnListing}
unitType={unitType}
- handleBookingSubmit={handleBookingSubmit}
+ onSubmit={handleBookingSubmit}
title={bookingTitle}
subTitle={bookingSubTitle}
authorDisplayName={authorDisplayName}
diff --git a/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap b/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap
index e8df2d935c..03d7922570 100644
--- a/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap
+++ b/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap
@@ -265,7 +265,6 @@ exports[`ListingPage matches snapshot 1`] = `
({ type: SEND_REVIEW_REQUEST });
const sendReviewSuccess = () => ({ type: SEND_REVIEW_SUCCESS });
const sendReviewError = e => ({ type: SEND_REVIEW_ERROR, error: true, payload: e });
+const fetchTimeSlotsRequest = () => ({ type: FETCH_TIME_SLOTS_REQUEST });
+const fetchTimeSlotsSuccess = timeSlots => ({
+ type: FETCH_TIME_SLOTS_SUCCESS,
+ payload: timeSlots,
+});
+const fetchTimeSlotsError = e => ({
+ type: FETCH_TIME_SLOTS_ERROR,
+ error: true,
+ payload: e,
+});
+
// ================ Thunks ================ //
const listingRelationship = txResponse => {
return txResponse.data.data.relationships.listing.data;
};
-export const fetchTransaction = id => (dispatch, getState, sdk) => {
+export const fetchTransaction = (id, txRole) => (dispatch, getState, sdk) => {
dispatch(fetchTransactionRequest());
let txResponse = null;
@@ -234,11 +261,23 @@ export const fetchTransaction = id => (dispatch, getState, sdk) => {
const listingId = listingRelationship(response).id;
const entities = updatedEntities({}, response.data);
const listingRef = { id: listingId, type: 'listing' };
- const denormalised = denormalisedEntities(entities, [listingRef]);
+ const transactionRef = { id, type: 'transaction' };
+ const denormalised = denormalisedEntities(entities, [listingRef, transactionRef]);
const listing = denormalised[0];
+ const transaction = denormalised[1];
- const canFetchListing = listing && listing.attributes && !listing.attributes.deleted;
+ // Fetch time slots for transactions that are in enquired state
+ const canFetchTimeslots =
+ txRole === 'customer' &&
+ config.fetchAvailableTimeSlots &&
+ transaction &&
+ txIsEnquired(transaction);
+
+ if (canFetchTimeslots) {
+ dispatch(fetchTimeSlots(listingId));
+ }
+ const canFetchListing = listing && listing.attributes && !listing.attributes.deleted;
if (canFetchListing) {
return sdk.listings.show({
id: listingId,
@@ -475,12 +514,69 @@ const isNonEmpty = value => {
return typeof value === 'object' || Array.isArray(value) ? !isEmpty(value) : !!value;
};
+const timeSlotsRequest = params => (dispatch, getState, sdk) => {
+ return sdk.timeslots.query(params).then(response => {
+ return denormalisedResponseEntities(response);
+ });
+};
+
+const fetchTimeSlots = listingId => (dispatch, getState, sdk) => {
+ dispatch(fetchTimeSlotsRequest);
+
+ // Time slots can be fetched for 90 days at a time,
+ // for at most 180 days from now. If max number of bookable
+ // day exceeds 90, a second request is made.
+
+ const maxTimeSlots = 90;
+ // booking range: today + bookable days -1
+ const bookingRange = config.dayCountAvailableForBooking - 1;
+ const timeSlotsRange = Math.min(bookingRange, maxTimeSlots);
+
+ const start = moment
+ .utc()
+ .startOf('day')
+ .toDate();
+ const end = moment()
+ .utc()
+ .startOf('day')
+ .add(timeSlotsRange, 'days')
+ .toDate();
+ const params = { listingId, start, end };
+
+ return dispatch(timeSlotsRequest(params))
+ .then(timeSlots => {
+ const secondRequest = bookingRange > maxTimeSlots;
+
+ if (secondRequest) {
+ const secondRange = Math.min(maxTimeSlots, bookingRange - maxTimeSlots);
+ const secondParams = {
+ listingId,
+ start: end,
+ end: moment(end)
+ .add(secondRange, 'days')
+ .toDate(),
+ };
+
+ return dispatch(timeSlotsRequest(secondParams)).then(secondBatch => {
+ const combined = timeSlots.concat(secondBatch);
+ dispatch(fetchTimeSlotsSuccess(combined));
+ });
+ } else {
+ dispatch(fetchTimeSlotsSuccess(timeSlots));
+ }
+ })
+ .catch(e => {
+ dispatch(fetchTimeSlotsError(storableError(e)));
+ });
+};
+
// loadData is a collection of async calls that need to be made
// before page has all the info it needs to render itself
export const loadData = params => (dispatch, getState) => {
const txId = new UUID(params.id);
const state = getState().TransactionPage;
const txRef = state.transactionRef;
+ const txRole = params.transactionRole;
// In case a transaction reference is found from a previous
// data load -> clear the state. Otherwise keep the non-null
@@ -489,5 +585,5 @@ export const loadData = params => (dispatch, getState) => {
dispatch(setInitialValues(initialValues));
// Sale / order (i.e. transaction entity in API)
- return Promise.all([dispatch(fetchTransaction(txId)), dispatch(fetchMessages(txId, 1))]);
+ return Promise.all([dispatch(fetchTransaction(txId, txRole)), dispatch(fetchMessages(txId, 1))]);
};
diff --git a/src/containers/TransactionPage/TransactionPage.js b/src/containers/TransactionPage/TransactionPage.js
index 0ba4a089e2..a16a1ff909 100644
--- a/src/containers/TransactionPage/TransactionPage.js
+++ b/src/containers/TransactionPage/TransactionPage.js
@@ -2,10 +2,14 @@ import React from 'react';
import PropTypes from 'prop-types';
import { compose } from 'redux';
import { connect } from 'react-redux';
+import { withRouter } from 'react-router-dom';
import classNames from 'classnames';
import { FormattedMessage, intlShape, injectIntl } from 'react-intl';
+import { createResourceLocatorString, findRouteByRouteName } from '../../util/routes';
+import routeConfiguration from '../../routeConfiguration';
import { propTypes } from '../../util/types';
import { ensureListing, ensureTransaction } from '../../util/data';
+import { createSlug } from '../../util/urlHelpers';
import { getMarketplaceEntities } from '../../ducks/marketplaceData.duck';
import { isScrollingDisabled, manageDisableScrolling } from '../../ducks/UI.duck';
import {
@@ -44,6 +48,7 @@ export const TransactionPageComponent = props => {
totalMessagePages,
oldestMessagePageFetched,
fetchTransactionError,
+ history,
intl,
messages,
onManageDisableScrolling,
@@ -64,10 +69,43 @@ export const TransactionPageComponent = props => {
declineSaleError,
onAcceptSale,
onDeclineSale,
+ timeSlots,
+ fetchTimeSlotsError,
+ useInitialValues,
} = props;
const currentTransaction = ensureTransaction(transaction);
const currentListing = ensureListing(currentTransaction.listing);
+
+ const handleSubmitBookingRequest = values => {
+ const { bookingDates, ...bookingData } = values;
+
+ const initialValues = {
+ listing: currentListing,
+ enquiredTransaction: currentTransaction,
+ bookingData,
+ bookingDates: {
+ bookingStart: bookingDates.startDate,
+ bookingEnd: bookingDates.endDate,
+ },
+ };
+
+ const routes = routeConfiguration();
+ // Customize checkout page state with current listing and selected bookingDates
+ const { setInitialValues } = findRouteByRouteName('CheckoutPage', routes);
+ useInitialValues(setInitialValues, initialValues);
+
+ // Redirect to CheckoutPage
+ history.push(
+ createResourceLocatorString(
+ 'CheckoutPage',
+ routes,
+ { id: currentListing.id.uuid, slug: createSlug(currentListing.attributes.title) },
+ {}
+ )
+ );
+ };
+
const deletedListingTitle = intl.formatMessage({
id: 'TransactionPage.deletedListing',
});
@@ -158,6 +196,9 @@ export const TransactionPageComponent = props => {
declineInProgress={declineInProgress}
acceptSaleError={acceptSaleError}
declineSaleError={declineSaleError}
+ onSubmitBookingRequest={handleSubmitBookingRequest}
+ timeSlots={timeSlots}
+ fetchTimeSlotsError={fetchTimeSlotsError}
/>
) : (
loadingOrFailedFetching
@@ -192,6 +233,8 @@ TransactionPageComponent.defaultProps = {
fetchMessagesError: null,
initialMessageFailedToTransaction: null,
sendMessageError: null,
+ timeSlots: null,
+ fetchTimeSlotsError: null,
};
const { bool, func, oneOf, shape, string, arrayOf, number } = PropTypes;
@@ -218,6 +261,17 @@ TransactionPageComponent.propTypes = {
sendMessageError: propTypes.error,
onShowMoreMessages: func.isRequired,
onSendMessage: func.isRequired,
+ timeSlots: arrayOf(propTypes.timeSlot),
+ fetchTimeSlotsError: propTypes.error,
+ useInitialValues: func.isRequired,
+
+ // from withRouter
+ history: shape({
+ push: func.isRequired,
+ }).isRequired,
+ location: shape({
+ search: string,
+ }).isRequired,
// from injectIntl
intl: intlShape.isRequired,
@@ -241,6 +295,8 @@ const mapStateToProps = state => {
sendMessageError,
sendReviewInProgress,
sendReviewError,
+ timeSlots,
+ fetchTimeSlotsError,
} = state.TransactionPage;
const { currentUser } = state.user;
@@ -266,6 +322,8 @@ const mapStateToProps = state => {
sendMessageError,
sendReviewInProgress,
sendReviewError,
+ timeSlots,
+ fetchTimeSlotsError,
};
};
@@ -279,10 +337,12 @@ const mapDispatchToProps = dispatch => {
dispatch(manageDisableScrolling(componentId, disableScrolling)),
onSendReview: (role, tx, reviewRating, reviewContent) =>
dispatch(sendReview(role, tx, reviewRating, reviewContent)),
+ useInitialValues: (setInitialValues, values) => dispatch(setInitialValues(values)),
};
};
const TransactionPage = compose(
+ withRouter,
connect(
mapStateToProps,
mapDispatchToProps
diff --git a/src/containers/TransactionPage/TransactionPage.test.js b/src/containers/TransactionPage/TransactionPage.test.js
index e62e6d3c5a..ccec9181e8 100644
--- a/src/containers/TransactionPage/TransactionPage.test.js
+++ b/src/containers/TransactionPage/TransactionPage.test.js
@@ -38,6 +38,7 @@ describe('TransactionPage - Sale', () => {
onAcceptSale: noop,
onDeclineSale: noop,
scrollingDisabled: false,
+ useInitialValues: noop,
transaction,
totalMessages: 0,
totalMessagePages: 0,
@@ -48,6 +49,15 @@ describe('TransactionPage - Sale', () => {
onSendMessage: noop,
onResetForm: noop,
intl: fakeIntl,
+
+ location: {
+ pathname: `/sale/${txId}/details`,
+ search: '',
+ hash: '',
+ },
+ history: {
+ push: () => console.log('HistoryPush called'),
+ },
};
const tree = renderShallow();
@@ -82,6 +92,7 @@ describe('TransactionPage - Order', () => {
fetchMessagesInProgress: false,
sendMessageInProgress: false,
scrollingDisabled: false,
+ useInitialValues: noop,
transaction,
onShowMoreMessages: noop,
onSendMessage: noop,
@@ -92,6 +103,15 @@ describe('TransactionPage - Order', () => {
declineInProgress: false,
onAcceptSale: noop,
onDeclineSale: noop,
+
+ location: {
+ pathname: `/order/${txId}/details`,
+ search: '',
+ hash: '',
+ },
+ history: {
+ push: () => console.log('HistoryPush called'),
+ },
};
const tree = renderShallow();
diff --git a/src/containers/TransactionPage/__snapshots__/TransactionPage.test.js.snap b/src/containers/TransactionPage/__snapshots__/TransactionPage.test.js.snap
index c969f6a5c1..53c4905035 100644
--- a/src/containers/TransactionPage/__snapshots__/TransactionPage.test.js.snap
+++ b/src/containers/TransactionPage/__snapshots__/TransactionPage.test.js.snap
@@ -48,6 +48,7 @@ exports[`TransactionPage - Order matches snapshot 1`] = `
declineSaleError={null}
fetchMessagesError={null}
fetchMessagesInProgress={false}
+ fetchTimeSlotsError={null}
initialMessageFailed={false}
messages={Array []}
oldestMessagePageFetched={0}
@@ -55,8 +56,10 @@ exports[`TransactionPage - Order matches snapshot 1`] = `
onDeclineSale={[Function]}
onSendMessage={[Function]}
onShowMoreMessages={[Function]}
+ onSubmitBookingRequest={[Function]}
sendMessageError={null}
sendMessageInProgress={false}
+ timeSlots={null}
totalMessagePages={0}
transaction={
Object {
@@ -244,6 +247,7 @@ exports[`TransactionPage - Sale matches snapshot 1`] = `
declineInProgress={false}
declineSaleError={null}
fetchMessagesError={null}
+ fetchTimeSlotsError={null}
initialMessageFailed={false}
messages={Array []}
oldestMessagePageFetched={0}
@@ -251,8 +255,10 @@ exports[`TransactionPage - Sale matches snapshot 1`] = `
onDeclineSale={[Function]}
onSendMessage={[Function]}
onShowMoreMessages={[Function]}
+ onSubmitBookingRequest={[Function]}
sendMessageError={null}
sendMessageInProgress={false}
+ timeSlots={null}
totalMessagePages={0}
transaction={
Object {
diff --git a/src/forms/StripePaymentForm/StripePaymentForm.js b/src/forms/StripePaymentForm/StripePaymentForm.js
index d49db25c0c..e8779e0107 100644
--- a/src/forms/StripePaymentForm/StripePaymentForm.js
+++ b/src/forms/StripePaymentForm/StripePaymentForm.js
@@ -192,6 +192,7 @@ class StripePaymentForm extends Component {
paymentInfo,
onChange,
authorDisplayName,
+ showInitialMessageInput,
intl,
} = this.props;
const submitInProgress = this.state.submitting || inProgress;
@@ -225,6 +226,24 @@ class StripePaymentForm extends Component {
);
+ const initialMessage = showInitialMessageInput ? (
+
+
+
+
+
+
+
+ ) : null;
+
return (