Add Sale page messaging components

This commit is contained in:
Kimmo Puputti 2017-11-15 16:59:51 +02:00
parent 154b6d2ecb
commit 07cadc88fe
7 changed files with 1482 additions and 905 deletions

View file

@ -1,6 +1,7 @@
@import '../../marketplace.css';
.root {
position: relative;
}
.container {
@ -15,6 +16,16 @@
}
}
.saleInfo {
margin-bottom: 210px;
@media (--viewportLarge) {
max-width: 538px;
margin-right: 108px;
margin-bottom: 0;
}
}
.nowrap {
white-space: nowrap;
}
@ -59,13 +70,6 @@
width: 100%;
}
.info {
@media (--viewportLarge) {
margin-right: 132px;
max-width: 519px;
}
}
.title {
margin: 22px 24px 0 24px;
@ -139,7 +143,7 @@
}
.breakdownContainerMobile {
margin: 0 24px 122px 24px;
margin: 0 24px 0 24px;
@media (--viewportLarge) {
display: none;
@ -185,6 +189,30 @@
margin: 48px;
}
.messagesContainer {
margin: 0 24px 0 24px;
@media (--viewportMedium) {
margin: 0;
}
}
.messagesContainerWithInfoAbove {
}
.messagesHeading {
color: var(--matterColorAnti);
margin: 48px 0 0 0;
@media (--viewportMedium) {
margin: 48px 0 0 0;
}
}
.messages {
margin-top: 24px;
}
.actionButtons {
/* Position action button row above the footer */
z-index: 9;

View file

@ -13,6 +13,7 @@ import {
ResponsiveImage,
PrimaryButton,
SecondaryButton,
Messages,
} from '../../components';
import css from './SaleDetailsPanel.css';
@ -84,6 +85,7 @@ export const SaleDetailsPanelComponent = props => {
const {
rootClassName,
className,
currentUser,
transaction,
onAcceptSale,
onDeclineSale,
@ -141,11 +143,26 @@ export const SaleDetailsPanelComponent = props => {
id: 'SaleDetailsPanel.customerBannedStatus',
})
: saleInfoText(currentTransaction, customerDisplayName);
const showInfoMessage = !!infoText;
const listingTitle = currentListing.attributes.title;
const firstImage =
currentListing.images && currentListing.images.length > 0 ? currentListing.images[0] : null;
const messagesContainerClasses = classNames(css.messagesContainer, {
[css.messagesContainerWithInfoAbove]: showInfoMessage,
});
const showMessages = messages.length > 0 || fetchMessagesError;
// TODO: handle fetchMessagesError
const messagesContainer = showMessages ? (
<div className={messagesContainerClasses}>
<h3 className={css.messagesHeading}>
<FormattedMessage id="SaleDetailsPanel.messagesHeading" />
</h3>
<Messages className={css.messages} messages={messages} currentUser={currentUser} />
</div>
) : null;
const canShowButtons = propTypes.txIsPreauthorized(currentTransaction) && !isCustomerBanned;
const buttonsDisabled = acceptInProgress || declineInProgress;
@ -190,24 +207,24 @@ export const SaleDetailsPanelComponent = props => {
return (
<div className={classes}>
<div className={css.container}>
<div className={css.imageWrapperMobile}>
<div className={css.aspectWrapper}>
<ResponsiveImage
rootClassName={css.rootForImage}
alt={listingTitle}
image={firstImage}
nameSet={[
{ name: 'landscape-crop', size: '400w' },
{ name: 'landscape-crop2x', size: '800w' },
]}
sizes="100vw"
/>
<div className={css.saleInfo}>
<div className={css.imageWrapperMobile}>
<div className={css.aspectWrapper}>
<ResponsiveImage
rootClassName={css.rootForImage}
alt={listingTitle}
image={firstImage}
nameSet={[
{ name: 'landscape-crop', size: '400w' },
{ name: 'landscape-crop2x', size: '800w' },
]}
sizes="100vw"
/>
</div>
</div>
<div className={css.avatarWrapper}>
<AvatarMedium user={currentCustomer} className={css.avatarMobile} />
</div>
</div>
<div className={css.avatarWrapper}>
<AvatarMedium user={currentCustomer} className={css.avatarMobile} />
</div>
<div className={css.info}>
<div className={css.avatarWrapper}>
<AvatarLarge user={currentCustomer} className={css.avatarDesktop} />
</div>
@ -217,14 +234,15 @@ export const SaleDetailsPanelComponent = props => {
{acceptErrorMessage}
{declineErrorMessage}
</div>
<div className={css.breakdownContainerMobile}>
<h3 className={css.breakdownTitleMobile}>
<FormattedMessage id="SaleDetailsPanel.bookingBreakdownTitle" />
</h3>
{bookingInfo}
</div>
{messagesContainer}
{actionButtons}
</div>
<div className={css.breakdownContainerMobile}>
<h3 className={css.breakdownTitleMobile}>
<FormattedMessage id="SaleDetailsPanel.bookingBreakdownTitle" />
</h3>
{bookingInfo}
</div>
<div className={css.breakdownContainerDesktop}>
<div className={css.breakdownImageWrapper}>
<div className={css.aspectWrapper}>
@ -253,6 +271,7 @@ export const SaleDetailsPanelComponent = props => {
SaleDetailsPanelComponent.defaultProps = {
rootClassName: null,
className: null,
currentUser: null,
acceptSaleError: null,
declineSaleError: null,
fetchMessagesError: null,
@ -265,6 +284,7 @@ SaleDetailsPanelComponent.propTypes = {
rootClassName: string,
className: string,
currentUser: propTypes.currentUser,
transaction: propTypes.transaction.isRequired,
onAcceptSale: func.isRequired,
onDeclineSale: func.isRequired,

View file

@ -6,15 +6,14 @@
flex: 1;
}
/* actionButtons are fixed positioned until viewportLarge is reached */
/* margin-bottom gives space for those buttons when scrolling to bottom */
.footerWrapper {
margin-bottom: 101px;
.footer {
display: none;
@media (--viewportLarge) {
margin-bottom: 0;
display: block;
}
}
.loading {
margin-left: 24px;
margin-right: 24px;

View file

@ -6,7 +6,7 @@ import classNames from 'classnames';
import { FormattedMessage, intlShape, injectIntl } from 'react-intl';
import { reset as resetForm } from 'redux-form';
import * as propTypes from '../../util/propTypes';
import { ensureListing, ensureUser, ensureTransaction } from '../../util/data';
import { ensureListing, ensureTransaction } from '../../util/data';
import { getMarketplaceEntities } from '../../ducks/marketplaceData.duck';
import { isScrollingDisabled } from '../../ducks/UI.duck';
import { acceptSale, declineSale, loadData, sendMessage } from './SalePage.duck';
@ -79,18 +79,11 @@ export const SalePageComponent = props => {
</p>
);
// We need to know if the customer is in pending state -
// to show action buttons bar and reserve space below footer.
// (accept reject buttons are position: fixed on mobile layout)
const currentCustomer = ensureUser(currentTransaction.customer);
const customerLoaded = !!currentCustomer.id;
const isCustomerBanned = customerLoaded || currentCustomer.attributes.banned;
const isPending = propTypes.txIsPreauthorized(currentTransaction) && !isCustomerBanned;
const panel =
isDataAvailable && currentTransaction.id ? (
<SaleDetailsPanel
className={detailsClassName}
currentUser={currentUser}
transaction={currentTransaction}
onAcceptSale={onAcceptSale}
onDeclineSale={onDeclineSale}
@ -121,7 +114,7 @@ export const SalePageComponent = props => {
<LayoutWrapperMain>
<div className={css.root}>{panel}</div>
</LayoutWrapperMain>
<LayoutWrapperFooter className={classNames({ [css.footerWrapper]: isPending })}>
<LayoutWrapperFooter className={css.footer}>
<Footer />
</LayoutWrapperFooter>
</LayoutSingleColumn>

View file

@ -24,6 +24,26 @@ exports[`SalePage matches snapshot 1`] = `
acceptInProgress={false}
acceptSaleError={null}
className="undefined"
currentUser={
Object {
"attributes": Object {
"banned": false,
"email": "provider1@example.com",
"emailVerified": true,
"profile": Object {
"abbreviatedName": "provider1 abbreviated name",
"displayName": "provider1 display name",
"firstName": "provider1 first name",
"lastName": "provider1 last name",
},
"stripeConnected": true,
},
"id": UUID {
"uuid": "provider1",
},
"type": "currentUser",
}
}
declineInProgress={false}
declineSaleError={null}
fetchMessagesError={null}
@ -143,7 +163,7 @@ exports[`SalePage matches snapshot 1`] = `
</div>
</LayoutWrapperMain>
<LayoutWrapperFooter
className=""
className={null}
rootClassName={null}
>
<InjectIntl(Footer) />

View file

@ -431,6 +431,7 @@
"SaleDetailsPanel.listingDeclinedTitle": "{customerName} requested to book {listingLink}",
"SaleDetailsPanel.listingDeliveredTitle": "{customerName} booked {listingLink}",
"SaleDetailsPanel.listingRequestedTitle": "{customerName} has requested to book {listingLink}",
"SaleDetailsPanel.messagesHeading": "Activity",
"SaleDetailsPanel.saleAcceptedStatus": "You accepted the request on {formattedDate}.",
"SaleDetailsPanel.saleAutoDeclinedStatus": "You did not react to the request on time. The request expired on {formattedDate}.",
"SaleDetailsPanel.saleCanceledStatus": "The request was cancelled on {formattedDate}.",