Merge pull request #110 from sharetribe/booking-form

Booking form
This commit is contained in:
Vesa Luusua 2017-04-10 00:38:57 +03:00 committed by GitHub
commit d0581861c4
37 changed files with 554 additions and 127 deletions

View file

@ -0,0 +1,18 @@
.container {
display: flex;
flex-direction: column;
}
.row {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.totalDivider {
width: 100%;
}
.totalPrice {
font-weight: bold;
}

View file

@ -1,12 +1,11 @@
/* eslint-disable import/prefer-default-export */
import { types } from '../../util/sdkLoader';
import BookingInfo from './BookingInfo';
export const Empty = {
component: BookingInfo,
props: {
pricePerDay: '55\u20AC',
bookingPeriod: 'Jan 2nd - Jan 4th',
bookingDuration: '3 days',
total: '165\u20AC',
unitPrice: new types.Money(10, 'USD'),
bookingStart: new Date('Fri, 14 Apr 2017 GMT'),
bookingEnd: new Date('Sun, 16 Apr 2017 GMT'),
},
};

View file

@ -1,32 +1,91 @@
/**
* This component will show the booking info and calculated total price.
* I.e. dates and other details related to payment decision in receipt format.
*/
import React, { PropTypes } from 'react';
import { FormattedMessage, intlShape, injectIntl } from 'react-intl';
import Decimal from 'decimal.js';
import moment from 'moment';
import classNames from 'classnames';
import config from '../../config';
import { convertMoneyToNumber } from '../../util/currency';
import { types } from '../../util/sdkLoader';
import css from './BookingInfo.css';
const BookingInfoComponent = props => {
const { bookingStart, bookingEnd, className, intl, unitPrice } = props;
const hasSelectedDays = bookingStart && bookingEnd;
const bookingPeriod = hasSelectedDays
? <FormattedMessage
id="BookingInfo.bookingPeriod"
values={{
bookingStart: moment(bookingStart).format('L'),
bookingEnd: moment(bookingEnd).format('L'),
}}
/>
: '';
const nightCount = hasSelectedDays ? moment(bookingEnd).diff(moment(bookingStart), 'days') : null;
const nightCountMessage = nightCount
? <FormattedMessage id="BookingInfo.nightCount" values={{ count: nightCount }} />
: null;
const priceAsNumber = convertMoneyToNumber(unitPrice, config.currencyConfig.subUnitDivisor);
const formattedPrice = intl.formatNumber(priceAsNumber, config.currencyConfig);
const totalPriceAsNumber = hasSelectedDays
? new Decimal(priceAsNumber).times(nightCount).toNumber()
: null;
const formattedTotalPrice = hasSelectedDays
? intl.formatNumber(totalPriceAsNumber, config.currencyConfig)
: null;
const BookingInfo = props => {
const { pricePerDay, bookingPeriod, bookingDuration, total } = props;
return (
<dl>
<dt>
Price per day:
</dt>
<dd>{pricePerDay}</dd>
<dt>
Booking period:
</dt>
<dd>{`${bookingPeriod}, ${bookingDuration}`}</dd>
<dt>
Total
</dt>
<dd>{total}</dd>
</dl>
<div className={classNames(css.container, className)}>
<div className={css.row}>
<div className={css.priceUnitLabel}>
<FormattedMessage id="BookingInfo.pricePerDay" />
</div>
<div className={css.priceUnitPrice}>
{formattedPrice}
</div>
</div>
<div className={css.row}>
<div className={css.bookingPeriodLabel}>
<FormattedMessage id="BookingInfo.bookingPeriodLabel" />
<br />
{bookingPeriod}
</div>
<div className={css.bookedDatesCount}>
{nightCountMessage}
</div>
</div>
<hr className={css.totalDivider} />
<div className={css.row}>
<div className={css.totalLabel}>
<FormattedMessage id="BookingInfo.total" />
</div>
<div className={css.totalPrice}>
{formattedTotalPrice}
</div>
</div>
</div>
);
};
const { string } = PropTypes;
BookingInfoComponent.defaultProps = { bookingStart: null, bookingEnd: null, className: '' };
BookingInfo.propTypes = {
pricePerDay: string.isRequired,
bookingPeriod: string.isRequired,
bookingDuration: string.isRequired,
total: string.isRequired,
const { instanceOf, string } = PropTypes;
BookingInfoComponent.propTypes = {
bookingStart: instanceOf(Date),
bookingEnd: instanceOf(Date),
className: string,
intl: intlShape.isRequired,
unitPrice: instanceOf(types.Money).isRequired,
};
const BookingInfo = injectIntl(BookingInfoComponent);
BookingInfo.displayName = 'BookingInfo';
export default BookingInfo;

View file

@ -1,15 +1,17 @@
import React from 'react';
import { fakeIntl } from '../../util/test-data';
import { renderDeep } from '../../util/test-helpers';
import { types } from '../../util/sdkLoader';
import BookingInfo from './BookingInfo';
describe('BookingInfo', () => {
it('matches snapshot', () => {
const tree = renderDeep(
<BookingInfo
pricePerDay="55\\u20AC"
bookingPeriod="Jan 2nd - Jan 4th"
bookingDuration="3 days"
total="165\u20AC"
unitPrice={new types.Money(1000, 'USD')}
bookingStart={new Date('Fri, 14 Apr 2017 GMT')}
bookingEnd={new Date('Sun, 16 Apr 2017 GMT')}
intl={fakeIntl}
/>
);
expect(tree).toMatchSnapshot();

View file

@ -1,22 +1,52 @@
exports[`BookingInfo matches snapshot 1`] = `
<dl>
<dt>
Price per day:
</dt>
<dd>
55\\\\u20AC
</dd>
<dt>
Booking period:
</dt>
<dd>
Jan 2nd - Jan 4th, 3 days
</dd>
<dt>
Total
</dt>
<dd>
165\\u20AC
</dd>
</dl>
<div
className="">
<div
className={undefined}>
<div
className={undefined}>
<span>
Price per day:
</span>
</div>
<div
className={undefined}>
$10.00
</div>
</div>
<div
className={undefined}>
<div
className={undefined}>
<span>
Booking period:
</span>
<br />
<span>
04/14/2017 - 04/16/2017
</span>
</div>
<div
className={undefined}>
<span>
2 days
</span>
</div>
</div>
<hr
className={undefined} />
<div
className={undefined}>
<div
className={undefined}>
<span>
Total:
</span>
</div>
<div
className={undefined}>
$20.00
</div>
</div>
</div>
`;

View file

@ -1,4 +1,3 @@
/* eslint-disable import/prefer-default-export */
import React, { PropTypes } from 'react';
import { IntlProvider, addLocaleData } from 'react-intl';
import en from 'react-intl/locale-data/en';

View file

@ -1,4 +1,4 @@
/* eslint-disable import/prefer-default-export, no-console */
/* eslint-disable no-console */
import React, { PropTypes } from 'react';
import { Field, reduxForm, propTypes as formPropTypes } from 'redux-form';
import moment from 'moment';

View file

@ -3,10 +3,10 @@
* Styles for SingleDatePicker can be found from 'public/reactDates.css'.
* CSS modules can't handle global styles so they are currently added separately
*/
import React, { PropTypes } from 'react';
import momentPropTypes from 'react-moment-proptypes';
import moment from 'moment';
import React, { Component, PropTypes } from 'react';
import { intlShape, injectIntl } from 'react-intl';
import { SingleDatePicker, isInclusivelyAfterDay } from 'react-dates';
import moment from 'moment';
const HORIZONTAL_ORIENTATION = 'horizontal';
const ANCHOR_LEFT = 'left';
@ -18,10 +18,10 @@ const defaultProps = {
// input related props
id: 'date',
placeholder: 'Date',
placeholder: null, // Handled inside component
disabled: false,
required: true,
screenReaderInputMessage: '',
required: false,
screenReaderInputMessage: null, // Handled inside component
showClearDate: false,
// calendar presentation and interaction related props
@ -52,12 +52,12 @@ const defaultProps = {
displayFormat: () => moment.localeData().longDateFormat('L'),
monthFormat: 'MMMM YYYY',
phrases: {
closeDatePicker: 'Close',
clearDate: 'Clear Date',
closeDatePicker: null, // Handled inside component
clearDate: null, // Handled inside component
},
};
class DateInput extends React.Component {
class DateInputComponent extends Component {
constructor(props) {
super(props);
this.state = {
@ -69,7 +69,7 @@ class DateInput extends React.Component {
}
onDateChange(date) {
this.props.onChange(date instanceof moment ? date.clone() : null);
this.props.onChange(date instanceof moment ? date.toDate() : null);
}
onFocusChange({ focused }) {
@ -85,9 +85,34 @@ class DateInput extends React.Component {
render() {
const { focused } = this.state;
// eslint-disable-next-line no-unused-vars
const { initialDate, onBlur, onChange, onFocus, value, ...datePickerProps } = this.props;
const date = value instanceof moment ? value.clone() : initialDate;
/* eslint-disable no-unused-vars */
const {
initialDate,
intl,
placeholder,
onBlur,
onChange,
onFocus,
phrases,
screenReaderInputMessage,
value,
...datePickerProps
} = this.props;
/* eslint-enable no-unused-vars */
const initialMoment = initialDate ? moment(initialDate) : null;
const date = value instanceof Date ? moment(value) : initialMoment;
const placeholderText = placeholder ||
intl.formatMessage({ id: 'DateInput.defaultPlaceholder' });
const screenReaderInputText = screenReaderInputMessage ||
intl.formatMessage({ id: 'DateInput.screenReaderInputMessage' });
const closeDatePickerText = phrases.closeDatePicker
? phrases.closeDatePicker
: intl.formatMessage({ id: 'DateInput.closeDatePicker' });
const clearDateText = phrases.clearDate
? phrases.clearDate
: intl.formatMessage({ id: 'DateInput.clearDate' });
return (
<div>
@ -98,23 +123,37 @@ class DateInput extends React.Component {
focused={focused}
onDateChange={this.onDateChange}
onFocusChange={this.onFocusChange}
placeholder={placeholderText}
screenReaderInputMessage={screenReaderInputText}
phrases={{ closeDatePicker: closeDatePickerText, clearDate: clearDateText }}
/>
</div>
);
}
}
DateInput.defaultProps = defaultProps;
DateInputComponent.defaultProps = defaultProps;
const { func, string } = PropTypes;
const { func, instanceOf, shape, string } = PropTypes;
DateInput.propTypes = {
initialDate: momentPropTypes.momentObj,
DateInputComponent.propTypes = {
initialDate: instanceOf(Date),
intl: intlShape.isRequired,
isOutsideRange: func,
onChange: func.isRequired,
onBlur: func.isRequired,
onFocus: func.isRequired,
phrases: shape({
closeDatePicker: string,
clearDate: string,
}),
placeholder: string,
value: momentPropTypes.momentObj,
screenReaderInputMessage: string,
value: instanceOf(Date),
};
const DateInput = injectIntl(DateInputComponent);
DateInput.displayName = 'DateInput';
export default DateInput;

View file

@ -8,8 +8,8 @@ exports[`DateInput matches snapshot 1`] = `
<div
className="DateInput">
<input
aria-describedby=""
aria-label="Date"
aria-describedby="DateInput__screen-reader-message-date-input"
aria-label="Date input"
autoComplete="off"
className="DateInput__input needsclick"
disabled={false}
@ -18,15 +18,19 @@ exports[`DateInput matches snapshot 1`] = `
onChange={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
placeholder="Date"
placeholder="Date input"
readOnly={false}
required={true}
required={false}
type="text"
value="" />
<p
className="screen-reader-only"
id="DateInput__screen-reader-message-date-input">
Date input
</p>
<div
className="DateInput__display-text">
Date
Date input
</div>
</div>
</div>

View file

@ -1,4 +1,4 @@
/* eslint-disable no-console, import/prefer-default-export */
/* eslint-disable no-console */
import React from 'react';
import ListingCard from './ListingCard';
import { createUser, createListing, currencyConfig, fakeIntl } from '../../util/test-data';

View file

@ -1,4 +1,4 @@
/* eslint-disable no-console, import/prefer-default-export */
/* eslint-disable no-console */
import React, { Component } from 'react';
import { Button } from '../../components';
import ModalInMobile from './ModalInMobile';

View file

@ -1,4 +1,4 @@
/* eslint-disable no-console, import/prefer-default-export */
/* eslint-disable no-console */
import NamedLink from './NamedLink';
export const NamedLinkToSearchPage = {

View file

@ -1,5 +1,5 @@
import React, { PropTypes } from 'react';
import { BookingInfo, NamedLink } from '../../components';
import { NamedLink } from '../../components';
import css from './OrderDetailsPanel.css';
@ -24,14 +24,13 @@ ContactInfo.propTypes = {
};
const OrderDetailsPanel = props => {
const { className, orderId, title, imageUrl, info, contact, confirmationCode } = props;
const { className, orderId, title, imageUrl, contact, confirmationCode } = props;
return (
<div className={className}>
<img alt={title} src={imageUrl} style={{ width: '100%' }} />
<h3>{title}</h3>
<ContactInfo {...contact} />
<p>Confirmation code {confirmationCode}</p>
<BookingInfo {...info} />
<p>Cancel booking</p>
<NamedLink className={css.buttonLink} name="OrderDiscussionPage" params={{ id: orderId }}>
You have a new message!
@ -47,7 +46,6 @@ OrderDetailsPanel.propTypes = {
orderId: oneOfType([string, number]).isRequired,
title: string.isRequired,
imageUrl: string.isRequired,
info: object.isRequired,
contact: object.isRequired,
confirmationCode: string.isRequired,
};

View file

@ -20,11 +20,6 @@ exports[`OrderDetailsPanel matches snapshot 1`] = `
Confirmation code
some-test-confirmation-code
</p>
<BookingInfo
bookingDuration="some booking duration"
bookingPeriod="some booking period"
pricePerDay="10$"
total="100$" />
<p>
Cancel booking
</p>

View file

@ -2,6 +2,7 @@ import AddImages from './AddImages/AddImages';
import BookingInfo from './BookingInfo/BookingInfo';
import Button from './Button/Button';
import CurrencyInput from './CurrencyInput/CurrencyInput';
import DateInput from './DateInput/DateInput';
import Discussion from './Discussion/Discussion';
import FilterPanel from './FilterPanel/FilterPanel';
import HeroSection from './HeroSection/HeroSection';
@ -30,6 +31,7 @@ export {
BookingInfo,
Button,
CurrencyInput,
DateInput,
Discussion,
FilterPanel,
HeroSection,

View file

@ -0,0 +1,23 @@
.receipt {
margin-top: 1rem;
margin-bottom: 2rem;
}
.totalDivider {
width: 100%;
}
.totalPrice {
font-weight: bold;
}
.error {
color: red;
margin-top: 10px;
display: inline-block;
}
.smallPrint {
font-size: 12px;
text-align: center;
}

View file

@ -0,0 +1,13 @@
/* eslint-disable no-console */
import { types } from '../../util/sdkLoader';
import BookingDatesForm from './BookingDatesForm';
export const Empty = {
component: BookingDatesForm,
props: {
onSubmit: values => {
console.log('Submit BookingDatesForm with values:', values);
},
price: new types.Money(1099, 'USD'),
},
};

View file

@ -0,0 +1,147 @@
import React, { PropTypes } from 'react';
import { compose } from 'redux';
import { connect } from 'react-redux';
import { Field, reduxForm, formValueSelector, propTypes as formPropTypes } from 'redux-form';
import { FormattedMessage, intlShape, injectIntl } from 'react-intl';
import { isInclusivelyAfterDay, isInclusivelyBeforeDay } from 'react-dates';
import moment from 'moment';
import { types } from '../../util/sdkLoader';
import { required } from '../../util/validators';
import { Button, BookingInfo, DateInput } from '../../components';
import css from './BookingDatesForm.css';
const EnhancedDateInput = props => {
const { input, isOutsideRange, labelMessage, placeholder, meta } = props;
const { onBlur, onChange, onFocus, value } = input;
const { touched, error } = meta;
const maybeIsOutsideRange = isOutsideRange ? { isOutsideRange } : {};
const inputProps = { ...maybeIsOutsideRange, onBlur, onChange, onFocus, placeholder, value };
return (
<div>
{labelMessage ? <label htmlFor="bookingStart">{labelMessage}</label> : null}
<DateInput {...inputProps} />
{touched && error ? <span className={css.error}>{error}</span> : null}
</div>
);
};
EnhancedDateInput.defaultProps = {
input: null,
isOutsideRange: null,
labelMessage: null,
placeholder: 'Date',
};
const { bool, func, instanceOf, node, object, shape, string } = PropTypes;
EnhancedDateInput.propTypes = {
input: object,
isOutsideRange: func,
labelMessage: node,
meta: shape({
touched: bool,
error: string,
}).isRequired,
placeholder: string,
};
export const BookingDatesFormComponent = props => {
const {
bookingStart,
bookingEnd,
className,
handleSubmit,
intl,
price,
pristine,
submitting,
} = props;
const placeholderText = intl.formatMessage({ id: 'BookingDatesForm.placeholder' });
const bookingStartLabel = intl.formatMessage({ id: 'BookingDatesForm.bookingStartTitle'});
const bookingEndLabel = intl.formatMessage({ id: 'BookingDatesForm.bookingEndTitle'});
const requiredMessage = intl.formatMessage({ id: 'BookingDatesForm.requiredDate' });
// A day is outside range if it is between today and booking end date (if end date has been chosen)
const isOutsideRangeStart = bookingEnd
? {
isOutsideRange: day =>
!(isInclusivelyAfterDay(day, moment()) &&
isInclusivelyBeforeDay(day, moment(bookingEnd))),
}
: {};
// A day is outside range if it is after booking start date (or today if none is chosen)
const startOfBookingEndRange = bookingStart ? moment(bookingStart) : moment();
const isOutsideRangeEnd = bookingStart
? { isOutsideRange: day => !isInclusivelyAfterDay(day, startOfBookingEndRange) }
: {};
const bookingInfo = price
? <BookingInfo
className={css.receipt}
bookingStart={bookingStart}
bookingEnd={bookingEnd}
unitPrice={price}
/>
: null;
const invalid = !(bookingStart && bookingEnd);
return (
<form className={className} onSubmit={handleSubmit}>
<Field
name="bookingStart"
labelMessage={bookingStartLabel}
component={EnhancedDateInput}
format={null}
placeholder={placeholderText}
{...isOutsideRangeStart}
validate={[required(requiredMessage)]}
/>
<Field
name="bookingEnd"
labelMessage={bookingEndLabel}
component={EnhancedDateInput}
format={null}
placeholder={placeholderText}
{...isOutsideRangeEnd}
validate={[required(requiredMessage)]}
/>
{bookingInfo}
<p className={css.smallPrint}>
<FormattedMessage id="BookingDatesForm.youWontBeChargedInfo" />
</p>
<Button type="submit" disabled={pristine || submitting || invalid}>
<FormattedMessage id="BookingDatesForm.requestToBook" />
</Button>
</form>
);
};
BookingDatesFormComponent.propTypes = {
...formPropTypes,
intl: intlShape.isRequired,
price: instanceOf(types.Money).isRequired,
};
const formName = 'bookingDates';
// When a field depends on the value of another field, we must connect
// to the store and select the required values to inject to the
// component.
//
// See: http://redux-form.com/6.6.1/examples/selectingFormValues/
const selector = formValueSelector(formName);
const mapStateToProps = state => {
return selector(state, 'bookingStart', 'bookingEnd');
};
const BookingDatesForm = compose(
connect(mapStateToProps),
reduxForm({ form: formName }),
injectIntl
)(BookingDatesFormComponent);
BookingDatesForm.displayName = 'BookingDatesForm';
export default BookingDatesForm;

View file

@ -0,0 +1,22 @@
import React from 'react';
import { types } from '../../util/sdkLoader';
import { renderShallow } from '../../util/test-helpers';
import { fakeIntl, fakeFormProps } from '../../util/test-data';
import { BookingDatesFormComponent } from './BookingDatesForm';
const noop = () => null;
describe('BookingDatesForm', () => {
it('matches snapshot', () => {
const tree = renderShallow(
<BookingDatesFormComponent
{...fakeFormProps}
intl={fakeIntl}
dispatch={noop}
onSubmit={v => v}
price={new types.Money(1099, 'USD')}
/>
);
expect(tree).toMatchSnapshot();
});
});

View file

@ -0,0 +1,47 @@
exports[`BookingDatesForm matches snapshot 1`] = `
<form
onSubmit={[Function]}>
<Field
component={[Function]}
format={null}
labelMessage="BookingDatesForm.bookingStartTitle"
name="bookingStart"
placeholder="BookingDatesForm.placeholder"
validate={
Array [
[Function],
]
} />
<Field
component={[Function]}
format={null}
labelMessage="BookingDatesForm.bookingEndTitle"
name="bookingEnd"
placeholder="BookingDatesForm.placeholder"
validate={
Array [
[Function],
]
} />
<BookingInfo
unitPrice={
Money {
"amount": 1099,
"currency": "USD",
}
} />
<p>
<FormattedMessage
id="BookingDatesForm.youWontBeChargedInfo"
values={Object {}} />
</p>
<Button
className={null}
disabled={true}
type="submit">
<FormattedMessage
id="BookingDatesForm.requestToBook"
values={Object {}} />
</Button>
</form>
`;

View file

@ -1,4 +1,4 @@
/* eslint-disable no-console, import/prefer-default-export */
/* eslint-disable no-console */
import ChangeAccountPasswordForm from './ChangeAccountPasswordForm';
export const Empty = {

View file

@ -1,4 +1,4 @@
/* eslint-disable no-console, import/prefer-default-export */
/* eslint-disable no-console */
import ChangePasswordForm from './ChangePasswordForm';
export const Empty = {

View file

@ -1,4 +1,4 @@
/* eslint-disable no-console, import/prefer-default-export */
/* eslint-disable no-console */
import EditListingForm from './EditListingForm';
export const Empty = {

View file

@ -1,4 +1,4 @@
/* eslint-disable no-console, import/prefer-default-export */
/* eslint-disable no-console */
import HeroSearchForm from './HeroSearchForm';
export const Empty = {

View file

@ -67,7 +67,11 @@
background-color: #eee;
}
.openDatePickerForm {
.bookingForm {
margin: 0 1rem;
}
.openBookingForm {
position: fixed;
bottom: 0;
left: 0;

View file

@ -6,6 +6,7 @@ import config from '../../config';
import { types } from '../../util/sdkLoader';
import { convertMoneyToNumber } from '../../util/currency';
import { Button, Map, ModalInMobile, PageLayout } from '../../components';
import { BookingDatesForm } from '../../containers';
import { getListingsById } from '../../ducks/sdk.duck';
import { showListing } from './ListingPage.duck';
import css from './ListingPage.css';
@ -39,9 +40,22 @@ export class ListingPageComponent extends Component {
isBookingModalOpenOnMobile: tab && tab === 'book',
pageClassNames: '',
};
this.onSubmit = this.onSubmit.bind(this);
this.togglePageClassNames = this.togglePageClassNames.bind(this);
}
onSubmit(values) {
const { marketplaceData, params } = this.props;
const id = new UUID(params.id);
const listingsById = getListingsById(marketplaceData, [id]);
const currentListing = listingsById.length > 0 ? listingsById[0] : null;
this.setState({ isBookingModalOpenOnMobile: false });
// eslint-disable-next-line no-console
console.log('Submitting: bookedDates', values, 'and price', currentListing.attributes.price);
}
togglePageClassNames(className, addClass = true) {
this.setState(prevState => {
const prevPageClassNames = prevState.pageClassNames.split(' ');
@ -116,10 +130,10 @@ export class ListingPageComponent extends Component {
title={bookBtnMessage}
togglePageClassNames={this.togglePageClassNames}
>
<span>ModalInMobile content</span>
<BookingDatesForm className={css.bookingForm} onSubmit={this.onSubmit} price={price} />
</ModalInMobile>
{map ? <div className={css.map}>{map}</div> : null}
<div className={css.openDatePickerForm}>
<div className={css.openBookingForm}>
<Button onClick={() => this.setState({ isBookingModalOpenOnMobile: true })}>
{bookBtnMessage}
</Button>

View file

@ -11,14 +11,19 @@ const { UUID } = types;
describe('ListingPage', () => {
it('matches snapshot', () => {
const marketplaceData = { entities: { listing: { listing1: createListing('listing1') } } };
const tree = renderShallow(
<ListingPageComponent
params={{ slug: 'listing1-title', id: 'listing1' }}
marketplaceData={marketplaceData}
intl={fakeIntl}
onLoadListing={l => l}
/>
);
const props = {
flattenedRoutes: [],
location: { search: '' },
history: {
push: () => console.log('HistoryPush called'),
},
params: { slug: 'listing1-title', id: 'listing1' },
marketplaceData,
intl: fakeIntl,
onLoadListing: () => {},
};
const tree = renderShallow(<ListingPageComponent {...props} />);
expect(tree).toMatchSnapshot();
});

View file

@ -24,9 +24,14 @@ exports[`ListingPage matches snapshot 1`] = `
showAsModalMaxWidth={2500}
title="ListingPage.ctaButtonMessage"
togglePageClassNames={[Function]}>
<span>
ModalInMobile content
</span>
<BookingDatesForm
onSubmit={[Function]}
price={
Money {
"amount": 5500,
"currency": "USD",
}
} />
</InjectIntl(ModalInMobileComponent)>
<div>
<Map

View file

@ -1,4 +1,4 @@
/* eslint-disable no-console, import/prefer-default-export */
/* eslint-disable no-console */
import LoginForm from './LoginForm';
export const Empty = {

View file

@ -14,12 +14,6 @@ const OrderPage = props => {
title,
orderId,
imageUrl: 'http://placehold.it/750x470',
info: {
pricePerDay: '55\u20AC',
bookingPeriod: 'Jan 2nd - Jan 4th',
bookingDuration: '3 days',
total: '165\u20AC',
},
contact: {
addressLine1: '350 5th Avenue',
addressLine2: 'New York, NY 10118',

View file

@ -35,14 +35,6 @@ exports[`OrderPage matches snapshot 1`] = `
}
}
imageUrl="http://placehold.it/750x470"
info={
Object {
"bookingDuration": "3 days",
"bookingPeriod": "Jan 2nd - Jan 4th",
"pricePerDay": "55€",
"total": "165€",
}
}
orderId={1234}
title="Banyan Studios" />
<OrderDiscussionPanel

View file

@ -1,4 +1,4 @@
/* eslint-disable no-console, import/prefer-default-export */
/* eslint-disable no-console */
import PasswordForgottenForm from './PasswordForgottenForm';
export const Empty = {

View file

@ -1,4 +1,4 @@
/* eslint-disable no-console, import/prefer-default-export */
/* eslint-disable no-console */
import SignUpForm from './SignUpForm';
export const Empty = {

View file

@ -1,4 +1,5 @@
import AuthenticationPage from './AuthenticationPage/AuthenticationPage';
import BookingDatesForm from './BookingDatesForm/BookingDatesForm';
import ChangeAccountPasswordForm from './ChangeAccountPasswordForm/ChangeAccountPasswordForm';
import ChangePasswordForm from './ChangePasswordForm/ChangePasswordForm';
import CheckoutPage from './CheckoutPage/CheckoutPage';
@ -29,6 +30,7 @@ import Topbar from './Topbar/Topbar';
export {
AuthenticationPage,
BookingDatesForm,
ChangeAccountPasswordForm,
ChangePasswordForm,
CheckoutPage,

View file

@ -13,6 +13,7 @@ import * as StripeBankAccountToken
from './components/StripeBankAccountToken/StripeBankAccountToken.example';
// containers
import * as BookingDatesForm from './containers/BookingDatesForm/BookingDatesForm.example';
import * as ChangeAccountPasswordForm
from './containers/ChangeAccountPasswordForm/ChangeAccountPasswordForm.example';
import * as ChangePasswordForm from './containers/ChangePasswordForm/ChangePasswordForm.example';
@ -26,6 +27,7 @@ import * as StripePaymentForm from './containers/StripePaymentForm/StripePayment
export {
AddImages,
BookingDatesForm,
BookingInfo,
ChangeAccountPasswordForm,
ChangePasswordForm,

View file

@ -1,8 +1,23 @@
{
"AuthenticationPage.loginFailed": "The email and password you entered did not match our records. Please double-check and try again.",
"AuthenticationPage.loginRequiredFor": "You must log in to view the page at",
"BookingDatesForm.bookingEndTitle": "End date",
"BookingDatesForm.bookingStartTitle": "Start date",
"BookingDatesForm.placeholder": "mm/dd/yyyy",
"BookingDatesForm.requiredDate": "Oops, make sure your date is correct!",
"BookingDatesForm.requestToBook": "Request to book",
"BookingDatesForm.youWontBeChargedInfo": "You won't be charged yet",
"BookingInfo.bookingPeriodLabel": "Booking period:",
"BookingInfo.bookingPeriod": "{bookingStart} - {bookingEnd}",
"BookingInfo.nightCount": "{count, number} {count, plural, one {day} other {days}}",
"BookingInfo.pricePerDay":"Price per day:",
"BookingInfo.total": "Total:",
"CheckoutPage.title": "Book {listingTitle}",
"CheckoutPage.paymentTitle": "Payment",
"DateInput.clearDate": "Clear Date",
"DateInput.closeDatePicker": "Close",
"DateInput.defaultPlaceholder": "Date input",
"DateInput.screenReaderInputMessage": "Date input",
"CheckoutPage.paymentInfo": "You'll only be charged if your request is accepted by the provider.",
"EditListingForm.bankAccountNumberRequired": "You need to add a bank account number.",
"EditListingForm.descriptionRequired": "You need to add a description.",

View file

@ -1,5 +1,3 @@
/* eslint-disable import/prefer-default-export */
// async actions use request - success / failure pattern
const REQUEST = 'REQUEST';
const SUCCESS = 'SUCCESS';
@ -15,4 +13,3 @@ export const createRequestTypes = base =>
},
{}
);
/* eslint-enable import/prefer-default-export */