diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml
index ff497a107..f40c59996 100644
--- a/.github/workflows/codespell.yml
+++ b/.github/workflows/codespell.yml
@@ -12,6 +12,7 @@ jobs:
- uses: codespell-project/actions-codespell@master
with:
check_filenames: true
- # The a11y test file has a false positive and the ignore list does not work
+ # skip git, yarn, and i18n non-english resources.
+ # Also, the a11y test file has a false positive and the ignore list does not work
# see https://github.com/opentripplanner/otp-react-redux/pull/436/checks?check_run_id=3369380014
- skip: ./.git,yarn.lock,./a11y/a11y.test.js
+ skip: ./.git,yarn.lock,./a11y/a11y.test.js,./i18n/fr*
diff --git a/.github/workflows/node-ci.yml b/.github/workflows/node-ci.yml
index 635a49f38..f63891002 100644
--- a/.github/workflows/node-ci.yml
+++ b/.github/workflows/node-ci.yml
@@ -12,10 +12,10 @@ jobs:
steps:
- uses: actions/checkout@v2
- - name: Use Node.js 12.x
+ - name: Use Node.js 14.x
uses: actions/setup-node@v1
with:
- node-version: 12.x
+ node-version: 14.x
- name: Install npm packages using cache
uses: bahmutov/npm-install@v1
- name: Copy example config
diff --git a/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap b/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap
index 81e21e621..e83ba8e43 100644
--- a/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap
+++ b/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap
@@ -235,12 +235,10 @@ exports[`components > viewers > stop viewer should render countdown times after
@@ -294,12 +292,10 @@ exports[`components > viewers > stop viewer should render countdown times after
@@ -494,7 +490,7 @@ exports[`components > viewers > stop viewer should render countdown times after
viewers > stop viewer should render countdown times after
"marginRight": 2,
}
}
- type="clock-o"
>
viewers > stop viewer should render countdown times after
"marginRight": 2,
}
}
- type="clock-o"
/>
@@ -944,12 +938,10 @@ exports[`components > viewers > stop viewer should render countdown times after
@@ -1030,12 +1022,10 @@ exports[`components > viewers > stop viewer should render countdown times after
className=""
fixedWidth={true}
name="refresh"
- type="refresh"
>
@@ -1189,12 +1179,10 @@ exports[`components > viewers > stop viewer should render countdown times for st
@@ -1248,12 +1236,10 @@ exports[`components > viewers > stop viewer should render countdown times for st
@@ -1448,7 +1434,7 @@ exports[`components > viewers > stop viewer should render countdown times for st
viewers > stop viewer should render countdown times for st
"marginRight": 2,
}
}
- type="clock-o"
>
viewers > stop viewer should render countdown times for st
"marginRight": 2,
}
}
- type="clock-o"
/>
@@ -1709,12 +1693,10 @@ exports[`components > viewers > stop viewer should render countdown times for st
@@ -1795,12 +1777,10 @@ exports[`components > viewers > stop viewer should render countdown times for st
className=""
fixedWidth={true}
name="refresh"
- type="refresh"
>
@@ -2053,12 +2033,10 @@ exports[`components > viewers > stop viewer should render times after midnight w
@@ -2112,12 +2090,10 @@ exports[`components > viewers > stop viewer should render times after midnight w
@@ -2312,7 +2288,7 @@ exports[`components > viewers > stop viewer should render times after midnight w
viewers > stop viewer should render times after midnight w
"marginRight": 2,
}
}
- type="clock-o"
>
viewers > stop viewer should render times after midnight w
"marginRight": 2,
}
}
- type="clock-o"
/>
@@ -2771,12 +2745,10 @@ exports[`components > viewers > stop viewer should render times after midnight w
@@ -2857,12 +2829,10 @@ exports[`components > viewers > stop viewer should render times after midnight w
className=""
fixedWidth={true}
name="refresh"
- type="refresh"
>
@@ -3373,12 +3343,10 @@ exports[`components > viewers > stop viewer should render with OTP transit index
@@ -3432,12 +3400,10 @@ exports[`components > viewers > stop viewer should render with OTP transit index
@@ -3632,7 +3598,7 @@ exports[`components > viewers > stop viewer should render with OTP transit index
viewers > stop viewer should render with OTP transit index
"marginRight": 2,
}
}
- type="clock-o"
>
viewers > stop viewer should render with OTP transit index
"marginRight": 2,
}
}
- type="clock-o"
/>
@@ -4347,12 +4311,10 @@ exports[`components > viewers > stop viewer should render with OTP transit index
@@ -4549,7 +4511,6 @@ exports[`components > viewers > stop viewer should render with OTP transit index
"marginRight": 2,
}
}
- type="clock-o"
>
viewers > stop viewer should render with OTP transit index
"marginRight": 2,
}
}
- type="clock-o"
/>
@@ -4606,12 +4566,10 @@ exports[`components > viewers > stop viewer should render with OTP transit index
@@ -4808,7 +4766,6 @@ exports[`components > viewers > stop viewer should render with OTP transit index
"marginRight": 2,
}
}
- type="clock-o"
>
viewers > stop viewer should render with OTP transit index
"marginRight": 2,
}
}
- type="clock-o"
/>
@@ -4865,12 +4821,10 @@ exports[`components > viewers > stop viewer should render with OTP transit index
@@ -5121,7 +5075,6 @@ exports[`components > viewers > stop viewer should render with OTP transit index
"marginRight": 2,
}
}
- type="clock-o"
>
viewers > stop viewer should render with OTP transit index
"marginRight": 2,
}
}
- type="clock-o"
/>
@@ -5178,12 +5130,10 @@ exports[`components > viewers > stop viewer should render with OTP transit index
@@ -5264,12 +5214,10 @@ exports[`components > viewers > stop viewer should render with OTP transit index
className=""
fixedWidth={true}
name="refresh"
- type="refresh"
>
@@ -5775,12 +5723,10 @@ exports[`components > viewers > stop viewer should render with TriMet transit in
@@ -5834,12 +5780,10 @@ exports[`components > viewers > stop viewer should render with TriMet transit in
@@ -6034,7 +5978,7 @@ exports[`components > viewers > stop viewer should render with TriMet transit in
+ {/* This item is duplicated by the view-switcher, but only shown on mobile
+ when the view switcher isn't shown (using css) */}
+
- }
- {mailablesEnabled &&
-
-
+
+ >
)
}
}
@@ -200,8 +171,7 @@ class DefaultItinerary extends NarrativeItinerary {
LegIcon,
setActiveLeg,
showRealtimeAnnotation,
- timeFormat,
- use24HourFormat
+ timeFormat
} = this.props
const timeOptions = {
format: timeFormat,
@@ -214,11 +184,6 @@ class DefaultItinerary extends NarrativeItinerary {
onMouseEnter={this._onMouseEnter}
onMouseLeave={this._onMouseLeave}
role='presentation'
- // FIXME: Move style to css
- style={{
- backgroundColor: expanded ? 'white' : undefined,
- borderLeft: active && !expanded ? '4px teal solid' : undefined
- }}
>
{(active && !expanded) &&
-
+
-
+
}
{(active && expanded) &&
@@ -281,8 +245,7 @@ const mapStateToProps = (state, ownProps) => {
// The configured (ambient) currency is needed for rendering the cost
// of itineraries whether they include a fare or not, in which case
// we show $0.00 or its equivalent in the configured currency and selected locale.
- currency: state.otp.config.localization?.currency || 'USD',
- use24HourFormat: state.user.loggedInUser?.use24HourFormat ?? false
+ currency: state.otp.config.localization?.currency || 'USD'
}
}
diff --git a/lib/components/narrative/default/itinerary.css b/lib/components/narrative/default/itinerary.css
index b4203ba0f..63bdb7bec 100644
--- a/lib/components/narrative/default/itinerary.css
+++ b/lib/components/narrative/default/itinerary.css
@@ -5,7 +5,7 @@
}
/* If child component is focused, highlight itinerary option */
-.otp .option.default-itin:focus-within {
+.otp .option.default-itin:focus-within:not(.expanded) {
background-color: var(--hover-color);
}
@@ -19,6 +19,11 @@
border-top: 1px solid grey;
}
+/* Show side border if active and not expanded */
+.otp .option.default-itin.active:not(.expanded) {
+ border-left: 4px teal solid;
+}
+
/* FIXME: don't highlight if not active */
.otp .option.default-itin:hover:not(.active) {
background-color: var(--hover-color);
diff --git a/lib/components/narrative/default/transit-leg.js b/lib/components/narrative/default/transit-leg.js
index 2e2f3023d..c70107c5e 100644
--- a/lib/components/narrative/default/transit-leg.js
+++ b/lib/components/narrative/default/transit-leg.js
@@ -2,7 +2,7 @@ import coreUtils from '@opentripplanner/core-utils'
import PropTypes from 'prop-types'
import React, { Component } from 'react'
-import Icon from '../icon'
+import Icon from '../../util/icon'
import ViewTripButton from '../../viewers/view-trip-button'
import ViewStopButton from '../../viewers/view-stop-button'
diff --git a/lib/components/narrative/icon.js b/lib/components/narrative/icon.js
deleted file mode 100644
index cca4799b3..000000000
--- a/lib/components/narrative/icon.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import React, { Component } from 'react'
-import FontAwesome from 'react-fontawesome'
-
-export default class Icon extends Component {
- static propTypes = {
- // type: PropTypes.string.required
- }
- render () {
- return (
- )
- }
-}
diff --git a/lib/components/narrative/itinerary-carousel.js b/lib/components/narrative/itinerary-carousel.js
index f6b125edb..02fcee418 100644
--- a/lib/components/narrative/itinerary-carousel.js
+++ b/lib/components/narrative/itinerary-carousel.js
@@ -8,8 +8,8 @@ import SwipeableViews from 'react-swipeable-views'
import { setActiveItinerary, setActiveLeg, setActiveStep } from '../../actions/narrative'
import { ComponentContext } from '../../util/contexts'
import { getActiveItineraries, getActiveSearch } from '../../util/state'
+import Icon from '../util/icon'
-import Icon from './icon'
import Loading from './loading'
class ItineraryCarousel extends Component {
diff --git a/lib/components/narrative/line-itin/itin-summary.js b/lib/components/narrative/line-itin/itin-summary.js
index b457e95ac..96f130dab 100644
--- a/lib/components/narrative/line-itin/itin-summary.js
+++ b/lib/components/narrative/line-itin/itin-summary.js
@@ -2,8 +2,12 @@ import coreUtils from '@opentripplanner/core-utils'
import PropTypes from 'prop-types'
import React, { Component } from 'react'
import styled from 'styled-components'
+import { connect } from 'react-redux'
+import { FormattedNumber, FormattedMessage } from 'react-intl'
import { ComponentContext } from '../../../util/contexts'
+import FormattedDuration from '../../util/formatted-duration'
+import FormattedTimeRange from '../../util/formatted-time-range'
// TODO: make this a prop
const defaultRouteColor = '#008'
@@ -71,7 +75,7 @@ const ShortName = styled.div`
width: 30px;
`
-export default class ItinerarySummary extends Component {
+export class ItinerarySummary extends Component {
static propTypes = {
itinerary: PropTypes.object
}
@@ -83,11 +87,10 @@ export default class ItinerarySummary extends Component {
}
render () {
- const { itinerary, timeOptions } = this.props
+ const { currency, itinerary } = this.props
const { LegIcon } = this.context
const {
- centsToString,
maxTNCFare,
minTNCFare,
transitFare
@@ -97,34 +100,59 @@ export default class ItinerarySummary extends Component {
const maxTotalFare = maxTNCFare * 100 + transitFare
const { caloriesBurned } = coreUtils.itinerary.calculatePhysicalActivity(itinerary)
-
return (
{/* Travel time in hrs/mins */}
- {coreUtils.time.formatDuration(itinerary.duration)}
+
+
+
{/* Duration as time range */}
- {coreUtils.time.formatTime(itinerary.startTime, timeOptions)} - {coreUtils.time.formatTime(itinerary.endTime, timeOptions)}
+
{/* Fare / Calories */}
{minTotalFare > 0 &&
- {centsToString(minTotalFare)}
- {minTotalFare !== maxTotalFare && - {centsToString(maxTotalFare)}}
+
+ ),
+ minTotalFare: (
+
+ ),
+ useMaxFare: minTotalFare !== maxTotalFare ? 'true' : 'false'
+ }}
+ />
• }
- {Math.round(caloriesBurned)} Cals
+
{/* Number of transfers, if applicable */}
- {itinerary.transfers > 0 && (
-
- {itinerary.transfers} transfer{itinerary.transfers > 1 ? 's' : ''}
-
- )}
+
+
+
@@ -179,3 +207,10 @@ function getRouteNameForBadge (leg) {
function getRouteColorForBadge (leg) {
return leg.routeColor ? '#' + leg.routeColor : defaultRouteColor
}
+
+const mapStateToProps = (state, ownProps) => {
+ return {
+ currency: state.otp.config.localization?.currency || 'USD'
+ }
+}
+export default connect(mapStateToProps)(ItinerarySummary)
diff --git a/lib/components/narrative/loading.js b/lib/components/narrative/loading.js
index adc668564..efc749562 100644
--- a/lib/components/narrative/loading.js
+++ b/lib/components/narrative/loading.js
@@ -1,6 +1,6 @@
import React, { Component } from 'react'
-import Icon from './icon'
+import Icon from '../util/icon'
export default class Loading extends Component {
render () {
diff --git a/lib/components/narrative/mode-icon.js b/lib/components/narrative/mode-icon.js
index eacb1436e..fd88b3054 100644
--- a/lib/components/narrative/mode-icon.js
+++ b/lib/components/narrative/mode-icon.js
@@ -1,7 +1,7 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
-import Icon from './icon'
+import Icon from '../util/icon'
export default class ModeIcon extends Component {
static propTypes = {
diff --git a/lib/components/narrative/narrative-itineraries-errors.js b/lib/components/narrative/narrative-itineraries-errors.js
index 37ca1a098..fa94c77af 100644
--- a/lib/components/narrative/narrative-itineraries-errors.js
+++ b/lib/components/narrative/narrative-itineraries-errors.js
@@ -1,7 +1,7 @@
import { getCompanyIcon } from '@opentripplanner/icons/lib/companies'
import styled from 'styled-components'
-import Icon from '../narrative/icon'
+import Icon from '../util/icon'
import { getErrorMessage } from '../../util/state'
const IssueContainer = styled.div`
diff --git a/lib/components/narrative/narrative-itineraries-header.js b/lib/components/narrative/narrative-itineraries-header.js
index 54300bc81..e462de55d 100644
--- a/lib/components/narrative/narrative-itineraries-header.js
+++ b/lib/components/narrative/narrative-itineraries-header.js
@@ -1,6 +1,7 @@
import styled from 'styled-components'
+import { FormattedMessage, useIntl } from 'react-intl'
-import Icon from '../narrative/icon'
+import Icon from '../util/icon'
import PlanFirstLastButtons from './plan-first-last-buttons'
import SaveTripButton from './save-trip-button'
@@ -26,22 +27,7 @@ export default function NarrativeItinerariesHeader ({
showingErrors,
sort
}) {
- let resultText, titleText
- if (pending) {
- resultText = 'Finding your options...'
- titleText = 'Finding your options...'
- } else {
- const itineraryPlural = itineraries.length === 1
- ? 'itinerary'
- : 'itineraries'
- const issuePlural = errors.length === 1
- ? 'issue'
- : 'issues'
- resultText = `${itineraries.length} ${itineraryPlural} found.`
- titleText = errors.length > 0
- ? `${itineraries.length} ${itineraryPlural} (and ${errors.length} ${issuePlural}) found`
- : resultText
- }
+ const intl = useIntl()
return (
- View all options
+
{itineraryIsExpanded && (
// marginLeft: auto is a way of making something "float right"
@@ -72,15 +58,33 @@ export default function NarrativeItinerariesHeader ({
: <>
diff --git a/lib/components/narrative/narrative.css b/lib/components/narrative/narrative.css
index fd0d6534d..db5f6e664 100644
--- a/lib/components/narrative/narrative.css
+++ b/lib/components/narrative/narrative.css
@@ -198,6 +198,10 @@
color: #685c5c;
}
+.otp .tabbed-itineraries .tab-button .details > span {
+ display: block;
+}
+
.otp .tabbed-itineraries .tab-button:hover .title {
border-bottom: 3px solid #ddd;
}
diff --git a/lib/components/narrative/plan-first-last-buttons.js b/lib/components/narrative/plan-first-last-buttons.js
index fae5e4bf9..9209696e1 100644
--- a/lib/components/narrative/plan-first-last-buttons.js
+++ b/lib/components/narrative/plan-first-last-buttons.js
@@ -1,5 +1,6 @@
import React from 'react'
import {Button} from 'react-bootstrap'
+import { FormattedMessage } from 'react-intl'
import {connect} from 'react-redux'
import * as planActions from '../../actions/plan'
@@ -15,16 +16,16 @@ function PlanFirstLastButtons (props) {
return (
)
diff --git a/lib/components/narrative/realtime-annotation.js b/lib/components/narrative/realtime-annotation.js
index 325fe8167..a8662cc33 100644
--- a/lib/components/narrative/realtime-annotation.js
+++ b/lib/components/narrative/realtime-annotation.js
@@ -1,7 +1,10 @@
-import coreUtils from '@opentripplanner/core-utils'
import PropTypes from 'prop-types'
import React, { Component } from 'react'
import { Button, OverlayTrigger, Popover } from 'react-bootstrap'
+import { FormattedList, FormattedMessage } from 'react-intl'
+
+import FormattedDuration from '../util/formatted-duration'
+import Icon from '../util/icon'
export default class RealtimeAnnotation extends Component {
static propTypes = {
@@ -25,28 +28,30 @@ export default class RealtimeAnnotation extends Component {
const innerContent =
- Service update
+
+
-
+
{useRealtime
- ?
- Your trip results have been adjusted based on real-time
- information. Under normal conditions, this trip would take{' '}
- {coreUtils.time.formatDuration(realtimeEffects.normalDuration)}
- using the following routes:{' '}
- {filteredRoutes
- .map((route, idx) => (
-
- {route}
- {filteredRoutes.length - 1 > idx && ', '}
-
- ))
- }.
-
- :
- Your trip results are currently being affected by service delays.
- These delays do not factor into travel times shown below.
-
+ ? (
+
+
+
+ ),
+ routes: (
+ {route})}
+ />
+ )
+ }}
+ />
+ )
+ :
}
diff --git a/lib/components/narrative/save-trip-button.js b/lib/components/narrative/save-trip-button.js
index d5cabe2e7..5fe5f84a5 100644
--- a/lib/components/narrative/save-trip-button.js
+++ b/lib/components/narrative/save-trip-button.js
@@ -1,9 +1,11 @@
import React from 'react'
import { OverlayTrigger, Tooltip } from 'react-bootstrap'
+import { FormattedMessage, useIntl } from 'react-intl'
import { connect } from 'react-redux'
import { LinkContainerWithQuery } from '../form/connected-links'
import { CREATE_TRIP_PATH } from '../../util/constants'
+import Icon from '../util/icon'
import { itineraryCanBeMonitored } from '../../util/itinerary'
import { getActiveItinerary } from '../../util/state'
@@ -16,6 +18,7 @@ const SaveTripButton = ({
loggedInUser,
persistence
}) => {
+ const intl = useIntl()
// We are dealing with the following states:
// 1. Persistence disabled => just return null
// 2. User is not logged in => render something like: "Please sign in to save trip".
@@ -24,23 +27,23 @@ const SaveTripButton = ({
let buttonDisabled
let buttonText
let tooltipText
- let icon
+ let iconType
if (!persistence || !persistence.enabled) {
return null
} else if (!loggedInUser) {
buttonDisabled = true
- buttonText = 'Sign in to save trip'
- icon = 'fa fa-lock'
- tooltipText = 'Please sign in to save trip.'
+ buttonText =
+ iconType = 'lock'
+ tooltipText = intl.formatMessage({id: 'components.SaveTripButton.signInTooltip'})
} else if (!itineraryCanBeMonitored(itinerary)) {
buttonDisabled = true
- buttonText = 'Cannot save'
- icon = 'fa fa-ban'
- tooltipText = 'Only itineraries that include transit and no rentals or ride hailing can be monitored.'
+ buttonText =
+ iconType = 'ban'
+ tooltipText = intl.formatMessage({id: 'components.SaveTripButton.cantSaveTooltip'})
} else {
- buttonText = 'Save trip'
- icon = 'fa fa-plus-circle'
+ buttonText =
+ iconType = 'plus-circle'
}
const button = (
)
// Show tooltip with help text if button is disabled.
if (buttonDisabled) {
return (
{tooltipText}}
+ overlay={(
+
+ {/* Must get text using intl.formatMessage here because the rendering
+ of OverlayTrigger seems to occur outside of the IntlProvider context. */}
+ {tooltipText}
+
+ )}
placement='top'
>
-
+ {/* An active element around the disabled button is necessary
+ for the OverlayTrigger to render. */}
+
{button}
diff --git a/lib/components/narrative/simple-realtime-annotation.js b/lib/components/narrative/simple-realtime-annotation.js
index 07e1a4e7b..254714278 100644
--- a/lib/components/narrative/simple-realtime-annotation.js
+++ b/lib/components/narrative/simple-realtime-annotation.js
@@ -1,9 +1,13 @@
-import React, { Component } from 'react'
+import React from 'react'
+import { FormattedMessage } from 'react-intl'
-export default class SimpleRealtimeAnnotation extends Component {
- render () {
- return
- This trip uses real-time traffic and delay information
-