From 6a94f1b7ce7da1ccf58d734b0127245b7ccc2d7e Mon Sep 17 00:00:00 2001 From: Hannu Lyytikainen Date: Wed, 12 Dec 2018 11:55:33 +0200 Subject: [PATCH] Pass booking data from tx page to checkout --- .../BookingPanel/BookingPanel.example.js | 4 +- src/components/BookingPanel/BookingPanel.js | 6 +-- .../TransactionPanel.helpers.js | 2 + .../TransactionPanel/TransactionPanel.js | 3 ++ .../TransactionPanel/TransactionPanel.test.js | 2 + .../TransactionPanel.test.js.snap | 14 ++++++ .../CheckoutPage/CheckoutPage.duck.js | 1 + src/containers/CheckoutPage/CheckoutPage.js | 17 +++++-- .../CheckoutPage/CheckoutPage.test.js | 1 + .../CheckoutPageSessionHelpers.js | 33 +++++++++++-- src/containers/ListingPage/ListingPage.js | 2 +- .../__snapshots__/ListingPage.test.js.snap | 2 +- .../TransactionPage/TransactionPage.js | 48 +++++++++++++++++++ .../TransactionPage/TransactionPage.test.js | 20 ++++++++ .../TransactionPage.test.js.snap | 2 + 15 files changed, 143 insertions(+), 14 deletions(-) diff --git a/src/components/BookingPanel/BookingPanel.example.js b/src/components/BookingPanel/BookingPanel.example.js index 589027c148..5cabada42e 100644 --- a/src/components/BookingPanel/BookingPanel.example.js +++ b/src/components/BookingPanel/BookingPanel.example.js @@ -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: Booking title, 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: Booking title, subTitle: 'Hosted by Author N', authorDisplayName: 'Author Name', diff --git a/src/components/BookingPanel/BookingPanel.js b/src/components/BookingPanel/BookingPanel.js index bec513b007..eed0d224a2 100644 --- a/src/components/BookingPanel/BookingPanel.js +++ b/src/components/BookingPanel/BookingPanel.js @@ -55,7 +55,7 @@ const BookingPanel = props => { listing, isOwnListing, unitType, - handleBookingSubmit, + onSubmit, title, subTitle, authorDisplayName, @@ -107,7 +107,7 @@ const BookingPanel = props => { className={css.bookingForm} submitButtonWrapperClassName={css.bookingDatesSubmitButtonWrapper} unitType={unitType} - onSubmit={handleBookingSubmit} + onSubmit={onSubmit} price={price} isOwnListing={isOwnListing} timeSlots={timeSlots} @@ -158,7 +158,7 @@ BookingPanel.propTypes = { listing: propTypes.listing.isRequired, isOwnListing: bool, unitType: propTypes.bookingUnitType, - handleBookingSubmit: func.isRequired, + onSubmit: func.isRequired, title: oneOfType([object, string]).isRequired, subTitle: oneOfType([object, string]), authorDisplayName: string.isRequired, diff --git a/src/components/TransactionPanel/TransactionPanel.helpers.js b/src/components/TransactionPanel/TransactionPanel.helpers.js index 463b83b4c0..691bdacca9 100644 --- a/src/components/TransactionPanel/TransactionPanel.helpers.js +++ b/src/components/TransactionPanel/TransactionPanel.helpers.js @@ -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} diff --git a/src/components/TransactionPanel/TransactionPanel.js b/src/components/TransactionPanel/TransactionPanel.js index 8e736c2aa0..db6a235777 100644 --- a/src/components/TransactionPanel/TransactionPanel.js +++ b/src/components/TransactionPanel/TransactionPanel.js @@ -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, diff --git a/src/components/TransactionPanel/TransactionPanel.test.js b/src/components/TransactionPanel/TransactionPanel.test.js index 1b4faa091a..b3443d0a69 100644 --- a/src/components/TransactionPanel/TransactionPanel.test.js +++ b/src/components/TransactionPanel/TransactionPanel.test.js @@ -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', () => { diff --git a/src/components/TransactionPanel/__snapshots__/TransactionPanel.test.js.snap b/src/components/TransactionPanel/__snapshots__/TransactionPanel.test.js.snap index d8098efc67..cac213d58c 100644 --- a/src/components/TransactionPanel/__snapshots__/TransactionPanel.test.js.snap +++ b/src/components/TransactionPanel/__snapshots__/TransactionPanel.test.js.snap @@ -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 { diff --git a/src/containers/CheckoutPage/CheckoutPage.duck.js b/src/containers/CheckoutPage/CheckoutPage.duck.js index 5cb8a10b7f..059d1bc159 100644 --- a/src/containers/CheckoutPage/CheckoutPage.duck.js +++ b/src/containers/CheckoutPage/CheckoutPage.duck.js @@ -27,6 +27,7 @@ const initialState = { speculateTransactionInProgress: false, speculateTransactionError: null, speculatedTransaction: null, + enquiredTransaction: null, initiateOrderError: null, }; diff --git a/src/containers/CheckoutPage/CheckoutPage.js b/src/containers/CheckoutPage/CheckoutPage.js index 4b3d8d63c5..b2ba4cdfda 100644 --- a/src/containers/CheckoutPage/CheckoutPage.js +++ b/src/containers/CheckoutPage/CheckoutPage.js @@ -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, }; 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..f45c826999 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 === '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 {}; diff --git a/src/containers/ListingPage/ListingPage.js b/src/containers/ListingPage/ListingPage.js index 2d02d3a6e6..9fa61c00b7 100644 --- a/src/containers/ListingPage/ListingPage.js +++ b/src/containers/ListingPage/ListingPage.js @@ -441,7 +441,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 847f36678d..1962c361a1 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`] = ` { 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 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 085bbbbbbf..53c4905035 100644 --- a/src/containers/TransactionPage/__snapshots__/TransactionPage.test.js.snap +++ b/src/containers/TransactionPage/__snapshots__/TransactionPage.test.js.snap @@ -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}