mirror of
https://github.com/kingomarnajjar/flex-template-web.git
synced 2026-07-28 20:53:24 +10:00
Refactor booking info calculations
This commit is contained in:
parent
e12f2bedd6
commit
d909843d4f
18 changed files with 568 additions and 416 deletions
|
|
@ -26,12 +26,6 @@ export const BookingBreakdownComponent = props => {
|
|||
} = props;
|
||||
|
||||
const classes = classNames(rootClassName || css.root, className);
|
||||
const hasSelectedNights = bookingStart && bookingEnd;
|
||||
|
||||
// If there's not enough info, render an empty container
|
||||
if (!hasSelectedNights) {
|
||||
return <div className={classes} />;
|
||||
}
|
||||
|
||||
const bookingPeriod = (
|
||||
<FormattedMessage
|
||||
|
|
@ -71,9 +65,7 @@ export const BookingBreakdownComponent = props => {
|
|||
</div>
|
||||
);
|
||||
|
||||
// Total price can be given (when it comes from API)
|
||||
// or calculated: sub total - commission
|
||||
const totalPriceAsNumber = totalPrice ? convertMoneyToNumber(totalPrice, subUnitDivisor) : 0;
|
||||
const totalPriceAsNumber = convertMoneyToNumber(totalPrice, subUnitDivisor);
|
||||
const formattedTotalPrice = totalPriceAsNumber
|
||||
? intl.formatNumber(totalPriceAsNumber, currencyConfig)
|
||||
: null;
|
||||
|
|
@ -113,11 +105,7 @@ export const BookingBreakdownComponent = props => {
|
|||
BookingBreakdownComponent.defaultProps = {
|
||||
rootClassName: null,
|
||||
className: null,
|
||||
bookingStart: null,
|
||||
bookingEnd: null,
|
||||
unitPrice: null,
|
||||
commission: null,
|
||||
totalPrice: null,
|
||||
};
|
||||
|
||||
const { string, instanceOf } = PropTypes;
|
||||
|
|
@ -126,12 +114,12 @@ BookingBreakdownComponent.propTypes = {
|
|||
rootClassName: string,
|
||||
className: string,
|
||||
|
||||
bookingStart: instanceOf(Date),
|
||||
bookingEnd: instanceOf(Date),
|
||||
bookingStart: instanceOf(Date).isRequired,
|
||||
bookingEnd: instanceOf(Date).isRequired,
|
||||
|
||||
unitPrice: propTypes.money,
|
||||
unitPrice: propTypes.money.isRequired,
|
||||
totalPrice: propTypes.money.isRequired,
|
||||
commission: propTypes.money,
|
||||
totalPrice: propTypes.money,
|
||||
|
||||
// from injectIntl
|
||||
intl: intlShape.isRequired,
|
||||
|
|
|
|||
|
|
@ -8,9 +8,10 @@ describe('BookingBreakdown', () => {
|
|||
it('pretransaction data matches snapshot', () => {
|
||||
const tree = renderDeep(
|
||||
<BookingBreakdownComponent
|
||||
unitPrice={new types.Money(1000, 'USD')}
|
||||
bookingStart={new Date(Date.UTC(2017, 3, 14))}
|
||||
bookingEnd={new Date(Date.UTC(2017, 3, 16))}
|
||||
unitPrice={new types.Money(1000, 'USD')}
|
||||
totalPrice={new types.Money(2000, 'USD')}
|
||||
intl={fakeIntl}
|
||||
/>
|
||||
);
|
||||
|
|
@ -20,9 +21,9 @@ describe('BookingBreakdown', () => {
|
|||
it('customer transaction data matches snapshot', () => {
|
||||
const tree = renderDeep(
|
||||
<BookingBreakdownComponent
|
||||
unitPrice={new types.Money(1000, 'USD')}
|
||||
bookingStart={new Date(Date.UTC(2017, 3, 14))}
|
||||
bookingEnd={new Date(Date.UTC(2017, 3, 16))}
|
||||
unitPrice={new types.Money(1000, 'USD')}
|
||||
totalPrice={new types.Money(2000, 'USD')}
|
||||
intl={fakeIntl}
|
||||
/>
|
||||
|
|
@ -33,9 +34,9 @@ describe('BookingBreakdown', () => {
|
|||
it('provider transaction data matches snapshot', () => {
|
||||
const tree = renderDeep(
|
||||
<BookingBreakdownComponent
|
||||
unitPrice={new types.Money(1000, 'USD')}
|
||||
bookingStart={new Date(Date.UTC(2017, 3, 14))}
|
||||
bookingEnd={new Date(Date.UTC(2017, 3, 16))}
|
||||
unitPrice={new types.Money(1000, 'USD')}
|
||||
totalPrice={new types.Money(1800, 'USD')}
|
||||
commission={new types.Money(200, 'USD')}
|
||||
intl={fakeIntl}
|
||||
|
|
|
|||
|
|
@ -89,7 +89,9 @@ exports[`BookingBreakdown pretransaction data matches snapshot 1`] = `
|
|||
</span>
|
||||
</div>
|
||||
<div
|
||||
className={undefined} />
|
||||
className={undefined}>
|
||||
20
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
@import '../../marketplace.css';
|
||||
|
||||
.root {
|
||||
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 35px 24px 0 24px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,34 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import { FormattedDate, FormattedMessage } from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
import * as propTypes from '../../util/propTypes';
|
||||
import { createSlug } from '../../util/urlHelpers';
|
||||
import { types } from '../../util/sdkLoader';
|
||||
import { ensureListing, ensureTransaction, ensureBooking, ensureUser } from '../../util/data';
|
||||
import { BookingBreakdown, NamedLink } from '../../components';
|
||||
|
||||
import css from './OrderDetailsPanel.css';
|
||||
|
||||
const formatName = (user, defaultName) => {
|
||||
if (user && user.attributes && user.attributes.profile && user.attributes.profile.firstName) {
|
||||
return user.attributes.profile.firstName;
|
||||
const breakdown = transaction => {
|
||||
const tx = ensureTransaction(transaction);
|
||||
const listing = ensureListing(tx.listing);
|
||||
const booking = ensureBooking(tx.booking);
|
||||
const bookingStart = booking.attributes.start;
|
||||
const bookingEnd = booking.attributes.end;
|
||||
const unitPrice = listing.attributes.price;
|
||||
const totalPrice = tx.attributes.total;
|
||||
|
||||
if (!bookingStart || !bookingEnd || !unitPrice || !totalPrice) {
|
||||
return null;
|
||||
}
|
||||
return defaultName;
|
||||
return (
|
||||
<BookingBreakdown
|
||||
className={css.receipt}
|
||||
bookingStart={bookingStart}
|
||||
bookingEnd={bookingEnd}
|
||||
unitPrice={unitPrice}
|
||||
totalPrice={totalPrice}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const orderTitle = (orderState, listingLink, customerName, lastTransition) => {
|
||||
|
|
@ -105,52 +122,47 @@ const orderMessage = (
|
|||
|
||||
const OrderDetailsPanel = props => {
|
||||
const {
|
||||
rootClassName,
|
||||
className,
|
||||
totalPrice,
|
||||
orderState,
|
||||
lastTransitionedAt,
|
||||
lastTransition,
|
||||
booking,
|
||||
listing,
|
||||
provider,
|
||||
customer,
|
||||
transaction,
|
||||
} = props;
|
||||
const providerName = formatName(provider, '');
|
||||
const customerName = formatName(customer, '');
|
||||
const currentTransaction = ensureTransaction(transaction);
|
||||
const currentListing = ensureListing(currentTransaction.listing);
|
||||
const currentProvider = ensureUser(currentTransaction.provider);
|
||||
const currentCustomer = ensureUser(currentTransaction.customer);
|
||||
|
||||
const listingLinkParams = { id: listing.id.uuid, slug: createSlug(listing.attributes.title) };
|
||||
const listingLink = (
|
||||
<NamedLink name="ListingPage" params={listingLinkParams}>
|
||||
{listing.attributes.title}
|
||||
</NamedLink>
|
||||
);
|
||||
const providerName = currentProvider.attributes.profile.firstName;
|
||||
const customerName = currentCustomer.attributes.profile.firstName;
|
||||
const transactionState = currentTransaction.attributes.state;
|
||||
const lastTransitionedAt = currentTransaction.attributes.lastTransitionedAt;
|
||||
const lastTransition = currentTransaction.attributes.lastTransitione;
|
||||
|
||||
// TODO We can't use price from listing, since that might have changed.
|
||||
// When API includes unit price and possible additional fees, we need to change this.
|
||||
const unitPrice = listing.attributes.price;
|
||||
let listingLink = null;
|
||||
|
||||
const bookingInfo = unitPrice
|
||||
? <BookingBreakdown
|
||||
className={css.receipt}
|
||||
bookingStart={booking.attributes.start}
|
||||
bookingEnd={booking.attributes.end}
|
||||
unitPrice={unitPrice}
|
||||
totalPrice={totalPrice}
|
||||
/>
|
||||
: <p className={css.error}>{'priceRequiredMessage'}</p>;
|
||||
if (currentListing.id && currentListing.attributes.title) {
|
||||
const title = currentListing.attributes.title;
|
||||
const params = { id: currentListing.id.uuid, slug: createSlug(title) };
|
||||
listingLink = (
|
||||
<NamedLink name="ListingPage" params={params}>
|
||||
{title}
|
||||
</NamedLink>
|
||||
);
|
||||
}
|
||||
|
||||
// orderState affects to both title and message section
|
||||
const title = orderTitle(orderState, listingLink, customerName, lastTransition);
|
||||
const bookingInfo = breakdown(currentTransaction);
|
||||
const title = orderTitle(transactionState, listingLink, customerName, lastTransition);
|
||||
const message = orderMessage(
|
||||
orderState,
|
||||
transactionState,
|
||||
listingLink,
|
||||
providerName,
|
||||
lastTransitionedAt,
|
||||
lastTransition
|
||||
);
|
||||
|
||||
const classes = classNames(rootClassName || css.root, className);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className={classes}>
|
||||
<h1 className={css.title}>{title}</h1>
|
||||
<div className={css.message}>
|
||||
{message}
|
||||
|
|
@ -163,20 +175,18 @@ const OrderDetailsPanel = props => {
|
|||
);
|
||||
};
|
||||
|
||||
OrderDetailsPanel.defaultProps = { className: null, lastTransition: null };
|
||||
OrderDetailsPanel.defaultProps = {
|
||||
rootClassName: null,
|
||||
className: null,
|
||||
lastTransition: null,
|
||||
};
|
||||
|
||||
const { instanceOf, string } = PropTypes;
|
||||
const { string } = PropTypes;
|
||||
|
||||
OrderDetailsPanel.propTypes = {
|
||||
rootClassName: string,
|
||||
className: string,
|
||||
totalPrice: instanceOf(types.Money).isRequired,
|
||||
orderState: string.isRequired,
|
||||
lastTransitionedAt: instanceOf(Date).isRequired,
|
||||
lastTransition: string,
|
||||
booking: propTypes.booking.isRequired,
|
||||
listing: propTypes.listing.isRequired,
|
||||
provider: propTypes.user.isRequired,
|
||||
customer: propTypes.user.isRequired,
|
||||
transaction: propTypes.transaction.isRequired,
|
||||
};
|
||||
|
||||
export default OrderDetailsPanel;
|
||||
|
|
|
|||
|
|
@ -1,17 +1,18 @@
|
|||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { types } from '../../util/sdkLoader';
|
||||
import { createBooking, createListing, createUser } from '../../util/test-data';
|
||||
import { createBooking, createListing, createUser, createTransaction } from '../../util/test-data';
|
||||
import { renderShallow } from '../../util/test-helpers';
|
||||
import { BookingBreakdown } from '../../components';
|
||||
import OrderDetailsPanel from './OrderDetailsPanel.js';
|
||||
|
||||
describe('OrderDetailsPanel', () => {
|
||||
it('matches snapshot', () => {
|
||||
const { Money } = types;
|
||||
const props = {
|
||||
commission: null,
|
||||
totalPrice: new Money(16500, 'USD'),
|
||||
orderState: 'state/preauthorized',
|
||||
lastTransitionedAt: new Date(Date.UTC(2017, 5, 10)),
|
||||
const tx = createTransaction({
|
||||
id: 'order-tx',
|
||||
state: 'state/preauthorized',
|
||||
total: new Money(16500, 'USD'),
|
||||
booking: createBooking(
|
||||
'booking1',
|
||||
new Date(Date.UTC(2017, 5, 10)),
|
||||
|
|
@ -20,8 +21,27 @@ describe('OrderDetailsPanel', () => {
|
|||
listing: createListing('listing1'),
|
||||
provider: createUser('provider'),
|
||||
customer: createUser('customer'),
|
||||
};
|
||||
const tree = renderShallow(<OrderDetailsPanel {...props} />);
|
||||
});
|
||||
const tree = renderShallow(<OrderDetailsPanel transaction={tx} />);
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
it('renders correct total price', () => {
|
||||
const { Money } = types;
|
||||
const tx = createTransaction({
|
||||
id: 'order-tx',
|
||||
state: 'state/preauthorized',
|
||||
total: new Money(16500, 'USD'),
|
||||
booking: createBooking(
|
||||
'booking1',
|
||||
new Date(Date.UTC(2017, 5, 10)),
|
||||
new Date(Date.UTC(2017, 5, 13))
|
||||
),
|
||||
listing: createListing('listing1'),
|
||||
provider: createUser('provider'),
|
||||
customer: createUser('customer'),
|
||||
});
|
||||
const panel = shallow(<OrderDetailsPanel transaction={tx} />);
|
||||
const breakdownProps = panel.find(BookingBreakdown).props();
|
||||
expect(breakdownProps.totalPrice).toEqual(new Money(16500, 'USD'));
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
exports[`OrderDetailsPanel matches snapshot 1`] = `
|
||||
<div
|
||||
className={null}>
|
||||
className="">
|
||||
<h1>
|
||||
<span>
|
||||
<span>
|
||||
|
|
|
|||
|
|
@ -1,189 +1,237 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import { FormattedDate, FormattedMessage } from 'react-intl';
|
||||
import Decimal from 'decimal.js';
|
||||
import classNames from 'classnames';
|
||||
import * as propTypes from '../../util/propTypes';
|
||||
import { types } from '../../util/sdkLoader';
|
||||
import { createSlug } from '../../util/urlHelpers';
|
||||
import { ensureListing, ensureTransaction, ensureBooking, ensureUser } from '../../util/data';
|
||||
import { convertMoneyToNumber, convertUnitToSubUnit } from '../../util/currency';
|
||||
import config from '../../config';
|
||||
import { Avatar, BookingBreakdown, NamedLink } from '../../components';
|
||||
|
||||
import css from './SaleDetailsPanel.css';
|
||||
|
||||
const SaleDetailsPanel = props => {
|
||||
const {
|
||||
className,
|
||||
subtotalPrice,
|
||||
saleState,
|
||||
booking,
|
||||
lastTransitionedAt,
|
||||
lastTransition,
|
||||
listing,
|
||||
customer,
|
||||
commission,
|
||||
} = props;
|
||||
const { firstName, lastName } = customer.attributes.profile;
|
||||
const customerName = firstName ? `${firstName} ${lastName}` : '';
|
||||
// TODO: This is a temporary function to calculate the booking
|
||||
// price. This should be removed when the API supports dry-runs and we
|
||||
// can take the total price from the transaction itself.
|
||||
const estimatedProviderTotalPrice = (customerTotalPrice, commission) => {
|
||||
const { subUnitDivisor, currency } = config.currencyConfig;
|
||||
if (customerTotalPrice.currency !== currency || commission.currency !== currency) {
|
||||
throw new Error('Transaction total or commission currency does not match marketplace currency');
|
||||
}
|
||||
|
||||
const listingLinkParams = { id: listing.id.uuid, slug: createSlug(listing.attributes.title) };
|
||||
const listingLink = (
|
||||
<NamedLink name="ListingPage" params={listingLinkParams}>
|
||||
{listing.attributes.title}
|
||||
</NamedLink>
|
||||
);
|
||||
const numericCustomerTotalPrice = convertMoneyToNumber(customerTotalPrice, subUnitDivisor);
|
||||
const numericCommission = convertMoneyToNumber(commission, subUnitDivisor);
|
||||
const numericTotalPrice = new Decimal(numericCustomerTotalPrice)
|
||||
.minus(numericCommission)
|
||||
.toNumber();
|
||||
|
||||
// TODO We can't use price from listing, since that might have changed.
|
||||
// When API includes unit price and possible additional fees, we need to change this.
|
||||
return new types.Money(convertUnitToSubUnit(numericTotalPrice, subUnitDivisor), currency);
|
||||
};
|
||||
|
||||
const breakdown = transaction => {
|
||||
const tx = ensureTransaction(transaction);
|
||||
const listing = ensureListing(tx.listing);
|
||||
const booking = ensureBooking(tx.booking);
|
||||
const bookingStart = booking.attributes.start;
|
||||
const bookingEnd = booking.attributes.end;
|
||||
const unitPrice = listing.attributes.price;
|
||||
const customerTotalPrice = tx.attributes.total;
|
||||
const commission = tx.attributes.commission;
|
||||
|
||||
const bookingInfo = unitPrice
|
||||
? <BookingBreakdown
|
||||
className={css.receipt}
|
||||
bookingStart={booking.attributes.start}
|
||||
bookingEnd={booking.attributes.end}
|
||||
unitPrice={unitPrice}
|
||||
commission={commission}
|
||||
subtotalPrice={subtotalPrice}
|
||||
/>
|
||||
: <p className={css.error}>{'priceRequiredMessage'}</p>;
|
||||
if (!bookingStart || !bookingEnd || !unitPrice || !customerTotalPrice || !commission) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const totalPrice = estimatedProviderTotalPrice(customerTotalPrice, commission);
|
||||
|
||||
return (
|
||||
<BookingBreakdown
|
||||
className={css.receipt}
|
||||
bookingStart={bookingStart}
|
||||
bookingEnd={bookingEnd}
|
||||
unitPrice={unitPrice}
|
||||
totalPrice={totalPrice}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const saleTitle = (saleState, listingLink, customerName) => {
|
||||
switch (saleState) {
|
||||
case propTypes.TX_STATE_PREAUTHORIZED:
|
||||
return (
|
||||
<FormattedMessage
|
||||
id="SaleDetailsPanel.listingRequestedTitle"
|
||||
values={{ customerName: customerName, title: listingLink }}
|
||||
/>
|
||||
);
|
||||
case propTypes.TX_STATE_ACCEPTED:
|
||||
return (
|
||||
<FormattedMessage
|
||||
id="SaleDetailsPanel.listingAcceptedTitle"
|
||||
values={{ customerName: customerName, title: listingLink }}
|
||||
/>
|
||||
);
|
||||
case propTypes.TX_STATE_REJECTED:
|
||||
return (
|
||||
<FormattedMessage
|
||||
id="SaleDetailsPanel.listingRejectedTitle"
|
||||
values={{ customerName: customerName, title: listingLink }}
|
||||
/>
|
||||
);
|
||||
case propTypes.TX_STATE_DELIVERED:
|
||||
return (
|
||||
<FormattedMessage
|
||||
id="SaleDetailsPanel.listingDeliveredTitle"
|
||||
values={{ customerName: customerName, title: listingLink }}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const saleMessage = (saleState, customerName, lastTransitionedAt, lastTransition) => {
|
||||
const rejectedStatusTranslationId = lastTransition === propTypes.TX_TRANSITION_AUTO_REJECT
|
||||
? 'SaleDetailsPanel.saleAutoRejectedStatus'
|
||||
: 'SaleDetailsPanel.saleRejectedStatus';
|
||||
|
||||
// saleState affects to both title and message section
|
||||
let stateMsgData = {};
|
||||
switch (saleState) {
|
||||
case propTypes.TX_STATE_PREAUTHORIZED:
|
||||
stateMsgData = {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
id="SaleDetailsPanel.listingRequestedTitle"
|
||||
values={{ customerName: customerName, title: listingLink }}
|
||||
/>
|
||||
),
|
||||
message: (
|
||||
<div className={css.message}>
|
||||
<FormattedMessage id="SaleDetailsPanel.saleRequestedStatus" values={{ customerName }} />
|
||||
</div>
|
||||
),
|
||||
};
|
||||
break;
|
||||
return (
|
||||
<div className={css.message}>
|
||||
<FormattedMessage id="SaleDetailsPanel.saleRequestedStatus" values={{ customerName }} />
|
||||
</div>
|
||||
);
|
||||
case propTypes.TX_STATE_ACCEPTED:
|
||||
stateMsgData = {
|
||||
title: (
|
||||
return (
|
||||
<div className={css.message}>
|
||||
<FormattedMessage id="SaleDetailsPanel.saleAcceptedStatus" />
|
||||
<FormattedMessage
|
||||
id="SaleDetailsPanel.listingAcceptedTitle"
|
||||
values={{ customerName: customerName, title: listingLink }}
|
||||
id="SaleDetailsPanel.onDate"
|
||||
values={{
|
||||
formattedDate: (
|
||||
<FormattedDate
|
||||
value={lastTransitionedAt}
|
||||
year="numeric"
|
||||
month="short"
|
||||
day="numeric"
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
),
|
||||
message: (
|
||||
<div className={css.message}>
|
||||
<FormattedMessage id="SaleDetailsPanel.saleAcceptedStatus" />
|
||||
<FormattedMessage
|
||||
id="SaleDetailsPanel.onDate"
|
||||
values={{
|
||||
formattedDate: (
|
||||
<FormattedDate
|
||||
value={lastTransitionedAt}
|
||||
year="numeric"
|
||||
month="short"
|
||||
day="numeric"
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
break;
|
||||
</div>
|
||||
);
|
||||
case propTypes.TX_STATE_REJECTED:
|
||||
stateMsgData = {
|
||||
title: (
|
||||
return (
|
||||
<div className={css.message}>
|
||||
<FormattedMessage id={rejectedStatusTranslationId} />
|
||||
<FormattedMessage
|
||||
id="SaleDetailsPanel.listingRejectedTitle"
|
||||
values={{ customerName: customerName, title: listingLink }}
|
||||
id="SaleDetailsPanel.onDate"
|
||||
values={{
|
||||
formattedDate: (
|
||||
<FormattedDate
|
||||
value={lastTransitionedAt}
|
||||
year="numeric"
|
||||
month="short"
|
||||
day="numeric"
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
),
|
||||
message: (
|
||||
<div className={css.message}>
|
||||
<FormattedMessage id={rejectedStatusTranslationId} />
|
||||
<FormattedMessage
|
||||
id="SaleDetailsPanel.onDate"
|
||||
values={{
|
||||
formattedDate: (
|
||||
<FormattedDate
|
||||
value={lastTransitionedAt}
|
||||
year="numeric"
|
||||
month="short"
|
||||
day="numeric"
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
break;
|
||||
</div>
|
||||
);
|
||||
case propTypes.TX_STATE_DELIVERED:
|
||||
stateMsgData = {
|
||||
title: (
|
||||
return (
|
||||
<div className={css.message}>
|
||||
<FormattedMessage id="SaleDetailsPanel.saleDeliveredStatus" />
|
||||
<FormattedMessage
|
||||
id="SaleDetailsPanel.listingDeliveredTitle"
|
||||
values={{ customerName: customerName, title: listingLink }}
|
||||
id="SaleDetailsPanel.onDate"
|
||||
values={{
|
||||
formattedDate: (
|
||||
<FormattedDate
|
||||
value={lastTransitionedAt}
|
||||
year="numeric"
|
||||
month="short"
|
||||
day="numeric"
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
),
|
||||
message: (
|
||||
<div className={css.message}>
|
||||
<FormattedMessage id="SaleDetailsPanel.saleDeliveredStatus" />
|
||||
<FormattedMessage
|
||||
id="SaleDetailsPanel.onDate"
|
||||
values={{
|
||||
formattedDate: (
|
||||
<FormattedDate
|
||||
value={lastTransitionedAt}
|
||||
year="numeric"
|
||||
month="short"
|
||||
day="numeric"
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
break;
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
stateMsgData = { title: null, message: null };
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const SaleDetailsPanel = props => {
|
||||
const {
|
||||
rootClassName,
|
||||
className,
|
||||
transaction,
|
||||
} = props;
|
||||
const currentTransaction = ensureTransaction(transaction);
|
||||
const currentListing = ensureListing(currentTransaction.listing);
|
||||
const currentCustomer = ensureUser(currentTransaction.customer);
|
||||
|
||||
const customerFirstName = currentCustomer.attributes.profile.firstName;
|
||||
const customerLastName = currentCustomer.attributes.profile.lastName;
|
||||
const transactionState = currentTransaction.attributes.state;
|
||||
const lastTransitionedAt = currentTransaction.attributes.lastTransitionedAt;
|
||||
const lastTransition = currentTransaction.attributes.lastTransition;
|
||||
|
||||
let listingLink = null;
|
||||
|
||||
if (currentListing.id && currentListing.attributes.title) {
|
||||
const title = currentListing.attributes.title;
|
||||
const params = { id: currentListing.id.uuid, slug: createSlug(title) };
|
||||
listingLink = (
|
||||
<NamedLink name="ListingPage" params={params}>
|
||||
{title}
|
||||
</NamedLink>
|
||||
);
|
||||
}
|
||||
|
||||
const bookingInfo = breakdown(currentTransaction);
|
||||
const title = saleTitle(transactionState, listingLink, customerFirstName, lastTransition);
|
||||
const message = saleMessage(
|
||||
transactionState,
|
||||
customerFirstName,
|
||||
lastTransitionedAt,
|
||||
lastTransition
|
||||
);
|
||||
|
||||
const classes = classNames(rootClassName || css.root, className);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className={classes}>
|
||||
<div className={css.messagesContainer}>
|
||||
<div className={css.avatarWrapper}>
|
||||
<Avatar firstName={firstName} lastName={lastName} />
|
||||
<Avatar firstName={customerFirstName} lastName={customerLastName} />
|
||||
</div>
|
||||
<h1 className={css.title}>
|
||||
{stateMsgData.title}
|
||||
{title}
|
||||
</h1>
|
||||
{stateMsgData.message}
|
||||
{message}
|
||||
</div>
|
||||
{bookingInfo}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
SaleDetailsPanel.defaultProps = { className: null, lastTransition: null };
|
||||
SaleDetailsPanel.defaultProps = {
|
||||
rootClassName: null,
|
||||
className: null,
|
||||
lastTransition: null,
|
||||
};
|
||||
|
||||
const { instanceOf, string } = PropTypes;
|
||||
const { string } = PropTypes;
|
||||
|
||||
SaleDetailsPanel.propTypes = {
|
||||
rootClassName: string,
|
||||
className: string,
|
||||
subtotalPrice: instanceOf(types.Money).isRequired,
|
||||
commission: instanceOf(types.Money).isRequired,
|
||||
saleState: string.isRequired,
|
||||
lastTransitionedAt: instanceOf(Date).isRequired,
|
||||
lastTransition: string,
|
||||
booking: propTypes.booking.isRequired,
|
||||
listing: propTypes.listing.isRequired,
|
||||
customer: propTypes.user.isRequired,
|
||||
transaction: propTypes.transaction.isRequired,
|
||||
};
|
||||
|
||||
export default SaleDetailsPanel;
|
||||
|
|
|
|||
|
|
@ -1,17 +1,19 @@
|
|||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { types } from '../../util/sdkLoader';
|
||||
import { createBooking, createListing, createUser } from '../../util/test-data';
|
||||
import { createTransaction, createBooking, createListing, createUser } from '../../util/test-data';
|
||||
import { renderShallow } from '../../util/test-helpers';
|
||||
import { BookingBreakdown } from '../../components';
|
||||
import SaleDetailsPanel from './SaleDetailsPanel.js';
|
||||
|
||||
describe('SaleDetailsPanel', () => {
|
||||
it('matches snapshot', () => {
|
||||
const { Money } = types;
|
||||
const props = {
|
||||
commission: new Money(1650, 'USD'),
|
||||
subtotalPrice: new Money(16500, 'USD'),
|
||||
saleState: 'state/preauthorized',
|
||||
lastTransitionedAt: new Date(Date.UTC(2017, 5, 10)),
|
||||
const tx = createTransaction({
|
||||
id: 'sale-tx',
|
||||
state: 'state/preauthorized',
|
||||
total: new Money(16500, 'USD'),
|
||||
commission: new Money(1000, 'USD'),
|
||||
booking: createBooking(
|
||||
'booking1',
|
||||
new Date(Date.UTC(2017, 5, 10)),
|
||||
|
|
@ -19,8 +21,31 @@ describe('SaleDetailsPanel', () => {
|
|||
),
|
||||
listing: createListing('listing1'),
|
||||
customer: createUser('customer1'),
|
||||
};
|
||||
const tree = renderShallow(<SaleDetailsPanel {...props} />);
|
||||
lastTransitionedAt: new Date(Date.UTC(2017, 5, 10)),
|
||||
});
|
||||
const tree = renderShallow(<SaleDetailsPanel transaction={tx} />);
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
it('renders correct total price', () => {
|
||||
const { Money } = types;
|
||||
const tx = createTransaction({
|
||||
id: 'sale-tx',
|
||||
state: 'state/preauthorized',
|
||||
total: new Money(16500, 'USD'),
|
||||
commission: new Money(1000, 'USD'),
|
||||
booking: createBooking(
|
||||
'booking1',
|
||||
new Date(Date.UTC(2017, 5, 10)),
|
||||
new Date(Date.UTC(2017, 5, 13))
|
||||
),
|
||||
listing: createListing('listing1'),
|
||||
customer: createUser('customer1'),
|
||||
lastTransitionedAt: new Date(Date.UTC(2017, 5, 10)),
|
||||
});
|
||||
const panel = shallow(<SaleDetailsPanel transaction={tx} />);
|
||||
const breakdownProps = panel.find(BookingBreakdown).props();
|
||||
|
||||
// Total price for the provider should be transaction total minus the commission.
|
||||
expect(breakdownProps.totalPrice).toEqual(new Money(15500, 'USD'));
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
exports[`SaleDetailsPanel matches snapshot 1`] = `
|
||||
<div
|
||||
className={null}>
|
||||
className="">
|
||||
<div>
|
||||
<div>
|
||||
<Avatar
|
||||
|
|
@ -14,7 +14,7 @@ exports[`SaleDetailsPanel matches snapshot 1`] = `
|
|||
id="SaleDetailsPanel.listingRequestedTitle"
|
||||
values={
|
||||
Object {
|
||||
"customerName": "customer1 first name customer1 last name",
|
||||
"customerName": "customer1 first name",
|
||||
"title": <withFlattenedRoutes(withRouter(NamedLink))
|
||||
name="ListingPage"
|
||||
params={
|
||||
|
|
@ -33,7 +33,7 @@ exports[`SaleDetailsPanel matches snapshot 1`] = `
|
|||
id="SaleDetailsPanel.saleRequestedStatus"
|
||||
values={
|
||||
Object {
|
||||
"customerName": "customer1 first name customer1 last name",
|
||||
"customerName": "customer1 first name",
|
||||
}
|
||||
} />
|
||||
</div>
|
||||
|
|
@ -41,15 +41,9 @@ exports[`SaleDetailsPanel matches snapshot 1`] = `
|
|||
<BookingBreakdown
|
||||
bookingEnd={2017-06-13T00:00:00.000Z}
|
||||
bookingStart={2017-06-10T00:00:00.000Z}
|
||||
commission={
|
||||
totalPrice={
|
||||
Money {
|
||||
"amount": 1650,
|
||||
"currency": "USD",
|
||||
}
|
||||
}
|
||||
subtotalPrice={
|
||||
Money {
|
||||
"amount": 16500,
|
||||
"amount": 15500,
|
||||
"currency": "USD",
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,36 @@ import { Button, BookingBreakdown, DateInputField } from '../../components';
|
|||
|
||||
import css from './BookingDatesForm.css';
|
||||
|
||||
// TODO: This is a temporary function to calculate the booking
|
||||
// price. This should be removed when the API supports dry-runs and we
|
||||
// can take the total price from the transaction itself.
|
||||
const estimatedTotalPrice = (startDate, endDate, unitPrice) => {
|
||||
const { subUnitDivisor } = config.currencyConfig;
|
||||
const numericPrice = convertMoneyToNumber(unitPrice, subUnitDivisor);
|
||||
const nightCount = nightsBetween(startDate, endDate);
|
||||
const numericTotalPrice = new Decimal(numericPrice).times(nightCount).toNumber();
|
||||
return new types.Money(
|
||||
convertUnitToSubUnit(numericTotalPrice, subUnitDivisor),
|
||||
unitPrice.currency
|
||||
);
|
||||
};
|
||||
|
||||
const breakdown = (bookingStart, bookingEnd, unitPrice) => {
|
||||
if (!bookingStart || !bookingEnd || !unitPrice) {
|
||||
return null;
|
||||
}
|
||||
const totalPrice = estimatedTotalPrice(bookingStart, bookingEnd, unitPrice);
|
||||
return (
|
||||
<BookingBreakdown
|
||||
className={css.receipt}
|
||||
bookingStart={bookingStart}
|
||||
bookingEnd={bookingEnd}
|
||||
unitPrice={unitPrice}
|
||||
totalPrice={totalPrice}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const BookingDatesFormComponent = props => {
|
||||
const {
|
||||
rootClassName,
|
||||
|
|
@ -25,16 +55,16 @@ export const BookingDatesFormComponent = props => {
|
|||
form,
|
||||
invalid,
|
||||
handleSubmit,
|
||||
price,
|
||||
price: unitPrice,
|
||||
pristine,
|
||||
submitting,
|
||||
intl,
|
||||
} = props;
|
||||
|
||||
const classes = classNames(rootClassName || css.root, className);
|
||||
const { subUnitDivisor, currency } = config.currencyConfig;
|
||||
const { currency: marketplaceCurrency } = config.currencyConfig;
|
||||
|
||||
if (!price) {
|
||||
if (!unitPrice) {
|
||||
return (
|
||||
<div className={classes}>
|
||||
<p className={css.error}>
|
||||
|
|
@ -43,7 +73,7 @@ export const BookingDatesFormComponent = props => {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
if (price.currency !== currency) {
|
||||
if (unitPrice.currency !== marketplaceCurrency) {
|
||||
return (
|
||||
<div className={classes}>
|
||||
<p className={css.error}>
|
||||
|
|
@ -76,26 +106,7 @@ export const BookingDatesFormComponent = props => {
|
|||
: {};
|
||||
|
||||
const hasBookingInfo = bookingStart && bookingEnd;
|
||||
|
||||
// Estimate total price. NOTE: this will change when we can do a
|
||||
// dry-run to the API and get a proper breakdown of the price.
|
||||
const numericPrice = convertMoneyToNumber(price, subUnitDivisor);
|
||||
const nightCount = hasBookingInfo ? nightsBetween(bookingStart, bookingEnd) : 0;
|
||||
const numericTotalPrice = new Decimal(numericPrice).times(nightCount).toNumber();
|
||||
const totalPrice = new types.Money(
|
||||
convertUnitToSubUnit(numericTotalPrice, subUnitDivisor),
|
||||
currency
|
||||
);
|
||||
|
||||
const bookingInfo = hasBookingInfo
|
||||
? <BookingBreakdown
|
||||
className={css.receipt}
|
||||
bookingStart={bookingStart}
|
||||
bookingEnd={bookingEnd}
|
||||
unitPrice={price}
|
||||
totalPrice={totalPrice}
|
||||
/>
|
||||
: null;
|
||||
const bookingInfo = breakdown(bookingStart, bookingEnd, unitPrice);
|
||||
|
||||
const submitDisabled = pristine || submitting || invalid || !hasBookingInfo;
|
||||
|
||||
|
|
|
|||
|
|
@ -5,10 +5,14 @@ import { FormattedMessage, injectIntl, intlShape } from 'react-intl';
|
|||
import { withRouter } from 'react-router-dom';
|
||||
import { reduce } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import Decimal from 'decimal.js';
|
||||
import config from '../../config';
|
||||
import { types } from '../../util/sdkLoader';
|
||||
import { pathByRouteName } from '../../util/routes';
|
||||
import * as propTypes from '../../util/propTypes';
|
||||
import { withFlattenedRoutes } from '../../util/contextHelpers';
|
||||
import { nightsBetween } from '../../util/dates';
|
||||
import { convertMoneyToNumber, convertUnitToSubUnit } from '../../util/currency';
|
||||
import { AuthorInfo, BookingBreakdown, NamedRedirect, PageLayout } from '../../components';
|
||||
import { StripePaymentForm } from '../../containers';
|
||||
import { initiateOrder, setInitialValues } from './CheckoutPage.duck';
|
||||
|
|
@ -17,6 +21,36 @@ import css from './CheckoutPage.css';
|
|||
|
||||
const STORAGE_KEY = 'CheckoutPage';
|
||||
|
||||
// TODO: This is a temporary function to calculate the booking
|
||||
// price. This should be removed when the API supports dry-runs and we
|
||||
// can take the total price from the transaction itself.
|
||||
const estimatedTotalPrice = (startDate, endDate, unitPrice) => {
|
||||
const { subUnitDivisor } = config.currencyConfig;
|
||||
const numericPrice = convertMoneyToNumber(unitPrice, subUnitDivisor);
|
||||
const nightCount = nightsBetween(startDate, endDate);
|
||||
const numericTotalPrice = new Decimal(numericPrice).times(nightCount).toNumber();
|
||||
return new types.Money(
|
||||
convertUnitToSubUnit(numericTotalPrice, subUnitDivisor),
|
||||
unitPrice.currency
|
||||
);
|
||||
};
|
||||
|
||||
const breakdown = (bookingStart, bookingEnd, unitPrice) => {
|
||||
if (!bookingStart || !bookingEnd || !unitPrice) {
|
||||
return null;
|
||||
}
|
||||
const totalPrice = estimatedTotalPrice(bookingStart, bookingEnd, unitPrice);
|
||||
return (
|
||||
<BookingBreakdown
|
||||
className={css.receipt}
|
||||
bookingStart={bookingStart}
|
||||
bookingEnd={bookingEnd}
|
||||
unitPrice={unitPrice}
|
||||
totalPrice={totalPrice}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const ensureListingProperties = listing => {
|
||||
const empty = { id: null, type: 'listing', attributes: {}, author: {}, images: [] };
|
||||
// assume own properties: id, type, attributes etc.
|
||||
|
|
@ -155,7 +189,6 @@ export class CheckoutPageComponent extends Component {
|
|||
const pageData = bookingDates && listing ? { bookingDates, listing } : storedData();
|
||||
const { bookingStart, bookingEnd } = pageData.bookingDates || {};
|
||||
const currentListing = ensureListingProperties(pageData.listing);
|
||||
const price = currentListing.attributes.price;
|
||||
|
||||
const isOwnListing = currentListing.id &&
|
||||
currentUser &&
|
||||
|
|
@ -166,10 +199,27 @@ export class CheckoutPageComponent extends Component {
|
|||
// but show payment form only when user info is loaded.
|
||||
const showPaymentForm = currentUser && !isOwnListing;
|
||||
|
||||
if (!currentListing.id || !price || isOwnListing) {
|
||||
// Estimate total price. NOTE: this will change when we can do a
|
||||
// dry-run to the API and get a proper breakdown of the price.
|
||||
const { currency: marketplaceCurrency } = config.currencyConfig;
|
||||
const unitPrice = currentListing.attributes.price;
|
||||
|
||||
if (!unitPrice) {
|
||||
throw new Error('Listing has no price');
|
||||
}
|
||||
if (unitPrice.currency !== marketplaceCurrency) {
|
||||
throw new Error(
|
||||
`Listing currency different from marketplace currency: ${unitPrice.currency}`
|
||||
);
|
||||
}
|
||||
|
||||
const hasBookingInfo = bookingStart && bookingEnd;
|
||||
|
||||
if (!currentListing.id || isOwnListing || !hasBookingInfo) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(
|
||||
'Listing, price, or user invalid for checkout, redirecting back to listing page.'
|
||||
'Listing, user, or dates invalid for checkout, redirecting back to listing page.',
|
||||
{ currentListing, isOwnListing, hasBookingInfo, bookingStart, bookingEnd }
|
||||
);
|
||||
return <NamedRedirect name="ListingPage" params={params} />;
|
||||
}
|
||||
|
|
@ -189,16 +239,13 @@ export class CheckoutPageComponent extends Component {
|
|||
</p>
|
||||
: null;
|
||||
|
||||
const bookingInfo = breakdown(bookingStart, bookingEnd, unitPrice);
|
||||
|
||||
return (
|
||||
<PageLayout title={title}>
|
||||
<h1 className={css.title}>{title}</h1>
|
||||
<AuthorInfo author={currentListing.author} className={css.authorContainer} />
|
||||
<BookingBreakdown
|
||||
className={css.receipt}
|
||||
bookingStart={bookingStart}
|
||||
bookingEnd={bookingEnd}
|
||||
unitPrice={price}
|
||||
/>
|
||||
{bookingInfo}
|
||||
<section className={css.payment}>
|
||||
{errorMessage}
|
||||
<h2 className={css.paymentTitle}>
|
||||
|
|
|
|||
|
|
@ -23,6 +23,12 @@ exports[`CheckoutPage matches snapshot 1`] = `
|
|||
<BookingBreakdown
|
||||
bookingEnd={2017-04-16T00:00:00.000Z}
|
||||
bookingStart={2017-04-14T00:00:00.000Z}
|
||||
totalPrice={
|
||||
Money {
|
||||
"amount": 11000,
|
||||
"currency": "USD",
|
||||
}
|
||||
}
|
||||
unitPrice={
|
||||
Money {
|
||||
"amount": 5500,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { FormattedMessage, intlShape, injectIntl } from 'react-intl';
|
|||
import { connect } from 'react-redux';
|
||||
import { NamedRedirect, OrderDetailsPanel, PageLayout } from '../../components';
|
||||
import * as propTypes from '../../util/propTypes';
|
||||
import { ensureBooking, ensureListing, ensureTransaction, ensureUser } from '../../util/data';
|
||||
import { ensureListing, ensureTransaction } from '../../util/data';
|
||||
import { getMarketplaceEntities } from '../../ducks/marketplaceData.duck';
|
||||
import { loadData } from './OrderPage.duck';
|
||||
|
||||
|
|
@ -31,17 +31,6 @@ export const OrderPageComponent = props => {
|
|||
return <NamedRedirect name="InboxPage" params={{ tab: 'orders' }} />;
|
||||
}
|
||||
|
||||
const detailsProps = {
|
||||
totalPrice: currentTransaction.attributes.total,
|
||||
orderState: currentTransaction.attributes.state,
|
||||
lastTransitionedAt: currentTransaction.attributes.lastTransitionedAt,
|
||||
lastTransition: currentTransaction.attributes.lastTransition,
|
||||
listing: currentListing,
|
||||
booking: ensureBooking(currentTransaction.booking),
|
||||
provider: ensureUser(currentTransaction.provider),
|
||||
customer: ensureUser(currentTransaction.customer),
|
||||
};
|
||||
|
||||
const detailsClassName = classNames(css.tabContent, {
|
||||
[css.tabContentVisible]: props.tab === 'details',
|
||||
});
|
||||
|
|
@ -51,7 +40,7 @@ export const OrderPageComponent = props => {
|
|||
: <h1 className={css.title}><FormattedMessage id="OrderPage.loadingData" /></h1>;
|
||||
|
||||
const panel = isDataAvailable && currentTransaction.id
|
||||
? <OrderDetailsPanel className={detailsClassName} {...detailsProps} />
|
||||
? <OrderDetailsPanel className={detailsClassName} transaction={currentTransaction} />
|
||||
: loadingOrFaildFetching;
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -2,77 +2,82 @@ exports[`OrderPage matches snapshot 1`] = `
|
|||
<Connect(withRouter(PageLayout))
|
||||
title="OrderPage.title">
|
||||
<OrderDetailsPanel
|
||||
booking={
|
||||
Object {
|
||||
"attributes": Object {
|
||||
"end": 2017-06-13T00:00:00.000Z,
|
||||
"start": 2017-06-10T00:00:00.000Z,
|
||||
},
|
||||
"id": UUID {
|
||||
"uuid": "booking1",
|
||||
},
|
||||
"type": "booking",
|
||||
}
|
||||
}
|
||||
className="undefined"
|
||||
customer={
|
||||
Object {
|
||||
"attributes": Object {
|
||||
"profile": Object {
|
||||
"firstName": "customer1 first name",
|
||||
"lastName": "customer1 last name",
|
||||
},
|
||||
},
|
||||
"id": UUID {
|
||||
"uuid": "customer1",
|
||||
},
|
||||
"type": "user",
|
||||
}
|
||||
}
|
||||
lastTransition={null}
|
||||
lastTransitionedAt={2017-06-01T00:00:00.000Z}
|
||||
listing={
|
||||
rootClassName={null}
|
||||
transaction={
|
||||
Object {
|
||||
"attributes": Object {
|
||||
"address": "listing1 address",
|
||||
"description": "listing1 description",
|
||||
"geolocation": LatLng {
|
||||
"lat": 40,
|
||||
"lng": 60,
|
||||
},
|
||||
"price": Money {
|
||||
"amount": 5500,
|
||||
"commission": Money {
|
||||
"amount": 100,
|
||||
"currency": "USD",
|
||||
},
|
||||
"title": "listing1 title",
|
||||
},
|
||||
"author": null,
|
||||
"id": UUID {
|
||||
"uuid": "listing1",
|
||||
},
|
||||
"images": Array [],
|
||||
"type": "listing",
|
||||
}
|
||||
}
|
||||
orderState="state/preauthorized"
|
||||
provider={
|
||||
Object {
|
||||
"attributes": Object {
|
||||
"profile": Object {
|
||||
"firstName": "provider1 first name",
|
||||
"lastName": "provider1 last name",
|
||||
"createdAt": 2017-05-01T00:00:00.000Z,
|
||||
"lastTransitionedAt": 2017-06-01T00:00:00.000Z,
|
||||
"state": "state/preauthorized",
|
||||
"total": Money {
|
||||
"amount": 1000,
|
||||
"currency": "USD",
|
||||
},
|
||||
},
|
||||
"id": UUID {
|
||||
"uuid": "provider1",
|
||||
"booking": Object {
|
||||
"attributes": Object {
|
||||
"end": 2017-06-13T00:00:00.000Z,
|
||||
"start": 2017-06-10T00:00:00.000Z,
|
||||
},
|
||||
"id": UUID {
|
||||
"uuid": "booking1",
|
||||
},
|
||||
"type": "booking",
|
||||
},
|
||||
"type": "user",
|
||||
}
|
||||
}
|
||||
totalPrice={
|
||||
Money {
|
||||
"amount": 1000,
|
||||
"currency": "USD",
|
||||
"customer": Object {
|
||||
"attributes": Object {
|
||||
"profile": Object {
|
||||
"firstName": "customer1 first name",
|
||||
"lastName": "customer1 last name",
|
||||
},
|
||||
},
|
||||
"id": UUID {
|
||||
"uuid": "customer1",
|
||||
},
|
||||
"type": "user",
|
||||
},
|
||||
"id": UUID {
|
||||
"uuid": "tx-order-1",
|
||||
},
|
||||
"listing": Object {
|
||||
"attributes": Object {
|
||||
"address": "listing1 address",
|
||||
"description": "listing1 description",
|
||||
"geolocation": LatLng {
|
||||
"lat": 40,
|
||||
"lng": 60,
|
||||
},
|
||||
"price": Money {
|
||||
"amount": 5500,
|
||||
"currency": "USD",
|
||||
},
|
||||
"title": "listing1 title",
|
||||
},
|
||||
"author": null,
|
||||
"id": UUID {
|
||||
"uuid": "listing1",
|
||||
},
|
||||
"type": "listing",
|
||||
},
|
||||
"provider": Object {
|
||||
"attributes": Object {
|
||||
"profile": Object {
|
||||
"firstName": "provider1 first name",
|
||||
"lastName": "provider1 last name",
|
||||
},
|
||||
},
|
||||
"id": UUID {
|
||||
"uuid": "provider1",
|
||||
},
|
||||
"type": "user",
|
||||
},
|
||||
"type": "transaction",
|
||||
}
|
||||
} />
|
||||
</Connect(withRouter(PageLayout))>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { FormattedMessage, intlShape, injectIntl } from 'react-intl';
|
|||
import { compose } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import * as propTypes from '../../util/propTypes';
|
||||
import { ensureBooking, ensureListing, ensureTransaction, ensureUser } from '../../util/data';
|
||||
import { ensureListing, ensureTransaction } from '../../util/data';
|
||||
import { Button, NamedRedirect, SaleDetailsPanel, PageLayout } from '../../components';
|
||||
import { getMarketplaceEntities } from '../../ducks/marketplaceData.duck';
|
||||
import { acceptSale, rejectSale, loadData } from './SalePage.duck';
|
||||
|
|
@ -25,7 +25,7 @@ export const SalePageComponent = props => {
|
|||
} = props;
|
||||
const currentTransaction = ensureTransaction(transaction);
|
||||
const currentListing = ensureListing(currentTransaction.listing);
|
||||
const title = currentListing.attributes.title;
|
||||
const listingTitle = currentListing.attributes.title;
|
||||
|
||||
// Redirect users with someone else's direct link to their own inbox/sales page.
|
||||
const isDataAvailable = currentUser &&
|
||||
|
|
@ -39,18 +39,6 @@ export const SalePageComponent = props => {
|
|||
console.error('Tried to access a sale that was not owned by the current user');
|
||||
return <NamedRedirect name="InboxPage" params={{ tab: 'sales' }} />;
|
||||
}
|
||||
|
||||
const detailsProps = {
|
||||
subtotalPrice: currentTransaction.attributes.total,
|
||||
commission: currentTransaction.attributes.commission,
|
||||
saleState: currentTransaction.attributes.state,
|
||||
lastTransitionedAt: currentTransaction.attributes.lastTransitionedAt,
|
||||
lastTransition: currentTransaction.attributes.lastTransition,
|
||||
listing: currentListing,
|
||||
booking: ensureBooking(currentTransaction.booking),
|
||||
customer: ensureUser(currentTransaction.customer),
|
||||
};
|
||||
|
||||
const detailsClassName = classNames(css.tabContent, {
|
||||
[css.tabContentVisible]: props.tab === 'details',
|
||||
});
|
||||
|
|
@ -60,7 +48,7 @@ export const SalePageComponent = props => {
|
|||
: <h1 className={css.title}><FormattedMessage id="SalePage.loadingData" /></h1>;
|
||||
|
||||
const panel = isDataAvailable && currentTransaction.id
|
||||
? <SaleDetailsPanel className={detailsClassName} {...detailsProps} />
|
||||
? <SaleDetailsPanel className={detailsClassName} transaction={currentTransaction} />
|
||||
: loadingOrFaildFetching;
|
||||
|
||||
const isPreauthorizedState = currentTransaction.attributes.state ===
|
||||
|
|
@ -77,7 +65,7 @@ export const SalePageComponent = props => {
|
|||
: null;
|
||||
|
||||
return (
|
||||
<PageLayout title={intl.formatMessage({ id: 'SalePage.title' }, { title })}>
|
||||
<PageLayout title={intl.formatMessage({ id: 'SalePage.title' }, { title: listingTitle })}>
|
||||
{panel}
|
||||
{actionButtons}
|
||||
</PageLayout>
|
||||
|
|
|
|||
|
|
@ -2,69 +2,82 @@ exports[`SalePage matches snapshot 1`] = `
|
|||
<Connect(withRouter(PageLayout))
|
||||
title="SalePage.title">
|
||||
<SaleDetailsPanel
|
||||
booking={
|
||||
Object {
|
||||
"attributes": Object {
|
||||
"end": 2017-06-13T00:00:00.000Z,
|
||||
"start": 2017-06-10T00:00:00.000Z,
|
||||
},
|
||||
"id": UUID {
|
||||
"uuid": "booking1",
|
||||
},
|
||||
"type": "booking",
|
||||
}
|
||||
}
|
||||
className="undefined"
|
||||
commission={
|
||||
Money {
|
||||
"amount": 100,
|
||||
"currency": "USD",
|
||||
}
|
||||
}
|
||||
customer={
|
||||
Object {
|
||||
"attributes": Object {
|
||||
"profile": Object {
|
||||
"firstName": "customer1 first name",
|
||||
"lastName": "customer1 last name",
|
||||
},
|
||||
},
|
||||
"id": UUID {
|
||||
"uuid": "customer1",
|
||||
},
|
||||
"type": "user",
|
||||
}
|
||||
}
|
||||
lastTransition={null}
|
||||
lastTransitionedAt={2017-06-01T00:00:00.000Z}
|
||||
listing={
|
||||
rootClassName={null}
|
||||
transaction={
|
||||
Object {
|
||||
"attributes": Object {
|
||||
"address": "listing1 address",
|
||||
"description": "listing1 description",
|
||||
"geolocation": LatLng {
|
||||
"lat": 40,
|
||||
"lng": 60,
|
||||
},
|
||||
"price": Money {
|
||||
"amount": 5500,
|
||||
"commission": Money {
|
||||
"amount": 100,
|
||||
"currency": "USD",
|
||||
},
|
||||
"createdAt": 2017-05-01T00:00:00.000Z,
|
||||
"lastTransitionedAt": 2017-06-01T00:00:00.000Z,
|
||||
"state": "state/preauthorized",
|
||||
"total": Money {
|
||||
"amount": 1000,
|
||||
"currency": "USD",
|
||||
},
|
||||
"title": "listing1 title",
|
||||
},
|
||||
"author": null,
|
||||
"booking": Object {
|
||||
"attributes": Object {
|
||||
"end": 2017-06-13T00:00:00.000Z,
|
||||
"start": 2017-06-10T00:00:00.000Z,
|
||||
},
|
||||
"id": UUID {
|
||||
"uuid": "booking1",
|
||||
},
|
||||
"type": "booking",
|
||||
},
|
||||
"customer": Object {
|
||||
"attributes": Object {
|
||||
"profile": Object {
|
||||
"firstName": "customer1 first name",
|
||||
"lastName": "customer1 last name",
|
||||
},
|
||||
},
|
||||
"id": UUID {
|
||||
"uuid": "customer1",
|
||||
},
|
||||
"type": "user",
|
||||
},
|
||||
"id": UUID {
|
||||
"uuid": "listing1",
|
||||
"uuid": "tx-sale-1",
|
||||
},
|
||||
"images": Array [],
|
||||
"type": "listing",
|
||||
}
|
||||
}
|
||||
saleState="state/preauthorized"
|
||||
subtotalPrice={
|
||||
Money {
|
||||
"amount": 1000,
|
||||
"currency": "USD",
|
||||
"listing": Object {
|
||||
"attributes": Object {
|
||||
"address": "listing1 address",
|
||||
"description": "listing1 description",
|
||||
"geolocation": LatLng {
|
||||
"lat": 40,
|
||||
"lng": 60,
|
||||
},
|
||||
"price": Money {
|
||||
"amount": 5500,
|
||||
"currency": "USD",
|
||||
},
|
||||
"title": "listing1 title",
|
||||
},
|
||||
"author": null,
|
||||
"id": UUID {
|
||||
"uuid": "listing1",
|
||||
},
|
||||
"type": "listing",
|
||||
},
|
||||
"provider": Object {
|
||||
"attributes": Object {
|
||||
"profile": Object {
|
||||
"firstName": "provider1 first name",
|
||||
"lastName": "provider1 last name",
|
||||
},
|
||||
},
|
||||
"id": UUID {
|
||||
"uuid": "provider1",
|
||||
},
|
||||
"type": "user",
|
||||
},
|
||||
"type": "transaction",
|
||||
}
|
||||
} />
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -55,12 +55,12 @@ export const createImage = id => ({
|
|||
});
|
||||
|
||||
// Create a user that conforms to the util/propTypes listing schema
|
||||
export const createListing = (id, author = null) => ({
|
||||
export const createListing = (id, author = null, price = new Money(5500, 'USD')) => ({
|
||||
id: new UUID(id),
|
||||
type: 'listing',
|
||||
attributes: {
|
||||
title: `${id} title`,
|
||||
price: new Money(5500, 'USD'),
|
||||
price,
|
||||
description: `${id} description`,
|
||||
address: `${id} address`,
|
||||
geolocation: new LatLng(40, 60),
|
||||
|
|
@ -73,6 +73,7 @@ export const createTransaction = options => {
|
|||
id,
|
||||
state = 'state/preauthorized',
|
||||
total = new Money(1000, 'USD'),
|
||||
commission = new Money(100, 'USD'),
|
||||
booking = null,
|
||||
listing = null,
|
||||
customer = null,
|
||||
|
|
@ -83,7 +84,7 @@ export const createTransaction = options => {
|
|||
id: new UUID(id),
|
||||
type: 'transaction',
|
||||
attributes: {
|
||||
commission: new Money(100, 'USD'),
|
||||
commission,
|
||||
createdAt: new Date(Date.UTC(2017, 4, 1)),
|
||||
lastTransitionedAt,
|
||||
state,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue