Pass booking data from tx page to checkout

This commit is contained in:
Hannu Lyytikainen 2018-12-12 11:55:33 +02:00
parent a0d1984103
commit 7cc30ee3ef
15 changed files with 143 additions and 14 deletions

View file

@ -9,7 +9,7 @@ export const Default = {
props: {
className: css.example,
listing: createListing('listing_1'),
handleBookingSubmit: values => console.log('Submit:', values),
onSubmit: values => console.log('Submit:', values),
title: <span>Booking title</span>,
subTitle: 'Hosted by Author N',
authorDisplayName: 'Author Name',
@ -22,7 +22,7 @@ export const WithClosedListing = {
props: {
className: css.example,
listing: createListing('listing_1', { state: LISTING_STATE_CLOSED }),
handleBookingSubmit: values => console.log('Submit:', values),
onSubmit: values => console.log('Submit:', values),
title: <span>Booking title</span>,
subTitle: 'Hosted by Author N',
authorDisplayName: 'Author Name',

View file

@ -55,7 +55,7 @@ const BookingPanel = props => {
listing,
isOwnListing,
unitType,
handleBookingSubmit,
onSubmit,
title,
subTitle,
authorDisplayName,
@ -116,7 +116,7 @@ const BookingPanel = props => {
className={css.bookingForm}
submitButtonWrapperClassName={css.bookingDatesSubmitButtonWrapper}
unitType={unitType}
onSubmit={handleBookingSubmit}
onSubmit={onSubmit}
price={price}
isOwnListing={isOwnListing}
timeSlots={timeSlots}
@ -167,7 +167,7 @@ BookingPanel.propTypes = {
listing: propTypes.listing.isRequired,
isOwnListing: bool,
unitType: propTypes.bookingUnitType,
handleBookingSubmit: func.isRequired,
onSubmit: func.isRequired,
title: oneOfType([node, string]).isRequired,
subTitle: oneOfType([node, string]),
authorDisplayName: string.isRequired,

View file

@ -182,6 +182,7 @@ export const BookingPanelMaybe = props => {
listingTitle,
subTitle,
provider,
onSubmit,
onManageDisableScrolling,
timeSlots,
fetchTimeSlotsError,
@ -201,6 +202,7 @@ export const BookingPanelMaybe = props => {
title={listingTitle}
subTitle={subTitle}
authorDisplayName={authorDisplayName}
onSubmit={onSubmit}
onManageDisableScrolling={onManageDisableScrolling}
timeSlots={timeSlots}
fetchTimeSlotsError={fetchTimeSlotsError}

View file

@ -131,6 +131,7 @@ export class TransactionPanelComponent extends Component {
declineInProgress,
acceptSaleError,
declineSaleError,
onSubmitBookingRequest,
timeSlots,
fetchTimeSlotsError,
} = this.props;
@ -330,6 +331,7 @@ export class TransactionPanelComponent extends Component {
listingTitle={listingTitle}
subTitle={bookingSubTitle}
provider={currentProvider}
onSubmit={onSubmitBookingRequest}
onManageDisableScrolling={onManageDisableScrolling}
timeSlots={timeSlots}
fetchTimeSlotsError={fetchTimeSlotsError}
@ -394,6 +396,7 @@ TransactionPanelComponent.propTypes = {
onShowMoreMessages: func.isRequired,
onSendMessage: func.isRequired,
onSendReview: func.isRequired,
onSubmitBookingRequest: func.isRequired,
timeSlots: arrayOf(propTypes.timeSlot),
fetchTimeSlotsError: propTypes.error,

View file

@ -108,6 +108,7 @@ describe('TransactionPanel - Sale', () => {
onSendMessage: noop,
onSendReview: noop,
onResetForm: noop,
onSubmitBookingRequest: noop,
intl: fakeIntl,
};
@ -276,6 +277,7 @@ describe('TransactionPanel - Order', () => {
onDeclineSale: noop,
acceptInProgress: false,
declineInProgress: false,
onSubmitBookingRequest: noop,
};
it('enquired matches snapshot', () => {

View file

@ -1031,6 +1031,7 @@ exports[`TransactionPanel - Order accepted matches snapshot 1`] = `
}
listingTitle="listing1 title"
onManageDisableScrolling={[Function]}
onSubmit={[Function]}
provider={
Object {
"attributes": Object {
@ -2349,6 +2350,7 @@ exports[`TransactionPanel - Order autodeclined matches snapshot 1`] = `
}
listingTitle="listing1 title"
onManageDisableScrolling={[Function]}
onSubmit={[Function]}
provider={
Object {
"attributes": Object {
@ -3667,6 +3669,7 @@ exports[`TransactionPanel - Order canceled matches snapshot 1`] = `
}
listingTitle="listing1 title"
onManageDisableScrolling={[Function]}
onSubmit={[Function]}
provider={
Object {
"attributes": Object {
@ -4985,6 +4988,7 @@ exports[`TransactionPanel - Order declined matches snapshot 1`] = `
}
listingTitle="listing1 title"
onManageDisableScrolling={[Function]}
onSubmit={[Function]}
provider={
Object {
"attributes": Object {
@ -6303,6 +6307,7 @@ exports[`TransactionPanel - Order delivered matches snapshot 1`] = `
}
listingTitle="listing1 title"
onManageDisableScrolling={[Function]}
onSubmit={[Function]}
provider={
Object {
"attributes": Object {
@ -7621,6 +7626,7 @@ exports[`TransactionPanel - Order enquired matches snapshot 1`] = `
}
listingTitle="listing1 title"
onManageDisableScrolling={[Function]}
onSubmit={[Function]}
provider={
Object {
"attributes": Object {
@ -8939,6 +8945,7 @@ exports[`TransactionPanel - Order preauthorized matches snapshot 1`] = `
}
listingTitle="listing1 title"
onManageDisableScrolling={[Function]}
onSubmit={[Function]}
provider={
Object {
"attributes": Object {
@ -10257,6 +10264,7 @@ exports[`TransactionPanel - Sale accepted matches snapshot 1`] = `
}
listingTitle="listing1 title"
onManageDisableScrolling={[Function]}
onSubmit={[Function]}
provider={
Object {
"attributes": Object {
@ -11575,6 +11583,7 @@ exports[`TransactionPanel - Sale autodeclined matches snapshot 1`] = `
}
listingTitle="listing1 title"
onManageDisableScrolling={[Function]}
onSubmit={[Function]}
provider={
Object {
"attributes": Object {
@ -12893,6 +12902,7 @@ exports[`TransactionPanel - Sale canceled matches snapshot 1`] = `
}
listingTitle="listing1 title"
onManageDisableScrolling={[Function]}
onSubmit={[Function]}
provider={
Object {
"attributes": Object {
@ -14211,6 +14221,7 @@ exports[`TransactionPanel - Sale declined matches snapshot 1`] = `
}
listingTitle="listing1 title"
onManageDisableScrolling={[Function]}
onSubmit={[Function]}
provider={
Object {
"attributes": Object {
@ -15529,6 +15540,7 @@ exports[`TransactionPanel - Sale delivered matches snapshot 1`] = `
}
listingTitle="listing1 title"
onManageDisableScrolling={[Function]}
onSubmit={[Function]}
provider={
Object {
"attributes": Object {
@ -16847,6 +16859,7 @@ exports[`TransactionPanel - Sale enquired matches snapshot 1`] = `
}
listingTitle="listing1 title"
onManageDisableScrolling={[Function]}
onSubmit={[Function]}
provider={
Object {
"attributes": Object {
@ -18165,6 +18178,7 @@ exports[`TransactionPanel - Sale preauthorized matches snapshot 1`] = `
}
listingTitle="listing1 title"
onManageDisableScrolling={[Function]}
onSubmit={[Function]}
provider={
Object {
"attributes": Object {

View file

@ -27,6 +27,7 @@ const initialState = {
speculateTransactionInProgress: false,
speculateTransactionError: null,
speculatedTransaction: null,
enquiredTransaction: null,
initiateOrderError: null,
};

View file

@ -75,7 +75,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 +92,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 =
@ -465,6 +472,7 @@ CheckoutPageComponent.defaultProps = {
bookingDates: null,
speculateTransactionError: null,
speculatedTransaction: null,
enquiredTransaction: null,
currentUser: null,
};
@ -480,6 +488,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 +517,7 @@ const mapStateToProps = state => {
speculateTransactionInProgress,
speculateTransactionError,
speculatedTransaction,
enquiredTransaction,
initiateOrderError,
} = state.CheckoutPage;
const { currentUser } = state.user;
@ -519,6 +529,7 @@ const mapStateToProps = state => {
speculateTransactionInProgress,
speculateTransactionError,
speculatedTransaction,
enquiredTransaction,
listing,
initiateOrderError,
};

View file

@ -59,6 +59,7 @@ describe('CheckoutPage', () => {
speculateTransactionError: null,
speculateTransactionInProgress: false,
speculatedTransaction: null,
enquiredTransaction: null,
};
it('should return the initial state', () => {

View file

@ -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 === 'string' && 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 {};

View file

@ -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}

View file

@ -265,7 +265,6 @@ exports[`ListingPage matches snapshot 1`] = `
<withRouter(InjectIntl(BookingPanel))
authorDisplayName="user-1 display name"
fetchTimeSlotsError={null}
handleBookingSubmit={[Function]}
isOwnListing={false}
listing={
Object {
@ -305,6 +304,7 @@ exports[`ListingPage matches snapshot 1`] = `
}
}
onManageDisableScrolling={[Function]}
onSubmit={[Function]}
subTitle="ListingPage.bookingSubTitle"
timeSlots={null}
title="ListingPage.bookingSubTitle"

View file

@ -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,
@ -66,10 +71,41 @@ export const TransactionPageComponent = props => {
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',
});
@ -160,6 +196,7 @@ export const TransactionPageComponent = props => {
declineInProgress={declineInProgress}
acceptSaleError={acceptSaleError}
declineSaleError={declineSaleError}
onSubmitBookingRequest={handleSubmitBookingRequest}
timeSlots={timeSlots}
fetchTimeSlotsError={fetchTimeSlotsError}
/>
@ -226,6 +263,15 @@ TransactionPageComponent.propTypes = {
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,
@ -291,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

View file

@ -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(<TransactionPageComponent {...props} />);
@ -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(<TransactionPageComponent {...props} />);

View file

@ -56,6 +56,7 @@ exports[`TransactionPage - Order matches snapshot 1`] = `
onDeclineSale={[Function]}
onSendMessage={[Function]}
onShowMoreMessages={[Function]}
onSubmitBookingRequest={[Function]}
sendMessageError={null}
sendMessageInProgress={false}
timeSlots={null}
@ -254,6 +255,7 @@ exports[`TransactionPage - Sale matches snapshot 1`] = `
onDeclineSale={[Function]}
onSendMessage={[Function]}
onShowMoreMessages={[Function]}
onSubmitBookingRequest={[Function]}
sendMessageError={null}
sendMessageInProgress={false}
timeSlots={null}