diff --git a/app/component/FareDisclaimer.js b/app/component/FareDisclaimer.js
new file mode 100644
index 0000000000..5b84a4bc7e
--- /dev/null
+++ b/app/component/FareDisclaimer.js
@@ -0,0 +1,43 @@
+import React from 'react';
+import { FormattedMessage } from 'react-intl';
+import PropTypes from 'prop-types';
+import Icon from './Icon';
+
+const FareDisclaimer = ({ textId, values, href = null, linkText = null }) => {
+ return (
+
+
+
+
+
+
+
+ {href && (
+
+
+
+ )}
+
+
+ );
+};
+
+FareDisclaimer.propTypes = {
+ textId: PropTypes.string.isRequired,
+ values: PropTypes.oneOfType([
+ PropTypes.shape({
+ agencyName: PropTypes.string,
+ }),
+ PropTypes.object,
+ ]),
+ href: PropTypes.string,
+ linkText: PropTypes.string,
+};
+
+FareDisclaimer.defaultProps = {
+ values: {},
+ href: null,
+ linkText: null,
+};
+
+export default FareDisclaimer;
diff --git a/app/component/ItineraryTab.js b/app/component/ItineraryTab.js
index 1e87bfb5bd..26a1ab4b15 100644
--- a/app/component/ItineraryTab.js
+++ b/app/component/ItineraryTab.js
@@ -1,4 +1,3 @@
-import get from 'lodash/get';
import PropTypes from 'prop-types';
import React from 'react';
import { createFragmentContainer, graphql } from 'react-relay';
@@ -6,8 +5,7 @@ import cx from 'classnames';
import { matchShape, routerShape } from 'found';
import { FormattedMessage, intlShape } from 'react-intl';
import connectToStores from 'fluxible-addons-react/connectToStores';
-
-import Icon from './Icon';
+import get from 'lodash/get';
import TicketInformation from './TicketInformation';
import RouteInformation from './RouteInformation';
import ItinerarySummary from './ItinerarySummary';
@@ -15,17 +13,17 @@ import ItineraryLegs from './ItineraryLegs';
import BackButton from './BackButton';
import MobileTicketPurchaseInformation from './MobileTicketPurchaseInformation';
import {
- getRoutes,
- getZones,
compressLegs,
+ getRoutes,
getTotalBikingDistance,
getTotalBikingDuration,
+ getTotalDrivingDistance,
+ getTotalDrivingDuration,
getTotalWalkingDistance,
getTotalWalkingDuration,
- legContainsRentalBike,
- getTotalDrivingDuration,
- getTotalDrivingDistance,
+ getZones,
isCallAgencyPickupType,
+ legContainsRentalBike,
} from '../util/legUtils';
import { BreakpointConsumer } from '../util/withBreakpoint';
@@ -36,14 +34,15 @@ import {
} from '../util/fareUtils';
import { addAnalyticsEvent } from '../util/analyticsUtils';
import {
+ getCurrentMillis,
+ getFormattedTimeDate,
isToday,
isTomorrow,
- getFormattedTimeDate,
- getCurrentMillis,
} from '../util/timeUtils';
import CityBikeDurationInfo from './CityBikeDurationInfo';
import { getCityBikeNetworkId } from '../util/citybikes';
import { FareShape } from '../util/shapes';
+import FareDisclaimer from './FareDisclaimer';
const AlertShape = PropTypes.shape({ alertSeverityLevel: PropTypes.string });
@@ -87,7 +86,7 @@ class ItineraryTab extends React.Component {
static defaultProps = {
hideTitle: false,
- currentLanguage: "fi"
+ currentLanguage: 'fi',
};
static contextTypes = {
@@ -159,7 +158,7 @@ class ItineraryTab extends React.Component {
walkingDistance > 0 &&
(bikingDistance > 0 || drivingDistance > 0) &&
futureText !== '';
- const extraProps = {
+ return {
walking: {
duration: walkingDuration,
distance: walkingDistance,
@@ -175,7 +174,6 @@ class ItineraryTab extends React.Component {
futureText,
isMultiRow,
};
- return extraProps;
};
render() {
@@ -217,8 +215,60 @@ class ItineraryTab extends React.Component {
const suggestionIndex = this.context.match.params.secondHash
? Number(this.context.match.params.secondHash) + 1
: Number(this.context.match.params.hash) + 1;
- const itineraryContainsCallLegs = itinerary.legs.some(leg => isCallAgencyPickupType(leg));
-
+
+ const disclaimers = [];
+
+ if (shouldShowFareInfo(config) && fares.some(fare => fare.isUnknown)) {
+ const found = {};
+ itinerary.legs.forEach(leg => {
+ if (
+ config.modeDisclaimers?.[leg.mode] &&
+ !found[leg.mode]
+ ) {
+ found[leg.mode] = true;
+ const disclaimer = config.modeDisclaimers[leg.mode][currentLanguage];
+ disclaimers.push(
+ ,
+ );
+ }
+ });
+
+ if (config.callAgencyInfo && itinerary.legs.some(leg => isCallAgencyPickupType(leg))) {
+ disclaimers.push(
+ ,
+ );
+ }
+
+ if (!disclaimers.length) {
+ disclaimers.push(
+ ,
+ );
+ }
+ }
+
return (
@@ -241,7 +291,11 @@ class ItineraryTab extends React.Component {
futureText={extraProps.futureText}
isMultiRow={extraProps.isMultiRow}
isMobile={this.props.isMobile}
- hideBottomDivider={shouldShowFarePurchaseInfo(config, breakpoint, fares)}
+ hideBottomDivider={shouldShowFarePurchaseInfo(
+ config,
+ breakpoint,
+ fares,
+ )}
/>
) : (
<>
@@ -282,18 +336,19 @@ class ItineraryTab extends React.Component {
config={config}
/>
),
- shouldShowFareInfo(config) && (
- shouldShowFarePurchaseInfo(config,breakpoint,fares) ? (
+ shouldShowFareInfo(config) &&
+ (shouldShowFarePurchaseInfo(config, breakpoint, fares) ? (
) :
- (
+ ) : (
+ )
- ),
+ />
+ )),
- {shouldShowFareInfo(config) &&
- fares.some(fare => fare.isUnknown) && (
-
-
-
-
- {config.callAgencyInfo && itineraryContainsCallLegs ?
- (
- ) : (
-
-
-
- )}
-
- )}
+ <>{disclaimers}>
,
];
- const LegRouteName = leg.from.name.concat(' - ').concat(leg.to.name);
const modeClassName = mode.toLowerCase();
+ const LegRouteName = leg.from.name.concat(' - ').concat(leg.to.name);
const textVersionBeforeLink = (
)}
- {leg.fare && leg.fare.isUnknown && shouldShowFareInfo(config) && (
-
-
-
- {`${intl.formatMessage({ id: 'pay-attention' })} `}
-
- {intl.formatMessage({ id: 'separate-ticket-required' })}
+ {leg.fare?.isUnknown &&
+ shouldShowFareInfo(config) &&
+ (config.modeDisclaimers && config.modeDisclaimers[mode] ? (
+
-
-
{LegRouteName}
- {leg.fare.agency &&
- !config.hideExternalOperator(leg.fare.agency) && (
-
- {leg.fare.agency.name}
- {leg.fare.agency.fareUrl && (
-
- {intl.formatMessage({ id: 'extra-info' })}
-
- )}
-
- )}
+ ) : (
+
+
+
+ {`${intl.formatMessage({ id: 'pay-attention' })} `}
+
+ {intl.formatMessage({ id: 'separate-ticket-required' })}
+
+
+
{LegRouteName}
+ {leg.fare.agency &&
+ !config.hideExternalOperator(leg.fare.agency) && (
+
+ {leg.fare.agency.name}
+ {leg.fare.agency.fareUrl && (
+
+ {intl.formatMessage({ id: 'extra-info' })}
+
+ )}
+
+ )}
+
-
- )}
+ ))}
-
{alertSeverityDescription}
+
{alertSeverityDescription};
);
};
diff --git a/app/configurations/config.tampere.js b/app/configurations/config.tampere.js
index 92c6f74da2..bc8f37edf3 100644
--- a/app/configurations/config.tampere.js
+++ b/app/configurations/config.tampere.js
@@ -95,6 +95,29 @@ export default configMerger(walttiConfig, {
},
},
+ modeDisclaimers: {
+ RAIL: {
+ fi: {
+ disclaimer:
+ 'Nyssen liput käyvät junaliikenteessä rajoitetusti vain Nysse-alueella. Lue lisää ',
+ link: 'https://www.nysse.fi/junat',
+ text: 'nysse.fi/junat',
+ },
+ sv: {
+ disclaimer:
+ 'Nysse-biljetter är giltiga på tåg i Nysse-området, med vissa begränsningar. Läs mer på ',
+ link: 'https://www.nysse.fi/en/ways-to-get-around/train',
+ text: 'Trains in the Nysse area - Nysse, Tampere regional transport',
+ },
+ en: {
+ disclaimer:
+ 'Nysse tickets are valid on trains in the Nysse area with some limitations. Read more on ',
+ link: 'https://www.nysse.fi/en/ways-to-get-around/train',
+ text: 'Trains in the Nysse area - Nysse, Tampere regional transport',
+ },
+ },
+ },
+
// mapping fareId from OTP fare identifiers to human readable form
fareMapping: function mapFareId(fareId) {
return fareId && fareId.substring
diff --git a/app/util/legUtils.js b/app/util/legUtils.js
index 4d77fb4e50..b89b14edd5 100644
--- a/app/util/legUtils.js
+++ b/app/util/legUtils.js
@@ -17,23 +17,6 @@ function filterLegStops(leg, filter) {
return false;
}
-/**
- * Check if legs start stop pickuptype or end stop pickupType is CALL_AGENCY
- *
- * leg must have:
- * from.stop.gtfsId
- * to.stop.gtfsId
- * trip.stoptimes (with props:)
- * stop.gtfsId
- * pickupType
- */
-export function isCallAgencyPickupType(leg) {
- return (
- filterLegStops(leg, stoptime => stoptime.pickupType === 'CALL_AGENCY')
- .length > 0
- );
-}
-
export function isCallAgencyDeparture(departure) {
return departure.pickupType === 'CALL_AGENCY';
}
@@ -70,6 +53,7 @@ export const LegMode = {
CityBike: 'CITYBIKE',
Walk: 'WALK',
Car: 'CAR',
+ Rail: 'RAIL',
};
/**
@@ -94,11 +78,30 @@ export const getLegMode = legOrMode => {
return LegMode.Walk;
case LegMode.Car:
return LegMode.Car;
+ case LegMode.Rail:
+ return LegMode.Rail;
default:
return undefined;
}
};
+/**
+ * Check if legs start stop pickuptype or end stop pickupType is CALL_AGENCY
+ *
+ * leg must have:
+ * from.stop.gtfsId
+ * to.stop.gtfsId
+ * trip.stoptimes (with props:)
+ * stop.gtfsId
+ * pickupType
+ */
+export function isCallAgencyPickupType(leg) {
+ return (
+ filterLegStops(leg, stoptime => stoptime.pickupType === 'CALL_AGENCY')
+ .length > 0
+ );
+}
+
/**
* Checks if both of the legs exist and are taken with mode 'BICYCLE'.
*