Merge pull request #325 from sharetribe/order-page-responsive-styles

Responsive Order page
This commit is contained in:
Kimmo Puputti 2017-08-03 10:34:07 +03:00 committed by GitHub
commit b1003a62b0
7 changed files with 418 additions and 91 deletions

View file

@ -37,7 +37,7 @@
@media (--viewportMedium) {
font-weight: 700;
margin-top: 1px; /* align with baseline */
margin-top: -1px;
margin-bottom: 0;
}
}
@ -52,7 +52,7 @@
background-color: var(--matterColorNegative);
@media (--viewportMedium) {
margin: 2px 0 8px 0;
margin: 2px 0 14px 0;
}
}
@ -61,6 +61,10 @@
/* Move so that baseline aligns with the total price */
padding-top: 7px;
@media (--viewportMedium) {
padding-top: 8px;
}
}
.totalPrice {

View file

@ -4,8 +4,75 @@
}
.container {
position: relative;
display: flex;
flex-direction: column;
@media (--viewportMedium) {
flex-direction: row;
justify-content: center;
}
}
/* 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 */
@media (--viewportMedium) {
display: none;
}
}
.breakdownAspectWrapper {
position: relative;
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%;
}
.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;
width: 61px;
height: 61px;
}
.avatarMobile {
@media (--viewportMedium) {
display: none;
}
}
.orderInfo {
@media (--viewportMedium) {
max-width: 517px;
}
}
.title {
margin: 28px 24px 0 24px;
@media (--viewportMedium) {
margin-top: 177px;
}
}
.mainTitle {
@ -20,15 +87,76 @@
.message {
display: flex;
margin: 24px 24px 47px 24px;
@media (--viewportMedium) {
margin-top: 27px;
}
}
.transitionDate {
white-space: nowrap;
}
.breakdownMobile {
@media (--viewportMedium) {
display: none;
}
}
.breakdownDesktop {
display: none;
@media (--viewportMedium) {
display: block;
padding-bottom: 55px;
}
}
.bookingBreakdownContainer {
margin-bottom: 40px;
padding: 0 24px;
margin: 1px 0 40px 0;
@media (--viewportMedium) {
width: 409px;
margin-top: 122px;
margin-left: 24px;
margin-right: 24px;
box-shadow: var(--boxShadowBreakdown);
}
@media (--viewportLarge) {
margin-left: 131px;
}
}
.breakdownHeadings {
display: none;
@media (--viewportMedium) {
display: block;
margin-left: 24px;
margin-right: 24px;
}
}
.breakdownTitle {
margin-bottom: 10px;
@media (--viewportMedium) {
margin-top: 13px;
margin-bottom: 0;
}
}
.breakdownSubtitle {
@apply --marketplaceH5FontStyles;
color: var(--matterColorAnti);
margin-top: 0;
margin-bottom: 0;
@media (--viewportMedium) {
margin-top: 0;
margin-bottom: 0;
}
}
.bookingBreakdownTitle {
@ -37,11 +165,16 @@
margin-top: 5px;
margin-bottom: 14px;
margin-left: 24px;
margin-right: 24px;
@media (--viewportMedium) {
margin-top: 5px;
margin-bottom: 14px;
margin-top: 38px;
margin-bottom: 10px;
}
}
.receipt {
margin-left: 24px;
margin-right: 24px;
}

View file

@ -4,7 +4,7 @@ import classNames from 'classnames';
import * as propTypes from '../../util/propTypes';
import { createSlug } from '../../util/urlHelpers';
import { ensureListing, ensureTransaction, ensureBooking, ensureUser } from '../../util/data';
import { BookingBreakdown, NamedLink } from '../../components';
import { BookingBreakdown, NamedLink, ResponsiveImage, AvatarMedium } from '../../components';
import css from './OrderDetailsPanel.css';
@ -131,7 +131,9 @@ const OrderDetailsPanel = props => {
const currentProvider = ensureUser(currentTransaction.provider);
const currentCustomer = ensureUser(currentTransaction.customer);
const providerName = currentProvider.attributes.profile.firstName;
const providerProfile = currentProvider.attributes.profile;
const authorFirstName = providerProfile.firstName;
const authorLastName = providerProfile.lastName;
const customerName = currentCustomer.attributes.profile.firstName;
const transactionState = currentTransaction.attributes.state;
const lastTransitionedAt = currentTransaction.attributes.lastTransitionedAt;
@ -149,29 +151,86 @@ const OrderDetailsPanel = props => {
);
}
const listingTitle = currentListing.attributes.title;
const bookingInfo = breakdown(currentTransaction);
const title = orderTitle(transactionState, listingLink, customerName, lastTransition);
const message = orderMessage(
transactionState,
listingLink,
providerName,
authorFirstName,
lastTransitionedAt,
lastTransition
);
const firstImage = currentListing.images && currentListing.images.length > 0
? currentListing.images[0]
: null;
const classes = classNames(rootClassName || css.root, className);
return (
<div className={classes}>
<h1 className={css.title}>{title}</h1>
<div className={css.message}>
{message}
</div>
<div className={css.bookingBreakdownContainer}>
<h3 className={css.bookingBreakdownTitle}>
<FormattedMessage id="OrderDetailsPanel.bookingBreakdownTitle" />
</h3>
{bookingInfo}
<div className={css.container}>
<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 className={classNames(css.avatarWrapper, css.avatarMobile)}>
<AvatarMedium firstName={authorFirstName} lastName={authorLastName} />
</div>
<div className={css.orderInfo}>
<h1 className={css.title}>{title}</h1>
<div className={css.message}>
{message}
</div>
</div>
<div className={css.bookingBreakdownContainer}>
<div className={css.breakdownMobile}>
<h3 className={css.bookingBreakdownTitle}>
<FormattedMessage id="OrderDetailsPanel.bookingBreakdownTitle" />
</h3>
{bookingInfo}
</div>
<div className={css.breakdownDesktop}>
<div className={css.breakdownAspectWrapper}>
<ResponsiveImage
rootClassName={css.rootForImage}
alt={listingTitle}
image={firstImage}
nameSet={[
{ name: 'landscape-crop', size: '400w' },
{ name: 'landscape-crop2x', size: '800w' },
]}
sizes="100%"
/>
</div>
<div className={css.avatarWrapper}>
<AvatarMedium firstName={authorFirstName} lastName={authorLastName} />
</div>
<div className={css.breakdownHeadings}>
<h2 className={css.breakdownTitle}>{listingTitle}</h2>
<p className={css.breakdownSubtitle}>
<FormattedMessage
id="OrderDetailsPanel.hostedBy"
values={{ name: authorFirstName }}
/>
</p>
</div>
<h3 className={css.bookingBreakdownTitle}>
<FormattedMessage id="OrderDetailsPanel.bookingBreakdownTitle" />
</h3>
{bookingInfo}
</div>
</div>
</div>
</div>
);

View file

@ -1,87 +1,202 @@
exports[`OrderDetailsPanel matches snapshot 1`] = `
<div
className="">
<h1>
<span>
<span>
<div>
<div>
<ResponsiveImage
alt="listing1 title"
className={null}
image={null}
nameSet={
Array [
Object {
"name": "landscape-crop",
"size": "400w",
},
Object {
"name": "landscape-crop2x",
"size": "800w",
},
]
}
rootClassName={null}
sizes="100vw" />
</div>
<div
className="">
<AvatarMedium
firstName="provider first name"
lastName="provider last name" />
</div>
<div>
<h1>
<span>
<span>
<FormattedMessage
id="OrderDetailsPanel.orderPreauthorizedTitle"
values={
Object {
"customerName": "customer first name",
}
} />
</span>
<FormattedMessage
id="OrderDetailsPanel.orderPreauthorizedSubtitle"
values={
Object {
"listingLink": <withFlattenedRoutes(withRouter(NamedLink))
name="ListingPage"
params={
Object {
"id": "listing1",
"slug": "listing1-title",
}
}>
listing1 title
</withFlattenedRoutes(withRouter(NamedLink))>,
}
} />
</span>
</h1>
<div>
<FormattedMessage
id="OrderDetailsPanel.orderPreauthorizedTitle"
id="OrderDetailsPanel.orderPreauthorizedStatus"
values={
Object {
"customerName": "customer first name",
"providerName": "provider first name",
}
} />
</span>
<FormattedMessage
id="OrderDetailsPanel.orderPreauthorizedSubtitle"
values={
Object {
"listingLink": <withFlattenedRoutes(withRouter(NamedLink))
name="ListingPage"
params={
Object {
"id": "listing1",
"slug": "listing1-title",
}
}>
listing1 title
</withFlattenedRoutes(withRouter(NamedLink))>,
</div>
</div>
<div>
<div>
<h3>
<FormattedMessage
id="OrderDetailsPanel.bookingBreakdownTitle"
values={Object {}} />
</h3>
<BookingBreakdown
bookingEnd={2017-06-13T00:00:00.000Z}
bookingStart={2017-06-10T00:00:00.000Z}
lineItems={
Array [
Object {
"code": "line-item/night",
"lineTotal": Money {
"amount": 16500,
"currency": "USD",
},
"quantity": "3",
"unitPrice": Money {
"amount": 5500,
"currency": "USD",
},
},
Object {
"code": "line-item/provider-commission",
"lineTotal": Money {
"amount": -100,
"currency": "USD",
},
"unitPrice": Money {
"amount": -100,
"currency": "USD",
},
},
]
}
} />
</span>
</h1>
<div>
<FormattedMessage
id="OrderDetailsPanel.orderPreauthorizedStatus"
values={
Object {
"providerName": "provider first name",
}
} />
</div>
<div>
<h3>
<FormattedMessage
id="OrderDetailsPanel.bookingBreakdownTitle"
values={Object {}} />
</h3>
<BookingBreakdown
bookingEnd={2017-06-13T00:00:00.000Z}
bookingStart={2017-06-10T00:00:00.000Z}
lineItems={
Array [
Object {
"code": "line-item/night",
"lineTotal": Money {
payinTotal={
Money {
"amount": 16500,
"currency": "USD",
},
"quantity": "3",
"unitPrice": Money {
"amount": 5500,
}
}
userRole="customer" />
</div>
<div>
<div>
<ResponsiveImage
alt="listing1 title"
className={null}
image={null}
nameSet={
Array [
Object {
"name": "landscape-crop",
"size": "400w",
},
Object {
"name": "landscape-crop2x",
"size": "800w",
},
]
}
rootClassName={null}
sizes="100%" />
</div>
<div>
<AvatarMedium
firstName="provider first name"
lastName="provider last name" />
</div>
<div>
<h2>
listing1 title
</h2>
<p>
<FormattedMessage
id="OrderDetailsPanel.hostedBy"
values={
Object {
"name": "provider first name",
}
} />
</p>
</div>
<h3>
<FormattedMessage
id="OrderDetailsPanel.bookingBreakdownTitle"
values={Object {}} />
</h3>
<BookingBreakdown
bookingEnd={2017-06-13T00:00:00.000Z}
bookingStart={2017-06-10T00:00:00.000Z}
lineItems={
Array [
Object {
"code": "line-item/night",
"lineTotal": Money {
"amount": 16500,
"currency": "USD",
},
"quantity": "3",
"unitPrice": Money {
"amount": 5500,
"currency": "USD",
},
},
Object {
"code": "line-item/provider-commission",
"lineTotal": Money {
"amount": -100,
"currency": "USD",
},
"unitPrice": Money {
"amount": -100,
"currency": "USD",
},
},
]
}
payinTotal={
Money {
"amount": 16500,
"currency": "USD",
},
},
Object {
"code": "line-item/provider-commission",
"lineTotal": Money {
"amount": -100,
"currency": "USD",
},
"unitPrice": Money {
"amount": -100,
"currency": "USD",
},
},
]
}
payinTotal={
Money {
"amount": 16500,
"currency": "USD",
}
}
userRole="customer" />
}
}
userRole="customer" />
</div>
</div>
</div>
</div>
`;

View file

@ -41,15 +41,29 @@ const fetchOrderError = e => ({ type: FETCH_ORDER_ERROR, error: true, payload: e
// ================ Thunks ================ //
const listingRelationship = txResponse => {
return txResponse.data.data.relationships.listing.data;
};
export const fetchOrder = id =>
(dispatch, getState, sdk) => {
dispatch(fetchOrderRequest());
let txResponse = null;
return sdk.transactions
.show({ id, include: ['customer', 'provider', 'listing', 'booking'] }, { expand: true })
.then(response => {
txResponse = response;
const listingId = listingRelationship(response).id;
return sdk.listings.show({ id: listingId, include: ['author', 'images'] });
})
.then(response => {
dispatch(addMarketplaceEntities(txResponse));
dispatch(addMarketplaceEntities(response));
dispatch(fetchOrderSuccess(response));
dispatch(fetchOrderSuccess(txResponse));
return response;
})
.catch(e => {

View file

@ -60,6 +60,7 @@
--boxShadowLight: 0 2px 4px 0 rgba(0, 0, 0, 0.05);
--boxShadowPopup: 0 8px 16px 0 rgba(0, 0, 0, 0.3);
--boxShadowPopupLight: 0 3px 6px 0 rgba(0, 0, 0, 0.2);
--boxShadowBreakdown: 0 3px 6px 0 rgba(0, 0, 0, 0.2);
--boxShadowButton: 0 4px 8px 0 rgba(0, 0, 0, 0.10);
--boxShadowListingCard: 0 0 50px 0 rgba(0,0,0,0.1);

View file

@ -131,6 +131,7 @@
"OrderDetailsPanel.orderPreauthorizedTitle": "Great success, {customerName}!",
"OrderDetailsPanel.orderRejectedStatus": "Unfortunately {providerName} declined the request on {transitionDate}.",
"OrderDetailsPanel.orderRejectedTitle": "You requested to book {listingLink}",
"OrderDetailsPanel.hostedBy": "Hosted by {name}",
"OrderPage.fetchOrderFailed": "Fetching order data failed.",
"OrderPage.loadingData": "Loading order data.",
"OrderPage.title": "Order details: {listingTitle}",