mirror of
https://github.com/kingomarnajjar/flex-template-web.git
synced 2026-07-29 05:00:43 +10:00
Merge pull request #1194 from sharetribe/show-time-on-inboxpage
Show booking date and time correctly on InboxPage
This commit is contained in:
commit
a707c4b4dd
11 changed files with 330 additions and 61 deletions
|
|
@ -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)
|
||||
|
|
|
|||
14
src/components/BookingTimeInfo/BookingTimeInfo.css
Normal file
14
src/components/BookingTimeInfo/BookingTimeInfo.css
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
@import '../../marketplace.css';
|
||||
|
||||
.root {
|
||||
}
|
||||
|
||||
.bookingInfo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.dateSection {
|
||||
margin-right: 5px;
|
||||
}
|
||||
141
src/components/BookingTimeInfo/BookingTimeInfo.example.js
Normal file
141
src/components/BookingTimeInfo/BookingTimeInfo.example.js
Normal file
|
|
@ -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',
|
||||
};
|
||||
98
src/components/BookingTimeInfo/BookingTimeInfo.js
Normal file
98
src/components/BookingTimeInfo/BookingTimeInfo.js
Normal file
|
|
@ -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 (
|
||||
<div className={classNames(css.bookingInfo, bookingClassName)}>
|
||||
<span className={css.dateSection}>{`${bookingStart.date}`}</span>
|
||||
</div>
|
||||
);
|
||||
} else if (dateType === DATE_TYPE_DATE) {
|
||||
return (
|
||||
<div className={classNames(css.bookingInfo, bookingClassName)}>
|
||||
<span className={css.dateSection}>{`${bookingStart.date} -`}</span>
|
||||
<span className={css.dateSection}>{`${bookingEnd.date}`}</span>
|
||||
</div>
|
||||
);
|
||||
} else if (isSingleDay && dateType === DATE_TYPE_DATETIME) {
|
||||
return (
|
||||
<div className={classNames(css.bookingInfo, bookingClassName)}>
|
||||
<span className={css.dateSection}>
|
||||
{`${bookingStart.date}, ${bookingStart.time} - ${bookingEnd.time}`}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className={classNames(css.bookingInfo, bookingClassName)}>
|
||||
<span className={css.dateSection}>{`${bookingStart.dateAndTime} - `}</span>
|
||||
<span className={css.dateSection}>{`${bookingEnd.dateAndTime}`}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
// <div className={css.itemPrice}>{price}</div>
|
||||
|
||||
return (
|
||||
<div className={classNames(css.bookingInfo, bookingClassName)}>
|
||||
{dateInfo}
|
||||
<span className={css.itemPrice}>{price}</span>
|
||||
<div className={classNames(css.bookingInfoWrapper, bookingClassName)}>
|
||||
<BookingTimeInfo
|
||||
bookingClassName={bookingClassName}
|
||||
isOrder={isOrder}
|
||||
intl={intl}
|
||||
tx={tx}
|
||||
unitType={unitType}
|
||||
dateType={DATE_TYPE_DATE}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -429,10 +429,16 @@ exports[`InboxPage matches snapshot 2`] = `
|
|||
<div
|
||||
className=""
|
||||
>
|
||||
2017-02-15 - 2017-02-16
|
||||
<span>
|
||||
10
|
||||
</span>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
<span>
|
||||
Feb 15 -
|
||||
</span>
|
||||
<span>
|
||||
Feb 16
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
|
|
@ -883,10 +889,16 @@ exports[`InboxPage matches snapshot 4`] = `
|
|||
<div
|
||||
className=""
|
||||
>
|
||||
2017-02-15 - 2017-02-16
|
||||
<span>
|
||||
9
|
||||
</span>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
<span>
|
||||
Feb 15 -
|
||||
</span>
|
||||
<span>
|
||||
Feb 16
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue