@kiwicom/smart-faq
Version:
Smart FAQ
296 lines (270 loc) • 8.51 kB
JavaScript
// @flow
import idx from 'idx';
import * as React from 'react';
import { withRouter } from 'react-router-dom';
import { graphql, createFragmentContainer } from 'react-relay';
import differenceInHours from 'date-fns/difference_in_hours';
import { Stack, Button, ButtonLink, TextLink } from '@kiwicom/orbit-components';
import {
BaggageChecked,
Ticket,
Insurance,
} from '@kiwicom/orbit-components/lib/icons';
import Translate from '@kiwicom/nitro/lib/components/Translate';
import {
isUrgentBooking,
updateFAQSection,
getDepartureTimeByType,
} from '../common/booking/utils';
import OneWayTrip from './bookingTypes/OneWayTrip';
import ReturnTrip from './bookingTypes/ReturnTrip';
import MulticityOverlayTrip from './bookingTypes/MulticityOverlayTrip';
import Contact from './bookingItem/Contact';
import Notification from './bookingItem/Notification';
import Header from './bookingItem/Header';
import { simpleTracker } from '../../shared/helpers/analytics/trackers';
import ScrollableContent from '../common/ScrollableContent';
import bookingTypes from '../common/booking/bookingTypes';
import { URGENCY_THRESHOLD } from '../helpers/dateUtils';
import { replaceWithCurrentDomain, addDeepLink } from '../helpers/UrlHelpers';
import type { NearestBooking_booking } from './__generated__/NearestBookingQuery.graphql';
import FAQExtraInfoButton from '../../shared/StaticFAQ/FAQExtraInfo/FAQExtraInfoButton';
import { BookingState } from '../context/BookingState';
import features from '../../feature-toggles.json';
import { track } from '../../shared/cuckoo/tracker';
type ContextProps = {
onSetFAQSection: (isUrgent: boolean, isPastBooking: boolean) => void,
};
type ComponentProps = {
+booking: NearestBooking_booking,
onSetFAQSection: (isUrgent: boolean, isPastBooking: boolean) => void,
};
type Props = ComponentProps;
const goToMMB = () => {
simpleTracker('smartFAQBookingOverview', {
action: 'goToMMB',
});
track('BookingOverview', 'goToMMB');
};
const clickEticket = () => {
simpleTracker('smartFAQBookingOverview', {
action: 'clickOnEticket',
});
track('BookingOverview', 'clickOnEticket');
};
const addInsurance = () => {
simpleTracker('smartFAQBookingOverview', {
action: 'addInsurance',
});
track('BookingOverview', 'addInsurance');
};
class BookingDetail extends React.Component<Props> {
componentDidMount() {
updateFAQSection(this.props);
}
componentDidUpdate() {
updateFAQSection(this.props);
}
renderByType = (booking: NearestBooking_booking) => {
if (booking.type === bookingTypes.ONE_WAY) {
return <OneWayTrip booking={booking} />;
}
if (booking.type === bookingTypes.RETURN) {
return <ReturnTrip booking={booking} />;
}
if (booking.type === bookingTypes.MULTICITY) {
return <MulticityOverlayTrip booking={booking} />;
}
return null;
};
getArrivalByType = (booking: NearestBooking_booking) => {
let date = null;
if (booking.type === bookingTypes.ONE_WAY) {
date = idx(booking, _ => _.trip.arrival.time);
}
if (booking.type === bookingTypes.RETURN) {
date = idx(booking, _ => _.inbound.arrival.time);
}
if (booking.type === bookingTypes.MULTICITY) {
date = idx(booking, _ => _.end.time);
}
return date ? new Date(date) : null;
};
decideIfIsFutureAndUrgent = (time: ?Date) => {
const timeDelta = time ? differenceInHours(time, new Date()) : null;
const isUrgent =
timeDelta !== null && URGENCY_THRESHOLD > timeDelta && timeDelta >= 0;
return {
timeDelta,
isFuture: timeDelta !== null && timeDelta > 0,
isUrgent,
};
};
render() {
const { booking } = this.props;
const eTicketLink = idx(booking, _ => _.assets.ticketUrl);
const departureTime = getDepartureTimeByType(booking);
const arrivalTime = this.getArrivalByType(booking);
const departureInfo = this.decideIfIsFutureAndUrgent(departureTime);
const arrivalInfo = this.decideIfIsFutureAndUrgent(arrivalTime);
const { timeDelta, isFuture } = departureInfo;
const isUrgent = isUrgentBooking(booking.isPastBooking, departureTime);
const isInsuranceAvailable = Boolean(
(idx(booking, _ => _.availableServices.insurance.passengers) || [])
.length,
);
return (
<ScrollableContent
dataCy="nearestBooking"
styles="width: 100%; padding:40px; background-color: #ffffff; box-shadow: inset -1px 0 0 0 #e8edf1;"
>
<div data-test={arrivalInfo.isFuture ? 'upcoming' : 'past'}>
<Header booking={booking} isFuture={arrivalInfo.isFuture} />
</div>
{features.notification &&
(isFuture &&
booking.status === 'CONFIRMED' &&
timeDelta && (
<Notification hoursLeft={timeDelta} isUrgent={isUrgent} />
))}
<Stack
direction="row"
wrap
spaceAfter="normal"
dataTest="booking-sfaq-buttons"
>
{features.baggage_info && (
<FAQExtraInfoButton category="baggage" icon={<BaggageChecked />}>
<Translate
t={__('smartfaq.single_booking_page.booking_detail.baggage')}
/>
</FAQExtraInfoButton>
)}
<FAQExtraInfoButton
category="boarding-passes"
icon={<Ticket />}
dataTest="btn-boarding-passes"
>
<Translate
t={__(
'smartfaq.single_booking_page.booking_detail.boarding_passes',
)}
/>
</FAQExtraInfoButton>
{isInsuranceAvailable && (
<ButtonLink
onClick={addInsurance}
external
iconLeft={<Insurance />}
href={replaceWithCurrentDomain(
addDeepLink(booking.directAccessURL, 'insurance'),
)}
>
<Translate
t={__('smartfaq.single_booking_page.booking_detail.insurance')}
/>
</ButtonLink>
)}
</Stack>
<div data-test="booking-sfaq-itinerary">
{this.renderByType(booking)}
</div>
<Stack direction="column">
<Button
type="secondary"
external
onClick={goToMMB}
href={replaceWithCurrentDomain(booking.directAccessURL)}
dataTest="btn-manage-booking"
>
<Translate
t={__(
'smartfaq.single_booking_page.booking_detail.manage_my_booking',
)}
/>
</Button>
{eTicketLink && (
<TextLink
href={eTicketLink}
type="secondary"
external
onClick={clickEticket}
size="normal"
>
<Translate
t={__(
'smartfaq.single_booking_page.booking_detail.download_e_ticket',
)}
/>
</TextLink>
)}
</Stack>
{isUrgent && <Contact booking={booking} dataTest="contact" />}
</ScrollableContent>
);
}
}
export const RawBookingDetail = BookingDetail;
const BookingDetailWithFAQHandler = (props: ComponentProps) => (
<BookingState.Consumer>
{({ onSetFAQSection }: ContextProps) => (
<BookingDetail {...props} onSetFAQSection={onSetFAQSection} />
)}
</BookingState.Consumer>
);
export default createFragmentContainer(
withRouter(BookingDetailWithFAQHandler),
graphql`
fragment BookingDetail_booking on BookingInterface {
type: __typename
status
assets {
ticketUrl
}
availableServices {
insurance {
passengers {
databaseId
}
}
}
directAccessURL
isPastBooking
...Header_booking
... on BookingOneWay {
...OneWayTrip_booking
trip {
departure {
time
}
arrival {
time
}
}
}
... on BookingReturn {
...ReturnTrip_booking
outbound {
departure {
time
}
}
inbound {
arrival {
time
}
}
}
... on BookingMulticity {
...MulticityOverlayTrip_booking
start {
time
}
end {
time
}
}
...Contact_booking
}
`,
);