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

Extract timerange from bookingtimeinfo, update FieldRadioButton, IconClose and en.json #31

Merged
merged 7 commits into from
Nov 7, 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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ https://github.com/sharetribe/flex-template-web/

## Upcoming version 2019-XX-XX

- [change] Extract TimeRange component away from BookingTimeInfo. Updates also FieldRadioButton,
IconClose, and removes unused translations (EditListingAvailabilityForm was removed).
[#31](https://github.com/sharetribe/ftw-time/pull/31)

## [v4.1.0] 2019-11-04

### One change in this template
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ you can
[change the 'upstream' remote repository](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/configuring-a-remote-for-a-fork).

> Note: we have created [another template](https://github.com/sharetribe/ftw-time) for time-based
> processes. It is still in **beta** mode, but if you are taking time-based booking process into use,
> you should consider using it instead. You can read more from the related
> processes. It is still in **beta** mode, but if you are taking time-based booking process into
> use, you should consider using it instead. You can read more from the related
> [Flex Docs article](https://www.sharetribe.com/docs/background/time-based-template)

## Quick start
Expand Down
77 changes: 22 additions & 55 deletions src/components/BookingTimeInfo/BookingTimeInfo.js
Original file line number Diff line number Diff line change
@@ -1,81 +1,48 @@
import React from 'react';
import { bool, string } from 'prop-types';
import classNames from 'classnames';
import { string } from 'prop-types';
import { txIsEnquired } from '../../util/transaction';
import { daysBetween, formatDateToText } from '../../util/dates';
import { injectIntl, intlShape } from '../../util/reactIntl';
import { DATE_TYPE_DATE, DATE_TYPE_DATETIME, propTypes } from '../../util/types';
import { propTypes } from '../../util/types';

import css from './BookingTimeInfo.css';
import { TimeRange } from '../../components';

const bookingData = (unitType, tx, isOrder, intl, timeZone) => {
const bookingData = tx => {
// 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 = displayStart || start;
const endDate = displayEnd || end;
const isSingleDay = daysBetween(startDate, endDate) <= 1;
const bookingStart = formatDateToText(intl, startDate, timeZone);
const bookingEnd = formatDateToText(intl, endDate, timeZone);
return { bookingStart, bookingEnd, isSingleDay };
const bookingStart = displayStart || start;
const bookingEnd = displayEnd || end;
return { bookingStart, bookingEnd };
};

const BookingTimeInfoComponent = props => {
const { bookingClassName, isOrder, intl, tx, unitType, dateType, timeZone } = props;
const BookingTimeInfo = props => {
const { bookingClassName, tx, dateType, timeZone } = props;
const isEnquiry = txIsEnquired(tx);

if (isEnquiry) {
return null;
}

const bookingTimes = bookingData(unitType, tx, isOrder, intl, timeZone);

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>
);
}
const { bookingStart, bookingEnd } = bookingData(tx);

return (
<TimeRange
className={bookingClassName}
startDate={bookingStart}
endDate={bookingEnd}
dateType={dateType}
timeZone={timeZone}
/>
);
};

BookingTimeInfoComponent.defaultProps = { dateType: null, timeZone: null };
BookingTimeInfo.defaultProps = { dateType: null, timeZone: null };

BookingTimeInfoComponent.propTypes = {
intl: intlShape.isRequired,
isOrder: bool.isRequired,
BookingTimeInfo.propTypes = {
tx: propTypes.transaction.isRequired,
unitType: propTypes.bookingUnitType.isRequired,
dateType: propTypes.dateType,
timeZone: string,
};

const BookingTimeInfo = injectIntl(BookingTimeInfoComponent);

export default BookingTimeInfo;
12 changes: 9 additions & 3 deletions src/components/FieldRadioButton/FieldRadioButton.css
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,14 @@
&:hover + label .required,
&:focus + label .notChecked,
&:focus + label .required,
&:checked + label .notChecked,
&:checked + label .required {
stroke: var(--matterColorDark);
}

&:checked + label .notChecked {
stroke: none;
}

/* Hightlight the text on checked, hover and focus */
&:focus + label .text,
&:hover + label .text,
Expand All @@ -51,12 +54,15 @@
cursor: pointer;
}

.checked {
display: none;
.checkedStyle {
stroke: var(--marketplaceColor);
fill: var(--marketplaceColor);
}

.checked {
display: none;
}

.notChecked {
stroke: var(--matterColorAnti);
&:hover {
Expand Down
27 changes: 24 additions & 3 deletions src/components/FieldRadioButton/FieldRadioButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Field } from 'react-final-form';
import css from './FieldRadioButton.css';

const IconRadioButton = props => {
const { checkedClassName } = props;
return (
<div>
<svg className={props.className} width="14" height="14" xmlns="http://www.w3.org/2000/svg">
Expand All @@ -20,7 +21,12 @@ const IconRadioButton = props => {
fillRule="evenodd"
/>

<g className={css.checked} transform="translate(2 -12)" fill="none" fillRule="evenodd">
<g
className={classNames(css.checked, checkedClassName || css.checkedStyle)}
transform="translate(2 -12)"
fill="none"
fillRule="evenodd"
>
<circle strokeWidth="2" cx="5" cy="19" r="6" />
<circle fill="#FFF" fillRule="nonzero" cx="5" cy="19" r="3" />
</g>
Expand All @@ -34,7 +40,16 @@ IconRadioButton.defaultProps = { className: null };
IconRadioButton.propTypes = { className: string };

const FieldRadioButtonComponent = props => {
const { rootClassName, className, svgClassName, id, label, showAsRequired, ...rest } = props;
const {
rootClassName,
className,
svgClassName,
checkedClassName,
id,
label,
showAsRequired,
...rest
} = props;

const classes = classNames(rootClassName || css.root, className);
const radioButtonProps = {
Expand All @@ -50,7 +65,11 @@ const FieldRadioButtonComponent = props => {
<Field {...radioButtonProps} />
<label htmlFor={id} className={css.label}>
<span className={css.radioButtonWrapper}>
<IconRadioButton className={svgClassName} showAsRequired={showAsRequired} />
<IconRadioButton
className={svgClassName}
checkedClassName={checkedClassName}
showAsRequired={showAsRequired}
/>
</span>
<span className={css.text}>{label}</span>
</label>
Expand All @@ -62,13 +81,15 @@ FieldRadioButtonComponent.defaultProps = {
className: null,
rootClassName: null,
svgClassName: null,
checkedClassName: null,
label: null,
};

FieldRadioButtonComponent.propTypes = {
className: string,
rootClassName: string,
svgClassName: string,
checkedClassName: string,

// Id is needed to connect the label with input.
id: string.isRequired,
Expand Down
3 changes: 3 additions & 0 deletions src/components/IconClose/IconClose.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import classNames from 'classnames';

import css from './IconClose.css';
const SIZE_SMALL = 'small';
const SIZE_NORMAL = 'normal';

const IconClose = props => {
const { className, rootClassName, size } = props;
Expand Down Expand Up @@ -41,11 +42,13 @@ const { string } = PropTypes;
IconClose.defaultProps = {
className: null,
rootClassName: null,
size: SIZE_NORMAL,
};

IconClose.propTypes = {
className: string,
rootClassName: string,
size: string,
};

export default IconClose;
74 changes: 74 additions & 0 deletions src/components/TimeRange/TimeRange.example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import TimeRange from './TimeRange';
import { fakeIntl } from '../../util/test-data';

export const DateAndTimeSingleDay = {
component: TimeRange,
props: {
intl: fakeIntl,
startDate: new Date(Date.UTC(2019, 8, 30, 3, 0)),
endDate: new Date(Date.UTC(2019, 8, 30, 4, 0)),
dateType: 'datetime',
timeZone: 'Etc/UTC',
},
group: 'inbox',
};

export const DateAndTimeMultipleDays = {
component: TimeRange,
props: {
intl: fakeIntl,
startDate: new Date(Date.UTC(2019, 8, 28, 3, 0)),
endDate: new Date(Date.UTC(2019, 8, 30, 5, 0)),
dateType: 'datetime',
timeZone: 'Etc/UTC',
},
group: 'inbox',
};

export const OnlyDateSingleDay = {
component: TimeRange,
props: {
intl: fakeIntl,
startDate: new Date(Date.UTC(2019, 8, 29, 3, 0)),
endDate: new Date(Date.UTC(2019, 8, 30, 4, 0)),
dateType: 'date',
timeZone: 'Etc/UTC',
},
group: 'inbox',
};

export const OnlyDateMultipleDays = {
component: TimeRange,
props: {
intl: fakeIntl,
startDate: new Date(Date.UTC(2019, 8, 28, 3, 0)),
endDate: new Date(Date.UTC(2019, 8, 30, 5, 0)),
dateType: 'date',
timeZone: 'Etc/UTC',
},
group: 'inbox',
};

export const OnlyDateSingleNight = {
component: TimeRange,
props: {
intl: fakeIntl,
startDate: new Date(Date.UTC(2019, 8, 29, 3, 0)),
endDate: new Date(Date.UTC(2019, 8, 30, 4, 0)),
dateType: 'date',
timeZone: 'Etc/UTC',
},
group: 'inbox',
};

export const OnlyDateMultipleNights = {
component: TimeRange,
props: {
intl: fakeIntl,
startDate: new Date(Date.UTC(2019, 8, 28, 3, 0)),
endDate: new Date(Date.UTC(2019, 8, 30, 5, 0)),
dateType: 'date',
timeZone: 'Etc/UTC',
},
group: 'inbox',
};
66 changes: 66 additions & 0 deletions src/components/TimeRange/TimeRange.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React from 'react';
import { instanceOf, string } from 'prop-types';
import classNames from 'classnames';
import { daysBetween, formatDateToText } from '../../util/dates';
import { injectIntl, intlShape } from '../../util/reactIntl';
import { DATE_TYPE_DATE, DATE_TYPE_DATETIME, propTypes } from '../../util/types';

import css from './TimeRange.css';

export const TimeRangeComponent = props => {
const { rootClassName, className, startDate, endDate, dateType, timeZone, intl } = props;
const start = formatDateToText(intl, startDate, timeZone);
const end = formatDateToText(intl, endDate, timeZone);
const isSingleDay = daysBetween(startDate, endDate) <= 1;

const classes = classNames(rootClassName || css.root, className);

if (isSingleDay && dateType === DATE_TYPE_DATE) {
return (
<div className={classes}>
<span className={css.dateSection}>{`${start.date}`}</span>
</div>
);
} else if (dateType === DATE_TYPE_DATE) {
return (
<div className={classes}>
<span className={css.dateSection}>{`${start.date} -`}</span>
<span className={css.dateSection}>{`${end.date}`}</span>
</div>
);
} else if (isSingleDay && dateType === DATE_TYPE_DATETIME) {
return (
<div className={classes}>
<span className={css.dateSection}>{`${start.date}, ${start.time} - ${end.time}`}</span>
</div>
);
} else {
return (
<div className={classes}>
<span className={css.dateSection}>{`${start.dateAndTime} - `}</span>
<span className={css.dateSection}>{`${end.dateAndTime}`}</span>
</div>
);
}
};

TimeRangeComponent.defaultProps = {
rootClassName: null,
className: null,
dateType: null,
timeZone: null,
};

TimeRangeComponent.propTypes = {
rootClassName: string,
className: string,
startDate: instanceOf(Date).isRequired,
endDate: instanceOf(Date).isRequired,
dateType: propTypes.dateType,
timeZone: string,

// from injectIntl
intl: intlShape.isRequired,
};

export default injectIntl(TimeRangeComponent);
Loading