From 00ffe8278c7f6265d41e9495878d589f23e34dd3 Mon Sep 17 00:00:00 2001 From: Vesa Luusua Date: Fri, 15 Dec 2017 02:33:37 +0200 Subject: [PATCH 01/10] ActivityFeed: handle banned user and deleted listing --- src/components/ActivityFeed/ActivityFeed.js | 17 +++++++++++++---- src/translations/en.json | 2 ++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/components/ActivityFeed/ActivityFeed.js b/src/components/ActivityFeed/ActivityFeed.js index 7df809c3..5ce36a93 100644 --- a/src/components/ActivityFeed/ActivityFeed.js +++ b/src/components/ActivityFeed/ActivityFeed.js @@ -5,7 +5,7 @@ import { dropWhile } from 'lodash'; import classNames from 'classnames'; import { Avatar, InlineTextButton, ReviewRating } from '../../components'; import { formatDate } from '../../util/dates'; -import { ensureTransaction, ensureUser, ensureListing } from '../../util/data'; +import { ensureTransaction, ensureUser, ensureListing, userDisplayName } from '../../util/data'; import * as propTypes from '../../util/propTypes'; import * as log from '../../util/log'; @@ -212,7 +212,13 @@ const Transition = props => { const currentTransaction = ensureTransaction(transaction); const customer = currentTransaction.customer; const provider = currentTransaction.provider; - const listingTitle = currentTransaction.listing.attributes.title; + + const deletedListing = intl.formatMessage({ + id: 'ActivityFeed.deletedListing', + }); + const listingTitle = currentTransaction.listing.attributes.deleted + ? deletedListing + : currentTransaction.listing.attributes.title; const lastTransition = currentTransaction.attributes.lastTransition; const ownRole = @@ -220,10 +226,13 @@ const Transition = props => { ? propTypes.TX_TRANSITION_ACTOR_CUSTOMER : propTypes.TX_TRANSITION_ACTOR_PROVIDER; + const bannedUserDisplayName = intl.formatMessage({ + id: 'ActivityFeed.bannedUserDisplayName', + }); const otherUsersName = ownRole === propTypes.TX_TRANSITION_ACTOR_CUSTOMER - ? provider.attributes.profile.displayName - : customer.attributes.profile.displayName; + ? userDisplayName(provider, bannedUserDisplayName) + : userDisplayName(customer, bannedUserDisplayName); const transitionMessage = resolveTransitionMessage( transition, diff --git a/src/translations/en.json b/src/translations/en.json index 7c5cf3cf..1907fede 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1,4 +1,6 @@ { + "ActivityFeed.bannedUserDisplayName": "Banned user", + "ActivityFeed.deletedListing": "deleted listing", "ActivityFeed.leaveAReview": "Leave a review for { displayName }.", "ActivityFeed.leaveAReviewSecond": "Leave a review for { displayName } to see their review.", "ActivityFeed.ownTransitionAccept": "You accepted the booking request.", From f2630f0123ebd481ed70154183342019cdd9edf0 Mon Sep 17 00:00:00 2001 From: Vesa Luusua Date: Fri, 15 Dec 2017 02:34:50 +0200 Subject: [PATCH 02/10] SendMessageForm: mobile paddings need to work with autosize's content-box --- src/containers/SendMessageForm/SendMessageForm.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/containers/SendMessageForm/SendMessageForm.css b/src/containers/SendMessageForm/SendMessageForm.css index ae0bf2ea..b775125d 100644 --- a/src/containers/SendMessageForm/SendMessageForm.css +++ b/src/containers/SendMessageForm/SendMessageForm.css @@ -23,6 +23,10 @@ margin: 0; max-height: 183px; + /* We need to remove horizontal paddings, + since textarea uses content-box (autosize library fix). */ + width: calc(100vw - 100px); + background-color: var(--matterColorLight); @media (--viewportMedium) { @@ -32,6 +36,7 @@ padding: 0 0 5px 0; margin: 0; max-height: initial; + width: 100%; border-bottom-width: 2px; border-bottom-color: var(--attentionColor); background-color: transparent; From 921cd9e27a290d718a8db1d795c8d4c01e568298 Mon Sep 17 00:00:00 2001 From: Vesa Luusua Date: Fri, 15 Dec 2017 02:49:02 +0200 Subject: [PATCH 03/10] propTypes: attributes are null for a deleted listing --- src/util/propTypes.js | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/src/util/propTypes.js b/src/util/propTypes.js index 748de3dc..44cb80c1 100644 --- a/src/util/propTypes.js +++ b/src/util/propTypes.js @@ -23,7 +23,18 @@ import { types as sdkTypes } from './sdkLoader'; import { ensureTransaction } from './data'; const { UUID, LatLng, LatLngBounds, Money } = sdkTypes; -const { arrayOf, bool, func, instanceOf, number, object, oneOf, shape, string } = PropTypes; +const { + arrayOf, + bool, + func, + instanceOf, + number, + object, + oneOf, + oneOfType, + shape, + string, +} = PropTypes; // Fixed value export const value = val => oneOf([val]); @@ -113,19 +124,25 @@ export const user = shape({ profileImage: image, }); +const listingAttributes = shape({ + title: string.isRequired, + description: string.isRequired, + address: string.isRequired, + geolocation: latlng.isRequired, + closed: bool.isRequired, + deleted: value(false).isRequired, + price: money, +}); + +const deletedListingAttributes = shape({ + deleted: value(true).isRequired, +}); + // Denormalised listing object export const listing = shape({ id: uuid.isRequired, type: value('listing').isRequired, - attributes: shape({ - title: string.isRequired, - description: string.isRequired, - address: string.isRequired, - geolocation: latlng.isRequired, - closed: bool.isRequired, - deleted: bool.isRequired, - price: money, - }).isRequired, + attributes: oneOfType([listingAttributes, deletedListingAttributes]).isRequired, author: user, images: arrayOf(image), }); From fcfc069df7cc8901b7249334ece3222323a440da Mon Sep 17 00:00:00 2001 From: Vesa Luusua Date: Fri, 15 Dec 2017 02:54:32 +0200 Subject: [PATCH 04/10] TransactionPage can handle deleted listing --- src/containers/TransactionPage/TransactionPage.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/containers/TransactionPage/TransactionPage.js b/src/containers/TransactionPage/TransactionPage.js index b1e9db68..0b50c217 100644 --- a/src/containers/TransactionPage/TransactionPage.js +++ b/src/containers/TransactionPage/TransactionPage.js @@ -71,7 +71,10 @@ export const TransactionPageComponent = props => { const currentTransaction = ensureTransaction(transaction); const currentListing = ensureListing(currentTransaction.listing); - const listingTitle = currentListing.attributes.title; + const deletedListingTitle = intl.formatMessage({ + id: 'TransactionPage.deletedListing', + }); + const listingTitle = currentListing.attributes.title || deletedListingTitle; // Redirect users with someone else's direct link to their own inbox/sales or inbox/orders page. const isDataAvailable = From 57e59860cbbf54ccf328195cac14ffc5cc1ac898 Mon Sep 17 00:00:00 2001 From: Vesa Luusua Date: Fri, 15 Dec 2017 02:56:12 +0200 Subject: [PATCH 05/10] TransactionPanel (combined content of OrderDetailsPanel and SaleDetailsPanel --- .../TransactionPanel/TransactionPanel.css | 408 +++++++++++++++++ .../TransactionPanel.helpers.js | 421 ++++++++++++++++++ .../TransactionPanel/TransactionPanel.js | 383 ++++++++++++++++ src/components/index.js | 1 + src/translations/en.json | 29 ++ 5 files changed, 1242 insertions(+) create mode 100644 src/components/TransactionPanel/TransactionPanel.css create mode 100644 src/components/TransactionPanel/TransactionPanel.helpers.js create mode 100644 src/components/TransactionPanel/TransactionPanel.js diff --git a/src/components/TransactionPanel/TransactionPanel.css b/src/components/TransactionPanel/TransactionPanel.css new file mode 100644 index 00000000..350189d4 --- /dev/null +++ b/src/components/TransactionPanel/TransactionPanel.css @@ -0,0 +1,408 @@ +@import '../../marketplace.css'; + +.root { + position: relative; +} + +.container { + display: flex; + flex-direction: column; + justify-content: space-between; + + @media (--viewportLarge) { + flex-direction: row; + max-width: 1156px; /* 1060 + (paddingLeft + paddingRight) */ + margin: 0 auto 57px auto; + padding: 0 48px; + } +} + +.txInfo { + margin-bottom: 210px; + + @media (--viewportLarge) { + flex-basis: 538px; + flex-grow: 0; + flex-shrink: 1; + margin-right: 56px; + margin-bottom: 0; + } +} + +.imageWrapperMobile { + /* Layout */ + display: block; + width: 100%; + position: relative; + + @media (--viewportLarge) { + display: none; + } +} + +/* Firefox doesn't support image aspect ratio inside flexbox */ +.aspectWrapper { + padding-bottom: 66.6667%; /* 3:2 Aspect Ratio */ + background-color: var(--matterColorNegative); /* Loading BG color */ +} + +.rootForImage { + /* Layout - image will take space defined by aspect ratio wrapper */ + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + width: 100%; + + @media (--viewportLarge) { + border-radius: 2px 2px 0 0; + } +} + +.avatarWrapperMobile { + /* Position (over the listing image)*/ + margin-left: 24px; + margin-top: -31px; + + /* Rendering context to the same lavel as listing image */ + position: relative; + + /* Layout */ + display: block; +} + +.avatarWrapperCustomerDesktop { + composes: avatarWrapperMobile; + + @media (--viewportLarge) { + margin-left: 48px; + } +} + +.avatarMobile { + @media (--viewportLarge) { + display: none; + } +} + +.avatarDesktop { + display: none; + + @media (--viewportLarge) { + display: flex; + margin-top: 119px; + } +} + +.headingOrder { + margin: 29px 24px 0 24px; + + @media (--viewportMedium) { + margin: 25px 24px 0 24px; + max-width: 80%; + } + + @media (--viewportLarge) { + max-width: 100%; + margin: 177px 0 0 0; + } +} + +.headingSale { + margin: 22px 24px 0 24px; + + @media (--viewportMedium) { + max-width: 80%; + } + + @media (--viewportLarge) { + max-width: 100%; + margin: 42px 0 0 0; + } +} + +.mainTitle { + display: block; +} + +.transactionInfoMessage { + margin: 18px 24px 0 24px; + + @media (--viewportMedium) { + margin: 23px 24px 0 24px; + } + @media (--viewportLarge) { + margin: 23px 0 0 0; + } +} + +.transitionDate { + white-space: nowrap; +} + +.breakdownMobile { + margin-top: 47px; + + @media (--viewportMedium) { + margin-top: 43px; + } + @media (--viewportLarge) { + display: none; + } +} + +.desktopAside { + margin: 1px 0 0 0; + + @media (--viewportLarge) { + margin-top: 123px; + margin-left: 0; + margin-right: 0; + } +} + +.breakdownDesktop { + display: none; + + position: sticky; + top: -272px; /* This is a hack to showcase how the component would look when the image isn't sticky */ + width: 409px; + background-color: var(--matterColorLight); + border: 1px solid var(--matterColorNegative); + border-radius: 2px; + + @media (--viewportLarge) { + display: block; + } +} + +.breakdownImageWrapper { + /* Layout */ + display: block; + width: 100%; + position: relative; +} + +.breakdownHeadings { + display: none; + + @media (--viewportLarge) { + display: block; + margin-left: 48px; + margin-right: 48px; + } +} + +.breakdownTitle { + margin-bottom: 10px; + + @media (--viewportLarge) { + margin-top: 14px; + margin-bottom: 0; + } +} + +.breakdownSubtitle { + @apply --marketplaceH5FontStyles; + color: var(--matterColorAnti); + + margin-top: 0; + margin-bottom: 0; + + @media (--viewportLarge) { + margin-top: 1px; + margin-bottom: 0; + } +} + +.bookingBreakdownTitle { + /* Font */ + color: var(--matterColorAnti); + + margin: 0 24px 0 24px; + + @media (--viewportLarge) { + margin: 37px 48px 26px 48px; + } +} + +.breakdown { + margin: 14px 24px 0 24px; + + @media (--viewportMedium) { + margin: 18px 24px 0 24px; + } + @media (--viewportLarge) { + margin: 14px 48px 47px 48px; + } +} + +.feedContainer { + margin: 46px 24px 0 24px; + + @media (--viewportMedium) { + margin: 46px 24px 0 24px; + } + @media (--viewportLarge) { + margin: 43px 0 0 0; + } +} + +.feedContainerWithInfoAbove { + @media (--viewportLarge) { + margin: 38px 0 0 0; + } +} + +.feedHeading { + color: var(--matterColorAnti); + margin: 0; + + @media (--viewportMedium) { + margin: 0; + } +} + +.feed { + margin-top: 20px; +} + +.sendMessageForm { + position: relative; + margin-top: 46px; + width: 100%; + + @media (--viewportMedium) { + margin-top: 49px; + } + @media (--viewportLarge) { + margin-top: 47px; + } +} + +.sendMessageFormFixed { + position: fixed; + bottom: 0; + box-shadow: var(--boxShadowBottomForm); + + @media (--viewportLarge) { + position: relative; + box-shadow: none; + } +} + +.sendMessageFormFocusedInMobileSafari { + position: absolute; + + @media (--viewportLarge) { + position: relative; + } +} + +.actionButtons { + /* Position action button row above the footer */ + z-index: 9; + position: fixed; + bottom: 0; + width: 100%; + padding: 18px 24px 18px 24px; + + /* Contain repainting to this component only */ + /* 3D painting container helps scrolling */ + transform: translate3d(0, 0, 0); + + box-shadow: var(--boxShadowTop); + background-color: white; + + @media (--viewportLarge) { + z-index: unset; + position: static; + box-shadow: none; + width: auto; + margin: 0 48px 0 48px; + padding: 0; + } +} + +.mobileActionButtons { + display: block; + + @media (--viewportLarge) { + display: none; + } +} + +.desktopActionButtons { + display: none; + + @media (--viewportLarge) { + display: block; + margin-bottom: 48px; + } +} + +.actionButtonsWithFormFocused { + display: none; + + @media (--viewportLarge) { + display: block; + } +} + +.actionButtonWrapper { + width: 100%; + display: flex; + flex-direction: row; + + @media (--viewportLarge) { + flex-direction: column; + } + + & button:first-child { + margin: 0 12px 0 0; + + @media (--viewportLarge) { + margin: 8px 0 0 0; + + /* Switch order in desktop layout with accept button being on the top */ + order: 1; + } + } +} + +.messageError { + color: var(--failColor); + margin: 13px 0 22px 0; + + @media (--viewportMedium) { + margin: 13px 0 23px 0; + } + + @media (--viewportLarge) { + margin: 12px 0 23px 0; + } +} + +.actionError { + @apply --marketplaceH5FontStyles; + color: var(--failColor); + margin: 0 0 11px 0; + + @media (--viewportMedium) { + margin: 0 0 10px 0; + } + @media (--viewportLarge) { + margin: 0; + } +} + +.actionErrors { + width: 100%; + text-align: center; + + @media (--viewportLarge) { + position: absolute; + top: 151px; + } +} diff --git a/src/components/TransactionPanel/TransactionPanel.helpers.js b/src/components/TransactionPanel/TransactionPanel.helpers.js new file mode 100644 index 00000000..bc70cd28 --- /dev/null +++ b/src/components/TransactionPanel/TransactionPanel.helpers.js @@ -0,0 +1,421 @@ +import React from 'react'; +import { FormattedMessage } from 'react-intl'; +import classNames from 'classnames'; +import * as propTypes from '../../util/propTypes'; +import { userDisplayName } from '../../util/data'; +import { createSlug } from '../../util/urlHelpers'; +import { + ActivityFeed, + BookingBreakdown, + NamedLink, + PrimaryButton, + SecondaryButton, +} from '../../components'; +import css from './TransactionPanel.css'; + +// Functional component as a helper to build ActivityFeed section +export const FeedSection = props => { + const { + className, + rootClassName, + currentTransaction, + currentUser, + fetchMessagesError, + fetchMessagesInProgress, + initialMessageFailed, + messages, + oldestMessagePageFetched, + onShowMoreMessages, + onOpenReviewModal, + totalMessagePages, + } = props; + + const txTransitions = currentTransaction.attributes.transitions + ? currentTransaction.attributes.transitions + : []; + const hasOlderMessages = totalMessagePages > oldestMessagePageFetched; + + const showFeed = + messages.length > 0 || txTransitions.length > 0 || initialMessageFailed || fetchMessagesError; + + const classes = classNames(rootClassName || css.feedContainer, className); + + return showFeed ? ( +
+

+ +

+ {initialMessageFailed ? ( +

+ +

+ ) : null} + {fetchMessagesError ? ( +

+ +

+ ) : null} + { + onShowMoreMessages(currentTransaction.id); + }} + fetchMessagesInProgress={fetchMessagesInProgress} + /> +
+ ) : null; +}; + +// Functional component as a helper to build BookingBreakdown +export const BreakdownMaybe = props => { + const { className, rootClassName, transaction, transactionRole } = props; + const loaded = transaction && transaction.id && transaction.booking && transaction.booking.id; + + const classes = classNames(rootClassName || css.breakdown, className); + return loaded ? ( + + ) : null; +}; + +// Functional component as a helper to build ActionButtons +// Provider only when state is preauthorized +export const ActionButtonsMaybe = props => { + const { + className, + rootClassName, + canShowButtons, + transaction, + acceptInProgress, + declineInProgress, + acceptSaleError, + declineSaleError, + onAcceptSale, + onDeclineSale, + } = props; + + const buttonsDisabled = acceptInProgress || declineInProgress; + + const acceptErrorMessage = acceptSaleError ? ( +

+ +

+ ) : null; + const declineErrorMessage = declineSaleError ? ( +

+ +

+ ) : null; + + const classes = classNames(rootClassName || css.actionButtons, className); + + return canShowButtons ? ( +
+
+ {acceptErrorMessage} + {declineErrorMessage} +
+
+ onDeclineSale(transaction.id)} + > + + + onAcceptSale(transaction.id)} + > + + +
+
+ ) : null; +}; + +const createListingLink = (listingLoaded, title, id) => { + if (listingLoaded && title) { + const params = { id: id.uuid, slug: createSlug(title) }; + return ( + + {title} + + ); + } else { + return ; + } +}; + +// Functional component as a helper to build order title +export const OrderTitle = props => { + const { + className, + rootClassName, + transaction, + customerDisplayName: customerName, + currentListing, + } = props; + const listingLoaded = !!currentListing.id; + const listingTitle = currentListing.attributes.title; // TODO deleted listing + const listingLink = createListingLink(listingLoaded, listingTitle, currentListing.id); + + const classes = classNames(rootClassName || css.headingOrder, className); + + 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) || + propTypes.txHasFirstReview(transaction) || + propTypes.txIsReviewed(transaction) + ) { + return ( +

+ +

+ ); + } else { + return null; + } +}; + +// Functional component as a helper to build order message below title +export const OrderMessage = props => { + const { + className, + rootClassName, + transaction, + authorDisplayName: providerName, + listingDeleted, + } = props; + const classes = classNames(rootClassName || css.transactionInfoMessage, className); + + if (!listingDeleted && propTypes.txIsPreauthorized(transaction)) { + return ( +

+ +

+ ); + } else if (listingDeleted) { + return ( +

+ +

+ ); + } + return null; +}; + +// Functional component as a helper to build sale title +export const SaleTitle = props => { + const { + className, + rootClassName, + transaction, + customerDisplayName: customerName, + currentListing, + } = props; + const listingLoaded = !!currentListing.id; + const listingTitle = currentListing.attributes.title; + const listingLink = createListingLink(listingLoaded, listingTitle, currentListing.id); + + const classes = classNames(rootClassName || css.headingSale, className); + + 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) || + propTypes.txHasFirstReview(transaction) || + propTypes.txIsReviewed(transaction) + ) { + return ( +

+ +

+ ); + } else { + return null; + } +}; + +// Functional component as a helper to build sale message below title +export const SaleMessage = props => { + const { + className, + rootClassName, + transaction, + customerDisplayName: customerName, + isCustomerBanned, + } = props; + const classes = classNames(rootClassName || css.transactionInfoMessage, className); + + if (!isCustomerBanned && propTypes.txIsPreauthorized(transaction)) { + return ( +

+ +

+ ); + } else if (isCustomerBanned) { + return ( +

+ +

+ ); + } + return null; +}; + +// Functional component as a helper to choose and show Order or Sale title +export const TransactionPageTitle = props => { + return props.transactionRole === 'customer' ? ( + + ) : ( + + ); +}; + +// Functional component as a helper to choose and show Order or Sale message +export const TransactionPageMessage = props => { + return props.transactionRole === 'customer' ? ( + + ) : ( + + ); +}; + +// Helper function to get display names for different roles +export const displayNames = ( + currentUser, + currentProvider, + currentCustomer, + bannedUserDisplayName +) => { + const authorDisplayName = userDisplayName(currentProvider, bannedUserDisplayName); + const customerDisplayName = userDisplayName(currentCustomer, bannedUserDisplayName); + + let otherUserDisplayName = ''; + const currentUserIsCustomer = + currentUser.id && currentCustomer.id && currentUser.id.uuid === currentCustomer.id.uuid; + const currentUserIsProvider = + currentUser.id && currentProvider.id && currentUser.id.uuid === currentProvider.id.uuid; + + if (currentUserIsCustomer) { + otherUserDisplayName = authorDisplayName; + } else if (currentUserIsProvider) { + otherUserDisplayName = customerDisplayName; + } + + return { + authorDisplayName, + customerDisplayName, + otherUserDisplayName, + }; +}; diff --git a/src/components/TransactionPanel/TransactionPanel.js b/src/components/TransactionPanel/TransactionPanel.js new file mode 100644 index 00000000..f96403e9 --- /dev/null +++ b/src/components/TransactionPanel/TransactionPanel.js @@ -0,0 +1,383 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { injectIntl, intlShape, FormattedMessage } from 'react-intl'; +import classNames from 'classnames'; +import * as propTypes from '../../util/propTypes'; +import { ensureListing, ensureTransaction, ensureUser } from '../../util/data'; +import { isMobileSafari } from '../../util/userAgent'; +import { AvatarMedium, AvatarLarge, ResponsiveImage, ReviewModal } from '../../components'; +import { SendMessageForm } from '../../containers'; + +// These are internal components that make this file more readable. +import { + ActionButtonsMaybe, + BreakdownMaybe, + FeedSection, + TransactionPageTitle, + TransactionPageMessage, + displayNames, +} from './TransactionPanel.helpers'; +import css from './TransactionPanel.css'; + +export class TransactionPanelComponent extends Component { + constructor(props) { + super(props); + this.state = { + sendMessageFormFocused: false, + isReviewModalOpen: false, + reviewSubmitted: false, + }; + this.isMobSaf = false; + this.sendMessageFormName = 'TransactionPanel.SendMessageForm'; + + this.onOpenReviewModal = this.onOpenReviewModal.bind(this); + this.onSubmitReview = this.onSubmitReview.bind(this); + this.onSendMessageFormFocus = this.onSendMessageFormFocus.bind(this); + this.onSendMessageFormBlur = this.onSendMessageFormBlur.bind(this); + this.onMessageSubmit = this.onMessageSubmit.bind(this); + this.scrollToMessage = this.scrollToMessage.bind(this); + } + + componentWillMount() { + this.isMobSaf = isMobileSafari(); + } + + onOpenReviewModal() { + this.setState({ isReviewModalOpen: true }); + } + + onSubmitReview(values) { + const { onSendReview, transaction } = this.props; + const currentTransaction = ensureTransaction(transaction); + const { reviewRating, reviewContent } = values; + const rating = Number.parseInt(reviewRating, 10); + onSendReview(currentTransaction, rating, reviewContent) + .then(r => this.setState({ isReviewModalOpen: false, reviewSubmitted: true })) + .catch(e => { + // Do nothing. + }); + } + + onSendMessageFormFocus() { + this.setState({ sendMessageFormFocused: true }); + if (this.isMobSaf) { + // Scroll to bottom + window.scroll({ top: document.body.scrollHeight, left: 0, behavior: 'smooth' }); + } + } + + onSendMessageFormBlur() { + this.setState({ sendMessageFormFocused: false }); + } + + onMessageSubmit(values) { + const message = values.message ? values.message.trim() : null; + const { transaction, onResetForm, onSendMessage } = this.props; + const ensuredTransaction = ensureTransaction(transaction); + + if (!message) { + return; + } + onSendMessage(ensuredTransaction.id, message) + .then(messageId => { + onResetForm(this.sendMessageFormName); + this.scrollToMessage(messageId); + }) + .catch(e => { + // Ignore, Redux handles the error + }); + } + + scrollToMessage(messageId) { + const selector = `#msg-${messageId.uuid}`; + const el = document.querySelector(selector); + if (el) { + el.scrollIntoView({ + block: 'start', + behavior: 'smooth', + }); + } + } + + render() { + const { + rootClassName, + className, + currentUser, + transaction, + totalMessagePages, + oldestMessagePageFetched, + messages, + initialMessageFailed, + fetchMessagesInProgress, + fetchMessagesError, + sendMessageInProgress, + sendMessageError, + sendReviewInProgress, + sendReviewError, + onManageDisableScrolling, + onShowMoreMessages, + transactionRole, + intl, + onAcceptSale, + onDeclineSale, + acceptInProgress, + declineInProgress, + acceptSaleError, + declineSaleError, + } = this.props; + + const currentTransaction = ensureTransaction(transaction); + const currentListing = ensureListing(currentTransaction.listing); + const currentProvider = ensureUser(currentTransaction.provider); + const currentCustomer = ensureUser(currentTransaction.customer); + const isCustomer = transactionRole === 'customer'; + const isProvider = transactionRole === 'provider'; + + const listingLoaded = !!currentListing.id; + const listingDeleted = listingLoaded && currentListing.attributes.deleted; + const customerLoaded = !!currentCustomer.id; + const isCustomerBanned = customerLoaded && currentCustomer.attributes.banned; + const canShowButtons = + isProvider && propTypes.txIsPreauthorized(currentTransaction) && !isCustomerBanned; + + const bannedUserDisplayName = intl.formatMessage({ + id: 'TransactionPanel.bannedUserDisplayName', + }); + const deletedListingTitle = intl.formatMessage({ + id: 'TransactionPanel.deletedListingTitle', + }); + + const { authorDisplayName, customerDisplayName, otherUserDisplayName } = displayNames( + currentUser, + currentProvider, + currentCustomer, + bannedUserDisplayName + ); + + const listingTitle = currentListing.attributes.deleted + ? deletedListingTitle + : currentListing.attributes.title; + + const firstImage = + currentListing.images && currentListing.images.length > 0 ? currentListing.images[0] : null; + + const actionButtonClasses = classNames(css.actionButtons, { + [css.actionButtonsWithFormFocused]: this.state.sendMessageFormFocused, + }); + + const actionButtons = ( + + ); + + const sendMessagePlaceholder = intl.formatMessage( + { id: 'TransactionPanel.sendMessagePlaceholder' }, + { name: otherUserDisplayName } + ); + + const sendMessageFormClasses = classNames(css.sendMessageForm, { + [css.sendMessageFormFixed]: !canShowButtons || this.state.sendMessageFormFocused, + [css.sendMessageFormFocusedInMobileSafari]: + this.isMobSaf && this.state.sendMessageFormFocused, + }); + + const showInfoMessage = + listingDeleted || (!listingDeleted && propTypes.txIsPreauthorized(transaction)); // !!orderInfoMessage; + + const feedContainerClasses = classNames(css.feedContainer, { + [css.feedContainerWithInfoAbove]: showInfoMessage, + }); + + const classes = classNames(rootClassName || css.root, className); + + return ( +
+
+
+
+
+ +
+
+
+ +
+ {isProvider ? ( +
+ +
+ ) : null} + + + + +
+

