From 92caa18402ff6def63978ede985f0f617066b1bd Mon Sep 17 00:00:00 2001 From: Kimmo Puputti Date: Thu, 9 Nov 2017 16:29:30 +0200 Subject: [PATCH 1/2] Add messages to the order page --- src/components/Messages/Messages.css | 1 + .../OrderDetailsPanel/OrderDetailsPanel.css | 105 +++- .../OrderDetailsPanel/OrderDetailsPanel.js | 152 ++--- .../OrderDetailsPanel.test.js | 21 +- .../OrderDetailsPanel.test.js.snap | 574 ++++++++---------- src/containers/OrderPage/OrderPage.duck.js | 5 +- src/containers/OrderPage/OrderPage.js | 48 +- src/containers/OrderPage/OrderPage.test.js | 20 +- .../__snapshots__/OrderPage.test.js.snap | 23 + src/translations/en.json | 8 +- 10 files changed, 498 insertions(+), 459 deletions(-) diff --git a/src/components/Messages/Messages.css b/src/components/Messages/Messages.css index c00e5534..ff66a6da 100644 --- a/src/components/Messages/Messages.css +++ b/src/components/Messages/Messages.css @@ -24,6 +24,7 @@ .ownMessage { /* Preserve newlines, but collapse other whitespace */ white-space: pre-line; + width: 100%; max-width: 433px; } diff --git a/src/components/OrderDetailsPanel/OrderDetailsPanel.css b/src/components/OrderDetailsPanel/OrderDetailsPanel.css index 9b3fb636..43a0abef 100644 --- a/src/components/OrderDetailsPanel/OrderDetailsPanel.css +++ b/src/components/OrderDetailsPanel/OrderDetailsPanel.css @@ -4,6 +4,18 @@ position: relative; } +.error { + color: var(--failColor); + margin: 13px 0 22px 0; + + @media (--viewportMedium) { + margin: 13px 0 23px 0; + } + @media (--viewportLarge) { + margin: 12px 0 23px 0; + } +} + .container { display: flex; flex-direction: column; @@ -72,13 +84,13 @@ .orderInfo { @media (--viewportLarge) { - max-width: 519px; - margin-right: 132px; + max-width: 538px; + margin-right: 108px; } } .heading { - margin: 28px 24px 0 24px; + margin: 27px 24px 0 24px; @media (--viewportMedium) { max-width: 80%; @@ -94,18 +106,27 @@ display: block; } -.messagesContainer { - display: flex; - flex-direction: column; +.orderInfoText { + margin: 18px 24px 0 24px; + + @media (--viewportMedium) { + margin: 23px 24px 0 24px; + } + @media (--viewportLarge) { + margin: 23px 0 0 0; + } } -.message { - display: flex; - margin: 24px 24px 47px 24px; +.infoTextDivider { + display: none; + + height: 1px; + background-color: var(--matterColorNegative); + border: none; + margin: 37px 0 0 0; @media (--viewportLarge) { - margin: 31px 0 0 0; - line-height: 32px; + display: block; } } @@ -114,6 +135,11 @@ } .breakdownMobile { + margin-top: 47px; + + @media (--viewportMedium) { + margin-top: 43px; + } @media (--viewportLarge) { display: none; } @@ -176,7 +202,7 @@ margin-bottom: 0; @media (--viewportLarge) { - margin-top: 0; + margin-top: 1px; margin-bottom: 0; } } @@ -185,25 +211,54 @@ /* Font */ color: var(--matterColorAnti); - margin-top: 5px; - margin-bottom: 14px; - margin-left: 24px; - margin-right: 24px; + margin: 0 24px 0 24px; @media (--viewportLarge) { - margin-top: 38px; - margin-bottom: 25px; - margin-left: 48px; - margin-right: 48px; + margin: 37px 48px 26px 48px; } } -.receipt { - margin-left: 24px; - margin-right: 24px; +.breakdown { + margin: 14px 24px 0 24px; + @media (--viewportMedium) { + margin: 18px 24px 0 24px; + } @media (--viewportLarge) { - margin-left: 48px; - margin-right: 48px; + margin: 14px 48px 0 48px; + } +} + +.messagesContainer { + margin: 46px 24px 0 24px; + + @media (--viewportMedium) { + margin: 46px 24px 0 24px; + } + @media (--viewportLarge) { + margin: 43px 0 0 0; + } +} + +.messagesContainerWithInfoAbove { + @media (--viewportLarge) { + margin: 38px 0 0 0; + } +} + +.messagesHeading { + color: var(--matterColorAnti); + margin: 0; + + @media (--viewportMedium) { + margin: 0; + } +} + +.messages { + margin-top: 17px; + + @media (--viewportMedium) { + margin-top: 19px; } } diff --git a/src/components/OrderDetailsPanel/OrderDetailsPanel.js b/src/components/OrderDetailsPanel/OrderDetailsPanel.js index 40d919c7..d45c9b76 100644 --- a/src/components/OrderDetailsPanel/OrderDetailsPanel.js +++ b/src/components/OrderDetailsPanel/OrderDetailsPanel.js @@ -1,11 +1,17 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { injectIntl, intlShape, FormattedDate, FormattedMessage } from 'react-intl'; +import { injectIntl, intlShape, FormattedMessage } from 'react-intl'; import classNames from 'classnames'; import * as propTypes from '../../util/propTypes'; import { createSlug } from '../../util/urlHelpers'; import { ensureListing, ensureTransaction, ensureUser, userDisplayName } from '../../util/data'; -import { BookingBreakdown, NamedLink, ResponsiveImage, AvatarMedium } from '../../components'; +import { + BookingBreakdown, + NamedLink, + ResponsiveImage, + AvatarMedium, + Messages, +} from '../../components'; import css from './OrderDetailsPanel.css'; @@ -14,7 +20,7 @@ const breakdown = transaction => { return loaded ? ( { } }; -const orderMessage = (transaction, listingTitle, providerName) => { - const transitionDate = ( - - - - ); +const orderMessage = (transaction, providerName) => { if (propTypes.txIsPreauthorized(transaction)) { return ( ); - } else if (propTypes.txIsAccepted(transaction)) { - return ( - - ); - } else if (propTypes.txIsDeclined(transaction)) { - return ( - - ); - } else if (propTypes.txIsAutodeclined(transaction)) { - return ( - - ); - } else if (propTypes.txIsCanceled(transaction)) { - return ( - - ); - } else if (propTypes.txIsDelivered(transaction)) { - return ( - - ); - } else { - return null; } + return null; }; export const OrderDetailsPanelComponent = props => { - const { rootClassName, className, transaction, intl } = props; + const { + rootClassName, + className, + currentUser, + transaction, + messages, + initialMessageFailed, + fetchMessagesError, + intl, + } = props; const currentTransaction = ensureTransaction(transaction); const currentListing = ensureListing(currentTransaction.listing); const currentProvider = ensureUser(currentTransaction.provider); @@ -160,46 +132,76 @@ export const OrderDetailsPanelComponent = props => { const bookingInfo = breakdown(currentTransaction); const orderHeading = orderTitle(currentTransaction, listingLink, customerDisplayName); - const message = listingDeleted + const orderInfoText = listingDeleted ? orderMessageDeletedListing - : orderMessage(currentTransaction, listingLink, authorDisplayName); + : orderMessage(currentTransaction, authorDisplayName); + const showInfoMessage = !!orderInfoText; const firstImage = currentListing.images && currentListing.images.length > 0 ? currentListing.images[0] : null; + const messagesContainerClasses = classNames(css.messagesContainer, { + [css.messagesContainerWithInfoAbove]: showInfoMessage, + }); + const messagesContainer = + messages.length > 0 ? ( +
+

+ +

+ {initialMessageFailed + ? ( +

+ +

+ ) + : null} + {fetchMessagesError + ? ( +

+ +

+ ) + : null} + +
+ ) : null; + const classes = classNames(rootClassName || css.root, className); return (
-
-
- -
-
-
- -
+
+
+ +
+
+
+ +

{orderHeading}

-
{message}
-
-
+ {showInfoMessage ?

{orderInfoText}

: null} + {showInfoMessage ?
: null}

{bookingInfo}
+ {messagesContainer} +
+
@@ -241,14 +243,20 @@ export const OrderDetailsPanelComponent = props => { OrderDetailsPanelComponent.defaultProps = { rootClassName: null, className: null, + fetchMessagesError: null, }; -const { string } = PropTypes; +const { string, arrayOf, bool } = PropTypes; OrderDetailsPanelComponent.propTypes = { rootClassName: string, className: string, + + currentUser: propTypes.currentUser, transaction: propTypes.transaction.isRequired, + messages: arrayOf(propTypes.message).isRequired, + initialMessageFailed: bool.isRequired, + fetchMessagesError: propTypes.error, // from injectIntl intl: intlShape, diff --git a/src/components/OrderDetailsPanel/OrderDetailsPanel.test.js b/src/components/OrderDetailsPanel/OrderDetailsPanel.test.js index 59b1bd39..f4663e5b 100644 --- a/src/components/OrderDetailsPanel/OrderDetailsPanel.test.js +++ b/src/components/OrderDetailsPanel/OrderDetailsPanel.test.js @@ -58,39 +58,46 @@ const txDelivered = createTransaction({ }); describe('OrderDetailsPanel', () => { + + const panelBaseProps = { + intl: fakeIntl, + messages: [], + initialMessageFailed: false, + }; + it('preauthorized matches snapshot', () => { const tree = renderShallow( - + ); expect(tree).toMatchSnapshot(); }); it('accepted matches snapshot', () => { const tree = renderShallow( - + ); expect(tree).toMatchSnapshot(); }); it('declined matches snapshot', () => { const tree = renderShallow( - + ); expect(tree).toMatchSnapshot(); }); it('autodeclined matches snapshot', () => { const tree = renderShallow( - + ); expect(tree).toMatchSnapshot(); }); it('canceled matches snapshot', () => { const tree = renderShallow( - + ); expect(tree).toMatchSnapshot(); }); it('delivered matches snapshot', () => { const tree = renderShallow( - + ); expect(tree).toMatchSnapshot(); }); @@ -108,7 +115,7 @@ describe('OrderDetailsPanel', () => { provider: createUser('provider'), customer: createUser('customer'), }); - const panel = shallow(); + const panel = shallow(); const breakdownProps = panel.find(BookingBreakdown).props(); expect(breakdownProps.transaction.attributes.payinTotal).toEqual(new Money(16500, 'USD')); }); diff --git a/src/components/OrderDetailsPanel/__snapshots__/OrderDetailsPanel.test.js.snap b/src/components/OrderDetailsPanel/__snapshots__/OrderDetailsPanel.test.js.snap index ef2c7ae2..add241f4 100644 --- a/src/components/OrderDetailsPanel/__snapshots__/OrderDetailsPanel.test.js.snap +++ b/src/components/OrderDetailsPanel/__snapshots__/OrderDetailsPanel.test.js.snap @@ -7,50 +7,50 @@ exports[`OrderDetailsPanel accepted matches snapshot 1`] = `
- + +
+
+
+
-
-
- -
-

@@ -83,26 +83,6 @@ exports[`OrderDetailsPanel accepted matches snapshot 1`] = ` />

-
- - - , - } - } - /> -
-
-

+
+
@@ -429,50 +411,50 @@ exports[`OrderDetailsPanel autodeclined matches snapshot 1`] = `
- + +
+
+
+
-
-
- -
-

-
- - - , - } - } - /> -
-
-

+
+
@@ -839,50 +803,50 @@ exports[`OrderDetailsPanel canceled matches snapshot 1`] = `
- + +
+
+
+
-
-
- -
-

@@ -915,7 +879,7 @@ exports[`OrderDetailsPanel canceled matches snapshot 1`] = ` />

-
+

-

-
-
+

+

+
+
@@ -1253,50 +1218,50 @@ exports[`OrderDetailsPanel declined matches snapshot 1`] = `
- + +
+
+
+
-
-
- -
-

-
- - - , - } - } - /> -
-
-

+
+
@@ -1663,50 +1610,50 @@ exports[`OrderDetailsPanel delivered matches snapshot 1`] = `
- + +
+
+
+
-
-
- -
-

-
- - - , - } - } - /> -
-
-

+
+
@@ -2073,50 +2002,50 @@ exports[`OrderDetailsPanel preauthorized matches snapshot 1`] = `
- + +
+
+
+
-
-
- -
-

@@ -2149,7 +2078,7 @@ exports[`OrderDetailsPanel preauthorized matches snapshot 1`] = ` />

-
+

-

-
-
+

+

+
+
diff --git a/src/containers/OrderPage/OrderPage.duck.js b/src/containers/OrderPage/OrderPage.duck.js index fac9620b..2a715359 100644 --- a/src/containers/OrderPage/OrderPage.duck.js +++ b/src/containers/OrderPage/OrderPage.duck.js @@ -118,12 +118,15 @@ export const fetchMessages = txId => (dispatch, getState, sdk) => { dispatch(fetchMessagesRequest()); return sdk.messages - .query({ transaction_id: txId }) + .query({ transaction_id: txId, include: ['sender', 'sender.profileImage'] }) .then(response => { const entities = updatedEntities({}, response.data); const messageIds = response.data.data.map(d => d.id); const denormalized = denormalisedEntities(entities, 'message', messageIds); + // Messages come latest first, so we need to reverse the order + denormalized.reverse(); + dispatch(fetchMessagesSuccess(denormalized)); }) .catch(e => { diff --git a/src/containers/OrderPage/OrderPage.js b/src/containers/OrderPage/OrderPage.js index 64440a9b..25aa2416 100644 --- a/src/containers/OrderPage/OrderPage.js +++ b/src/containers/OrderPage/OrderPage.js @@ -29,6 +29,8 @@ export const OrderPageComponent = props => { const { currentUser, fetchOrderError, + fetchMessagesError, + messages, messageSendingFailedToTransaction, intl, params, @@ -39,13 +41,11 @@ export const OrderPageComponent = props => { const currentListing = ensureListing(currentTransaction.listing); const listingTitle = currentListing.attributes.title; - if (messageSendingFailedToTransaction) { - // TODO: render error message with other messages - console.error( - 'failed to send initial message to transaction:', - messageSendingFailedToTransaction - ); - } + const initialMessageFailed = !!( + messageSendingFailedToTransaction && + currentTransaction.id && + messageSendingFailedToTransaction.uuid === currentTransaction.id.uuid + ); // Redirect users with someone else's direct link to their own inbox/orders page. const isDataAvailable = @@ -77,7 +77,14 @@ export const OrderPageComponent = props => { const panel = isDataAvailable && currentTransaction.id ? ( - + ) : ( loadingOrFaildFetching ); @@ -103,31 +110,46 @@ export const OrderPageComponent = props => { OrderPageComponent.defaultProps = { currentUser: null, fetchOrderError: null, + fetchMessagesError: null, + messageSendingFailedToTransaction: null, transaction: null, }; -const { bool, oneOf, shape, string } = PropTypes; +const { bool, oneOf, shape, string, array } = PropTypes; OrderPageComponent.propTypes = { + params: shape({ id: string }).isRequired, + tab: oneOf(['details', 'discussion']).isRequired, + currentUser: propTypes.currentUser, fetchOrderError: propTypes.error, + fetchMessagesError: propTypes.error, + messages: array.isRequired, messageSendingFailedToTransaction: propTypes.uuid, - intl: intlShape.isRequired, - params: shape({ id: string }).isRequired, scrollingDisabled: bool.isRequired, - tab: oneOf(['details', 'discussion']).isRequired, transaction: propTypes.transaction, + + // from injectIntl + intl: intlShape.isRequired, }; const mapStateToProps = state => { - const { fetchOrderError, transactionRef, messageSendingFailedToTransaction } = state.OrderPage; const { currentUser } = state.user; + const { + fetchOrderError, + transactionRef, + fetchMessagesError, + messages, + messageSendingFailedToTransaction, + } = state.OrderPage; const transactions = getMarketplaceEntities(state, transactionRef ? [transactionRef] : []); const transaction = transactions.length > 0 ? transactions[0] : null; return { currentUser, fetchOrderError, + fetchMessagesError, + messages, messageSendingFailedToTransaction, scrollingDisabled: isScrollingDisabled(state), transaction, diff --git a/src/containers/OrderPage/OrderPage.test.js b/src/containers/OrderPage/OrderPage.test.js index 9525e239..e01e2c4b 100644 --- a/src/containers/OrderPage/OrderPage.test.js +++ b/src/containers/OrderPage/OrderPage.test.js @@ -29,23 +29,15 @@ describe('OrderPage', () => { }); const props = { - authInProgress: false, - currentUserHasListings: false, - isAuthenticated: false, - onLogout: noop, - onManageDisableScrolling: noop, - currentUser: createCurrentUser('customer1'), params: { id: txId }, - transaction, tab: 'details', - intl: fakeIntl, + + currentUser: createCurrentUser('customer1'), + messages: [], scrollingDisabled: false, - location: { search: '' }, - history: { - push: () => console.log('HistoryPush called'), - }, - sendVerificationEmailInProgress: false, - onResendVerificationEmail: noop, + transaction, + + intl: fakeIntl, }; const tree = renderShallow(); diff --git a/src/containers/OrderPage/__snapshots__/OrderPage.test.js.snap b/src/containers/OrderPage/__snapshots__/OrderPage.test.js.snap index 86857724..4fb96448 100644 --- a/src/containers/OrderPage/__snapshots__/OrderPage.test.js.snap +++ b/src/containers/OrderPage/__snapshots__/OrderPage.test.js.snap @@ -21,6 +21,29 @@ exports[`OrderPage matches snapshot 1`] = ` > Date: Fri, 10 Nov 2017 11:25:56 +0200 Subject: [PATCH 2/2] Show error messages also when there are no messages --- .../OrderDetailsPanel/OrderDetailsPanel.js | 42 +++++++++---------- .../OrderDetailsPanel.test.js | 1 - 2 files changed, 19 insertions(+), 24 deletions(-) diff --git a/src/components/OrderDetailsPanel/OrderDetailsPanel.js b/src/components/OrderDetailsPanel/OrderDetailsPanel.js index d45c9b76..d728d2e7 100644 --- a/src/components/OrderDetailsPanel/OrderDetailsPanel.js +++ b/src/components/OrderDetailsPanel/OrderDetailsPanel.js @@ -143,29 +143,25 @@ export const OrderDetailsPanelComponent = props => { const messagesContainerClasses = classNames(css.messagesContainer, { [css.messagesContainerWithInfoAbove]: showInfoMessage, }); - const messagesContainer = - messages.length > 0 ? ( -
-

- -

- {initialMessageFailed - ? ( -

- -

- ) - : null} - {fetchMessagesError - ? ( -

- -

- ) - : null} - -
- ) : null; + const showMessages = messages.length > 0 || initialMessageFailed || fetchMessagesError; + const messagesContainer = showMessages ? ( +
+

+ +

+ {initialMessageFailed ? ( +

+ +

+ ) : null} + {fetchMessagesError ? ( +

+ +

+ ) : null} + +
+ ) : null; const classes = classNames(rootClassName || css.root, className); diff --git a/src/components/OrderDetailsPanel/OrderDetailsPanel.test.js b/src/components/OrderDetailsPanel/OrderDetailsPanel.test.js index f4663e5b..ccaed4fd 100644 --- a/src/components/OrderDetailsPanel/OrderDetailsPanel.test.js +++ b/src/components/OrderDetailsPanel/OrderDetailsPanel.test.js @@ -58,7 +58,6 @@ const txDelivered = createTransaction({ }); describe('OrderDetailsPanel', () => { - const panelBaseProps = { intl: fakeIntl, messages: [],