diff --git a/CHANGELOG.md b/CHANGELOG.md
index d9092b53db..658a164a73 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,9 @@ way to update this template, but currently, we follow a pattern:
## Upcoming version 2019-XX-XX
+- [change] Move `BookingTimeInfo` to separate component from `InboxPage`. Add options to show only
+ booking dates or booking dates and times.
+ [#1194](https://github.com/sharetribe/flex-template-web/pull/1194)
- [add] Add new Spanish translations related to storing payment card.
[#1193](https://github.com/sharetribe/flex-template-web/pull/1193)
- [fix] Update yarn.lock (there was Lodash version resolution missing)
diff --git a/src/components/BookingTimeInfo/BookingTimeInfo.css b/src/components/BookingTimeInfo/BookingTimeInfo.css
new file mode 100644
index 0000000000..0fff0e5e8b
--- /dev/null
+++ b/src/components/BookingTimeInfo/BookingTimeInfo.css
@@ -0,0 +1,14 @@
+@import '../../marketplace.css';
+
+.root {
+}
+
+.bookingInfo {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+}
+
+.dateSection {
+ margin-right: 5px;
+}
diff --git a/src/components/BookingTimeInfo/BookingTimeInfo.example.js b/src/components/BookingTimeInfo/BookingTimeInfo.example.js
new file mode 100644
index 0000000000..41f292f46e
--- /dev/null
+++ b/src/components/BookingTimeInfo/BookingTimeInfo.example.js
@@ -0,0 +1,141 @@
+import BookingTimeInfo from './BookingTimeInfo';
+import {
+ fakeIntl,
+ createBooking,
+ createTransaction,
+ createUser,
+ createListing,
+} from '../../util/test-data';
+import { LINE_ITEM_DAY, LINE_ITEM_NIGHT, LINE_ITEM_UNITS } from '../../util/types';
+
+export const DateAndTimeSingleDay = {
+ component: BookingTimeInfo,
+ props: {
+ isOrder: true,
+ intl: fakeIntl,
+ tx: createTransaction({
+ customer: createUser('user1'),
+ provider: createUser('user2'),
+ listing: createListing('Listing'),
+ booking: createBooking('example-booking', {
+ displayStart: new Date(Date.UTC(2019, 8, 30, 3, 0)),
+ displayEnd: new Date(Date.UTC(2019, 8, 30, 4, 0)),
+ start: new Date(Date.UTC(2019, 8, 30, 3, 0)),
+ end: new Date(Date.UTC(2019, 8, 30, 4, 0)),
+ }),
+ }),
+ unitType: LINE_ITEM_UNITS,
+ dateType: 'datetime',
+ },
+ group: 'inbox',
+};
+
+export const DateAndTimeMultipleDays = {
+ component: BookingTimeInfo,
+ props: {
+ isOrder: true,
+ intl: fakeIntl,
+ tx: createTransaction({
+ customer: createUser('user1'),
+ provider: createUser('user2'),
+ listing: createListing('Listing'),
+ booking: createBooking('example-booking', {
+ displayStart: new Date(Date.UTC(2019, 8, 28, 3, 0)),
+ displayEnd: new Date(Date.UTC(2019, 8, 30, 5, 0)),
+ start: new Date(Date.UTC(2019, 8, 28, 3, 0)),
+ end: new Date(Date.UTC(2019, 8, 30, 5, 0)),
+ }),
+ }),
+ unitType: LINE_ITEM_UNITS,
+ dateType: 'datetime',
+ },
+ group: 'inbox',
+};
+
+export const OnlyDateSingleDay = {
+ component: BookingTimeInfo,
+ props: {
+ isOrder: true,
+ intl: fakeIntl,
+ tx: createTransaction({
+ customer: createUser('user1'),
+ provider: createUser('user2'),
+ listing: createListing('Listing'),
+ booking: createBooking('example-booking', {
+ displayStart: new Date(Date.UTC(2019, 8, 29, 3, 0)),
+ displayEnd: new Date(Date.UTC(2019, 8, 30, 4, 0)),
+ start: new Date(Date.UTC(2019, 8, 29, 3, 0)),
+ end: new Date(Date.UTC(2019, 8, 30, 4, 0)),
+ }),
+ }),
+ unitType: LINE_ITEM_DAY,
+ dateType: 'date',
+ },
+ group: 'inbox',
+};
+
+export const OnlyDateMultipleDays = {
+ component: BookingTimeInfo,
+ props: {
+ isOrder: true,
+ intl: fakeIntl,
+ tx: createTransaction({
+ customer: createUser('user1'),
+ provider: createUser('user2'),
+ listing: createListing('Listing'),
+ booking: createBooking('example-booking', {
+ displayStart: new Date(Date.UTC(2019, 8, 28, 3, 0)),
+ displayEnd: new Date(Date.UTC(2019, 8, 30, 5, 0)),
+ start: new Date(Date.UTC(2019, 8, 28, 3, 0)),
+ end: new Date(Date.UTC(2019, 8, 30, 5, 0)),
+ }),
+ }),
+ unitType: LINE_ITEM_DAY,
+ dateType: 'date',
+ },
+ group: 'inbox',
+};
+
+export const OnlyDateSingleNight = {
+ component: BookingTimeInfo,
+ props: {
+ isOrder: true,
+ intl: fakeIntl,
+ tx: createTransaction({
+ customer: createUser('user1'),
+ provider: createUser('user2'),
+ listing: createListing('Listing'),
+ booking: createBooking('example-booking', {
+ displayStart: new Date(Date.UTC(2019, 8, 29, 3, 0)),
+ displayEnd: new Date(Date.UTC(2019, 8, 30, 4, 0)),
+ start: new Date(Date.UTC(2019, 8, 29, 3, 0)),
+ end: new Date(Date.UTC(2019, 8, 30, 4, 0)),
+ }),
+ }),
+ unitType: LINE_ITEM_NIGHT,
+ dateType: 'date',
+ },
+ group: 'inbox',
+};
+
+export const OnlyDateMultipleNights = {
+ component: BookingTimeInfo,
+ props: {
+ isOrder: true,
+ intl: fakeIntl,
+ tx: createTransaction({
+ customer: createUser('user1'),
+ provider: createUser('user2'),
+ listing: createListing('Listing'),
+ booking: createBooking('example-booking', {
+ displayStart: new Date(Date.UTC(2019, 8, 28, 3, 0)),
+ displayEnd: new Date(Date.UTC(2019, 8, 30, 5, 0)),
+ start: new Date(Date.UTC(2019, 8, 28, 3, 0)),
+ end: new Date(Date.UTC(2019, 8, 30, 5, 0)),
+ }),
+ }),
+ unitType: LINE_ITEM_NIGHT,
+ dateType: 'date',
+ },
+ group: 'inbox',
+};
diff --git a/src/components/BookingTimeInfo/BookingTimeInfo.js b/src/components/BookingTimeInfo/BookingTimeInfo.js
new file mode 100644
index 0000000000..b90a47816a
--- /dev/null
+++ b/src/components/BookingTimeInfo/BookingTimeInfo.js
@@ -0,0 +1,98 @@
+import React from 'react';
+import moment from 'moment';
+import { bool, oneOf } from 'prop-types';
+import classNames from 'classnames';
+import { txIsEnquired } from '../../util/transaction';
+import { dateFromAPIToLocalNoon, daysBetween, formatDateToText } from '../../util/dates';
+import { injectIntl, intlShape } from '../../util/reactIntl';
+import {
+ LINE_ITEM_DAY,
+ LINE_ITEM_NIGHT,
+ LINE_ITEM_UNITS,
+ DATE_TYPE_DATE,
+ DATE_TYPE_DATETIME,
+ propTypes,
+} from '../../util/types';
+
+import css from './BookingTimeInfo.css';
+
+const bookingData = (unitType, tx, isOrder, intl) => {
+ // 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
+ // where there are preparation time needed between bookings.
+ // Read more: https://www.sharetribe.com/api-reference/#bookings
+ const { start, end, displayStart, displayEnd } = tx.booking.attributes;
+ const startDate = dateFromAPIToLocalNoon(displayStart || start);
+ const endDateRaw = dateFromAPIToLocalNoon(displayEnd || end);
+ const isDaily = unitType === LINE_ITEM_DAY;
+ const isNightly = unitType === LINE_ITEM_NIGHT;
+ const isUnits = unitType === LINE_ITEM_UNITS;
+ const isSingleDay = !isNightly && daysBetween(startDate, endDateRaw) <= 1;
+ const bookingStart = formatDateToText(intl, startDate);
+ // Shift the exclusive API end date with daily bookings
+ const endDate =
+ isDaily || isUnits
+ ? moment(endDateRaw)
+ .subtract(1, 'days')
+ .toDate()
+ : endDateRaw;
+ const bookingEnd = formatDateToText(intl, endDate);
+ return { bookingStart, bookingEnd, isSingleDay };
+};
+
+const BookingTimeInfoComponent = props => {
+ const { bookingClassName, isOrder, intl, tx, unitType, dateType } = props;
+ const isEnquiry = txIsEnquired(tx);
+
+ if (isEnquiry) {
+ return null;
+ }
+
+ const bookingTimes = bookingData(unitType, tx, isOrder, intl);
+
+ const { bookingStart, bookingEnd, isSingleDay } = bookingTimes;
+
+ if (isSingleDay && dateType === DATE_TYPE_DATE) {
+ return (
+
+ {`${bookingStart.date}`}
+
+ );
+ } else if (dateType === DATE_TYPE_DATE) {
+ return (
+
+ {`${bookingStart.date} -`}
+ {`${bookingEnd.date}`}
+
+ );
+ } else if (isSingleDay && dateType === DATE_TYPE_DATETIME) {
+ return (
+
+
+ {`${bookingStart.date}, ${bookingStart.time} - ${bookingEnd.time}`}
+
+
+ );
+ } else {
+ return (
+
+ {`${bookingStart.dateAndTime} - `}
+ {`${bookingEnd.dateAndTime}`}
+
+ );
+ }
+};
+
+BookingTimeInfoComponent.defaultProps = { dateType: null };
+
+BookingTimeInfoComponent.propTypes = {
+ intl: intlShape.isRequired,
+ isOrder: bool.isRequired,
+ tx: propTypes.transaction.isRequired,
+ unitType: propTypes.bookingUnitType.isRequired,
+ dateType: oneOf(DATE_TYPE_DATE, DATE_TYPE_DATETIME),
+};
+
+const BookingTimeInfo = injectIntl(BookingTimeInfoComponent);
+
+export default BookingTimeInfo;
diff --git a/src/components/index.js b/src/components/index.js
index 7d037632dd..1254210cdd 100644
--- a/src/components/index.js
+++ b/src/components/index.js
@@ -116,6 +116,7 @@ export { default as AddImages } from './AddImages/AddImages';
export { default as Avatar, AvatarMedium, AvatarLarge } from './Avatar/Avatar';
export { default as BookingBreakdown } from './BookingBreakdown/BookingBreakdown';
export { default as BookingDateRangeFilter } from './BookingDateRangeFilter/BookingDateRangeFilter';
+export { default as BookingTimeInfo } from './BookingTimeInfo/BookingTimeInfo';
export { default as BookingPanel } from './BookingPanel/BookingPanel';
export { default as Discussion } from './Discussion/Discussion';
export { default as FilterPlain } from './FilterPlain/FilterPlain';
diff --git a/src/containers/InboxPage/InboxPage.css b/src/containers/InboxPage/InboxPage.css
index c18a8d2233..817ba7d04e 100644
--- a/src/containers/InboxPage/InboxPage.css
+++ b/src/containers/InboxPage/InboxPage.css
@@ -273,29 +273,23 @@
}
}
-.bookingInfo {
+.bookingInfoWrapper {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+
font-size: 14px;
line-height: 14px;
- margin-top: 3px;
+ margin-top: 2px;
+ padding-top: 2px;
@media (--viewportMedium) {
+ padding-top: 0px;
margin-top: 8px;
line-height: 16px;
}
}
-.itemTimestamp {
- /* Font */
- @apply --marketplaceH5FontStyles;
-
- margin-top: 0px;
- margin-bottom: 0px;
- @media (--viewportMedium) {
- margin-top: 4px;
- margin-bottom: 0;
- }
-}
-
.itemPrice {
&::before {
font-size: 10px;
diff --git a/src/containers/InboxPage/InboxPage.js b/src/containers/InboxPage/InboxPage.js
index 4207296422..26d43ab5c9 100644
--- a/src/containers/InboxPage/InboxPage.js
+++ b/src/containers/InboxPage/InboxPage.js
@@ -1,9 +1,8 @@
import React from 'react';
-import PropTypes from 'prop-types';
+import { arrayOf, bool, number, oneOf, shape, string } from 'prop-types';
import { compose } from 'redux';
import { connect } from 'react-redux';
import { FormattedMessage, injectIntl, intlShape } from '../../util/reactIntl';
-import moment from 'moment';
import classNames from 'classnames';
import {
txIsAccepted,
@@ -15,14 +14,13 @@ import {
txIsPaymentExpired,
txIsPaymentPending,
} from '../../util/transaction';
-import { LINE_ITEM_DAY, LINE_ITEM_UNITS, propTypes } from '../../util/types';
-import { formatMoney } from '../../util/currency';
+import { propTypes, DATE_TYPE_DATE } from '../../util/types';
import { ensureCurrentUser } from '../../util/data';
-import { dateFromAPIToLocalNoon, daysBetween } from '../../util/dates';
import { getMarketplaceEntities } from '../../ducks/marketplaceData.duck';
import { isScrollingDisabled } from '../../ducks/UI.duck';
import {
Avatar,
+ BookingTimeInfo,
NamedLink,
NotificationBadge,
Page,
@@ -43,8 +41,6 @@ import config from '../../config';
import { loadData } from './InboxPage.duck';
import css from './InboxPage.css';
-const { arrayOf, bool, number, oneOf, shape, string } = PropTypes;
-
const formatDate = (intl, date) => {
return {
short: intl.formatDate(date, {
@@ -157,33 +153,7 @@ export const txState = (intl, tx, type) => {
}
};
-const bookingData = (unitType, tx, isOrder, intl) => {
- // 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
- // where there are preparation time needed between bookings.
- // Read more: https://www.sharetribe.com/api-reference/#bookings
- const { start, end, displayStart, displayEnd } = tx.booking.attributes;
- const startDate = dateFromAPIToLocalNoon(displayStart || start);
- const endDateRaw = dateFromAPIToLocalNoon(displayEnd || end);
- const isDaily = unitType === LINE_ITEM_DAY;
- const isUnits = unitType === LINE_ITEM_UNITS;
- const isSingleDay = isDaily && daysBetween(startDate, endDateRaw) === 1;
- const bookingStart = formatDate(intl, startDate);
-
- // Shift the exclusive API end date with daily bookings
- const endDate =
- isDaily || isUnits
- ? moment(endDateRaw)
- .subtract(1, 'days')
- .toDate()
- : endDateRaw;
- const bookingEnd = formatDate(intl, endDate);
- const bookingPrice = isOrder ? tx.attributes.payinTotal : tx.attributes.payoutTotal;
- const price = formatMoney(intl, bookingPrice);
- return { bookingStart, bookingEnd, price, isSingleDay };
-};
-
-// Functional component as internal helper to print BookingInfo if that is needed
+// Functional component as internal helper to print BookingTimeInfo if that is needed
const BookingInfoMaybe = props => {
const { bookingClassName, isOrder, intl, tx, unitType } = props;
const isEnquiry = txIsEnquired(tx);
@@ -192,13 +162,26 @@ const BookingInfoMaybe = props => {
return null;
}
- const { bookingStart, bookingEnd, price, isSingleDay } = bookingData(unitType, tx, isOrder, intl);
- const dateInfo = isSingleDay ? bookingStart.short : `${bookingStart.short} - ${bookingEnd.short}`;
+ // If you want to show the booking price after the booking time on InboxPage you can
+ // add the price after the BookingTimeInfo component. You can get the price by uncommenting
+ // sthe following lines:
+
+ // const bookingPrice = isOrder ? tx.attributes.payinTotal : tx.attributes.payoutTotal;
+ // const price = bookingPrice ? formatMoney(intl, bookingPrice) : null;
+
+ // Remember to also add formatMoney function from 'util/currency.js' and add this after BookingTimeInfo:
+ // {price}
return (
-
- {dateInfo}
-
{price}
+
+
);
};
diff --git a/src/containers/InboxPage/__snapshots__/InboxPage.test.js.snap b/src/containers/InboxPage/__snapshots__/InboxPage.test.js.snap
index 10fbe7cf07..5f70f293b2 100644
--- a/src/containers/InboxPage/__snapshots__/InboxPage.test.js.snap
+++ b/src/containers/InboxPage/__snapshots__/InboxPage.test.js.snap
@@ -429,10 +429,16 @@ exports[`InboxPage matches snapshot 2`] = `
- 2017-02-15 - 2017-02-16
-
- 10
-
+
+
+ Feb 15 -
+
+
+ Feb 16
+
+
@@ -883,10 +889,16 @@ exports[`InboxPage matches snapshot 4`] = `
- 2017-02-15 - 2017-02-16
-
- 9
-
+
+
+ Feb 15 -
+
+
+ Feb 16
+
+
diff --git a/src/examples.js b/src/examples.js
index 5af40b8fa8..1ec5b9ae8c 100644
--- a/src/examples.js
+++ b/src/examples.js
@@ -5,6 +5,7 @@ import * as Avatar from './components/Avatar/Avatar.example';
import * as BookingBreakdown from './components/BookingBreakdown/BookingBreakdown.example';
import * as BookingPanel from './components/BookingPanel/BookingPanel.example';
import * as BookingDateRangeFilter from './components/BookingDateRangeFilter/BookingDateRangeFilter.example';
+import * as BookingTimeInfo from './components/BookingTimeInfo/BookingTimeInfo.example';
import * as Button from './components/Button/Button.example';
import * as ExpandingTextarea from './components/ExpandingTextarea/ExpandingTextarea.example';
import * as FieldBirthdayInput from './components/FieldBirthdayInput/FieldBirthdayInput.example';
@@ -101,6 +102,7 @@ export {
BookingBreakdown,
BookingDateRangeFilter,
BookingDatesForm,
+ BookingTimeInfo,
BookingPanel,
Button,
Colors,
diff --git a/src/util/dates.js b/src/util/dates.js
index 360721471b..17bc71c760 100644
--- a/src/util/dates.js
+++ b/src/util/dates.js
@@ -257,3 +257,20 @@ export const getExclusiveEndDate = dateString => {
.startOf('day')
.toDate();
};
+
+export const formatDateToText = (intl, date) => {
+ return {
+ date: intl.formatDate(date, {
+ month: 'short',
+ day: 'numeric',
+ }),
+ time: intl.formatDate(date, {
+ hour: 'numeric',
+ minute: 'numeric',
+ }),
+ dateAndTime: intl.formatTime(date, {
+ month: 'short',
+ day: 'numeric',
+ }),
+ };
+};
diff --git a/src/util/types.js b/src/util/types.js
index 10ee1b8363..c8e4133d61 100644
--- a/src/util/types.js
+++ b/src/util/types.js
@@ -497,3 +497,7 @@ propTypes.error = shape({
});
export { propTypes };
+
+// Options for showing just date or date and time on BookingTimeInfo and BookingBreakdown
+export const DATE_TYPE_DATE = 'date';
+export const DATE_TYPE_DATETIME = 'datetime';