+ +

+ +
+ + + + + {isProvider ?
{actionButtons}
: null} +
+ +
+
+
+
+ +
+
+ {isCustomer ? ( +
+ +
+ ) : null} + {isCustomer ? ( +
+

{listingTitle}

+

+ +

+
+ ) : null} +

+ +

+ + {isProvider ?
{actionButtons}
: null} +
+
+
+ this.setState({ isReviewModalOpen: false })} + onManageDisableScrolling={onManageDisableScrolling} + onSubmitReview={this.onSubmitReview} + revieweeName={authorDisplayName} + reviewSent={this.state.reviewSubmitted} + sendReviewInProgress={sendReviewInProgress} + sendReviewError={sendReviewError} + /> +
+ ); + } +} + +TransactionPanelComponent.defaultProps = { + rootClassName: null, + className: null, + currentUser: null, + acceptSaleError: null, + declineSaleError: null, + fetchMessagesError: null, + initialMessageFailed: null, + sendMessageError: null, + sendReviewError: null, +}; + +const { arrayOf, bool, func, number, string } = PropTypes; + +TransactionPanelComponent.propTypes = { + rootClassName: string, + className: string, + + currentUser: propTypes.currentUser, + transaction: propTypes.transaction.isRequired, + totalMessagePages: number.isRequired, + oldestMessagePageFetched: number.isRequired, + messages: arrayOf(propTypes.message).isRequired, + initialMessageFailed: bool, + fetchMessagesInProgress: bool.isRequired, + fetchMessagesError: propTypes.error, + sendMessageInProgress: bool.isRequired, + sendMessageError: propTypes.error, + sendReviewInProgress: bool.isRequired, + sendReviewError: propTypes.error, + onManageDisableScrolling: func.isRequired, + onShowMoreMessages: func.isRequired, + onSendMessage: func.isRequired, + onSendReview: func.isRequired, + onResetForm: func.isRequired, + + // Sale related props + onAcceptSale: func.isRequired, + onDeclineSale: func.isRequired, + acceptInProgress: bool.isRequired, + declineInProgress: bool.isRequired, + acceptSaleError: propTypes.error, + declineSaleError: propTypes.error, + + // from injectIntl + intl: intlShape, +}; + +const TransactionPanel = injectIntl(TransactionPanelComponent); + +export default TransactionPanel; diff --git a/src/components/index.js b/src/components/index.js index 8cd12c2b..6b9c7a0e 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -107,6 +107,7 @@ export { default as TextInputField } from './TextInputField/TextInputField'; export { default as Topbar } from './Topbar/Topbar'; export { default as TopbarDesktop } from './TopbarDesktop/TopbarDesktop'; export { default as TopbarMobileMenu } from './TopbarMobileMenu/TopbarMobileMenu'; +export { default as TransactionPanel } from './TransactionPanel/TransactionPanel'; export { default as UserCard } from './UserCard/UserCard'; export { default as UserNav } from './UserNav/UserNav'; export { default as ValidationError } from './ValidationError/ValidationError'; diff --git a/src/translations/en.json b/src/translations/en.json index 1907fede..31ec7272 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -628,6 +628,35 @@ "TransactionPage.loadingOrderData": "Loading order data.", "TransactionPage.loadingSaleData": "Loading sale data.", "TransactionPage.title": "Sale details: {title}", + "TransactionPanel.acceptButton": "Accept", + "TransactionPanel.acceptSaleFailed": "Oops, accepting failed. Please try again.", + "TransactionPanel.activityHeading": "Activity", + "TransactionPanel.bannedUserDisplayName": "Banned user", + "TransactionPanel.bookingBreakdownTitle": "Booking breakdown", + "TransactionPanel.customerBannedStatus": "The user made the request, but was later banned.", + "TransactionPanel.declineButton": "Decline", + "TransactionPanel.declineSaleFailed": "Oops, declining failed. Please try again.", + "TransactionPanel.deletedListingOrderTitle": "a listing", + "TransactionPanel.deletedListingTitle": "Deleted listing", + "TransactionPanel.hostedBy": "Hosted by {name}", + "TransactionPanel.initialMessageFailed": "Whoops, failed to send message from checkout.", + "TransactionPanel.messageDeletedListing": "However, the listing is deleted and cannot be viewed anymore.", + "TransactionPanel.messageLoadingFailed": "Something went wrong when loading messages. Please refresh the page and try again.", + "TransactionPanel.orderAcceptedSubtitle": "Your booking for {listingLink} has been accepted.", + "TransactionPanel.orderAcceptedTitle": "Woohoo {customerName}!", + "TransactionPanel.orderCancelledTitle": "{customerName}, your booking for {listingLink} has been cancelled.", + "TransactionPanel.orderDeclinedTitle": "{customerName}, your booking for {listingLink} has been declined.", + "TransactionPanel.orderDeliveredTitle": "{customerName}, your booking for {listingLink} has been completed.", + "TransactionPanel.orderPreauthorizedInfo": "{providerName} has been notified about the booking request. Sit back and relax.", + "TransactionPanel.orderPreauthorizedSubtitle": "You have requested to book {listingLink}.", + "TransactionPanel.orderPreauthorizedTitle": "Great success, {customerName}!", + "TransactionPanel.saleAcceptedTitle": "You accepted a request from {customerName} to book {listingLink}.", + "TransactionPanel.saleCancelledTitle": "The booking from {customerName} for {listingLink} has been cancelled.", + "TransactionPanel.saleDeclinedTitle": "The request from {customerName} to book {listingLink} has been declined.", + "TransactionPanel.saleDeliveredTitle": "The booking from {customerName} for {listingLink} has been completed.", + "TransactionPanel.saleRequestedInfo": "{customerName} is waiting for your response.", + "TransactionPanel.saleRequestedTitle": "{customerName} has requested to book {listingLink}.", + "TransactionPanel.sendMessagePlaceholder": "Send a message to {name}…", "UserCard.contactUser": "Contact", "UserCard.heading": "Hello, I'm {name}.", "UserCard.showFullBioLink": "more", From 5e0f0a1d8a07ae1eee57e842f30f93967db11781 Mon Sep 17 00:00:00 2001 From: Vesa Luusua Date: Fri, 15 Dec 2017 03:33:47 +0200 Subject: [PATCH 06/10] Tests combined for TransactionPanel --- .../TransactionPanel/TransactionPanel.test.js | 321 + .../TransactionPanel.test.js.snap | 9553 +++++++++++++++++ 2 files changed, 9874 insertions(+) create mode 100644 src/components/TransactionPanel/TransactionPanel.test.js create mode 100644 src/components/TransactionPanel/__snapshots__/TransactionPanel.test.js.snap diff --git a/src/components/TransactionPanel/TransactionPanel.test.js b/src/components/TransactionPanel/TransactionPanel.test.js new file mode 100644 index 00000000..d4b7633c --- /dev/null +++ b/src/components/TransactionPanel/TransactionPanel.test.js @@ -0,0 +1,321 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import { types } from '../../util/sdkLoader'; +import { + createTransaction, + createBooking, + createListing, + createUser, + createCurrentUser, + createMessage, +} from '../../util/test-data'; +import { renderShallow } from '../../util/test-helpers'; +import { fakeIntl } from '../../util/test-data'; +import * as propTypes from '../../util/propTypes'; +import { BreakdownMaybe } from './TransactionPanel.helpers'; +import { TransactionPanelComponent } from './TransactionPanel'; + +const noop = () => null; + +const { Money } = types; + +describe('TransactionPanel - Sale', () => { + const baseTxAttrs = { + total: new Money(16500, 'USD'), + commission: new Money(1000, 'USD'), + booking: createBooking('booking1', { + start: new Date(Date.UTC(2017, 5, 10)), + end: new Date(Date.UTC(2017, 5, 13)), + }), + listing: createListing('listing1'), + customer: createUser('customer1'), + lastTransitionedAt: new Date(Date.UTC(2017, 5, 10)), + }; + const txPreauthorized = createTransaction({ + id: 'sale-preauthorized', + lastTransition: propTypes.TX_TRANSITION_PREAUTHORIZE, + ...baseTxAttrs, + }); + + const txAccepted = createTransaction({ + id: 'sale-accepted', + lastTransition: propTypes.TX_TRANSITION_ACCEPT, + ...baseTxAttrs, + }); + + const txDeclined = createTransaction({ + id: 'sale-declined', + lastTransition: propTypes.TX_TRANSITION_DECLINE, + ...baseTxAttrs, + }); + + const txAutoDeclined = createTransaction({ + id: 'sale-autodeclined', + lastTransition: propTypes.TX_TRANSITION_AUTO_DECLINE, + ...baseTxAttrs, + }); + + const txCanceled = createTransaction({ + id: 'sale-canceled', + lastTransition: propTypes.TX_TRANSITION_CANCELED, + ...baseTxAttrs, + }); + + const txDelivered = createTransaction({ + id: 'sale-delivered', + lastTransition: propTypes.TX_TRANSITION_MARK_DELIVERED, + ...baseTxAttrs, + }); + + const panelBaseProps = { + onAcceptSale: noop, + onDeclineSale: noop, + acceptInProgress: false, + declineInProgress: false, + currentUser: createCurrentUser('user1'), + totalMessages: 2, + totalMessagePages: 1, + oldestMessagePageFetched: 1, + messages: [ + createMessage('msg1', {}, { sender: createUser('user1') }), + createMessage('msg2', {}, { sender: createUser('user2') }), + ], + initialMessageFailed: false, + fetchMessagesInProgress: false, + sendMessageInProgress: false, + sendReviewInProgress: false, + onManageDisableScrolling: noop, + onOpenReviewModal: noop, + onShowMoreMessages: noop, + onSendMessage: noop, + onSendReview: noop, + onResetForm: noop, + intl: fakeIntl, + }; + + it('preauthorized matches snapshot', () => { + const props = { + ...panelBaseProps, + transaction: txPreauthorized, + }; + const tree = renderShallow(); + expect(tree).toMatchSnapshot(); + }); + it('accepted matches snapshot', () => { + const props = { + ...panelBaseProps, + transaction: txAccepted, + }; + const tree = renderShallow(); + expect(tree).toMatchSnapshot(); + }); + it('declined matches snapshot', () => { + const props = { + ...panelBaseProps, + transaction: txDeclined, + }; + const tree = renderShallow(); + expect(tree).toMatchSnapshot(); + }); + it('autodeclined matches snapshot', () => { + const props = { + ...panelBaseProps, + transaction: txAutoDeclined, + }; + const tree = renderShallow(); + expect(tree).toMatchSnapshot(); + }); + it('canceled matches snapshot', () => { + const props = { + ...panelBaseProps, + transaction: txCanceled, + }; + const tree = renderShallow(); + expect(tree).toMatchSnapshot(); + }); + it('delivered matches snapshot', () => { + const props = { + ...panelBaseProps, + transaction: txDelivered, + }; + const tree = renderShallow(); + expect(tree).toMatchSnapshot(); + }); + it('renders correct total price', () => { + const transaction = createTransaction({ + id: 'sale-tx', + lastTransition: propTypes.TX_TRANSITION_PREAUTHORIZE, + total: new Money(16500, 'USD'), + commission: new Money(1000, 'USD'), + booking: createBooking('booking1', { + start: new Date(Date.UTC(2017, 5, 10)), + end: new Date(Date.UTC(2017, 5, 13)), + }), + listing: createListing('listing1'), + customer: createUser('customer1'), + lastTransitionedAt: new Date(Date.UTC(2017, 5, 10)), + }); + const props = { + ...panelBaseProps, + transaction, + }; + const panel = shallow(); + const breakdownProps = panel + .find(BreakdownMaybe) + .first() + .props(); + + // Total price for the provider should be transaction total minus the commission. + expect(breakdownProps.transaction.attributes.payoutTotal).toEqual(new Money(15500, 'USD')); + }); +}); + +describe('TransactionPanel - Order', () => { + const baseTxAttrs = { + total: new Money(16500, 'USD'), + booking: createBooking('booking1', { + start: new Date(Date.UTC(2017, 5, 10)), + end: new Date(Date.UTC(2017, 5, 13)), + }), + listing: createListing('listing1'), + provider: createUser('provider'), + customer: createUser('customer'), + }; + + const txPreauthorized = createTransaction({ + id: 'order-preauthorized', + lastTransition: propTypes.TX_TRANSITION_PREAUTHORIZE, + ...baseTxAttrs, + }); + + const txAccepted = createTransaction({ + id: 'order-accepted', + lastTransition: propTypes.TX_TRANSITION_ACCEPT, + ...baseTxAttrs, + }); + + const txDeclined = createTransaction({ + id: 'order-declined', + lastTransition: propTypes.TX_TRANSITION_DECLINE, + ...baseTxAttrs, + }); + + const txAutoDeclined = createTransaction({ + id: 'order-autodeclined', + lastTransition: propTypes.TX_TRANSITION_AUTO_DECLINE, + ...baseTxAttrs, + }); + + const txCanceled = createTransaction({ + id: 'order-canceled', + lastTransition: propTypes.TX_TRANSITION_CANCELED, + ...baseTxAttrs, + }); + + const txDelivered = createTransaction({ + id: 'order-delivered', + lastTransition: propTypes.TX_TRANSITION_MARK_DELIVERED, + ...baseTxAttrs, + }); + + const panelBaseProps = { + intl: fakeIntl, + currentUser: createCurrentUser('user2'), + totalMessages: 2, + totalMessagePages: 1, + oldestMessagePageFetched: 1, + messages: [ + createMessage('msg1', {}, { sender: createUser('user1') }), + createMessage('msg2', {}, { sender: createUser('user2') }), + ], + initialMessageFailed: false, + fetchMessagesInProgress: false, + sendMessageInProgress: false, + sendReviewInProgress: false, + onManageDisableScrolling: noop, + onOpenReviewModal: noop, + onShowMoreMessages: noop, + onSendMessage: noop, + onSendReview: noop, + onResetForm: noop, + onAcceptSale: noop, + onDeclineSale: noop, + acceptInProgress: false, + declineInProgress: false, + }; + + it('preauthorized matches snapshot', () => { + const props = { + ...panelBaseProps, + transaction: txPreauthorized, + }; + + const tree = renderShallow(); + expect(tree).toMatchSnapshot(); + }); + it('accepted matches snapshot', () => { + const props = { + ...panelBaseProps, + transaction: txAccepted, + }; + const tree = renderShallow(); + expect(tree).toMatchSnapshot(); + }); + it('declined matches snapshot', () => { + const props = { + ...panelBaseProps, + transaction: txDeclined, + }; + const tree = renderShallow(); + expect(tree).toMatchSnapshot(); + }); + it('autodeclined matches snapshot', () => { + const props = { + ...panelBaseProps, + transaction: txAutoDeclined, + }; + const tree = renderShallow(); + expect(tree).toMatchSnapshot(); + }); + it('canceled matches snapshot', () => { + const props = { + ...panelBaseProps, + transaction: txCanceled, + }; + const tree = renderShallow(); + expect(tree).toMatchSnapshot(); + }); + it('delivered matches snapshot', () => { + const props = { + ...panelBaseProps, + transaction: txDelivered, + }; + const tree = renderShallow(); + expect(tree).toMatchSnapshot(); + }); + it('renders correct total price', () => { + const { Money } = types; + const tx = createTransaction({ + id: 'order-tx', + lastTransition: propTypes.TX_TRANSITION_PREAUTHORIZE, + total: new Money(16500, 'USD'), + booking: createBooking('booking1', { + start: new Date(Date.UTC(2017, 5, 10)), + end: new Date(Date.UTC(2017, 5, 13)), + }), + listing: createListing('listing1'), + provider: createUser('provider'), + customer: createUser('customer'), + }); + const props = { + ...panelBaseProps, + transaction: tx, + }; + const panel = shallow(); + const breakdownProps = panel + .find(BreakdownMaybe) + .first() + .props(); + expect(breakdownProps.transaction.attributes.payinTotal).toEqual(new Money(16500, 'USD')); + }); +}); diff --git a/src/components/TransactionPanel/__snapshots__/TransactionPanel.test.js.snap b/src/components/TransactionPanel/__snapshots__/TransactionPanel.test.js.snap new file mode 100644 index 00000000..aa00230f --- /dev/null +++ b/src/components/TransactionPanel/__snapshots__/TransactionPanel.test.js.snap @@ -0,0 +1,9553 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`TransactionPanel - Order accepted matches snapshot 1`] = ` +
+
+
+
+
+ +
+
+
+ +
+ + +
+

+ +

+ +
+ + +
+
+
+
+
+ +
+
+

+ +

+ +
+
+
+ +
+`; + +exports[`TransactionPanel - Order autodeclined matches snapshot 1`] = ` +
+
+
+
+
+ +
+
+
+ +
+ + +
+

+ +

+ +
+ + +
+
+
+
+
+ +
+
+

+ +

+ +
+
+
+ +
+`; + +exports[`TransactionPanel - Order canceled matches snapshot 1`] = ` +
+
+
+
+
+ +
+
+
+ +
+ + +
+

+ +

+ +
+ + +
+
+
+
+
+ +
+
+

+ +

+ +
+
+
+ +
+`; + +exports[`TransactionPanel - Order declined matches snapshot 1`] = ` +
+
+
+
+
+ +
+
+
+ +
+ + +
+

+ +

+ +
+ + +
+
+
+
+
+ +
+
+

+ +

+ +
+
+
+ +
+`; + +exports[`TransactionPanel - Order delivered matches snapshot 1`] = ` +
+
+
+
+
+ +
+
+
+ +
+ + +
+

+ +

+ +
+ + +
+
+
+
+
+ +
+
+

+ +

+ +
+
+
+ +
+`; + +exports[`TransactionPanel - Order preauthorized matches snapshot 1`] = ` +
+
+
+
+
+ +
+
+
+ +
+ + +
+

+ +

+ +
+ + +
+
+
+
+
+ +
+
+

+ +

+ +
+
+
+ +
+`; + +exports[`TransactionPanel - Sale accepted matches snapshot 1`] = ` +
+
+
+
+
+ +
+
+
+ +
+ + +
+

+ +

+ +
+ + +
+
+
+
+
+ +
+
+

+ +

+ +
+
+
+ +
+`; + +exports[`TransactionPanel - Sale autodeclined matches snapshot 1`] = ` +
+
+
+
+
+ +
+
+
+ +
+ + +
+

+ +

+ +
+ + +
+
+
+
+
+ +
+
+

+ +

+ +
+
+
+ +
+`; + +exports[`TransactionPanel - Sale canceled matches snapshot 1`] = ` +
+
+
+
+
+ +
+
+
+ +
+ + +
+

+ +

+ +
+ + +
+
+
+
+
+ +
+
+

+ +

+ +
+
+
+ +
+`; + +exports[`TransactionPanel - Sale declined matches snapshot 1`] = ` +
+
+
+
+
+ +
+
+
+ +
+ + +
+

+ +

+ +
+ + +
+
+
+
+
+ +
+
+

+ +

+ +
+
+
+ +
+`; + +exports[`TransactionPanel - Sale delivered matches snapshot 1`] = ` +
+
+
+
+
+ +
+
+
+ +
+ + +
+

+ +

+ +
+ + +
+
+
+
+
+ +
+
+

+ +

+ +
+
+
+ +
+`; + +exports[`TransactionPanel - Sale preauthorized matches snapshot 1`] = ` +
+
+
+
+
+ +
+
+
+ +
+ + +
+

+ +

+ +
+ + +
+
+
+
+
+ +
+
+

+ +

+ +
+
+
+ +
+`; From 817cc992f359bf12c930b3519af886e0b6e9b122 Mon Sep 17 00:00:00 2001 From: Vesa Luusua Date: Fri, 15 Dec 2017 03:49:52 +0200 Subject: [PATCH 07/10] TransactionPage uses TransactionPanel --- .../TransactionPage/TransactionPage.js | 51 ++++++------------- .../TransactionPage.test.js.snap | 13 ++++- src/translations/en.json | 1 + 3 files changed, 27 insertions(+), 38 deletions(-) diff --git a/src/containers/TransactionPage/TransactionPage.js b/src/containers/TransactionPage/TransactionPage.js index 0b50c217..6fb07d4e 100644 --- a/src/containers/TransactionPage/TransactionPage.js +++ b/src/containers/TransactionPage/TransactionPage.js @@ -11,8 +11,7 @@ import { getMarketplaceEntities } from '../../ducks/marketplaceData.duck'; import { isScrollingDisabled, manageDisableScrolling } from '../../ducks/UI.duck'; import { NamedRedirect, - OrderDetailsPanel, - SaleDetailsPanel, + TransactionPanel, Page, LayoutSingleColumn, LayoutWrapperTopbar, @@ -125,42 +124,16 @@ export const TransactionPageComponent = props => {

); - const salePanel = isDataAvailable ? ( - - ) : null; - const initialMessageFailed = !!( initialMessageFailedToTransaction && currentTransaction.id && initialMessageFailedToTransaction.uuid === currentTransaction.id.uuid ); - const orderPanel = isDataAvailable ? ( - { onSendMessage={onSendMessage} onSendReview={onSendReview} onResetForm={onResetForm} + transactionRole={transactionRole} + onAcceptSale={onAcceptSale} + onDeclineSale={onDeclineSale} + acceptInProgress={acceptInProgress} + declineInProgress={declineInProgress} + acceptSaleError={acceptSaleError} + declineSaleError={declineSaleError} /> - ) : null; - - const saleOrOrderPanel = isDataAvailable && isOwnSale ? salePanel : orderPanel; - const panel = isDataAvailable ? saleOrOrderPanel : loadingOrFailedFetching; + ) : ( + loadingOrFailedFetching + ); return (
-
@@ -204,7 +211,7 @@ exports[`TransactionPage - Sale matches snapshot 1`] = ` rootClassName={null} >
-
diff --git a/src/translations/en.json b/src/translations/en.json index 31ec7272..41922fea 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -623,6 +623,7 @@ "TopbarMobileMenu.yourListingsLink": "Your listings", "TopbarSearchForm.placeholder": "Search saunas…", "TopbarSearchForm.searchHelp": "Tip: You can also search saunas by zip code, for example \"00500\" or city district \"Sörnäinen\".", + "TransactionPage.deletedListing": "deleted listing", "TransactionPage.fetchOrderFailed": "Fetching order data failed.", "TransactionPage.fetchSaleFailed": "Fetching sale data failed.", "TransactionPage.loadingOrderData": "Loading order data.", From ed28ce65d28fd249aebbb24326d8815f5751a4dd Mon Sep 17 00:00:00 2001 From: Vesa Luusua Date: Fri, 15 Dec 2017 03:57:02 +0200 Subject: [PATCH 08/10] Remove OrderDetailsPanel and SaleDetailsPanel --- .../OrderDetailsPanel/OrderDetailsPanel.css | 288 -- .../OrderDetailsPanel/OrderDetailsPanel.js | 439 -- .../OrderDetailsPanel.test.js | 146 - .../OrderDetailsPanel.test.js.snap | 3933 ----------------- .../SaleDetailsPanel/SaleDetailsPanel.css | 343 -- .../SaleDetailsPanel/SaleDetailsPanel.js | 459 -- .../SaleDetailsPanel/SaleDetailsPanel.test.js | 167 - .../SaleDetailsPanel.test.js.snap | 3661 --------------- src/components/index.js | 2 - src/translations/en.json | 34 - 10 files changed, 9472 deletions(-) delete mode 100644 src/components/OrderDetailsPanel/OrderDetailsPanel.css delete mode 100644 src/components/OrderDetailsPanel/OrderDetailsPanel.js delete mode 100644 src/components/OrderDetailsPanel/OrderDetailsPanel.test.js delete mode 100644 src/components/OrderDetailsPanel/__snapshots__/OrderDetailsPanel.test.js.snap delete mode 100644 src/components/SaleDetailsPanel/SaleDetailsPanel.css delete mode 100644 src/components/SaleDetailsPanel/SaleDetailsPanel.js delete mode 100644 src/components/SaleDetailsPanel/SaleDetailsPanel.test.js delete mode 100644 src/components/SaleDetailsPanel/__snapshots__/SaleDetailsPanel.test.js.snap diff --git a/src/components/OrderDetailsPanel/OrderDetailsPanel.css b/src/components/OrderDetailsPanel/OrderDetailsPanel.css deleted file mode 100644 index 8b7e84a2..00000000 --- a/src/components/OrderDetailsPanel/OrderDetailsPanel.css +++ /dev/null @@ -1,288 +0,0 @@ -@import '../../marketplace.css'; - -.root { - 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; - - @media (--viewportLarge) { - flex-direction: row; - justify-content: center; - max-width: 1156px; /* 1060 + (paddingLeft + paddingRight) */ - margin: 0 auto 57px auto; - padding: 0 48px; - } -} - -.imageWrapperMobile { - /* Layout */ - display: block; - width: 100%; - position: relative; - - @media (--viewportLarge) { - display: none; - } -} - -/* Firefox doesn't support image aspect ratio inside flexbox */ -.aspectWrapper { - padding-bottom: 66.6667%; /* 3:2 Aspect Ratio */ - background-color: var(--matterColorNegative); /* Loading BG color */ -} - -.rootForImage { - /* Layout - image will take space defined by aspect ratio wrapper */ - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - width: 100%; - - @media (--viewportLarge) { - border-radius: 2px 2px 0 0; - } -} - -.avatarWrapper { - /* Position (over the listing image)*/ - margin-left: 24px; - margin-top: -31px; - - /* Rendering context to the same lavel as listing image */ - position: relative; - - /* Layout */ - display: block; - - @media (--viewportLarge) { - margin-left: 48px; - } -} - -.avatarMobile { - @media (--viewportLarge) { - display: none; - } -} - -.orderInfo { - margin-bottom: 210px; - - @media (--viewportLarge) { - max-width: 538px; - margin-right: 108px; - margin-bottom: 0; - } -} - -.heading { - margin: 29px 24px 0 24px; - - @media (--viewportMedium) { - margin: 25px 24px 0 24px; - max-width: 80%; - } - - @media (--viewportLarge) { - max-width: 100%; - margin: 177px 0 0 0; - } -} - -.mainTitle { - display: block; -} - -.orderInfoText { - margin: 18px 24px 0 24px; - - @media (--viewportMedium) { - margin: 23px 24px 0 24px; - } - @media (--viewportLarge) { - margin: 23px 0 0 0; - } -} - -.infoTextDivider { - display: none; - - height: 1px; - background-color: var(--matterColorNegative); - border: none; - margin: 37px 0 0 0; - - @media (--viewportLarge) { - display: block; - } -} - -.transitionDate { - white-space: nowrap; -} - -.breakdownMobile { - margin-top: 47px; - - @media (--viewportMedium) { - margin-top: 43px; - } - @media (--viewportLarge) { - display: none; - } -} - -.breakdownDesktop { - display: none; - - position: sticky; - top: 24px; - width: 409px; - padding-bottom: 55px; - background-color: var(--matterColorLight); - border: 1px solid var(--matterColorNegative); - border-radius: 2px; - - @media (--viewportLarge) { - display: block; - } -} - -.breakdownImageWrapper { - /* Layout */ - display: block; - width: 100%; - position: relative; -} - -.bookingBreakdownContainer { - margin: 1px 0 0 0; - - @media (--viewportLarge) { - margin-top: 123px; - margin-left: 0; - margin-right: 0; - } -} - -.breakdownHeadings { - display: none; - - @media (--viewportLarge) { - display: block; - margin-left: 48px; - margin-right: 48px; - } -} - -.breakdownTitle { - margin-bottom: 10px; - - @media (--viewportLarge) { - margin-top: 14px; - margin-bottom: 0; - } -} - -.breakdownSubtitle { - @apply --marketplaceH5FontStyles; - color: var(--matterColorAnti); - - margin-top: 0; - margin-bottom: 0; - - @media (--viewportLarge) { - margin-top: 1px; - margin-bottom: 0; - } -} - -.bookingBreakdownTitle { - /* Font */ - color: var(--matterColorAnti); - - margin: 0 24px 0 24px; - - @media (--viewportLarge) { - margin: 37px 48px 26px 48px; - } -} - -.breakdown { - margin: 14px 24px 0 24px; - - @media (--viewportMedium) { - margin: 18px 24px 0 24px; - } - @media (--viewportLarge) { - margin: 14px 48px 0 48px; - } -} - -.feedContainer { - margin: 46px 24px 0 24px; - - @media (--viewportMedium) { - margin: 46px 24px 0 24px; - } - @media (--viewportLarge) { - margin: 43px 0 0 0; - } -} - -.feedContainerWithInfoAbove { - @media (--viewportLarge) { - margin: 38px 0 0 0; - } -} - -.feedHeading { - color: var(--matterColorAnti); - margin: 0; - - @media (--viewportMedium) { - margin: 0; - } -} - -.feed { - margin-top: 20px; -} - -.sendMessageForm { - position: fixed; - bottom: 0; - width: 100%; - box-shadow: var(--boxShadowBottomForm); - - @media (--viewportLarge) { - margin-top: 46px; - position: relative; - box-shadow: none; - } -} - -.sendMessageFormFocusedInMobileSafari { - position: absolute; - - @media (--viewportLarge) { - position: relative; - } -} diff --git a/src/components/OrderDetailsPanel/OrderDetailsPanel.js b/src/components/OrderDetailsPanel/OrderDetailsPanel.js deleted file mode 100644 index 47f0bf4b..00000000 --- a/src/components/OrderDetailsPanel/OrderDetailsPanel.js +++ /dev/null @@ -1,439 +0,0 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -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 { isMobileSafari } from '../../util/userAgent'; -import { - ActivityFeed, - AvatarMedium, - BookingBreakdown, - NamedLink, - ResponsiveImage, - ReviewModal, -} from '../../components'; -import { SendMessageForm } from '../../containers'; - -import css from './OrderDetailsPanel.css'; - -const breakdown = transaction => { - const loaded = transaction && transaction.id && transaction.booking && transaction.booking.id; - - return loaded ? ( - - ) : null; -}; - -const orderTitle = (transaction, listingLink, customerName) => { - 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) || - propTypes.txHasFirstReview(transaction) || - propTypes.txIsReviewed(transaction) - ) { - return ( - - ); - } else { - return null; - } -}; - -const orderMessage = (transaction, providerName) => { - if (propTypes.txIsPreauthorized(transaction)) { - return ( - - ); - } - return null; -}; - -export class OrderDetailsPanelComponent extends Component { - constructor(props) { - super(props); - this.state = { - sendMessageFormFocused: false, - isReviewModalOpen: false, - reviewSubmitted: false, - }; - this.onOpenReviewModal = this.onOpenReviewModal.bind(this); - this.onSubmitReview = this.onSubmitReview.bind(this); - } - - onOpenReviewModal() { - this.setState({ isReviewModalOpen: true }); - } - - onSubmitReview(values) { - const { onSendReview, transaction } = this.props; - const currentTransaction = ensureTransaction(transaction); - const { reviewRating, reviewContent } = values; - const rating = Number.parseInt(reviewRating, 10); - onSendReview(currentTransaction, rating, reviewContent) - .then(r => this.setState({ isReviewModalOpen: false, reviewSubmitted: true })) - .catch(e => { - // Do nothing. - }); - } - - render() { - const { - rootClassName, - className, - currentUser, - transaction, - totalMessagePages, - oldestMessagePageFetched, - messages, - initialMessageFailed, - fetchMessagesInProgress, - fetchMessagesError, - sendMessageInProgress, - sendMessageError, - sendReviewInProgress, - sendReviewError, - onManageDisableScrolling, - onShowMoreMessages, - onSendMessage, - onResetForm, - intl, - } = this.props; - - const currentTransaction = ensureTransaction(transaction); - const currentListing = ensureListing(currentTransaction.listing); - const currentProvider = ensureUser(currentTransaction.provider); - const currentCustomer = ensureUser(currentTransaction.customer); - - const listingLoaded = !!currentListing.id; - const listingDeleted = listingLoaded && currentListing.attributes.deleted; - - const bannedUserDisplayName = intl.formatMessage({ - id: 'OrderDetailsPanel.bannedUserDisplayName', - }); - const deletedListingTitle = intl.formatMessage({ - id: 'OrderDetailsPanel.deletedListingTitle', - }); - const deletedListingOrderTitle = intl.formatMessage({ - id: 'OrderDetailsPanel.deletedListingOrderTitle', - }); - const orderMessageDeletedListing = intl.formatMessage({ - id: 'OrderDetailsPanel.messageDeletedListing', - }); - - const authorDisplayName = userDisplayName(currentProvider, bannedUserDisplayName); - const customerDisplayName = userDisplayName(currentCustomer, bannedUserDisplayName); - - let otherUserDisplayName = ''; - const currentUserIsCustomer = - currentUser.id && currentCustomer.id && currentUser.id.uuid === currentCustomer.id.uuid; - const currentUserIsProvider = - currentUser.id && currentProvider.id && currentUser.id.uuid === currentProvider.id.uuid; - - if (currentUserIsCustomer) { - otherUserDisplayName = authorDisplayName; - } else if (currentUserIsProvider) { - otherUserDisplayName = customerDisplayName; - } - - let listingLink = null; - - if (listingLoaded && currentListing.attributes.title) { - const title = currentListing.attributes.title; - const params = { id: currentListing.id.uuid, slug: createSlug(title) }; - listingLink = ( - - {title} - - ); - } else { - listingLink = deletedListingOrderTitle; - } - - const listingTitle = currentListing.attributes.deleted - ? deletedListingTitle - : currentListing.attributes.title; - - const bookingInfo = breakdown(currentTransaction); - const orderHeading = orderTitle(currentTransaction, listingLink, customerDisplayName); - const orderInfoText = listingDeleted - ? orderMessageDeletedListing - : orderMessage(currentTransaction, authorDisplayName); - const showInfoMessage = !!orderInfoText; - - const firstImage = - currentListing.images && currentListing.images.length > 0 ? currentListing.images[0] : null; - - const feedContainerClasses = classNames(css.feedContainer, { - [css.feedContainerWithInfoAbove]: showInfoMessage, - }); - const txTransitions = currentTransaction.attributes.transitions - ? currentTransaction.attributes.transitions - : []; - const hasOlderMessages = totalMessagePages > oldestMessagePageFetched; - const handleShowOlderMessages = () => { - onShowMoreMessages(currentTransaction.id); - }; - const showFeed = - messages.length > 0 || txTransitions.length > 0 || initialMessageFailed || fetchMessagesError; - const feedContainer = showFeed ? ( -
-

- -

- {initialMessageFailed ? ( -

- -

- ) : null} - {fetchMessagesError ? ( -

- -

- ) : null} - -
- ) : null; - - const sendMessagePlaceholder = intl.formatMessage( - { id: 'OrderDetailsPanel.sendMessagePlaceholder' }, - { name: otherUserDisplayName } - ); - - const sendMessageFormName = 'OrderDetailsPanel.SendMessageForm'; - const scrollToMessage = messageId => { - const selector = `#msg-${messageId.uuid}`; - const el = document.querySelector(selector); - if (el) { - el.scrollIntoView({ - block: 'start', - behavior: 'smooth', - }); - } - }; - const isMobSaf = isMobileSafari(); - const handleSendMessageFormFocus = () => { - this.setState({ sendMessageFormFocused: true }); - if (isMobSaf) { - // Scroll to bottom - window.scroll({ top: document.body.scrollHeight, left: 0, behavior: 'smooth' }); - } - }; - const handleSendMessageFormBlur = () => { - this.setState({ sendMessageFormFocused: false }); - }; - const handleMessageSubmit = values => { - const message = values.message ? values.message.trim() : null; - if (!message) { - return; - } - onSendMessage(currentTransaction.id, message) - .then(messageId => { - onResetForm(sendMessageFormName); - scrollToMessage(messageId); - }) - .catch(e => { - // Ignore, Redux handles the error - }); - }; - const sendMessageFormClasses = classNames(css.sendMessageForm, { - [css.sendMessageFormFocusedInMobileSafari]: isMobSaf && this.state.sendMessageFormFocused, - }); - - const classes = classNames(rootClassName || css.root, className); - - return ( -
-
-
-
-
- -
-
-
- -
-

{orderHeading}

- {showInfoMessage ?

{orderInfoText}

: null} - {showInfoMessage ?
: null} -
-

- -

- {bookingInfo} -
- {feedContainer} - -
-
-
-
-
- -
-
-
- -
-
-

{listingTitle}

-

- -

-
-

- -

- {bookingInfo} -
-
-
- this.setState({ isReviewModalOpen: false })} - onManageDisableScrolling={onManageDisableScrolling} - onSubmitReview={this.onSubmitReview} - revieweeName={authorDisplayName} - reviewSent={this.state.reviewSubmitted} - sendReviewInProgress={sendReviewInProgress} - sendReviewError={sendReviewError} - /> -
- ); - } -} - -OrderDetailsPanelComponent.defaultProps = { - rootClassName: null, - className: null, - currentUser: null, - fetchMessagesError: null, - sendMessageError: null, - sendReviewError: null, -}; - -const { string, arrayOf, bool, func, number } = PropTypes; - -OrderDetailsPanelComponent.propTypes = { - rootClassName: string, - className: string, - - currentUser: propTypes.currentUser, - transaction: propTypes.transaction.isRequired, - totalMessagePages: number.isRequired, - oldestMessagePageFetched: number.isRequired, - messages: arrayOf(propTypes.message).isRequired, - initialMessageFailed: bool.isRequired, - fetchMessagesInProgress: bool.isRequired, - fetchMessagesError: propTypes.error, - sendMessageInProgress: bool.isRequired, - sendMessageError: propTypes.error, - sendReviewInProgress: bool.isRequired, - sendReviewError: propTypes.error, - onManageDisableScrolling: func.isRequired, - onShowMoreMessages: func.isRequired, - onSendMessage: func.isRequired, - onSendReview: func.isRequired, - onResetForm: func.isRequired, - - // from injectIntl - intl: intlShape, -}; - -const OrderDetailsPanel = injectIntl(OrderDetailsPanelComponent); - -export default OrderDetailsPanel; diff --git a/src/components/OrderDetailsPanel/OrderDetailsPanel.test.js b/src/components/OrderDetailsPanel/OrderDetailsPanel.test.js deleted file mode 100644 index 54f7f87d..00000000 --- a/src/components/OrderDetailsPanel/OrderDetailsPanel.test.js +++ /dev/null @@ -1,146 +0,0 @@ -import React from 'react'; -import { shallow } from 'enzyme'; -import { types } from '../../util/sdkLoader'; -import { - createBooking, - createListing, - createUser, - createTransaction, - createCurrentUser, - createMessage, -} from '../../util/test-data'; -import { renderShallow } from '../../util/test-helpers'; -import { fakeIntl } from '../../util/test-data'; -import * as propTypes from '../../util/propTypes'; -import { BookingBreakdown } from '../../components'; -import { OrderDetailsPanelComponent } from './OrderDetailsPanel.js'; - -const { Money } = types; - -const baseTxAttrs = { - total: new Money(16500, 'USD'), - booking: createBooking('booking1', { - start: new Date(Date.UTC(2017, 5, 10)), - end: new Date(Date.UTC(2017, 5, 13)), - }), - listing: createListing('listing1'), - provider: createUser('provider'), - customer: createUser('customer'), -}; - -const txPreauthorized = createTransaction({ - id: 'order-preauthorized', - lastTransition: propTypes.TX_TRANSITION_PREAUTHORIZE, - ...baseTxAttrs, -}); - -const txAccepted = createTransaction({ - id: 'order-accepted', - lastTransition: propTypes.TX_TRANSITION_ACCEPT, - ...baseTxAttrs, -}); - -const txDeclined = createTransaction({ - id: 'order-declined', - lastTransition: propTypes.TX_TRANSITION_DECLINE, - ...baseTxAttrs, -}); - -const txAutoDeclined = createTransaction({ - id: 'order-autodeclined', - lastTransition: propTypes.TX_TRANSITION_AUTO_DECLINE, - ...baseTxAttrs, -}); - -const txCanceled = createTransaction({ - id: 'order-canceled', - lastTransition: propTypes.TX_TRANSITION_CANCELED, - ...baseTxAttrs, -}); - -const txDelivered = createTransaction({ - id: 'order-delivered', - lastTransition: propTypes.TX_TRANSITION_MARK_DELIVERED, - ...baseTxAttrs, -}); - -const noop = () => null; - -describe('OrderDetailsPanel', () => { - const panelBaseProps = { - intl: fakeIntl, - currentUser: createCurrentUser('user2'), - totalMessages: 2, - totalMessagePages: 1, - oldestMessagePageFetched: 1, - messages: [ - createMessage('msg1', {}, { sender: createUser('user1') }), - createMessage('msg2', {}, { sender: createUser('user2') }), - ], - initialMessageFailed: false, - fetchMessagesInProgress: false, - sendMessageInProgress: false, - sendReviewInProgress: false, - onManageDisableScrolling: noop, - onOpenReviewModal: noop, - onShowMoreMessages: noop, - onSendMessage: noop, - onSendReview: noop, - onResetForm: noop, - }; - - 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(); - }); - it('renders correct total price', () => { - const { Money } = types; - const tx = createTransaction({ - id: 'order-tx', - lastTransition: propTypes.TX_TRANSITION_PREAUTHORIZE, - total: new Money(16500, 'USD'), - booking: createBooking('booking1', { - start: new Date(Date.UTC(2017, 5, 10)), - end: new Date(Date.UTC(2017, 5, 13)), - }), - listing: createListing('listing1'), - provider: createUser('provider'), - customer: createUser('customer'), - }); - 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 deleted file mode 100644 index ba14c0f9..00000000 --- a/src/components/OrderDetailsPanel/__snapshots__/OrderDetailsPanel.test.js.snap +++ /dev/null @@ -1,3933 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`OrderDetailsPanel accepted matches snapshot 1`] = ` -
-
-
-
-
- -
-
-
- -
-

- - - - - - listing1 title - , - } - } - /> - -

-
-

- -

- -
-
-

- -

- -
- -
-
-
-
-
- -
-
-
- -
-
-

- listing1 title -

-

- -

-
-

- -

- -
-
-
- -
-`; - -exports[`OrderDetailsPanel autodeclined matches snapshot 1`] = ` -
-
-
-
-
- -
-
-
- -
-

- - listing1 title - , - } - } - /> -

-
-

- -

- -
-
-

- -

- -
- -
-
-
-
-
- -
-
-
- -
-
-

- listing1 title -

-

- -

-
-

- -

- -
-
-
- -
-`; - -exports[`OrderDetailsPanel canceled matches snapshot 1`] = ` -
-
-
-
-
- -
-
-
- -
-

- - - - - - listing1 title - , - } - } - /> - -

-
-

- -

- -
-
-

- -

- -
- -
-
-
-
-
- -
-
-
- -
-
-

- listing1 title -

-

- -

-
-

- -

- -
-
-
- -
-`; - -exports[`OrderDetailsPanel declined matches snapshot 1`] = ` -
-
-
-
-
- -
-
-
- -
-

- - listing1 title - , - } - } - /> -

-
-

- -

- -
-
-

- -

- -
- -
-
-
-
-
- -
-
-
- -
-
-

- listing1 title -

-

- -

-
-

- -

- -
-
-
- -
-`; - -exports[`OrderDetailsPanel delivered matches snapshot 1`] = ` -
-
-
-
-
- -
-
-
- -
-

- - listing1 title - , - } - } - /> -

-
-

- -

- -
-
-

- -

- -
- -
-
-
-
-
- -
-
-
- -
-
-

- listing1 title -

-

- -

-
-

- -

- -
-
-
- -
-`; - -exports[`OrderDetailsPanel preauthorized matches snapshot 1`] = ` -
-
-
-
-
- -
-
-
- -
-

- - - - - - listing1 title - , - } - } - /> - -

-

- -

-
-
-

- -

- -
-
-

- -

- -
- -
-
-
-
-
- -
-
-
- -
-
-

- listing1 title -

-

- -

-
-

- -

- -
-
-
- -
-`; diff --git a/src/components/SaleDetailsPanel/SaleDetailsPanel.css b/src/components/SaleDetailsPanel/SaleDetailsPanel.css deleted file mode 100644 index 0aa98539..00000000 --- a/src/components/SaleDetailsPanel/SaleDetailsPanel.css +++ /dev/null @@ -1,343 +0,0 @@ -@import '../../marketplace.css'; - -.root { - position: relative; -} - -.container { - display: flex; - flex-direction: column; - justify-content: space-between; - - @media (--viewportLarge) { - flex-direction: row; - max-width: 1156px; /* 1060 + (paddingLeft + paddingRight) */ - margin: 0 auto 57px auto; - padding: 0 48px; - } -} - -.saleInfo { - margin-bottom: 104px; - - @media (--viewportLarge) { - max-width: 542px; - margin-right: 56px; - } -} - -.nowrap { - white-space: nowrap; -} - -.imageWrapperMobile { - /* Layout */ - display: block; - width: 100%; - position: relative; - - @media (--viewportLarge) { - display: none; - } -} - -.aspectWrapper { - padding-bottom: 66.6667%; /* 3:2 Aspect Ratio */ - background-color: var(--matterColorNegative); /* Loading BG color */ -} - -.aspectWrapperMobile { - @media (--viewportLarge) { - display: none; - } -} - -.aspectWrapperDesktop { - display: none; - - @media (--viewportLarge) { - display: block; - } -} - -.rootForImage { - /* Layout - image will take space defined by aspect ratio wrapper */ - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - width: 100%; -} - -.title { - margin: 22px 24px 0 24px; - - @media (--viewportMedium) { - max-width: 80%; - } - - @media (--viewportLarge) { - max-width: 100%; - margin: 42px 0 0 0; - } -} - -.infoText { - margin: 18px 24px 0 24px; - - @media (--viewportMedium) { - margin: 23px 24px 0 24px; - } - @media (--viewportLarge) { - margin: 22px 0 -2px 0; - } -} - -.messagesError { - color: var(--failColor); - margin: 13px 0 0 0; -} - -.actionError { - @apply --marketplaceH5FontStyles; - color: var(--failColor); - margin: 0 0 11px 0; - - @media (--viewportMedium) { - margin: 0 0 10px 0; - } - @media (--viewportLarge) { - margin: 0; - } -} - -.actionErrors { - width: 100%; - text-align: center; - - @media (--viewportLarge) { - position: absolute; - top: 151px; - } -} - -.avatarMobile { - /* Position (over the listing image)*/ - margin-top: -31px; - margin-left: 24px; - - /* Bring on top of image */ - position: relative; - - @media (--viewportLarge) { - display: none; - } -} - -.avatarDesktop { - display: none; - - @media (--viewportLarge) { - display: flex; - margin-top: 119px; - } -} - -.breakdown { - margin: 14px 0 0 0; - - @media (--viewportMedium) { - margin: 18px 0 0 0; - } - @media (--viewportLarge) { - margin: 14px 0 0 0; - } -} - -.breakdownContainerMobile { - margin: 47px 24px 0 24px; - - @media (--viewportMedium) { - margin-top: 44px; - } - @media (--viewportLarge) { - display: none; - } -} - -.desktopAside { - display: none; - - @media (--viewportLarge) { - display: block; - margin: 120px 0 0 0; - } -} - -.breakdownContainerDesktop { - position: sticky; - top: -272px; /* This is a hack to showcase how the component would look when the image isn't sticky */ - background-color: var(--matterColorLight); - border: 1px solid var(--matterColorNegative); - border-radius: 2px; - width: 409px; -} - -.breakdownImageWrapper { - /* Layout */ - display: block; - width: 100%; - position: relative; -} - -.breakdownTitleMobile { - /* Font */ - color: var(--matterColorAnti); - margin: 0; - - @media (--viewportMedium) { - margin: 0; - } -} - -.breakdownTitleDesktop { - margin: 44px 48px 18px 48px; - color: var(--matterColorAnti); -} - -.breakdownDesktop { - margin: 0 48px 47px 48px; -} - -.feedContainer { - margin: 46px 24px 36px 24px; - - @media (--viewportMedium) { - margin: 46px 24px 0 24px; - } - @media (--viewportLarge) { - margin: 43px 0 0 0; - } -} - -.feedContainerWithInfoAbove { - @media (--viewportLarge) { - margin: 38px 0 0 0; - } -} - -.feedHeading { - color: var(--matterColorAnti); - margin: 0; - - @media (--viewportMedium) { - margin: 0; - } -} - -.feed { - margin-top: 20px; -} - -.sendMessageForm { - position: relative; - margin-top: 46px; - width: 100%; - - @media (--viewportMedium) { - margin-top: 49px; - } - @media (--viewportLarge) { - margin-top: 47px; - } -} - -.sendMessageFormFixed { - position: fixed; - bottom: 0; - box-shadow: var(--boxShadowBottomForm); - - @media (--viewportLarge) { - position: relative; - box-shadow: none; - } -} - -.sendMessageFormFocusedInMobileSafari { - position: absolute; - - @media (--viewportLarge) { - position: relative; - } -} - -.actionButtons { - /* Position action button row above the footer */ - z-index: 9; - position: fixed; - bottom: 0; - width: 100%; - padding: 18px 24px 18px 24px; - - /* Contain repainting to this component only */ - /* 3D painting container helps scrolling */ - transform: translate3d(0, 0, 0); - - box-shadow: var(--boxShadowTop); - background-color: white; - - @media (--viewportLarge) { - z-index: unset; - position: static; - box-shadow: none; - width: auto; - margin: 0 48px 48px 48px; - padding: 0; - } -} - -.mobileActionButtons { - display: block; - - @media (--viewportLarge) { - display: none; - } -} - -.desktopActionButtons { - display: none; - - @media (--viewportLarge) { - display: block; - } -} - -.actionButtonsWithFormFocused { - display: none; - - @media (--viewportLarge) { - display: block; - } -} - -.actionButtonWrapper { - width: 100%; - display: flex; - flex-direction: row; - - @media (--viewportLarge) { - flex-direction: column; - } - - & button:first-child { - margin: 0 12px 0 0; - - @media (--viewportLarge) { - margin: 8px 0 0 0; - - /* Switch order in desktop layout with accept button being on the top */ - order: 1; - } - } -} diff --git a/src/components/SaleDetailsPanel/SaleDetailsPanel.js b/src/components/SaleDetailsPanel/SaleDetailsPanel.js deleted file mode 100644 index a3434e6a..00000000 --- a/src/components/SaleDetailsPanel/SaleDetailsPanel.js +++ /dev/null @@ -1,459 +0,0 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -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 { isMobileSafari } from '../../util/userAgent'; -import { - ActivityFeed, - AvatarLarge, - AvatarMedium, - BookingBreakdown, - NamedLink, - PrimaryButton, - ResponsiveImage, - ReviewModal, - SecondaryButton, -} from '../../components'; -import { SendMessageForm } from '../../containers'; - -import css from './SaleDetailsPanel.css'; - -const breakdown = transaction => { - const loaded = transaction && transaction.id && transaction.booking && transaction.booking.id; - - return loaded ? ( - - ) : null; -}; - -const saleTitle = (transaction, listingLink, customerName) => { - 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) || - propTypes.txHasFirstReview(transaction) || - propTypes.txIsReviewed(transaction) - ) { - return ( - - ); - } else { - return null; - } -}; - -const saleInfoText = (transaction, customerName) => { - if (propTypes.txIsPreauthorized(transaction)) { - return ; - } - return null; -}; - -export class SaleDetailsPanelComponent extends Component { - constructor(props) { - super(props); - this.state = { - sendMessageFormFocused: false, - isReviewModalOpen: false, - reviewSubmitted: false, - }; - this.onOpenReviewModal = this.onOpenReviewModal.bind(this); - this.onSubmitReview = this.onSubmitReview.bind(this); - } - - onOpenReviewModal() { - this.setState({ isReviewModalOpen: true }); - } - - onSubmitReview(values) { - const { onSendReview, transaction } = this.props; - const currentTransaction = ensureTransaction(transaction); - const { reviewRating, reviewContent } = values; - const rating = Number.parseInt(reviewRating, 10); - onSendReview(currentTransaction, rating, reviewContent) - .then(r => this.setState({ isReviewModalOpen: false, reviewSubmitted: true })) - .catch(e => { - // Do nothing. - }); - } - - render() { - const { - rootClassName, - className, - currentUser, - transaction, - onAcceptSale, - onDeclineSale, - acceptInProgress, - declineInProgress, - acceptSaleError, - declineSaleError, - fetchMessagesInProgress, - fetchMessagesError, - totalMessagePages, - oldestMessagePageFetched, - messages, - sendMessageInProgress, - sendMessageError, - sendReviewInProgress, - sendReviewError, - onManageDisableScrolling, - onShowMoreMessages, - onSendMessage, - onResetForm, - intl, - } = this.props; - - const currentTransaction = ensureTransaction(transaction); - const currentListing = ensureListing(currentTransaction.listing); - const currentCustomer = ensureUser(currentTransaction.customer); - const currentProvider = ensureUser(currentTransaction.provider); - const customerLoaded = !!currentCustomer.id; - const isCustomerBanned = customerLoaded && currentCustomer.attributes.banned; - - const bannedUserDisplayName = intl.formatMessage({ - id: 'SaleDetailsPanel.bannedUserDisplayName', - }); - - const authorDisplayName = userDisplayName(currentProvider, bannedUserDisplayName); - const customerDisplayName = userDisplayName(currentCustomer, bannedUserDisplayName); - - let otherUserDisplayName = ''; - const currentUserIsCustomer = - currentUser.id && currentCustomer.id && currentUser.id.uuid === currentCustomer.id.uuid; - const currentUserIsProvider = - currentUser.id && currentProvider.id && currentUser.id.uuid === currentProvider.id.uuid; - - if (currentUserIsCustomer) { - otherUserDisplayName = authorDisplayName; - } else if (currentUserIsProvider) { - otherUserDisplayName = customerDisplayName; - } - - let listingLink = null; - - if (currentListing.id && currentListing.attributes.title) { - const title = currentListing.attributes.title; - const params = { id: currentListing.id.uuid, slug: createSlug(title) }; - listingLink = ( - - {title} - - ); - } - - const bookingInfo = breakdown(currentTransaction); - - const title = saleTitle(currentTransaction, listingLink, customerDisplayName); - const infoText = isCustomerBanned - ? intl.formatMessage({ - id: 'SaleDetailsPanel.customerBannedStatus', - }) - : saleInfoText(currentTransaction, customerDisplayName); - const showInfoText = !!infoText; - - const listingTitle = currentListing.attributes.title; - const firstImage = - currentListing.images && currentListing.images.length > 0 ? currentListing.images[0] : null; - - const feedContainerClasses = classNames(css.feedContainer, { - [css.feedContainerWithInfoAbove]: showInfoText, - }); - const txTransitions = currentTransaction.attributes.transitions - ? currentTransaction.attributes.transitions - : []; - const hasOlderMessages = totalMessagePages > oldestMessagePageFetched; - const handleShowOlderMessages = () => { - onShowMoreMessages(currentTransaction.id); - }; - const showFeed = messages.length > 0 || txTransitions.length > 0 || fetchMessagesError; - const feedContainer = showFeed ? ( -
-

- -

- {fetchMessagesError ? ( -

- -

- ) : null} - -
- ) : null; - - const canShowButtons = propTypes.txIsPreauthorized(currentTransaction) && !isCustomerBanned; - const buttonsDisabled = acceptInProgress || declineInProgress; - - const acceptErrorMessage = acceptSaleError ? ( -

- -

- ) : null; - const declineErrorMessage = declineSaleError ? ( -

- -

- ) : null; - - const actionButtonClasses = classNames(css.actionButtons, { - [css.actionButtonsWithFormFocused]: this.state.sendMessageFormFocused, - }); - const actionButtons = canShowButtons ? ( -
-
- {acceptErrorMessage} - {declineErrorMessage} -
-
- onDeclineSale(currentTransaction.id)} - > - - - onAcceptSale(currentTransaction.id)} - > - - -
-
- ) : null; - - const sendMessagePlaceholder = intl.formatMessage( - { id: 'SaleDetailsPanel.sendMessagePlaceholder' }, - { name: otherUserDisplayName } - ); - - const sendMessageFormName = 'SaleDetailsPanel.SendMessageForm'; - const scrollToMessage = messageId => { - const selector = `#msg-${messageId.uuid}`; - const el = document.querySelector(selector); - if (el) { - el.scrollIntoView({ - block: 'start', - behavior: 'smooth', - }); - } - }; - const isMobSaf = isMobileSafari(); - const handleSendMessageFormFocus = () => { - this.setState({ sendMessageFormFocused: true }); - if (isMobSaf) { - // Scroll to bottom - window.scroll({ top: document.body.scrollHeight, left: 0, behavior: 'smooth' }); - } - }; - const handleSendMessageFormBlur = () => { - this.setState({ sendMessageFormFocused: false }); - }; - const handleMessageSubmit = values => { - const message = values.message ? values.message.trim() : null; - if (!message) { - return; - } - onSendMessage(currentTransaction.id, message) - .then(messageId => { - onResetForm(sendMessageFormName); - scrollToMessage(messageId); - }) - .catch(e => { - // Ignore, Redux handles the error - }); - }; - const sendMessageFormClasses = classNames(css.sendMessageForm, { - [css.sendMessageFormFixed]: this.state.sendMessageFormFocused || !canShowButtons, - [css.sendMessageFormFocusedInMobileSafari]: isMobSaf && this.state.sendMessageFormFocused, - }); - - const classes = classNames(rootClassName || css.root, className); - - return ( -
-
-
-
-
- -
-
-
- -
-
- -
-

{title}

- {showInfoText ?

{infoText}

: null} -
-

- -

- {bookingInfo} -
- {feedContainer} - -
{actionButtons}
-
-
-
-
-
- -
-
-

- -

-
{bookingInfo}
-
{actionButtons}
-
-
-
- this.setState({ isReviewModalOpen: false })} - onManageDisableScrolling={onManageDisableScrolling} - onSubmitReview={this.onSubmitReview} - revieweeName={customerDisplayName} - reviewSent={this.state.reviewSubmitted} - sendReviewInProgress={sendReviewInProgress} - sendReviewError={sendReviewError} - /> -
- ); - } -} - -SaleDetailsPanelComponent.defaultProps = { - rootClassName: null, - className: null, - currentUser: null, - acceptSaleError: null, - declineSaleError: null, - fetchMessagesError: null, - sendMessageError: null, - sendReviewError: null, -}; - -const { bool, func, string, arrayOf, number } = PropTypes; - -SaleDetailsPanelComponent.propTypes = { - rootClassName: string, - className: string, - - currentUser: propTypes.currentUser, - transaction: propTypes.transaction.isRequired, - onAcceptSale: func.isRequired, - onDeclineSale: func.isRequired, - acceptInProgress: bool.isRequired, - declineInProgress: bool.isRequired, - acceptSaleError: propTypes.error, - declineSaleError: propTypes.error, - fetchMessagesInProgress: bool.isRequired, - fetchMessagesError: propTypes.error, - totalMessagePages: number.isRequired, - oldestMessagePageFetched: number.isRequired, - messages: arrayOf(propTypes.message).isRequired, - sendMessageInProgress: bool.isRequired, - sendMessageError: propTypes.error, - sendReviewInProgress: bool.isRequired, - sendReviewError: propTypes.error, - onManageDisableScrolling: func.isRequired, - onShowMoreMessages: func.isRequired, - onSendMessage: func.isRequired, - onSendReview: func.isRequired, - onResetForm: func.isRequired, - - // from injectIntl - intl: intlShape.isRequired, -}; - -const SaleDetailsPanel = injectIntl(SaleDetailsPanelComponent); - -export default SaleDetailsPanel; diff --git a/src/components/SaleDetailsPanel/SaleDetailsPanel.test.js b/src/components/SaleDetailsPanel/SaleDetailsPanel.test.js deleted file mode 100644 index aa4384ea..00000000 --- a/src/components/SaleDetailsPanel/SaleDetailsPanel.test.js +++ /dev/null @@ -1,167 +0,0 @@ -import React from 'react'; -import { shallow } from 'enzyme'; -import { types } from '../../util/sdkLoader'; -import { - createTransaction, - createBooking, - createListing, - createUser, - createCurrentUser, - createMessage, -} from '../../util/test-data'; -import { renderShallow } from '../../util/test-helpers'; -import { fakeIntl } from '../../util/test-data'; -import * as propTypes from '../../util/propTypes'; -import { BookingBreakdown } from '../../components'; -import { SaleDetailsPanelComponent } from './SaleDetailsPanel.js'; - -const noop = () => null; - -const { Money } = types; - -const baseTxAttrs = { - total: new Money(16500, 'USD'), - commission: new Money(1000, 'USD'), - booking: createBooking('booking1', { - start: new Date(Date.UTC(2017, 5, 10)), - end: new Date(Date.UTC(2017, 5, 13)), - }), - listing: createListing('listing1'), - customer: createUser('customer1'), - lastTransitionedAt: new Date(Date.UTC(2017, 5, 10)), -}; -const txPreauthorized = createTransaction({ - id: 'sale-preauthorized', - lastTransition: propTypes.TX_TRANSITION_PREAUTHORIZE, - ...baseTxAttrs, -}); - -const txAccepted = createTransaction({ - id: 'sale-accepted', - lastTransition: propTypes.TX_TRANSITION_ACCEPT, - ...baseTxAttrs, -}); - -const txDeclined = createTransaction({ - id: 'sale-declined', - lastTransition: propTypes.TX_TRANSITION_DECLINE, - ...baseTxAttrs, -}); - -const txAutoDeclined = createTransaction({ - id: 'sale-autodeclined', - lastTransition: propTypes.TX_TRANSITION_AUTO_DECLINE, - ...baseTxAttrs, -}); - -const txCanceled = createTransaction({ - id: 'sale-canceled', - lastTransition: propTypes.TX_TRANSITION_CANCELED, - ...baseTxAttrs, -}); - -const txDelivered = createTransaction({ - id: 'sale-delivered', - lastTransition: propTypes.TX_TRANSITION_MARK_DELIVERED, - ...baseTxAttrs, -}); - -describe('SaleDetailsPanel', () => { - const panelBaseProps = { - onAcceptSale: noop, - onDeclineSale: noop, - acceptInProgress: false, - declineInProgress: false, - currentUser: createCurrentUser('user1'), - totalMessages: 2, - totalMessagePages: 1, - oldestMessagePageFetched: 1, - messages: [ - createMessage('msg1', {}, { sender: createUser('user1') }), - createMessage('msg2', {}, { sender: createUser('user2') }), - ], - fetchMessagesInProgress: false, - sendMessageInProgress: false, - sendReviewInProgress: false, - onManageDisableScrolling: noop, - onOpenReviewModal: noop, - onShowMoreMessages: noop, - onSendMessage: noop, - onSendReview: noop, - onResetForm: noop, - intl: fakeIntl, - }; - - it('preauthorized matches snapshot', () => { - const props = { - ...panelBaseProps, - transaction: txPreauthorized, - }; - const tree = renderShallow(); - expect(tree).toMatchSnapshot(); - }); - it('accepted matches snapshot', () => { - const props = { - ...panelBaseProps, - transaction: txAccepted, - }; - const tree = renderShallow(); - expect(tree).toMatchSnapshot(); - }); - it('declined matches snapshot', () => { - const props = { - ...panelBaseProps, - transaction: txDeclined, - }; - const tree = renderShallow(); - expect(tree).toMatchSnapshot(); - }); - it('autodeclined matches snapshot', () => { - const props = { - ...panelBaseProps, - transaction: txAutoDeclined, - }; - const tree = renderShallow(); - expect(tree).toMatchSnapshot(); - }); - it('canceled matches snapshot', () => { - const props = { - ...panelBaseProps, - transaction: txCanceled, - }; - const tree = renderShallow(); - expect(tree).toMatchSnapshot(); - }); - it('delivered matches snapshot', () => { - const props = { - ...panelBaseProps, - transaction: txDelivered, - }; - const tree = renderShallow(); - expect(tree).toMatchSnapshot(); - }); - it('renders correct total price', () => { - const transaction = createTransaction({ - id: 'sale-tx', - lastTransition: propTypes.TX_TRANSITION_PREAUTHORIZE, - total: new Money(16500, 'USD'), - commission: new Money(1000, 'USD'), - booking: createBooking('booking1', { - start: new Date(Date.UTC(2017, 5, 10)), - end: new Date(Date.UTC(2017, 5, 13)), - }), - listing: createListing('listing1'), - customer: createUser('customer1'), - lastTransitionedAt: new Date(Date.UTC(2017, 5, 10)), - }); - const props = { - ...panelBaseProps, - transaction, - }; - const panel = shallow(); - const breakdownProps = panel.find(BookingBreakdown).props(); - - // Total price for the provider should be transaction total minus the commission. - expect(breakdownProps.transaction.attributes.payoutTotal).toEqual(new Money(15500, 'USD')); - }); -}); diff --git a/src/components/SaleDetailsPanel/__snapshots__/SaleDetailsPanel.test.js.snap b/src/components/SaleDetailsPanel/__snapshots__/SaleDetailsPanel.test.js.snap deleted file mode 100644 index bc3882e1..00000000 --- a/src/components/SaleDetailsPanel/__snapshots__/SaleDetailsPanel.test.js.snap +++ /dev/null @@ -1,3661 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`SaleDetailsPanel accepted matches snapshot 1`] = ` -
-
-
-
-
- -
-
-
- -
-
- -
-

- - listing1 title - , - } - } - /> -

