mirror of
https://github.com/kingomarnajjar/flex-template-web.git
synced 2026-07-28 20:53:24 +10:00
Change booking button behaviour
If no start or end date is missing for the booking instead of being disabled the booking button when clicked moves focus to the date picker.
This commit is contained in:
parent
9b900d682e
commit
5994d56807
4 changed files with 162 additions and 96 deletions
|
|
@ -108,6 +108,15 @@ class DateRangeInputComponent extends Component {
|
|||
this.onFocusChange = this.onFocusChange.bind(this);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
// Update focusedInput in case a new value for it is
|
||||
// passed in the props. This may occur if the focus
|
||||
// is manually set to the date picker.
|
||||
if (nextProps.focusedInput && nextProps.focusedInput !== this.props.focusedInput) {
|
||||
this.setState({ focusedInput: nextProps.focusedInput });
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.clearTimeout(this.blurTimeoutId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,9 +14,21 @@ class DateRangeInputFieldComponent extends Component {
|
|||
this.handleFocus = this.handleFocus.bind(this);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
// Update focusedInput in case a new value for it is
|
||||
// passed in the props. This may occur if the focus
|
||||
// is manually set to the date picker.
|
||||
if (nextProps.focusedInput && nextProps.focusedInput !== this.props.focusedInput) {
|
||||
this.setState({ focusedInput: nextProps.focusedInput });
|
||||
}
|
||||
}
|
||||
|
||||
handleBlur(focusedInput) {
|
||||
this.setState({ focusedInput: null });
|
||||
this.props.input.onBlur(focusedInput);
|
||||
// notify the containing component that the focused
|
||||
// input has changed.
|
||||
this.props.onFocusedInputChange(null);
|
||||
}
|
||||
|
||||
handleFocus(focusedInput) {
|
||||
|
|
@ -25,6 +37,7 @@ class DateRangeInputFieldComponent extends Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
/* eslint-disable no-unused-vars */
|
||||
const {
|
||||
className,
|
||||
rootClassName,
|
||||
|
|
@ -35,8 +48,13 @@ class DateRangeInputFieldComponent extends Component {
|
|||
input,
|
||||
meta,
|
||||
useMobileMargins,
|
||||
// extract focusedInput and onFocusedInputChange so that
|
||||
// the same values will not be passed on to subcomponents
|
||||
focusedInput,
|
||||
onFocusedInputChange,
|
||||
...rest
|
||||
} = this.props;
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
if (startDateLabel && !startDateId) {
|
||||
throw new Error('startDateId required when a startDateLabel is given');
|
||||
|
|
@ -86,6 +104,7 @@ class DateRangeInputFieldComponent extends Component {
|
|||
useMobileMargins,
|
||||
...restOfInput,
|
||||
...rest,
|
||||
focusedInput: this.state.focusedInput,
|
||||
};
|
||||
const classes = classNames(rootClassName || css.fieldRoot, className);
|
||||
const errorClasses = classNames({ [css.mobileMargins]: useMobileMargins });
|
||||
|
|
@ -118,9 +137,11 @@ DateRangeInputFieldComponent.defaultProps = {
|
|||
startDateId: null,
|
||||
startDateLabel: null,
|
||||
startDatePlaceholderText: null,
|
||||
focusedInput: null,
|
||||
onFocusedInputChange: null,
|
||||
};
|
||||
|
||||
const { bool, object, string } = PropTypes;
|
||||
const { bool, func, object, oneOf, string } = PropTypes;
|
||||
|
||||
DateRangeInputFieldComponent.propTypes = {
|
||||
className: string,
|
||||
|
|
@ -134,6 +155,8 @@ DateRangeInputFieldComponent.propTypes = {
|
|||
startDatePlaceholderText: string,
|
||||
input: object.isRequired,
|
||||
meta: object.isRequired,
|
||||
focusedInput: oneOf([START_DATE, END_DATE]),
|
||||
onFocusedInputChange: func,
|
||||
};
|
||||
|
||||
const DateRangeInputField = props => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { compose } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import { reduxForm, formValueSelector, propTypes as formPropTypes } from 'redux-form';
|
||||
|
|
@ -14,6 +14,7 @@ import * as propTypes from '../../util/propTypes';
|
|||
import config from '../../config';
|
||||
import { Form, PrimaryButton, BookingBreakdown, DateRangeInputField } from '../../components';
|
||||
|
||||
import { START_DATE, END_DATE } from '../../components/DateRangeInputField/DateRangeInput';
|
||||
import css from './BookingDatesForm.css';
|
||||
|
||||
const estimatedTotalPrice = (unitPrice, nightCount) => {
|
||||
|
|
@ -80,107 +81,139 @@ const estimatedBreakdown = (bookingStart, bookingEnd, unitPrice) => {
|
|||
);
|
||||
};
|
||||
|
||||
export const BookingDatesFormComponent = props => {
|
||||
const {
|
||||
rootClassName,
|
||||
className,
|
||||
bookingDates,
|
||||
form,
|
||||
invalid,
|
||||
handleSubmit,
|
||||
price: unitPrice,
|
||||
submitting,
|
||||
intl,
|
||||
startDatePlaceholder,
|
||||
endDatePlaceholder,
|
||||
isOwnListing,
|
||||
} = props;
|
||||
|
||||
const { startDate, endDate } = bookingDates;
|
||||
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>
|
||||
);
|
||||
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);
|
||||
}
|
||||
|
||||
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: 'DateRangeInputField.invalidStartDate' });
|
||||
const endDateErrorMessage = intl.formatMessage({ id: 'DateRangeInputField.invalidEndDate' });
|
||||
// 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 });
|
||||
}
|
||||
|
||||
const hasBookingInfo = startDate && endDate;
|
||||
const bookingInfo = hasBookingInfo
|
||||
? <div className={css.priceBreakdownContainer}>
|
||||
<h3 className={css.priceBreakdownTitle}>
|
||||
<FormattedMessage id="BookingDatesForm.priceBreakdownTitle" />
|
||||
</h3>
|
||||
{estimatedBreakdown(startDate, endDate, unitPrice)}
|
||||
</div>
|
||||
: null;
|
||||
// In case start or end date for the booking is missing
|
||||
// focus on that input, otherwise continue witht the
|
||||
// default handleSubmit function.
|
||||
handleFormSubmit(e) {
|
||||
const { startDate, endDate } = this.props.bookingDates;
|
||||
if (!startDate) {
|
||||
e.preventDefault();
|
||||
this.setState({ focusedInput: START_DATE });
|
||||
} else if (!endDate) {
|
||||
e.preventDefault();
|
||||
this.setState({ focusedInput: END_DATE });
|
||||
} else {
|
||||
this.props.handleSubmit();
|
||||
}
|
||||
}
|
||||
|
||||
const submitDisabled = submitting || invalid || !hasBookingInfo;
|
||||
render() {
|
||||
const {
|
||||
rootClassName,
|
||||
className,
|
||||
bookingDates,
|
||||
form,
|
||||
price: unitPrice,
|
||||
intl,
|
||||
startDatePlaceholder,
|
||||
endDatePlaceholder,
|
||||
isOwnListing,
|
||||
} = this.props;
|
||||
|
||||
const dateFormatOptions = {
|
||||
weekday: 'short',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
};
|
||||
const { startDate, endDate } = bookingDates;
|
||||
const classes = classNames(rootClassName || css.root, className);
|
||||
|
||||
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);
|
||||
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 (
|
||||
<Form className={className} onSubmit={handleSubmit}>
|
||||
<DateRangeInputField
|
||||
className={css.bookingDates}
|
||||
name="bookingDates"
|
||||
startDateId={`${form}.bookingStartDate`}
|
||||
startDateLabel={bookingStartLabel}
|
||||
startDatePlaceholderText={startDatePlaceholderText}
|
||||
endDateId={`${form}.bookingEndDate`}
|
||||
endDateLabel={bookingEndLabel}
|
||||
endDatePlaceholderText={endDatePlaceholderText}
|
||||
format={null}
|
||||
useMobileMargins
|
||||
validate={[
|
||||
required(requiredMessage),
|
||||
bookingDatesRequired(startDateErrorMessage, endDateErrorMessage),
|
||||
]}
|
||||
/>
|
||||
{bookingInfo}
|
||||
<p className={css.smallPrint}>
|
||||
<FormattedMessage
|
||||
id={
|
||||
isOwnListing ? 'BookingDatesForm.ownListing' : 'BookingDatesForm.youWontBeChargedInfo'
|
||||
}
|
||||
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: 'DateRangeInputField.invalidStartDate',
|
||||
});
|
||||
const endDateErrorMessage = intl.formatMessage({ id: 'DateRangeInputField.invalidEndDate' });
|
||||
|
||||
const hasBookingInfo = startDate && endDate;
|
||||
const bookingInfo = hasBookingInfo
|
||||
? <div className={css.priceBreakdownContainer}>
|
||||
<h3 className={css.priceBreakdownTitle}>
|
||||
<FormattedMessage id="BookingDatesForm.priceBreakdownTitle" />
|
||||
</h3>
|
||||
{estimatedBreakdown(startDate, endDate, unitPrice)}
|
||||
</div>
|
||||
: null;
|
||||
|
||||
const dateFormatOptions = {
|
||||
weekday: 'short',
|
||||
month: 'long',
|
||||
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);
|
||||
|
||||
return (
|
||||
<Form className={className} onSubmit={this.handleFormSubmit}>
|
||||
<DateRangeInputField
|
||||
className={css.bookingDates}
|
||||
name="bookingDates"
|
||||
startDateId={`${form}.bookingStartDate`}
|
||||
startDateLabel={bookingStartLabel}
|
||||
startDatePlaceholderText={startDatePlaceholderText}
|
||||
endDateId={`${form}.bookingEndDate`}
|
||||
endDateLabel={bookingEndLabel}
|
||||
endDatePlaceholderText={endDatePlaceholderText}
|
||||
focusedInput={this.state.focusedInput}
|
||||
onFocusedInputChange={this.onFocusedInputChange}
|
||||
format={null}
|
||||
useMobileMargins
|
||||
validate={[
|
||||
required(requiredMessage),
|
||||
bookingDatesRequired(startDateErrorMessage, endDateErrorMessage),
|
||||
]}
|
||||
/>
|
||||
</p>
|
||||
<PrimaryButton className={css.submitButton} type="submit" disabled={submitDisabled}>
|
||||
<FormattedMessage id="BookingDatesForm.requestToBook" />
|
||||
</PrimaryButton>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
{bookingInfo}
|
||||
<p className={css.smallPrint}>
|
||||
<FormattedMessage
|
||||
id={
|
||||
isOwnListing ? 'BookingDatesForm.ownListing' : 'BookingDatesForm.youWontBeChargedInfo'
|
||||
}
|
||||
/>
|
||||
</p>
|
||||
<PrimaryButton className={css.submitButton} type="submit">
|
||||
<FormattedMessage id="BookingDatesForm.requestToBook" />
|
||||
</PrimaryButton>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
BookingDatesFormComponent.defaultProps = {
|
||||
rootClassName: null,
|
||||
|
|
|
|||
|
|
@ -6,8 +6,10 @@ exports[`BookingDatesForm matches snapshot without selected dates 1`] = `
|
|||
endDateId="fakeTestForm.bookingEndDate"
|
||||
endDateLabel="BookingDatesForm.bookingEndTitle"
|
||||
endDatePlaceholderText="tomorrow"
|
||||
focusedInput={null}
|
||||
format={null}
|
||||
name="bookingDates"
|
||||
onFocusedInputChange={[Function]}
|
||||
startDateId="fakeTestForm.bookingStartDate"
|
||||
startDateLabel="BookingDatesForm.bookingStartTitle"
|
||||
startDatePlaceholderText="today"
|
||||
|
|
@ -24,7 +26,6 @@ exports[`BookingDatesForm matches snapshot without selected dates 1`] = `
|
|||
values={Object {}} />
|
||||
</p>
|
||||
<PrimaryButton
|
||||
disabled={true}
|
||||
type="submit">
|
||||
<FormattedMessage
|
||||
id="BookingDatesForm.requestToBook"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue