import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { injectIntl, intlShape } from 'react-intl'; import classNames from 'classnames'; import { txIsAccepted, txIsCanceled, txIsDeclined, txIsEnquired, txIsPaymentExpired, txIsPaymentPending, txIsRequested, txHasBeenDelivered, } from '../../util/transaction'; import { LINE_ITEM_NIGHT, LINE_ITEM_DAY, propTypes } from '../../util/types'; import { ensureListing, ensureTransaction, ensureUser, userDisplayNameAsString, } from '../../util/data'; import { isMobileSafari } from '../../util/userAgent'; import { formatMoney } from '../../util/currency'; import { AvatarLarge, BookingPanel, ReviewModal, UserDisplayName } from '../../components'; import { SendMessageForm } from '../../forms'; import config from '../../config'; // These are internal components that make this file more readable. import AddressLinkMaybe from './AddressLinkMaybe'; import BreakdownMaybe from './BreakdownMaybe'; import DetailCardHeadingsMaybe from './DetailCardHeadingsMaybe'; import DetailCardImage from './DetailCardImage'; import FeedSection from './FeedSection'; import SaleActionButtonsMaybe from './SaleActionButtonsMaybe'; import PanelHeading, { HEADING_ENQUIRED, HEADING_PAYMENT_PENDING, HEADING_PAYMENT_EXPIRED, HEADING_REQUESTED, HEADING_ACCEPTED, HEADING_DECLINED, HEADING_CANCELED, HEADING_DELIVERED, } from './PanelHeading'; import css from './TransactionPanel.css'; // Helper function to get display names for different roles const displayNames = (currentUser, currentProvider, currentCustomer, intl) => { const authorDisplayName = ; const customerDisplayName = ; let otherUserDisplayName = ''; let otherUserDisplayNameString = ''; 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; otherUserDisplayNameString = userDisplayNameAsString(currentProvider, ''); } else if (currentUserIsProvider) { otherUserDisplayName = customerDisplayName; otherUserDisplayNameString = userDisplayNameAsString(currentCustomer, ''); } return { authorDisplayName, customerDisplayName, otherUserDisplayName, otherUserDisplayNameString, }; }; 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, transactionRole } = this.props; const currentTransaction = ensureTransaction(transaction); const { reviewRating, reviewContent } = values; const rating = Number.parseInt(reviewRating, 10); onSendReview(transactionRole, 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, form) { const message = values.message ? values.message.trim() : null; const { transaction, onSendMessage } = this.props; const ensuredTransaction = ensureTransaction(transaction); if (!message) { return; } onSendMessage(ensuredTransaction.id, message) .then(messageId => { form.reset(); 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, onSubmitBookingRequest, timeSlots, fetchTimeSlotsError, } = 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 iscustomerLoaded = !!currentCustomer.id; const isCustomerBanned = iscustomerLoaded && currentCustomer.attributes.banned; const isCustomerDeleted = iscustomerLoaded && currentCustomer.attributes.deleted; const isProviderLoaded = !!currentProvider.id; const isProviderBanned = isProviderLoaded && currentProvider.attributes.banned; const isProviderDeleted = isProviderLoaded && currentProvider.attributes.deleted; const stateDataFn = tx => { if (txIsEnquired(tx)) { return { headingState: HEADING_ENQUIRED, showBookingPanel: isCustomer && !isProviderBanned, }; } else if (txIsPaymentPending(tx)) { return { headingState: HEADING_PAYMENT_PENDING, showDetailCardHeadings: isCustomer, }; } else if (txIsPaymentExpired(tx)) { return { headingState: HEADING_PAYMENT_EXPIRED, showDetailCardHeadings: isCustomer, }; } else if (txIsRequested(tx)) { return { headingState: HEADING_REQUESTED, showDetailCardHeadings: isCustomer, showSaleButtons: isProvider && !isCustomerBanned, }; } else if (txIsAccepted(tx)) { return { headingState: HEADING_ACCEPTED, showDetailCardHeadings: isCustomer, showAddress: isCustomer, }; } else if (txIsDeclined(tx)) { return { headingState: HEADING_DECLINED, showDetailCardHeadings: isCustomer, }; } else if (txIsCanceled(tx)) { return { headingState: HEADING_CANCELED, showDetailCardHeadings: isCustomer, }; } else if (txHasBeenDelivered(tx)) { return { headingState: HEADING_DELIVERED, showDetailCardHeadings: isCustomer, showAddress: isCustomer, }; } else { return { headingState: 'unknown' }; } }; const stateData = stateDataFn(currentTransaction); const deletedListingTitle = intl.formatMessage({ id: 'TransactionPanel.deletedListingTitle', }); const { authorDisplayName, customerDisplayName, otherUserDisplayName, otherUserDisplayNameString, } = displayNames(currentUser, currentProvider, currentCustomer, intl); const { publicData, geolocation } = currentListing.attributes; const location = publicData && publicData.location ? publicData.location : {}; const listingTitle = currentListing.attributes.deleted ? deletedListingTitle : currentListing.attributes.title; const unitType = config.bookingUnitType; const isNightly = unitType === LINE_ITEM_NIGHT; const isDaily = unitType === LINE_ITEM_DAY; const unitTranslationKey = isNightly ? 'TransactionPanel.perNight' : isDaily ? 'TransactionPanel.perDay' : 'TransactionPanel.perUnit'; const price = currentListing.attributes.price; const bookingSubTitle = price ? `${formatMoney(intl, price)} ${intl.formatMessage({ id: unitTranslationKey })}` : ''; const firstImage = currentListing.images && currentListing.images.length > 0 ? currentListing.images[0] : null; const saleButtons = ( onAcceptSale(currentTransaction.id)} onDeclineSale={() => onDeclineSale(currentTransaction.id)} /> ); const showSendMessageForm = !isCustomerBanned && !isCustomerDeleted && !isProviderBanned && !isProviderDeleted; const sendMessagePlaceholder = intl.formatMessage( { id: 'TransactionPanel.sendMessagePlaceholder' }, { name: otherUserDisplayNameString } ); const sendingMessageNotAllowed = intl.formatMessage({ id: 'TransactionPanel.sendingMessageNotAllowed', }); const classes = classNames(rootClassName || css.root, className); return ( {isProvider ? ( ) : null} onShowMoreMessages(currentTransaction.id)} totalMessagePages={totalMessagePages} /> {showSendMessageForm ? ( ) : ( {sendingMessageNotAllowed} )} {stateData.showSaleButtons ? ( {saleButtons} ) : null} {stateData.showBookingPanel ? ( ) : null} {stateData.showSaleButtons ? ( {saleButtons} ) : null} this.setState({ isReviewModalOpen: false })} onManageDisableScrolling={onManageDisableScrolling} onSubmitReview={this.onSubmitReview} revieweeName={otherUserDisplayName} 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, timeSlots: null, fetchTimeSlotsError: 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, onSubmitBookingRequest: func.isRequired, timeSlots: arrayOf(propTypes.timeSlot), fetchTimeSlotsError: propTypes.error, // 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;