-
-

- -

- -
-
-

- -

- -
- -
-
-
-
-
-
- -
-
-

- -

-
- -
-
-
-
-
- -
-`; - -exports[`SaleDetailsPanel autodeclined matches snapshot 1`] = ` -
-
-
-
-
- -
-
-
- -
-
- -
-

- - listing1 title - , - } - } - /> -

-
-

- -

- -
-
-

- -

- -
- -
-
-
-
-
-
- -
-
-

- -

-
- -
-
-
-
-
- -
-`; - -exports[`SaleDetailsPanel canceled matches snapshot 1`] = ` -
-
-
-
-
- -
-
-
- -
-
- -
-

- - listing1 title - , - } - } - /> -

-
-

- -

- -
-
-

- -

- -
- -
-
-
-
-
-
- -
-
-

- -

-
- -
-
-
-
-
- -
-`; - -exports[`SaleDetailsPanel declined matches snapshot 1`] = ` -
-
-
-
-
- -
-
-
- -
-
- -
-

- - listing1 title - , - } - } - /> -

-
-

- -

- -
-
-

- -

- -
- -
-
-
-
-
-
- -
-
-

- -

-
- -
-
-
-
-
- -
-`; - -exports[`SaleDetailsPanel delivered matches snapshot 1`] = ` -
-
-
-
-
- -
-
-
- -
-
- -
-

- - listing1 title - , - } - } - /> -

-
-

- -

- -
-
-

- -

- -
- -
-
-
-
-
-
- -
-
-

- -

-
- -
-
-
-
-
- -
-`; - -exports[`SaleDetailsPanel preauthorized matches snapshot 1`] = ` -
-
-
-
-
- -
-
-
- -
-
- -
-

- - listing1 title - , - } - } - /> -

-

- -

-
-

- -

- -
-
-

- -

- -
- -
-
-
-
- - - - - - -
-
-
-
-
-
-
-
- -
-
-

- -

