flex-template-web/src/forms/BookingDatesForm/BookingDatesForm.js

235 lines
7.8 KiB
JavaScript

import React, { Component } from 'react';
import { string, bool, arrayOf } from 'prop-types';
import { compose } from 'redux';
import { Form as FinalForm } from 'react-final-form';
import { FormattedMessage, intlShape, injectIntl } from 'react-intl';
import classNames from 'classnames';
import moment from 'moment';
import { required, bookingDatesRequired, composeValidators } from '../../util/validators';
import { START_DATE, END_DATE } from '../../util/dates';
import { propTypes } from '../../util/types';
import config from '../../config';
import { Form, PrimaryButton, FieldDateRangeInput } from '../../components';
import EstimatedBreakdownMaybe from './EstimatedBreakdownMaybe';
import css from './BookingDatesForm.css';
const identity = v => v;
export class BookingDatesFormComponent extends Component {
constructor(props) {
super(props);
this.state = { focusedInput: null };
this.handleFormSubmit = this.handleFormSubmit.bind(this);
this.onFocusedInputChange = this.onFocusedInputChange.bind(this);
}
// Function that can be passed to nested components
// so that they can notify this component when the
// focused input changes.
onFocusedInputChange(focusedInput) {
this.setState({ focusedInput });
}
// In case start or end date for the booking is missing
// focus on that input, otherwise continue with the
// default handleSubmit function.
handleFormSubmit(e) {
const { startDate, endDate } = e.bookingDates || {};
if (!startDate) {
e.preventDefault();
this.setState({ focusedInput: START_DATE });
} else if (!endDate) {
e.preventDefault();
this.setState({ focusedInput: END_DATE });
} else {
this.props.onSubmit(e);
}
}
render() {
const { rootClassName, className, price: unitPrice, ...rest } = this.props;
const classes = classNames(rootClassName || css.root, className);
if (!unitPrice) {
return (
<div className={classes}>
<p className={css.error}>
<FormattedMessage id="BookingDatesForm.listingPriceMissing" />
</p>
</div>
);
}
if (unitPrice.currency !== config.currency) {
return (
<div className={classes}>
<p className={css.error}>
<FormattedMessage id="BookingDatesForm.listingCurrencyInvalid" />
</p>
</div>
);
}
return (
<FinalForm
{...rest}
unitPrice={unitPrice}
onSubmit={this.handleFormSubmit}
render={fieldRenderProps => {
const {
endDatePlaceholder,
startDatePlaceholder,
formId,
handleSubmit,
intl,
isOwnListing,
submitButtonWrapperClassName,
unitPrice,
unitType,
values,
timeSlots,
fetchTimeSlotsError,
} = fieldRenderProps;
const { startDate, endDate } = values && values.bookingDates ? values.bookingDates : {};
const bookingStartLabel = intl.formatMessage({
id: 'BookingDatesForm.bookingStartTitle',
});
const bookingEndLabel = intl.formatMessage({ id: 'BookingDatesForm.bookingEndTitle' });
const requiredMessage = intl.formatMessage({ id: 'BookingDatesForm.requiredDate' });
const startDateErrorMessage = intl.formatMessage({
id: 'FieldDateRangeInput.invalidStartDate',
});
const endDateErrorMessage = intl.formatMessage({
id: 'FieldDateRangeInput.invalidEndDate',
});
const timeSlotsError = fetchTimeSlotsError ? (
<p className={css.timeSlotsError}>
<FormattedMessage id="BookingDatesForm.timeSlotsError" />
</p>
) : null;
// This is the place to collect breakdown estimation data. See the
// EstimatedBreakdownMaybe component to change the calculations
// for customized payment processes.
const bookingData =
startDate && endDate
? {
unitType,
unitPrice,
startDate,
endDate,
// NOTE: If unitType is `line-item/units`, a new picker
// for the quantity should be added to the form.
quantity: 1,
}
: null;
const bookingInfo = bookingData ? (
<div className={css.priceBreakdownContainer}>
<h3 className={css.priceBreakdownTitle}>
<FormattedMessage id="BookingDatesForm.priceBreakdownTitle" />
</h3>
<EstimatedBreakdownMaybe bookingData={bookingData} />
</div>
) : null;
const dateFormatOptions = {
weekday: 'short',
month: 'short',
day: 'numeric',
};
const now = moment();
const today = now.startOf('day').toDate();
const tomorrow = now
.startOf('day')
.add(1, 'days')
.toDate();
const startDatePlaceholderText =
startDatePlaceholder || intl.formatDate(today, dateFormatOptions);
const endDatePlaceholderText =
endDatePlaceholder || intl.formatDate(tomorrow, dateFormatOptions);
const submitButtonClasses = classNames(
submitButtonWrapperClassName || css.submitButtonWrapper
);
return (
<Form onSubmit={handleSubmit} className={classes}>
{timeSlotsError}
<FieldDateRangeInput
className={css.bookingDates}
name="bookingDates"
unitType={unitType}
startDateId={`${formId}.bookingStartDate`}
startDateLabel={bookingStartLabel}
startDatePlaceholderText={startDatePlaceholderText}
endDateId={`${formId}.bookingEndDate`}
endDateLabel={bookingEndLabel}
endDatePlaceholderText={endDatePlaceholderText}
focusedInput={this.state.focusedInput}
onFocusedInputChange={this.onFocusedInputChange}
format={identity}
timeSlots={timeSlots}
useMobileMargins
validate={composeValidators(
required(requiredMessage),
bookingDatesRequired(startDateErrorMessage, endDateErrorMessage)
)}
/>
{bookingInfo}
<p className={css.smallPrint}>
<FormattedMessage
id={
isOwnListing
? 'BookingDatesForm.ownListing'
: 'BookingDatesForm.youWontBeChargedInfo'
}
/>
</p>
<div className={submitButtonClasses}>
<PrimaryButton type="submit">
<FormattedMessage id="BookingDatesForm.requestToBook" />
</PrimaryButton>
</div>
</Form>
);
}}
/>
);
}
}
BookingDatesFormComponent.defaultProps = {
rootClassName: null,
className: null,
submitButtonWrapperClassName: null,
price: null,
isOwnListing: false,
startDatePlaceholder: null,
endDatePlaceholder: null,
timeSlots: null,
};
BookingDatesFormComponent.propTypes = {
rootClassName: string,
className: string,
submitButtonWrapperClassName: string,
unitType: propTypes.bookingUnitType.isRequired,
price: propTypes.money,
isOwnListing: bool,
timeSlots: arrayOf(propTypes.timeSlot),
// from injectIntl
intl: intlShape.isRequired,
// for tests
startDatePlaceholder: string,
endDatePlaceholder: string,
};
const BookingDatesForm = compose(injectIntl)(BookingDatesFormComponent);
BookingDatesForm.displayName = 'BookingDatesForm';
export default BookingDatesForm;