Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create BookingTimeForm #7

Merged
merged 10 commits into from
Oct 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ way to update this template, but currently, we follow a pattern:

## Upcoming version 2019-XX-XX

- [add] New components `BookingTimeForm` and `FieldDateAndTimeInput` for handling time-based
availability. Also change template to use time-based transaction process.
[#7](https://github.com/sharetribe/ftw-time/pull/7)
- [add] Inbox and TransactionPage use time zones to display booking times
[#10](https://github.com/sharetribe/ftw-time/pull/10)
- [add] Fetch timeSlots on ListingPage and TransactionPage.
Expand Down
17 changes: 12 additions & 5 deletions src/components/BookingBreakdown/BookingBreakdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {

import LineItemBookingPeriod from './LineItemBookingPeriod';
import LineItemBasePriceMaybe from './LineItemBasePriceMaybe';
import LineItemUnitsMaybe from './LineItemUnitsMaybe';
import LineItemUnitPriceMaybe from './LineItemUnitPriceMaybe';
import LineItemSubTotalMaybe from './LineItemSubTotalMaybe';
import LineItemCustomerCommissionMaybe from './LineItemCustomerCommissionMaybe';
import LineItemCustomerCommissionRefundMaybe from './LineItemCustomerCommissionRefundMaybe';
Expand All @@ -36,6 +36,7 @@ export const BookingBreakdownComponent = props => {
booking,
intl,
dateType,
timeZone,
} = props;

const isCustomer = userRole === 'customer';
Expand All @@ -57,12 +58,13 @@ export const BookingBreakdownComponent = props => {
*
* LineItemUnitsMaybe: if he unitType is line-item/unit print the name and
* quantity of the unit
* This line item is not used by default in the BookingBreakdown.
*
* LineItemUnitPriceMaybe: prints just the unit price, e.g. "Price per night $32.00".
*
* LineItemBasePriceMaybe: prints the base price calculation for the listing, e.g.
* "$150.00 * 2 nights $300"
*
* LineItemUnitPriceMaybe: prints just the unit price, e.g. "Price per night $32.00".
* This line item is not used by default in the BookingBreakdown.
*
* LineItemUnknownItemsMaybe: prints the line items that are unknown. In ideal case there
* should not be unknown line items. If you are using custom pricing, you should create
Expand All @@ -88,8 +90,13 @@ export const BookingBreakdownComponent = props => {

return (
<div className={classes}>
<LineItemBookingPeriod booking={booking} unitType={unitType} dateType={dateType} />
<LineItemUnitsMaybe transaction={transaction} unitType={unitType} />
<LineItemBookingPeriod
booking={booking}
unitType={unitType}
dateType={dateType}
timeZone={timeZone}
/>
<LineItemUnitPriceMaybe transaction={transaction} unitType={unitType} intl={intl} />

<LineItemBasePriceMaybe transaction={transaction} unitType={unitType} intl={intl} />
<LineItemUnknownItemsMaybe transaction={transaction} intl={intl} />
Expand Down
27 changes: 18 additions & 9 deletions src/components/BookingBreakdown/LineItemBookingPeriod.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import React from 'react';
import { FormattedMessage, FormattedDate } from '../../util/reactIntl';
import moment from 'moment';
import { LINE_ITEM_NIGHT, DATE_TYPE_DATE, propTypes } from '../../util/types';
import { LINE_ITEM_NIGHT, LINE_ITEM_UNITS, DATE_TYPE_DATE, propTypes } from '../../util/types';

import css from './BookingBreakdown.css';

const BookingPeriod = props => {
const { startDate, endDate, dateType } = props;
const { startDate, endDate, dateType, timeZone } = props;

const timeFormatOptions =
dateType === DATE_TYPE_DATE
Expand All @@ -24,6 +24,8 @@ const BookingPeriod = props => {
day: 'numeric',
};

const timeZoneMaybe = timeZone ? { timeZone } : null;

return (
<>
<div className={css.bookingPeriod}>
Expand All @@ -32,10 +34,10 @@ const BookingPeriod = props => {
<FormattedMessage id="BookingBreakdown.bookingStart" />
</div>
<div className={css.dayInfo}>
<FormattedDate value={startDate} {...timeFormatOptions} />
<FormattedDate value={startDate} {...timeFormatOptions} {...timeZoneMaybe} />
</div>
<div className={css.itemLabel}>
<FormattedDate value={startDate} {...dateFormatOptions} />
<FormattedDate value={startDate} {...dateFormatOptions} {...timeZoneMaybe} />
</div>
</div>

Expand All @@ -44,10 +46,10 @@ const BookingPeriod = props => {
<FormattedMessage id="BookingBreakdown.bookingEnd" />
</div>
<div className={css.dayInfo}>
<FormattedDate value={endDate} {...timeFormatOptions} />
<FormattedDate value={endDate} {...timeFormatOptions} {...timeZoneMaybe} />
</div>
<div className={css.itemLabel}>
<FormattedDate value={endDate} {...dateFormatOptions} />
<FormattedDate value={endDate} {...dateFormatOptions} {...timeZoneMaybe} />
</div>
</div>
</div>
Expand All @@ -56,7 +58,7 @@ const BookingPeriod = props => {
};

const LineItemBookingPeriod = props => {
const { booking, unitType, dateType } = props;
const { booking, unitType, dateType, timeZone } = props;

// Attributes: displayStart and displayEnd can be used to differentiate shown time range
// from actual start and end times used for availability reservation. It can help in situations
Expand All @@ -67,12 +69,19 @@ const LineItemBookingPeriod = props => {
const localEndDateRaw = displayEnd || end;

const isNightly = unitType === LINE_ITEM_NIGHT;
const endDay = isNightly ? localEndDateRaw : moment(localEndDateRaw).subtract(1, 'days');
const isUnit = unitType === LINE_ITEM_UNITS;
const endDay =
isUnit || isNightly ? localEndDateRaw : moment(localEndDateRaw).subtract(1, 'days');

return (
<>
<div className={css.lineItem}>
<BookingPeriod startDate={localStartDate} endDate={endDay} dateType={dateType} />
<BookingPeriod
startDate={localStartDate}
endDate={endDay}
dateType={dateType}
timeZone={timeZone}
/>
</div>
<hr className={css.totalDivider} />
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ exports[`BookingBreakdown customer transaction data matches snapshot 1`] = `
</div>,
<hr />,
]
<div>
<span>
<span>
BookingBreakdown.pricePerNight
</span>
</span>
<span>
10
</span>
</div>
<div>
<span>
<span>
Expand Down Expand Up @@ -116,6 +126,16 @@ exports[`BookingBreakdown pretransaction data matches snapshot 1`] = `
</div>,
<hr />,
]
<div>
<span>
<span>
BookingBreakdown.pricePerNight
</span>
</span>
<span>
10
</span>
</div>
<div>
<span>
<span>
Expand Down Expand Up @@ -187,6 +207,16 @@ exports[`BookingBreakdown provider canceled transaction data matches snapshot 1`
</div>,
<hr />,
]
<div>
<span>
<span>
BookingBreakdown.pricePerNight
</span>
</span>
<span>
10
</span>
</div>
<div>
<span>
<span>
Expand Down Expand Up @@ -306,6 +336,16 @@ exports[`BookingBreakdown provider transaction data matches snapshot 1`] = `
</div>,
<hr />,
]
<div>
<span>
<span>
BookingBreakdown.pricePerNight
</span>
</span>
<span>
10
</span>
</div>
<div>
<span>
<span>
Expand Down
2 changes: 1 addition & 1 deletion src/components/BookingPanel/BookingPanel.css
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@
}
}

.bookingDatesSubmitButtonWrapper {
.submitButtonWrapper {
flex-shrink: 0;
padding: 0 24px 24px 24px;
width: 100%;
Expand Down
7 changes: 5 additions & 2 deletions src/components/BookingPanel/BookingPanel.example.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const Default = {
component: BookingPanel,
props: {
className: css.example,
listing: createListing('listing_1'),
listing: createListing('listing_1', { availabilityPlan: { timezone: 'Etc/UTC' } }),
onSubmit: values => console.log('Submit:', values),
title: <span>Booking title</span>,
subTitle: 'Hosted by Author N',
Expand All @@ -21,7 +21,10 @@ export const WithClosedListing = {
component: BookingPanel,
props: {
className: css.example,
listing: createListing('listing_1', { state: LISTING_STATE_CLOSED }),
listing: createListing('listing_1', {
availabilityPlan: { timezone: 'Etc/UTC' },
state: LISTING_STATE_CLOSED,
}),
onSubmit: values => console.log('Submit:', values),
title: <span>Booking title</span>,
subTitle: 'Hosted by Author N',
Expand Down
52 changes: 36 additions & 16 deletions src/components/BookingPanel/BookingPanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ import React from 'react';
import { compose } from 'redux';
import { withRouter } from 'react-router-dom';
import { intlShape, injectIntl, FormattedMessage } from '../../util/reactIntl';
import { arrayOf, bool, func, node, oneOfType, shape, string } from 'prop-types';
import { bool, func, node, object, oneOfType, shape, string } from 'prop-types';
import classNames from 'classnames';
import omit from 'lodash/omit';
import { propTypes, LISTING_STATE_CLOSED, LINE_ITEM_NIGHT, LINE_ITEM_DAY } from '../../util/types';
import { formatMoney } from '../../util/currency';
import { dateIsAfter, monthIdStringInTimeZone } from '../../util/dates';
import { parse, stringify } from '../../util/urlHelpers';
import config from '../../config';
import { ModalInMobile, Button } from '../../components';
import { BookingDatesForm } from '../../forms';
import { BookingTimeForm } from '../../forms';

import css from './BookingPanel.css';

Expand Down Expand Up @@ -60,17 +61,18 @@ const BookingPanel = props => {
subTitle,
authorDisplayName,
onManageDisableScrolling,
timeSlots,
fetchTimeSlotsError,
monthlyTimeSlots,
history,
location,
intl,
} = props;

const price = listing.attributes.price;
const timeZone =
listing.attributes.availabilityPlan && listing.attributes.availabilityPlan.timezone;
const hasListingState = !!listing.attributes.state;
const isClosed = hasListingState && listing.attributes.state === LISTING_STATE_CLOSED;
const showBookingDatesForm = hasListingState && !isClosed;
const showBookingTimeForm = hasListingState && !isClosed;
const showClosedListingHelpText = listing.id && isClosed;
const { formattedPrice, priceTitle } = priceData(price, intl);
const isBook = !!parse(location.search).book;
Expand All @@ -93,11 +95,28 @@ const BookingPanel = props => {
const classes = classNames(rootClassName || css.root, className);
const titleClasses = classNames(titleClassName || css.bookingTitle);

const currentDate = new Date();
const monthId = monthIdStringInTimeZone(currentDate, timeZone);

const timeSlots =
!monthlyTimeSlots || Object.keys(monthlyTimeSlots).length === 0
? []
: monthlyTimeSlots[monthId] && monthlyTimeSlots[monthId].timeSlots
? monthlyTimeSlots[monthId].timeSlots
: [];

const hasTimeSlots = timeSlots && timeSlots[0];
const firstTimeSlotStart = hasTimeSlots ? timeSlots[0].attributes.start : null;
const initialStartDate =
!firstTimeSlotStart || (firstTimeSlotStart && dateIsAfter(currentDate, firstTimeSlotStart))
? currentDate
: firstTimeSlotStart;

return (
<div className={classes}>
<ModalInMobile
containerClassName={css.modalContainer}
id="BookingDatesFormInModal"
id="BookingTimeFormInModal"
isModalOpenOnMobile={isBook}
onClose={() => closeBookModal(history, location)}
showAsModalMaxWidth={MODAL_BREAKPOINT}
Expand All @@ -114,17 +133,20 @@ const BookingPanel = props => {
<h2 className={titleClasses}>{title}</h2>
{subTitleText ? <div className={css.bookingHelp}>{subTitleText}</div> : null}
</div>
{showBookingDatesForm ? (
<BookingDatesForm
{showBookingTimeForm ? (
<BookingTimeForm
className={css.bookingForm}
formId="BookingPanel"
submitButtonWrapperClassName={css.bookingDatesSubmitButtonWrapper}
submitButtonWrapperClassName={css.submitButtonWrapper}
unitType={unitType}
onSubmit={onSubmit}
price={price}
isOwnListing={isOwnListing}
timeSlots={timeSlots}
fetchTimeSlotsError={fetchTimeSlotsError}
monthlyTimeSlots={monthlyTimeSlots}
initialValues={{ bookingStartDate: { date: initialStartDate } }}
startDatePlaceholder={currentDate.toString()}
endDatePlaceholder={currentDate.toString()}
timeZone={timeZone}
/>
) : null}
</ModalInMobile>
Expand All @@ -138,7 +160,7 @@ const BookingPanel = props => {
</div>
</div>

{showBookingDatesForm ? (
{showBookingTimeForm ? (
<Button
rootClassName={css.bookButton}
onClick={() => openBookModal(isOwnListing, isClosed, history, location)}
Expand All @@ -162,8 +184,7 @@ BookingPanel.defaultProps = {
isOwnListing: false,
subTitle: null,
unitType: config.bookingUnitType,
timeSlots: null,
fetchTimeSlotsError: null,
monthlyTimeSlots: null,
};

BookingPanel.propTypes = {
Expand All @@ -178,8 +199,7 @@ BookingPanel.propTypes = {
subTitle: oneOfType([node, string]),
authorDisplayName: oneOfType([node, string]).isRequired,
onManageDisableScrolling: func.isRequired,
timeSlots: arrayOf(propTypes.timeSlot),
fetchTimeSlotsError: propTypes.error,
monthlyTimeSlots: object,

// from withRouter
history: shape({
Expand Down
14 changes: 11 additions & 3 deletions src/components/FieldDateInput/DateInput.css
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@
}

.inputRoot {
/* Contain repainting to this component only */
transform: translate3d(0, 0, 0);

/* Override react-dates default styles to match input styles */

& :global(.SingleDatePicker) {
Expand Down Expand Up @@ -240,6 +237,17 @@
& :global(.CalendarMonth_caption) {
text-transform: capitalize;
}

& :global(.DateInput__disabled) {
background: none;
}
& :global(.DateInput_input__disabled) {
border-bottom: 1px solid var(--matterColorNegative);
}
& :global(.DateInput_input__disabled::placeholder) {
color: var(--matterColorNegative);
font-style: normal;
}
}

/**
Expand Down
Loading