-
- -
-
-
-
-
- - - - - - -
-
-
-
-
-
- -
-`; diff --git a/src/components/index.js b/src/components/index.js index 6b9c7a0e..a88c5cad 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -76,7 +76,6 @@ export { default as ModalInMobile } from './ModalInMobile/ModalInMobile'; export { default as NamedLink } from './NamedLink/NamedLink'; export { default as NamedRedirect } from './NamedRedirect/NamedRedirect'; export { default as NotificationBadge } from './NotificationBadge/NotificationBadge'; -export { default as OrderDetailsPanel } from './OrderDetailsPanel/OrderDetailsPanel'; export { default as OrderDiscussionPanel } from './OrderDiscussionPanel/OrderDiscussionPanel'; export { default as Page } from './Page/Page'; export { default as PaginationLinks } from './PaginationLinks/PaginationLinks'; @@ -86,7 +85,6 @@ export { default as ResponsiveImage } from './ResponsiveImage/ResponsiveImage'; export { default as ReviewModal } from './ReviewModal/ReviewModal'; export { default as ReviewRating } from './ReviewRating/ReviewRating'; export { default as Reviews } from './Reviews/Reviews'; -export { default as SaleDetailsPanel } from './SaleDetailsPanel/SaleDetailsPanel'; export { default as SearchMap } from './SearchMap/SearchMap'; export { default as SearchMapGroupLabel } from './SearchMapGroupLabel/SearchMapGroupLabel'; export { default as SearchMapInfoCard } from './SearchMapInfoCard/SearchMapInfoCard'; diff --git a/src/translations/en.json b/src/translations/en.json index 41922fea..a4540ac1 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -286,24 +286,6 @@ "NotFoundPage.description": "We can't find the page or sauna you're looking for. Make sure you've typed in the URL correctly or try searching Saunatime.", "NotFoundPage.heading": "Sorry, we couldn't find that page.", "NotFoundPage.title": "Page not found", - "OrderDetailsPanel.activityHeading": "Activity", - "OrderDetailsPanel.bannedUserDisplayName": "Banned user", - "OrderDetailsPanel.bookingBreakdownTitle": "Booking breakdown", - "OrderDetailsPanel.deletedListingOrderTitle": "a listing", - "OrderDetailsPanel.deletedListingTitle": "Deleted listing", - "OrderDetailsPanel.hostedBy": "Hosted by {name}", - "OrderDetailsPanel.initialMessageFailed": "Whoops, failed to send message from checkout.", - "OrderDetailsPanel.messageDeletedListing": "However, the listing is deleted and cannot be viewed anymore.", - "OrderDetailsPanel.messageLoadingFailed": "Something went wrong when loading messages. Please refresh the page and try again.", - "OrderDetailsPanel.orderAcceptedSubtitle": "Your booking for {listingLink} has been accepted.", - "OrderDetailsPanel.orderAcceptedTitle": "Woohoo {customerName}!", - "OrderDetailsPanel.orderCancelledTitle": "{customerName}, your booking for {listingLink} has been cancelled.", - "OrderDetailsPanel.orderDeclinedTitle": "{customerName}, your booking for {listingLink} has been declined.", - "OrderDetailsPanel.orderDeliveredTitle": "{customerName}, your booking for {listingLink} has been completed.", - "OrderDetailsPanel.orderPreauthorizedInfo": "{providerName} has been notified about the booking request. Sit back and relax.", - "OrderDetailsPanel.orderPreauthorizedSubtitle": "You have requested to book {listingLink}.", - "OrderDetailsPanel.orderPreauthorizedTitle": "Great success, {customerName}!", - "OrderDetailsPanel.sendMessagePlaceholder": "Send a message to {name}…", "Page.schemaDescription": "Book a sauna using Saunatime or earn some income by sharing your sauna", "Page.schemaTitle": "Book saunas everywhere | {siteTitle}", "PaginationLinks.next": "Next page", @@ -460,22 +442,6 @@ "ReviewModal.description": "Reviews are an important part of the Saunatime community. Please share what went well and what could have been improved.", "ReviewModal.later": "Later", "ReviewModal.title": "Leave a review for {revieweeName}", - "SaleDetailsPanel.acceptButton": "Accept", - "SaleDetailsPanel.acceptSaleFailed": "Oops, accepting failed. Please try again.", - "SaleDetailsPanel.activityHeading": "Activity", - "SaleDetailsPanel.bannedUserDisplayName": "Banned user", - "SaleDetailsPanel.bookingBreakdownTitle": "Booking breakdown", - "SaleDetailsPanel.customerBannedStatus": "The user made the request, but was later banned.", - "SaleDetailsPanel.declineButton": "Decline", - "SaleDetailsPanel.declineSaleFailed": "Oops, declining failed. Please try again.", - "SaleDetailsPanel.messageLoadingFailed": "Something went wrong when loading messages. Please refresh the page and try again.", - "SaleDetailsPanel.saleAcceptedTitle": "You accepted a request from {customerName} to book {listingLink}.", - "SaleDetailsPanel.saleCancelledTitle": "The booking from {customerName} for {listingLink} has been cancelled.", - "SaleDetailsPanel.saleDeclinedTitle": "The request from {customerName} to book {listingLink} has been declined.", - "SaleDetailsPanel.saleDeliveredTitle": "The booking from {customerName} for {listingLink} has been completed.", - "SaleDetailsPanel.saleRequestedInfo": "{customerName} is waiting for your response.", - "SaleDetailsPanel.saleRequestedTitle": "{customerName} has requested to book {listingLink}.", - "SaleDetailsPanel.sendMessagePlaceholder": "Send a message to {name}…", "SearchMapInfoCard.noImage": "No image", "SearchPage.foundResults": "{count, number} {count, plural, one {sauna} other {saunas}} found", "SearchPage.foundResultsMobile": "{count, number} {count, plural, one {result} other {results}}", From 3a315ad23d189215b3b79bd16242347a913086ad Mon Sep 17 00:00:00 2001 From: Vesa Luusua Date: Mon, 18 Dec 2017 14:49:54 +0200 Subject: [PATCH 09/10] Review changes --- .../TransactionPanel/TransactionPanel.helpers.js | 4 ++-- .../TransactionPanel/TransactionPanel.js | 3 +++ .../__snapshots__/TransactionPanel.test.js.snap | 12 ++++++++++++ .../TransactionPage/TransactionPage.js | 4 +++- src/util/propTypes.js | 16 ++++++++-------- 5 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/components/TransactionPanel/TransactionPanel.helpers.js b/src/components/TransactionPanel/TransactionPanel.helpers.js index bc70cd28..fd4766eb 100644 --- a/src/components/TransactionPanel/TransactionPanel.helpers.js +++ b/src/components/TransactionPanel/TransactionPanel.helpers.js @@ -165,9 +165,9 @@ export const OrderTitle = props => { transaction, customerDisplayName: customerName, currentListing, + listingTitle, } = props; const listingLoaded = !!currentListing.id; - const listingTitle = currentListing.attributes.title; // TODO deleted listing const listingLink = createListingLink(listingLoaded, listingTitle, currentListing.id); const classes = classNames(rootClassName || css.headingOrder, className); @@ -276,9 +276,9 @@ export const SaleTitle = props => { transaction, customerDisplayName: customerName, currentListing, + listingTitle, } = props; const listingLoaded = !!currentListing.id; - const listingTitle = currentListing.attributes.title; const listingLink = createListingLink(listingLoaded, listingTitle, currentListing.id); const classes = classNames(rootClassName || css.headingSale, className); diff --git a/src/components/TransactionPanel/TransactionPanel.js b/src/components/TransactionPanel/TransactionPanel.js index f96403e9..01c643a1 100644 --- a/src/components/TransactionPanel/TransactionPanel.js +++ b/src/components/TransactionPanel/TransactionPanel.js @@ -231,6 +231,8 @@ export class TransactionPanelComponent extends Component { transaction={currentTransaction} customerDisplayName={customerDisplayName} currentListing={currentListing} + listingTitle={listingTitle} + transactionRole={transactionRole} />
diff --git a/src/components/TransactionPanel/__snapshots__/TransactionPanel.test.js.snap b/src/components/TransactionPanel/__snapshots__/TransactionPanel.test.js.snap index aa00230f..6296cce1 100644 --- a/src/components/TransactionPanel/__snapshots__/TransactionPanel.test.js.snap +++ b/src/components/TransactionPanel/__snapshots__/TransactionPanel.test.js.snap @@ -77,6 +77,7 @@ exports[`TransactionPanel - Order accepted matches snapshot 1`] = ` } } customerDisplayName="customer display name" + listingTitle="listing1 title" transaction={ Object { "attributes": Object { @@ -907,6 +908,7 @@ exports[`TransactionPanel - Order autodeclined matches snapshot 1`] = ` } } customerDisplayName="customer display name" + listingTitle="listing1 title" transaction={ Object { "attributes": Object { @@ -1737,6 +1739,7 @@ exports[`TransactionPanel - Order canceled matches snapshot 1`] = ` } } customerDisplayName="customer display name" + listingTitle="listing1 title" transaction={ Object { "attributes": Object { @@ -2567,6 +2570,7 @@ exports[`TransactionPanel - Order declined matches snapshot 1`] = ` } } customerDisplayName="customer display name" + listingTitle="listing1 title" transaction={ Object { "attributes": Object { @@ -3397,6 +3401,7 @@ exports[`TransactionPanel - Order delivered matches snapshot 1`] = ` } } customerDisplayName="customer display name" + listingTitle="listing1 title" transaction={ Object { "attributes": Object { @@ -4227,6 +4232,7 @@ exports[`TransactionPanel - Order preauthorized matches snapshot 1`] = ` } } customerDisplayName="customer display name" + listingTitle="listing1 title" transaction={ Object { "attributes": Object { @@ -5051,6 +5057,7 @@ exports[`TransactionPanel - Sale accepted matches snapshot 1`] = ` } } customerDisplayName="customer1 display name" + listingTitle="listing1 title" transaction={ Object { "attributes": Object { @@ -5813,6 +5820,7 @@ exports[`TransactionPanel - Sale autodeclined matches snapshot 1`] = ` } } customerDisplayName="customer1 display name" + listingTitle="listing1 title" transaction={ Object { "attributes": Object { @@ -6575,6 +6583,7 @@ exports[`TransactionPanel - Sale canceled matches snapshot 1`] = ` } } customerDisplayName="customer1 display name" + listingTitle="listing1 title" transaction={ Object { "attributes": Object { @@ -7337,6 +7346,7 @@ exports[`TransactionPanel - Sale declined matches snapshot 1`] = ` } } customerDisplayName="customer1 display name" + listingTitle="listing1 title" transaction={ Object { "attributes": Object { @@ -8099,6 +8109,7 @@ exports[`TransactionPanel - Sale delivered matches snapshot 1`] = ` } } customerDisplayName="customer1 display name" + listingTitle="listing1 title" transaction={ Object { "attributes": Object { @@ -8861,6 +8872,7 @@ exports[`TransactionPanel - Sale preauthorized matches snapshot 1`] = ` } } customerDisplayName="customer1 display name" + listingTitle="listing1 title" transaction={ Object { "attributes": Object { diff --git a/src/containers/TransactionPage/TransactionPage.js b/src/containers/TransactionPage/TransactionPage.js index 6fb07d4e..460ade0a 100644 --- a/src/containers/TransactionPage/TransactionPage.js +++ b/src/containers/TransactionPage/TransactionPage.js @@ -73,7 +73,9 @@ export const TransactionPageComponent = props => { const deletedListingTitle = intl.formatMessage({ id: 'TransactionPage.deletedListing', }); - const listingTitle = currentListing.attributes.title || deletedListingTitle; + const listingTitle = currentListing.attributes.deleted + ? deletedListingTitle + : currentListing.attributes.title; // Redirect users with someone else's direct link to their own inbox/sales or inbox/orders page. const isDataAvailable = diff --git a/src/util/propTypes.js b/src/util/propTypes.js index 44cb80c1..f2c63d2e 100644 --- a/src/util/propTypes.js +++ b/src/util/propTypes.js @@ -17,13 +17,7 @@ * defined. This way we get the validation errors only in the most * specific place and avoid duplicate errros. */ -import PropTypes from 'prop-types'; -import Decimal from 'decimal.js'; -import { types as sdkTypes } from './sdkLoader'; -import { ensureTransaction } from './data'; - -const { UUID, LatLng, LatLngBounds, Money } = sdkTypes; -const { +import { arrayOf, bool, func, @@ -34,7 +28,12 @@ const { oneOfType, shape, string, -} = PropTypes; +} from 'prop-types'; +import Decimal from 'decimal.js'; +import { types as sdkTypes } from './sdkLoader'; +import { ensureTransaction } from './data'; + +const { UUID, LatLng, LatLngBounds, Money } = sdkTypes; // Fixed value export const value = val => oneOf([val]); @@ -135,6 +134,7 @@ const listingAttributes = shape({ }); const deletedListingAttributes = shape({ + closed: bool.isRequired, deleted: value(true).isRequired, }); From 2c6cc889d3608e49f1c92e9ad87d50b85f927d55 Mon Sep 17 00:00:00 2001 From: Vesa Luusua Date: Mon, 18 Dec 2017 15:35:01 +0200 Subject: [PATCH 10/10] Remove empty wrappers - enquiry doesn't have breakdown --- .../TransactionPanel.helpers.js | 17 +++-- .../TransactionPanel/TransactionPanel.js | 11 +-- .../TransactionPanel.test.js.snap | 72 ------------------- 3 files changed, 17 insertions(+), 83 deletions(-) diff --git a/src/components/TransactionPanel/TransactionPanel.helpers.js b/src/components/TransactionPanel/TransactionPanel.helpers.js index fd4766eb..21abcbc8 100644 --- a/src/components/TransactionPanel/TransactionPanel.helpers.js +++ b/src/components/TransactionPanel/TransactionPanel.helpers.js @@ -78,12 +78,17 @@ export const BreakdownMaybe = props => { const classes = classNames(rootClassName || css.breakdown, className); return loaded ? ( - +
+

+ +

+ +
) : null; }; diff --git a/src/components/TransactionPanel/TransactionPanel.js b/src/components/TransactionPanel/TransactionPanel.js index 01c643a1..9a0600c9 100644 --- a/src/components/TransactionPanel/TransactionPanel.js +++ b/src/components/TransactionPanel/TransactionPanel.js @@ -274,7 +274,9 @@ export class TransactionPanelComponent extends Component { onBlur={this.onSendMessageFormBlur} onSubmit={this.onMessageSubmit} /> - {isProvider ?
{actionButtons}
: null} + {isProvider && canShowButtons ? ( +
{actionButtons}
+ ) : null}
@@ -309,11 +311,10 @@ export class TransactionPanelComponent extends Component {

) : null} -

- -

- {isProvider ?
{actionButtons}
: null} + {isProvider && canShowButtons ? ( +
{actionButtons}
+ ) : null}
diff --git a/src/components/TransactionPanel/__snapshots__/TransactionPanel.test.js.snap b/src/components/TransactionPanel/__snapshots__/TransactionPanel.test.js.snap index 6296cce1..790a8a41 100644 --- a/src/components/TransactionPanel/__snapshots__/TransactionPanel.test.js.snap +++ b/src/components/TransactionPanel/__snapshots__/TransactionPanel.test.js.snap @@ -688,12 +688,6 @@ exports[`TransactionPanel - Order accepted matches snapshot 1`] = ` />
-

- -

-

- -

-

- -

-

- -

-

- -

-

- -

-

- -

-

- -

-

- -

-

- -

-

- -

-

- -