Plan a trip:
@@ -47,8 +55,11 @@ function MapPopup ({
)
}
-const mapStateToProps = (state, ownProps) => {
- return {zoom: state.otp.config.map.initZoom}
+const mapStateToProps = (state: {
+ // FIXME: properly type
+ otp: { config: { map: { initZoom: number } } }
+}) => {
+ return { zoom: state.otp.config.map.initZoom }
}
const mapDispatchToProps = {
diff --git a/lib/components/narrative/default/default-itinerary.js b/lib/components/narrative/default/default-itinerary.js
index 674340da7..1f74183ab 100644
--- a/lib/components/narrative/default/default-itinerary.js
+++ b/lib/components/narrative/default/default-itinerary.js
@@ -1,3 +1,6 @@
+// This is a large file being touched by open PRs. It should be typescripted
+// in a separate PR.
+/* eslint-disable */
import coreUtils from '@opentripplanner/core-utils'
import React from 'react'
import {
@@ -26,7 +29,7 @@ import Icon from '../../util/icon'
import { FlexIndicator } from './flex-indicator'
import ItinerarySummary from './itinerary-summary'
-const { isBicycle, isMicromobility, isTransit } = coreUtils.itinerary
+const { isBicycle, isMicromobility, isTransit, isFlex, isOptional, isContinuousDropoff } = coreUtils.itinerary
// Styled components
const LegIconWrapper = styled.div`
@@ -58,7 +61,7 @@ const DetailsHint = styled.div`
/**
* Obtains the description of an itinerary in the given locale.
*/
-function ItineraryDescription ({ itinerary }) {
+function ItineraryDescription({ itinerary }) {
let primaryTransitDuration = 0
let accessModeId = 'walk'
let transitMode
@@ -169,10 +172,7 @@ class DefaultItinerary extends NarrativeItinerary {
_onMouseLeave = () => {
const { index, setVisibleItinerary, visibleItinerary } = this.props
- if (
- typeof setVisibleItinerary === 'function' &&
- visibleItinerary === index
- ) {
+ if (typeof setVisibleItinerary === 'function' && visibleItinerary === index) {
setVisibleItinerary({ index: null })
}
}
@@ -189,7 +189,7 @@ class DefaultItinerary extends NarrativeItinerary {
return false
}
- render () {
+ render() {
const {
accessibilityScoreGradationMap,
active,
diff --git a/lib/components/narrative/default/flex-indicator.js b/lib/components/narrative/default/flex-indicator.tsx
similarity index 70%
rename from lib/components/narrative/default/flex-indicator.js
rename to lib/components/narrative/default/flex-indicator.tsx
index c4cdaa356..678495990 100644
--- a/lib/components/narrative/default/flex-indicator.js
+++ b/lib/components/narrative/default/flex-indicator.tsx
@@ -11,14 +11,24 @@ import Icon from '../../util/icon'
export const FLEX_COLOR = '#FA6400'
const FLEX_COLOR_LIGHT = tinycolor(FLEX_COLOR).lighten(40).toHexString()
-const FlexNotice = ({ faKey, showText, text }) => (
+// FIXME: type once the support-gtfs-flex branch is merged
+// eslint-disable-next-line react/prop-types
+const FlexNotice = ({
+ faKey,
+ showText,
+ text
+}: {
+ faKey: string
+ showText: boolean
+ text: string | React.ReactElement
+}) => (
<>
-
+
{showText &&
{text}
}
>
)
-const FlexIndicatorWrapper = styled.div`
+const FlexIndicatorWrapper = styled.div<{ shrink: boolean }>`
background: ${FLEX_COLOR_LIGHT};
border-bottom-right-radius: 8px;
border-top-right-radius: 8px;
@@ -73,26 +83,41 @@ const FlexIndicatorWrapper = styled.div`
}
`
-export const FlexIndicator = ({ isCallAhead, isContinuousDropoff, phoneNumber, shrink }) => (
+export const FlexIndicator = ({
+ isCallAhead,
+ isContinuousDropoff,
+ phoneNumber,
+ shrink
+}: {
+ isCallAhead: boolean
+ isContinuousDropoff: boolean
+ phoneNumber: string
+ shrink: boolean
+}): React.ReactElement => (
{!shrink && (
-
+
)}
{isCallAhead && (
}
+ text={
+
+ }
/>
)}
{/* Only show continuous dropoff message if call ahead message isn't shown */}
{isContinuousDropoff && !isCallAhead && (
}
+ text={}
/>
)}
diff --git a/lib/components/narrative/default/tnc-leg.js b/lib/components/narrative/default/tnc-leg.js
index 721d25b9d..642cab70d 100644
--- a/lib/components/narrative/default/tnc-leg.js
+++ b/lib/components/narrative/default/tnc-leg.js
@@ -13,60 +13,85 @@ const { toSentenceCase } = coreUtils.itinerary
const { formatDuration } = coreUtils.time
const { isMobile } = coreUtils.ui
+const defaultTncRideTypes = {
+ LYFT: 'lyft',
+ UBER: 'a6eef2e1-c99a-436f-bde9-fefb9181c0b0'
+}
+
class TransportationNetworkCompanyLeg extends Component {
static propTypes = {
+ LYFT_CLIENT_ID: PropTypes.string,
+ UBER_CLIENT_ID: PropTypes.string,
+ // eslint-disable-next-line sort-keys
leg: PropTypes.object,
legMode: PropTypes.object
}
state = {}
- render () {
+ render() {
const { leg, legMode, LYFT_CLIENT_ID, UBER_CLIENT_ID } = this.props
const universalLinks = {
- 'LYFT': `https://lyft.com/ride?id=${defaultTncRideTypes['LYFT']}&partner=${LYFT_CLIENT_ID}&pickup[latitude]=${leg.from.lat}&pickup[longitude]=${leg.from.lon}&destination[latitude]=${leg.to.lat}&destination[longitude]=${leg.to.lon}`,
- 'UBER': `https://m.uber.com/${isMobile() ? 'ul/' : ''}?client_id=${UBER_CLIENT_ID}&action=setPickup&pickup[latitude]=${leg.from.lat}&pickup[longitude]=${leg.from.lon}&pickup[nickname]=${encodeURI(leg.from.name)}&dropoff[latitude]=${leg.to.lat}&dropoff[longitude]=${leg.to.lon}&dropoff[nickname]=${encodeURI(leg.to.name)}`
+ LYFT: `https://lyft.com/ride?id=${defaultTncRideTypes.LYFT}&partner=${LYFT_CLIENT_ID}&pickup[latitude]=${leg.from.lat}&pickup[longitude]=${leg.from.lon}&destination[latitude]=${leg.to.lat}&destination[longitude]=${leg.to.lon}`,
+ UBER: `https://m.uber.com/${
+ isMobile() ? 'ul/' : ''
+ }?client_id=${UBER_CLIENT_ID}&action=setPickup&pickup[latitude]=${
+ leg.from.lat
+ }&pickup[longitude]=${leg.from.lon}&pickup[nickname]=${encodeURI(
+ leg.from.name
+ )}&dropoff[latitude]=${leg.to.lat}&dropoff[longitude]=${
+ leg.to.lon
+ }&dropoff[nickname]=${encodeURI(leg.to.name)}`
}
const { tncData } = leg
return (
* estimated travel time does not account for traffic.
+ target={isMobile() ? '_self' : '_blank'}
+ >
Book Ride
- {tncData && tncData.estimatedArrival
- ?
ETA for a driver: {formatDuration(tncData.estimatedArrival)}
- :
Could not obtain eta estimate from {toSentenceCase(legMode.label)}!
- }
+ {tncData && tncData.estimatedArrival ? (
+
ETA for a driver: {formatDuration(tncData.estimatedArrival)}
+ ) : (
+
+ Could not obtain eta estimate from {toSentenceCase(legMode.label)}!
+
+ )}
{/* tncData && tncData.travelDuration &&
Estimated drive time: {formatDuration(tncData.travelDuration)}
*/}
- {tncData && tncData.minCost
- ?
Estimated cost: {
- `${currencyFormatter.format(tncData.minCost, { code: tncData.currency })} - ${currencyFormatter.format(tncData.maxCost, { code: tncData.currency })}`
- }
- :
Could not obtain ride estimate from {toSentenceCase(legMode.label)}!
}
- }
+ {tncData && tncData.minCost ? (
+
+ Estimated cost:{' '}
+ {`${currencyFormatter.format(tncData.minCost, {
+ code: tncData.currency
+ })} - ${currencyFormatter.format(tncData.maxCost, {
+ code: tncData.currency
+ })}`}
+
+ ) : (
+
+ Could not obtain ride estimate from {toSentenceCase(legMode.label)}!
+
+ )}
)
}
}
-const defaultTncRideTypes = {
- 'LYFT': 'lyft',
- 'UBER': 'a6eef2e1-c99a-436f-bde9-fefb9181c0b0'
-}
-
-const mapStateToProps = (state, ownProps) => {
+const mapStateToProps = (state) => {
const { LYFT_CLIENT_ID, UBER_CLIENT_ID } = state.otp.config
return {
- companies: state.otp.currentQuery.companies,
LYFT_CLIENT_ID,
- tncData: state.otp.tnc,
- UBER_CLIENT_ID
+ UBER_CLIENT_ID,
+ // eslint-disable-next-line sort-keys
+ companies: state.otp.currentQuery.companies,
+ tncData: state.otp.tnc
}
}
@@ -75,4 +100,7 @@ const mapDispatchToProps = {
getTransportationNetworkCompanyRideEstimate
}
-export default connect(mapStateToProps, mapDispatchToProps)(TransportationNetworkCompanyLeg)
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(TransportationNetworkCompanyLeg)
diff --git a/lib/components/narrative/narrative-itineraries-errors.js b/lib/components/narrative/narrative-itineraries-errors.tsx
similarity index 56%
rename from lib/components/narrative/narrative-itineraries-errors.js
rename to lib/components/narrative/narrative-itineraries-errors.tsx
index 555399107..786e4f30b 100644
--- a/lib/components/narrative/narrative-itineraries-errors.js
+++ b/lib/components/narrative/narrative-itineraries-errors.tsx
@@ -1,9 +1,10 @@
import { getCompanyIcon } from '@opentripplanner/icons/lib/companies'
import { useIntl } from 'react-intl'
+import React from 'react'
import styled from 'styled-components'
-import Icon from '../util/icon'
import { getErrorMessage } from '../../util/state'
+import Icon from '../util/icon'
const IssueContainer = styled.div`
border-top: 1px solid grey;
@@ -23,10 +24,19 @@ const IssueContents = styled.div`
text-align: left;
`
-export default function NarrativeItinerariesErrors ({ errors }) {
+export default function NarrativeItinerariesErrors({
+ errors
+}: {
+ // FIXME: what are these types
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ errorMessages: any
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ errors: any
+}): JSX.Element {
const intl = useIntl()
- return errors.map((error, idx) => {
- let icon =
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ return errors.map((error: { network: any }, idx: number) => {
+ let icon =
if (error.network) {
const CompanyIcon = getCompanyIcon(error.network)
// check if company icon exists to avoid rendering undefined
@@ -36,12 +46,8 @@ export default function NarrativeItinerariesErrors ({ errors }) {
}
return (
-
- {icon}
-
-
- {getErrorMessage(error, intl)}
-
+ {icon}
+ {getErrorMessage(error, intl)}
)
})
diff --git a/lib/components/narrative/narrative-itineraries-header.js b/lib/components/narrative/narrative-itineraries-header.js
deleted file mode 100644
index 9f0548505..000000000
--- a/lib/components/narrative/narrative-itineraries-header.js
+++ /dev/null
@@ -1,117 +0,0 @@
-import styled from 'styled-components'
-import { FormattedMessage, useIntl } from 'react-intl'
-
-import Icon from '../util/icon'
-
-import PlanFirstLastButtons from './plan-first-last-buttons'
-import SaveTripButton from './save-trip-button'
-
-const IssueButton = styled.button`
- background-color: #ECBE03;
- border: none;
- border-radius: 5px;
- display: inline-block;
- font-size: 12px;
- padding: 2px 4px;
-`
-
-export default function NarrativeItinerariesHeader ({
- errors,
- itineraries,
- itineraryIsExpanded,
- onSortChange,
- onSortDirChange,
- onToggleShowErrors,
- onViewAllOptions,
- pending,
- showingErrors,
- sort
-}) {
- const intl = useIntl()
-
- return (
-
- {(itineraryIsExpanded || showingErrors)
- ? <>
-
- {itineraryIsExpanded && (
- // marginLeft: auto is a way of making something "float right"
- // within a flex container
- // see https://stackoverflow.com/a/36182782/269834
-
-
-
- )}
- >
- : <>
-
-
-
-
- {errors.length > 0 && (
-
-
-
-
-
-
- )}
-
-
-
-
-
-
- >
- }
-
- )
-}
diff --git a/lib/components/narrative/narrative-itineraries-header.tsx b/lib/components/narrative/narrative-itineraries-header.tsx
new file mode 100644
index 000000000..f98f7cc65
--- /dev/null
+++ b/lib/components/narrative/narrative-itineraries-header.tsx
@@ -0,0 +1,155 @@
+import { FormattedMessage, useIntl } from 'react-intl'
+import React from 'react'
+import styled from 'styled-components'
+
+import Icon from '../util/icon'
+
+import PlanFirstLastButtons from './plan-first-last-buttons'
+import SaveTripButton from './save-trip-button'
+
+const IssueButton = styled.button`
+ background-color: #ecbe03;
+ border: none;
+ border-radius: 5px;
+ display: inline-block;
+ font-size: 12px;
+ padding: 2px 4px;
+`
+
+export default function NarrativeItinerariesHeader({
+ errors,
+ itineraries,
+ itineraryIsExpanded,
+ onSortChange,
+ onSortDirChange,
+ onToggleShowErrors,
+ onViewAllOptions,
+ pending,
+ showingErrors,
+ sort
+}: {
+ errors: unknown[]
+ itineraries: unknown[]
+ itineraryIsExpanded: boolean
+ onSortChange: () => void
+ onSortDirChange: () => void
+ onToggleShowErrors: () => void
+ onViewAllOptions: () => void
+ pending: boolean
+ showingErrors: boolean
+ sort: { direction: string; type: string }
+}): JSX.Element {
+ const intl = useIntl()
+
+ return (
+
+ {itineraryIsExpanded || showingErrors ? (
+ <>
+
+ {itineraryIsExpanded && (
+ // marginLeft: auto is a way of making something "float right"
+ // within a flex container
+ // see https://stackoverflow.com/a/36182782/269834
+
+
+
+ )}
+ >
+ ) : (
+ <>
+
+
+
+
+ {errors.length > 0 && (
+
+
+
+
+
+
+ )}
+
+
+
+
+
+
+ >
+ )}
+
+ )
+}
diff --git a/lib/components/narrative/narrative-itinerary.js b/lib/components/narrative/narrative-itinerary.js
index dd51139ba..10d8be9b4 100644
--- a/lib/components/narrative/narrative-itinerary.js
+++ b/lib/components/narrative/narrative-itinerary.js
@@ -1,4 +1,6 @@
-import { Component } from 'react'
+/* eslint-disable react/require-render-return */
+/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
+import React, { Component } from 'react'
import PropTypes from 'prop-types'
export default class NarrativeItinerary extends Component {
@@ -22,13 +24,13 @@ export default class NarrativeItinerary extends Component {
if (onClick) {
onClick()
} else if (!active) {
- setActiveItinerary && setActiveItinerary({index})
+ setActiveItinerary && setActiveItinerary({ index })
} else {
- setActiveItinerary && setActiveItinerary({index: null})
+ setActiveItinerary && setActiveItinerary({ index: null })
}
}
- render () {
+ render() {
throw new Error('render() called on abstract class NarrativeItinerary')
}
}
diff --git a/lib/components/narrative/tabbed-itineraries.js b/lib/components/narrative/tabbed-itineraries.js
index c523c343c..e6fa0cb16 100644
--- a/lib/components/narrative/tabbed-itineraries.js
+++ b/lib/components/narrative/tabbed-itineraries.js
@@ -1,142 +1,57 @@
+// TODO: typescript
+/* eslint-disable react/prop-types */
+import { Button } from 'react-bootstrap'
+import { connect } from 'react-redux'
+import { FormattedMessage, FormattedNumber } from 'react-intl'
import coreUtils from '@opentripplanner/core-utils'
import PropTypes from 'prop-types'
import React, { Component } from 'react'
-import { Button } from 'react-bootstrap'
-import { FormattedMessage, FormattedNumber } from 'react-intl'
-import { connect } from 'react-redux'
import styled from 'styled-components'
import * as narrativeActions from '../../actions/narrative'
import { ComponentContext } from '../../util/contexts'
-import { getTimeFormat } from '../../util/i18n'
import { getActiveSearch, getRealtimeEffects } from '../../util/state'
+import { getTimeFormat } from '../../util/i18n'
import FormattedDuration from '../util/formatted-duration'
import Icon from '../util/icon'
import { FLEX_COLOR } from './default/flex-indicator'
-const { calculateFares, calculatePhysicalActivity, getTimeZoneOffset } = coreUtils.itinerary
+const { calculateFares, calculatePhysicalActivity, getTimeZoneOffset } =
+ coreUtils.itinerary
const Bullet = styled.span`
::before {
- content: "•";
+ content: '•';
margin: 0 0.25em;
}
`
-class TabbedItineraries extends Component {
- static propTypes = {
- activeItinerary: PropTypes.number,
- itineraries: PropTypes.array,
- pending: PropTypes.bool,
- setActiveItinerary: PropTypes.func,
- setActiveLeg: PropTypes.func,
- setActiveStep: PropTypes.func,
- setUseRealtimeResponse: PropTypes.func,
- useRealtime: PropTypes.bool
- }
-
- static contextType = ComponentContext
-
- _toggleRealtimeItineraryClick = (e) => {
- const { setUseRealtimeResponse, useRealtime } = this.props
- setUseRealtimeResponse({ useRealtime: !useRealtime })
- }
-
- render () {
- const {
- activeItinerary,
- currency,
- defaultFareKey,
- itineraries,
- realtimeEffects,
- setActiveItinerary,
- timeFormat,
- useRealtime,
- ...itineraryBodyProps
- } = this.props
- const { ItineraryBody, LegIcon } = this.context
-
- if (!itineraries) return null
-
- /* TODO: should this be moved? */
- const showRealtimeAnnotation =
- realtimeEffects.isAffectedByRealtimeData && (
- realtimeEffects.exceedsThreshold ||
- realtimeEffects.routesDiffer ||
- !useRealtime
- )
- return (
-
-
- {itineraries.map((itinerary, index) => {
- return (
-
- )
- })}
-
-
- {/*
- */}
-
- {/* Show active itin if itineraries exist and active itin is defined. */}
- {(itineraries.length > 0 && activeItinerary >= 0)
- ? (
-
- )
- : null
- }
-
-
- )
- }
-}
-
class TabButton extends Component {
_onClick = () => {
- const {index, onClick} = this.props
+ const { index, onClick } = this.props
// FIXME: change signature once actions resolved with otp-ui
onClick(index)
}
- render () {
- const {currency, defaultFareKey, index, isActive, itinerary} = this.props
+ render() {
+ const { currency, defaultFareKey, index, isActive, itinerary } = this.props
const timezoneOffset = getTimeZoneOffset(itinerary)
const classNames = ['tab-button', 'clear-button-formatting']
const { caloriesBurned } = calculatePhysicalActivity(itinerary)
- const {
- maxTNCFare,
- minTNCFare,
- transitFares
- } = calculateFares(itinerary, true)
+ const { maxTNCFare, minTNCFare, transitFares } = calculateFares(
+ itinerary,
+ true
+ )
// TODO: support non-USD
- const transitFare = (transitFares?.[defaultFareKey] || transitFares.regular)?.transitFare || 0
+ const transitFare =
+ (transitFares?.[defaultFareKey] || transitFares.regular)?.transitFare || 0
const minTotalFare = minTNCFare * 100 + transitFare
const startTime = itinerary.startTime + timezoneOffset
const endTime = itinerary.endTime + timezoneOffset
- const mustCallAhead = itinerary.legs.some(coreUtils.itinerary.isReservationRequired)
+ const mustCallAhead = itinerary.legs.some(
+ coreUtils.itinerary.isReservationRequired
+ )
if (isActive) classNames.push('selected')
return (
@@ -145,26 +60,29 @@ class TabButton extends Component {
key={`tab-button-${index}`}
onClick={this._onClick}
>
-
- {mustCallAhead && }
+
+ {mustCallAhead && (
+
+ )}
-
- {mustCallAhead &&
-
- }
+
+ {mustCallAhead && (
+
+
+
+ )}
{/* The itinerary duration in hrs/mins */}
-
{/* The duration as a time range */}
0 && (
<>
minTNCFare,
minTotalFare: (
)
@@ -193,14 +113,16 @@ class TabButton extends Component {
>
)}
-
{/* The 'X tranfers' line, if applicable */}
-
+
@@ -208,9 +130,93 @@ class TabButton extends Component {
}
}
+class TabbedItineraries extends Component {
+ static propTypes = {
+ activeItinerary: PropTypes.number,
+ itineraries: PropTypes.array,
+ pending: PropTypes.bool,
+ setActiveItinerary: PropTypes.func,
+ setActiveLeg: PropTypes.func,
+ setActiveStep: PropTypes.func,
+ setUseRealtimeResponse: PropTypes.func,
+ useRealtime: PropTypes.bool
+ }
+
+ static contextType = ComponentContext
+
+ _toggleRealtimeItineraryClick = (e) => {
+ const { setUseRealtimeResponse, useRealtime } = this.props
+ setUseRealtimeResponse({ useRealtime: !useRealtime })
+ }
+
+ render() {
+ const {
+ activeItinerary,
+ currency,
+ defaultFareKey,
+ itineraries,
+ realtimeEffects,
+ setActiveItinerary,
+ timeFormat,
+ useRealtime,
+ ...itineraryBodyProps
+ } = this.props
+ const { ItineraryBody, LegIcon } = this.context
+
+ if (!itineraries) return null
+
+ /* TODO: should this be moved? */
+ const showRealtimeAnnotation =
+ realtimeEffects.isAffectedByRealtimeData &&
+ (realtimeEffects.exceedsThreshold ||
+ realtimeEffects.routesDiffer ||
+ !useRealtime)
+ return (
+
+
+ {itineraries.map((itinerary, index) => {
+ return (
+
+ )
+ })}
+
+
+ {/*
+ */}
+
+ {/* Show active itin if itineraries exist and active itin is defined. */}
+ {itineraries.length > 0 && activeItinerary >= 0 ? (
+
+ ) : null}
+
+ )
+ }
+}
// connect to the redux store
-const mapStateToProps = (state, ownProps) => {
+const mapStateToProps = (state) => {
const activeSearch = getActiveSearch(state)
const currency = state.otp.config.localization?.currency || 'USD'
const pending = activeSearch ? Boolean(activeSearch.pending) : false
@@ -233,28 +239,31 @@ const mapStateToProps = (state, ownProps) => {
}
}
-const mapDispatchToProps = (dispatch, ownProps) => {
- const {setActiveItinerary, setActiveLeg, setActiveStep, setUseRealtimeResponse} = narrativeActions
+const mapDispatchToProps = (dispatch) => {
+ const {
+ setActiveItinerary,
+ setActiveLeg,
+ setActiveStep,
+ setUseRealtimeResponse
+ } = narrativeActions
return {
// FIXME
setActiveItinerary: (index) => {
- dispatch(setActiveItinerary({index}))
+ dispatch(setActiveItinerary({ index }))
},
// FIXME
setActiveLeg: (index, leg) => {
- dispatch(setActiveLeg({index, leg}))
+ dispatch(setActiveLeg({ index, leg }))
},
// FIXME
setActiveStep: (index, step) => {
- dispatch(setActiveStep({index, step}))
+ dispatch(setActiveStep({ index, step }))
},
// FIXME
- setUseRealtimeResponse: ({useRealtime}) => {
- dispatch(setUseRealtimeResponse({useRealtime}))
+ setUseRealtimeResponse: ({ useRealtime }) => {
+ dispatch(setUseRealtimeResponse({ useRealtime }))
}
}
}
-export default connect(mapStateToProps, mapDispatchToProps)(
- TabbedItineraries
-)
+export default connect(mapStateToProps, mapDispatchToProps)(TabbedItineraries)
diff --git a/lib/components/user/monitored-trip/trip-basics-pane.js b/lib/components/user/monitored-trip/trip-basics-pane.tsx
similarity index 53%
rename from lib/components/user/monitored-trip/trip-basics-pane.js
rename to lib/components/user/monitored-trip/trip-basics-pane.tsx
index f02528e75..3c084c6f4 100644
--- a/lib/components/user/monitored-trip/trip-basics-pane.js
+++ b/lib/components/user/monitored-trip/trip-basics-pane.tsx
@@ -1,6 +1,7 @@
import { Field } from 'formik'
-import FormikErrorFocus from 'formik-error-focus'
-import React, { Component } from 'react'
+// No Typescript Yet
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-ignore
import {
ControlLabel,
FormControl,
@@ -10,12 +11,23 @@ import {
ProgressBar
} from 'react-bootstrap'
import { FormattedMessage, injectIntl } from 'react-intl'
+// No Typescript Yet
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-ignore
+import FormikErrorFocus from 'formik-error-focus'
+import React, { Component } from 'react'
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-ignore
import { connect } from 'react-redux'
import { Prompt } from 'react-router'
import styled from 'styled-components'
+import type { InjectedIntlProps } from 'react-intl'
import * as userActions from '../../../actions/user'
-import { ALL_DAYS, getFormattedDayOfWeekPlural } from '../../../util/monitored-trip'
+import {
+ ALL_DAYS,
+ getFormattedDayOfWeekPlural
+} from '../../../util/monitored-trip'
import { getErrorStates } from '../../../util/ui'
import FormattedDayOfWeekCompact from '../../util/formatted-day-of-week-compact'
import FormattedValidationError from '../../util/formatted-validation-error'
@@ -23,6 +35,23 @@ import FormattedValidationError from '../../util/formatted-validation-error'
import TripStatus from './trip-status'
import TripSummary from './trip-summary'
+type TripBasicsProps =
+ | {
+ canceled: boolean
+ checkItineraryExistence: (monitoredTrip: unknown) => void
+ clearItineraryExistence: () => void
+ dirty: boolean
+ errors: Record[]
+ isCreating: boolean
+ isSubmitting: boolean
+ itineraryExistence: Record
+ setFieldValue: (field: string, threshold: number | false) => void
+ values: { itinerary: unknown } // FIXME
+ } & InjectedIntlProps
+
+// FIXME: move to shared types file
+type errorStates = 'success' | 'warning' | 'error' | null | undefined
+
// Styles.
const TripDayLabel = styled.label`
border: 1px solid #ccc;
@@ -48,19 +77,20 @@ const TripDayLabel = styled.label`
* This component shows summary information for a trip
* and lets the user edit the trip name and day.
*/
-class TripBasicsPane extends Component {
+class TripBasicsPane extends Component {
/**
* For new trips only, update the Formik state to
* uncheck days for which the itinerary is not available.
*/
- _updateNewTripItineraryExistence = prevProps => {
+ _updateNewTripItineraryExistence = (prevProps: TripBasicsProps) => {
const { isCreating, itineraryExistence, setFieldValue } = this.props
- if (isCreating &&
- itineraryExistence &&
- itineraryExistence !== prevProps.itineraryExistence
+ if (
+ isCreating &&
+ itineraryExistence &&
+ itineraryExistence !== prevProps.itineraryExistence
) {
- ALL_DAYS.forEach(day => {
+ ALL_DAYS.forEach((day) => {
if (!itineraryExistence[day].valid) {
setFieldValue(day, false)
}
@@ -68,21 +98,21 @@ class TripBasicsPane extends Component {
}
}
- componentDidMount () {
+ componentDidMount() {
// Check itinerary availability (existence) for all days.
const { checkItineraryExistence, intl, values: monitoredTrip } = this.props
checkItineraryExistence(monitoredTrip, intl)
}
- componentDidUpdate (prevProps) {
+ componentDidUpdate(prevProps: TripBasicsProps) {
this._updateNewTripItineraryExistence(prevProps)
}
- componentWillUnmount () {
+ componentWillUnmount() {
this.props.clearItineraryExistence()
}
- render () {
+ render() {
const {
canceled,
dirty,
@@ -100,23 +130,30 @@ class TripBasicsPane extends Component {
const unsavedChanges = dirty && !isSubmitting && !canceled
// Message changes depending on if the new or existing trip is being edited
const unsavedChangesMessage = isCreating
- ? intl.formatMessage({id: 'components.TripBasicsPane.unsavedChangesNewTrip'})
- : intl.formatMessage({id: 'components.TripBasicsPane.unsavedChangesExistingTrip'})
+ ? intl.formatMessage({
+ id: 'components.TripBasicsPane.unsavedChangesNewTrip'
+ })
+ : intl.formatMessage({
+ id: 'components.TripBasicsPane.unsavedChangesExistingTrip'
+ })
if (!itinerary) {
return (
-
+
)
} else {
// Show an error indication when
// - monitoredTrip.tripName is not blank and that tripName is not already used.
// - no day is selected (show a combined error indication).
- const errorStates = getErrorStates(this.props)
+ // FIXME: type getErrorStates
+ const errorStates: Record = getErrorStates(
+ this.props
+ )
- let monitoredDaysValidationState = null
- ALL_DAYS.forEach(day => {
+ let monitoredDaysValidationState: errorStates | null = null
+ ALL_DAYS.forEach((day) => {
if (!monitoredDaysValidationState) {
monitoredDaysValidationState = errorStates[day]
}
@@ -126,25 +163,22 @@ class TripBasicsPane extends Component {
{/* TODO: This component does not block navigation on reload or using the back button.
This will have to be done at a higher level. See #376 */}
-
+
{/* Do not show trip status when saving trip for the first time
(it doesn't exist in backend yet). */}
{!isCreating &&
}
-
+
-
+
{/* onBlur, onChange, and value are passed automatically. */}
-
+
{errors.tripName && (
@@ -155,55 +189,70 @@ class TripBasicsPane extends Component {
-
+
- {ALL_DAYS.map(day => {
- const isDayDisabled = itineraryExistence && !itineraryExistence[day].valid
- const boxClass = isDayDisabled ? 'alert-danger' : (monitoredTrip[day] ? 'bg-primary' : '')
+ {ALL_DAYS.map((day) => {
+ const isDayDisabled =
+ itineraryExistence && !itineraryExistence[day].valid
+ const boxClass = isDayDisabled
+ ? 'alert-danger'
+ : monitoredTrip[day]
+ ? 'bg-primary'
+ : ''
const notAvailableText = isDayDisabled
? intl.formatMessage(
- {id: 'components.TripBasicsPane.tripNotAvailableOnDay'},
- {repeatedDay: getFormattedDayOfWeekPlural(day, intl)}
- )
+ { id: 'components.TripBasicsPane.tripNotAvailableOnDay' },
+ { repeatedDay: getFormattedDayOfWeekPlural(day, intl) }
+ )
: null
return (
-
+
- { // Let users save an existing trip, even though it may not be available on some days.
+ {
+ // Let users save an existing trip, even though it may not be available on some days.
// TODO: improve checking trip availability.
- isDayDisabled && isCreating
- ?
- :
+ isDayDisabled && isCreating ? (
+
+ ) : (
+
+ )
}
)
})}
-
+
- {itineraryExistence
- ? (
-
- ) : (
- }
- now={100}
- />
- )
- }
+ {itineraryExistence ? (
+
+ ) : (
+
+ }
+ now={100}
+ />
+ )}
{monitoredDaysValidationState && (
-
+
)}
{/* Scroll to the trip name/days fields if submitting and there is an error on these fields. */}
-
+
)
@@ -212,7 +261,11 @@ class TripBasicsPane extends Component {
}
// Connect to redux store
-const mapStateToProps = (state, ownProps) => {
+
+// TODO: state type
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-ignore
+const mapStateToProps = (state) => {
const { itineraryExistence } = state.user
return {
itineraryExistence
@@ -224,6 +277,7 @@ const mapDispatchToProps = {
clearItineraryExistence: userActions.clearItineraryExistence
}
-export default connect(mapStateToProps, mapDispatchToProps)(
- injectIntl(TripBasicsPane)
-)
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(injectIntl(TripBasicsPane))
diff --git a/lib/components/user/monitored-trip/trip-status-rendering-strategies/base-renderer.js b/lib/components/user/monitored-trip/trip-status-rendering-strategies/base-renderer.js
index cbb4c76be..6798f8a0b 100644
--- a/lib/components/user/monitored-trip/trip-status-rendering-strategies/base-renderer.js
+++ b/lib/components/user/monitored-trip/trip-status-rendering-strategies/base-renderer.js
@@ -1,5 +1,6 @@
-import moment from 'moment'
import { FormattedMessage } from 'react-intl'
+import moment from 'moment'
+import React from 'react'
import FormattedDuration from '../../../util/formatted-duration'
@@ -7,14 +8,20 @@ import FormattedDuration from '../../../util/formatted-duration'
* Calculate commonly-used pieces of data used to render the trip status
* component. The monitoredTrip param can be undefined.
*/
-export default function baseRenderer (monitoredTrip) {
+export default function baseRenderer(monitoredTrip) {
const data = {
// create some default display values in case another renderer doesn't
// calculate these values
- body: ,
- headingText: , // same default msg as body
+ body: (
+
+ ),
+ headingText: (
+
+ ), // same default msg as body
journeyState: monitoredTrip && monitoredTrip.journeyState,
- lastCheckedText: ,
+ lastCheckedText: (
+
+ ),
monitoredTrip: monitoredTrip,
tripIsActive: monitoredTrip && monitoredTrip.isActive,
tripIsSnoozed: monitoredTrip && monitoredTrip.snoozed
@@ -28,14 +35,16 @@ export default function baseRenderer (monitoredTrip) {
moment(data.journeyState.lastCheckedEpochMillis),
'seconds'
)
- data.lastCheckedText =
- }}
- />
+ data.lastCheckedText = (
+
+ )
+ }}
+ />
+ )
}
// set some alert data if the matching itinerary exists
@@ -51,26 +60,26 @@ export default function baseRenderer (monitoredTrip) {
if (data.tripIsActive) {
data.togglePauseTripButtonGlyphIcon = 'pause'
- data.togglePauseTripButtonText = ()
+ data.togglePauseTripButtonText = (
+
+ )
} else {
data.togglePauseTripButtonGlyphIcon = 'play'
- data.togglePauseTripButtonText = ()
+ data.togglePauseTripButtonText = (
+
+ )
}
if (data.tripIsSnoozed) {
data.toggleSnoozeTripButtonGlyphIcon = 'play'
- data.toggleSnoozeTripButtonText = ()
+ data.toggleSnoozeTripButtonText = (
+
+ )
} else {
data.toggleSnoozeTripButtonGlyphIcon = 'pause'
- data.toggleSnoozeTripButtonText = ()
+ data.toggleSnoozeTripButtonText = (
+
+ )
}
return data
diff --git a/lib/components/user/places/place-editor.js b/lib/components/user/places/place-editor.js
deleted file mode 100644
index 79351625c..000000000
--- a/lib/components/user/places/place-editor.js
+++ /dev/null
@@ -1,150 +0,0 @@
-import { Field } from 'formik'
-import coreUtils from '@opentripplanner/core-utils'
-import React, { Component } from 'react'
-import {
- FormControl,
- FormGroup,
- HelpBlock,
- ToggleButton,
- ToggleButtonGroup
-} from 'react-bootstrap'
-import { injectIntl } from 'react-intl'
-import styled from 'styled-components'
-
-import Icon from '../../util/icon'
-import { capitalizeFirst, getErrorStates } from '../../../util/ui'
-import { getFormattedPlace } from '../../../util/i18n'
-import { CUSTOM_PLACE_TYPES, isHomeOrWork } from '../../../util/user'
-import FormattedValidationError from '../../util/formatted-validation-error'
-
-import {
- makeLocationFieldLocation,
- PlaceLocationField
-} from './place-location-field'
-
-const { isMobile } = coreUtils.ui
-
-// Styled components
-const LargeIcon = styled(Icon)`
- font-size: 150%;
-`
-const FixedPlaceIcon = styled(LargeIcon)`
- margin-right: 10px;
- padding-top: 6px;
-`
-const FlexContainer = styled.div`
- display: flex;
-`
-const FlexFormGroup = styled(FormGroup)`
- flex-grow: 1;
-`
-const StyledToggleButtonGroup = styled(ToggleButtonGroup)`
- & > label {
- padding: 5px;
- }
-`
-
-/**
- * Contains the fields for editing a favorite place.
- * This component uses Formik props that are passed
- * within the Formik context set up by FavoritePlaceScreen.
- */
-class PlaceEditor extends Component {
- _handleLocationChange = ({ location }) => {
- const { setValues, values } = this.props
- const { lat, lon, name } = location
- setValues({
- ...values,
- address: name,
- lat,
- lon
- })
- }
-
- render () {
- const { errors, handleBlur, handleChange, intl, values: place } = this.props
- const isFixed = isHomeOrWork(place)
- const errorStates = getErrorStates(this.props)
- const namePlaceholder = intl.formatMessage({id: 'components.PlaceEditor.namePlaceholder'})
-
- return (
-
- {!isFixed && (
- <>
-
- {/* onBlur, onChange, and value are passed automatically. */}
-
-
- {errors.name && (
-
-
-
- )}
-
-
-
-
- {Object.keys(CUSTOM_PLACE_TYPES).map(k => {
- const { icon, type } = CUSTOM_PLACE_TYPES[k]
- const title = capitalizeFirst(getFormattedPlace(k, intl))
- return (
-
-
-
- )
- })}
-
-
- >
- )}
-
-
- {/* For fixed places, just show the icon for place type instead of all inputs and selectors */}
- {isFixed && }
-
-
-
-
- {errors.address && (
-
-
-
- )}
-
-
-
- )
- }
-}
-
-export default injectIntl(PlaceEditor)
diff --git a/lib/components/user/places/place-editor.tsx b/lib/components/user/places/place-editor.tsx
new file mode 100644
index 000000000..f8569a876
--- /dev/null
+++ b/lib/components/user/places/place-editor.tsx
@@ -0,0 +1,187 @@
+import { Field } from 'formik'
+import {
+ FormControl,
+ FormGroup,
+ HelpBlock,
+ ToggleButton,
+ ToggleButtonGroup
+} from 'react-bootstrap'
+import { injectIntl } from 'react-intl'
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-ignore
+import type { InjectedIntlProps } from 'react-intl'
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-ignore
+import coreUtils from '@opentripplanner/core-utils'
+import React, { Component } from 'react'
+import styled from 'styled-components'
+
+import { capitalizeFirst, getErrorStates } from '../../../util/ui'
+import { CUSTOM_PLACE_TYPES, isHomeOrWork } from '../../../util/user'
+import { getFormattedPlaces } from '../../../util/i18n'
+import FormattedValidationError from '../../util/formatted-validation-error'
+import Icon, { IconProps } from '../../util/icon'
+
+import {
+ makeLocationFieldLocation,
+ PlaceLocationField
+} from './place-location-field'
+
+const { isMobile } = coreUtils.ui
+
+// Styled components
+const LargeIcon = styled(Icon)`
+ font-size: 150%;
+`
+const FixedPlaceIcon = styled(LargeIcon)`
+ margin-right: 10px;
+ padding-top: 6px;
+`
+const FlexContainer = styled.div`
+ display: flex;
+`
+const FlexFormGroup = styled(FormGroup)`
+ flex-grow: 1;
+`
+const StyledToggleButtonGroup = styled(ToggleButtonGroup)`
+ & > label {
+ padding: 5px;
+ }
+`
+
+/**
+ * Contains the fields for editing a favorite place.
+ * This component uses Formik props that are passed
+ * within the Formik context set up by FavoritePlaceScreen.
+ */
+class PlaceEditor extends Component<
+ {
+ // FIXME: shared type for errors
+ errors: Record[]
+ handleBlur: () => void
+ handleChange: () => void
+ setValues: (values: unknown) => void
+ // Needed for prop spread
+ // eslint-disable-next-line @typescript-eslint/ban-types
+ values: Object
+ } & InjectedIntlProps
+> {
+ _handleLocationChange = ({
+ location
+ }: {
+ location: {
+ lat: number
+ lon: number
+ name: string
+ }
+ }) => {
+ const { setValues, values } = this.props
+ const { lat, lon, name } = location
+ setValues({
+ ...values,
+ address: name,
+ lat,
+ lon
+ })
+ }
+
+ render() {
+ const { errors, handleBlur, handleChange, intl, values: place } = this.props
+ const isFixed = isHomeOrWork(place)
+ const errorStates = getErrorStates(this.props)
+ const namePlaceholder = intl.formatMessage({
+ id: 'components.PlaceEditor.namePlaceholder'
+ })
+
+ return (
+
+ {!isFixed && (
+ <>
+ {/* TODO: properly type errorStates
+ eslint-disable-next-line @typescript-eslint/ban-ts-comment //
+ @ts-ignore */}
+
+ {/* onBlur, onChange, and value are passed automatically. */}
+
+
+ {errors.name && (
+
+
+
+ )}
+
+
+
+ {Object.keys(CUSTOM_PLACE_TYPES).map((k) => {
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore
+ const { icon, type } = CUSTOM_PLACE_TYPES[k]
+ const title = capitalizeFirst(getFormattedPlaces(k, intl))
+ return (
+
+
+
+ )
+ })}
+
+
+ >
+ )}
+
+
+ {/* For fixed places, just show the icon for place type instead of all inputs and selectors */}
+ {isFixed && }
+
+ {/* TODO: properly type errorStates
+ eslint-disable-next-line @typescript-eslint/ban-ts-comment //
+ @ts-ignore */}
+
+
+
+ {errors.address && (
+
+
+
+ )}
+
+
+
+ )
+ }
+}
+
+export default injectIntl(PlaceEditor)
diff --git a/lib/components/util/formatted-date-time-preview.js b/lib/components/util/formatted-date-time-preview.js
index 844889d6c..1cd1fc37b 100644
--- a/lib/components/util/formatted-date-time-preview.js
+++ b/lib/components/util/formatted-date-time-preview.js
@@ -1,21 +1,45 @@
+// REMOVE THIS LINE BEFORE EDITING THIS FILE
+/* eslint-disable */
import { FormattedMessage } from 'react-intl'
import PropTypes from 'prop-types'
+import React from 'react'
/**
* Returns a FormattedMessage component for date time preview strings such that i18n IDs
* are hardcoded and can be kept track of by format.js CLI tools
-*/
-const FormattedDateTimePreview = ({departArrive, endTime, routingType, startTime, time}) => {
+ */
+const FormattedDateTimePreview = ({
+ departArrive,
+ endTime,
+ routingType,
+ startTime,
+ time
+}) => {
if (routingType === 'ITINERARY') {
if (departArrive === 'NOW') {
- return
+ return
} else if (departArrive === 'ARRIVE') {
- return
+ return (
+
+ )
} else if (departArrive === 'DEPART') {
- return
+ return (
+
+ )
}
} else if (routingType === 'PROFILE') {
- return
+ return (
+
+ )
}
}
diff --git a/lib/components/util/formatted-day-of-week-compact.js b/lib/components/util/formatted-day-of-week-compact.js
deleted file mode 100644
index c8e193c9b..000000000
--- a/lib/components/util/formatted-day-of-week-compact.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import { FormattedMessage } from 'react-intl'
-import PropTypes from 'prop-types'
-
-/**
- * Returns a FormattedMessage component for the compact day of week such that i18n IDs
- * are hardcoded and can be kept track of by format.js tools
- */
-export default function FormattedDayOfWeekCompact ({day}) {
- switch (day) {
- case 'monday':
- return
- case 'tuesday':
- return
- case 'wednesday':
- return
- case 'thursday':
- return
- case 'friday':
- return
- case 'saturday':
- return
- case 'sunday':
- return
- default:
- return null
- }
-}
-
-FormattedDayOfWeekCompact.propTypes = {
- day: PropTypes.string.isRequired
-}
-
-FormattedDayOfWeekCompact.defaultProps = {
- day: ''
-}
diff --git a/lib/components/util/formatted-day-of-week-compact.tsx b/lib/components/util/formatted-day-of-week-compact.tsx
new file mode 100644
index 000000000..8666c187a
--- /dev/null
+++ b/lib/components/util/formatted-day-of-week-compact.tsx
@@ -0,0 +1,40 @@
+import { FormattedMessage } from 'react-intl'
+import PropTypes from 'prop-types'
+import React from 'react'
+
+/**
+ * Returns a FormattedMessage component for the compact day of week such that i18n IDs
+ * are hardcoded and can be kept track of by format.js tools
+ */
+export default function FormattedDayOfWeekCompact({
+ day
+}: {
+ day: string
+}): JSX.Element | null {
+ switch (day) {
+ case 'monday':
+ return
+ case 'tuesday':
+ return
+ case 'wednesday':
+ return
+ case 'thursday':
+ return
+ case 'friday':
+ return
+ case 'saturday':
+ return
+ case 'sunday':
+ return
+ default:
+ return null
+ }
+}
+
+FormattedDayOfWeekCompact.propTypes = {
+ day: PropTypes.string.isRequired
+}
+
+FormattedDayOfWeekCompact.defaultProps = {
+ day: ''
+}
diff --git a/lib/components/util/formatted-day-of-week.js b/lib/components/util/formatted-day-of-week.js
index c68e9898d..ec4744031 100644
--- a/lib/components/util/formatted-day-of-week.js
+++ b/lib/components/util/formatted-day-of-week.js
@@ -1,5 +1,6 @@
import { FormattedMessage } from 'react-intl'
import PropTypes from 'prop-types'
+import React from 'react'
/**
* Returns a FormattedMessage component for the compact day of week such that i18n IDs are
@@ -7,22 +8,22 @@ import PropTypes from 'prop-types'
* TODO: More general FormattedDayOfWeek component that accepts a type of DayOfWeek
* 'compact', or 'plural', etc. ?
*/
-export default function FormattedDayOfWeek ({day}) {
+export default function FormattedDayOfWeek({ day }) {
switch (day) {
case 'monday':
- return
+ return
case 'tuesday':
- return
+ return
case 'wednesday':
- return
+ return
case 'thursday':
- return
+ return
case 'friday':
- return
+ return
case 'saturday':
- return
+ return
case 'sunday':
- return
+ return
default:
return null
}
diff --git a/lib/components/util/formatted-duration.js b/lib/components/util/formatted-duration.tsx
similarity index 68%
rename from lib/components/util/formatted-duration.js
rename to lib/components/util/formatted-duration.tsx
index e806e9b81..632d24ea4 100644
--- a/lib/components/util/formatted-duration.js
+++ b/lib/components/util/formatted-duration.tsx
@@ -1,3 +1,4 @@
+import React from 'react'
import moment from 'moment-timezone'
import { FormattedMessage } from 'react-intl'
import PropTypes from 'prop-types'
@@ -5,14 +6,18 @@ import PropTypes from 'prop-types'
/**
* Formats the given duration according to the selected locale.
*/
-export default function FormattedDuration ({duration}) {
+export default function FormattedDuration({
+ duration
+}: {
+ duration: number
+}): JSX.Element {
const dur = moment.duration(duration, 'seconds')
const hours = dur.hours()
const minutes = dur.minutes()
return (
)
}
diff --git a/lib/components/util/formatted-mode.js b/lib/components/util/formatted-mode.js
index 4a9e78d36..0f3cf84a5 100644
--- a/lib/components/util/formatted-mode.js
+++ b/lib/components/util/formatted-mode.js
@@ -1,48 +1,49 @@
import { FormattedMessage } from 'react-intl'
import PropTypes from 'prop-types'
+import React from 'react'
import styled from 'styled-components'
/**
* Returns a FormattedMessage component for the "travel by" mode such that i18n IDs
* are hardcoded and can be kept track of by format.js CLI tools
-*/
+ */
// eslint-disable-next-line complexity
-const FormattedMode = ({mode}) => {
+const FormattedMode = ({ mode }) => {
switch (mode) {
case 'bicycle':
- return
+ return
case 'bicycle_rent':
- return
+ return
case 'bus':
- return
+ return
case 'cable_car':
- return
+ return
case 'car':
- return
+ return
case 'car_park':
- return
+ return
case 'drive':
- return
+ return
case 'ferry':
- return
+ return
case 'funicular':
- return
+ return
case 'gondola':
- return
+ return
case 'micromobility':
- return
+ return
case 'micromobility_rent':
- return
+ return
case 'rail':
- return
+ return
case 'subway':
- return
+ return
case 'tram':
- return
+ return
case 'transit':
- return
+ return
case 'walk':
- return
+ return
default:
return null
}
@@ -58,6 +59,6 @@ FormattedMode.defaultProps = {
// For lowercase context
export const StyledFormattedMode = styled(FormattedMode)`
- text-transform: lowercase;
+ text-transform: lowercase;
`
export default FormattedMode
diff --git a/lib/components/util/formatted-realtime-status-label.js b/lib/components/util/formatted-realtime-status-label.js
deleted file mode 100644
index 784650841..000000000
--- a/lib/components/util/formatted-realtime-status-label.js
+++ /dev/null
@@ -1,31 +0,0 @@
-import { FormattedMessage } from 'react-intl'
-import PropTypes from 'prop-types'
-
-/**
- * Returns a FormattedMessage component for realtime status labels such that i18n IDs
- * are hardcoded and can be kept track of by format.js CLI tools
-*/
-const FormattedRealtimeStatusLabel = ({minutes, status}) => {
- switch (status) {
- case 'early':
- return
- case 'late':
- return
- case 'onTime':
- return
- case 'scheduled':
- return
- default:
- return null
- }
-}
-
-FormattedRealtimeStatusLabel.propTypes = {
- status: PropTypes.string.isRequired
-}
-
-FormattedRealtimeStatusLabel.defaultProps = {
- status: ''
-}
-
-export default FormattedRealtimeStatusLabel
diff --git a/lib/components/util/formatted-realtime-status-label.tsx b/lib/components/util/formatted-realtime-status-label.tsx
new file mode 100644
index 000000000..10206dc50
--- /dev/null
+++ b/lib/components/util/formatted-realtime-status-label.tsx
@@ -0,0 +1,48 @@
+import { FormattedMessage } from 'react-intl'
+import PropTypes from 'prop-types'
+import React from 'react'
+
+/**
+ * Returns a FormattedMessage component for realtime status labels such that i18n IDs
+ * are hardcoded and can be kept track of by format.js CLI tools
+ */
+const FormattedRealtimeStatusLabel = ({
+ minutes,
+ status
+}: {
+ minutes: number
+ status: string
+}): JSX.Element | null => {
+ switch (status) {
+ case 'early':
+ return (
+
+ )
+ case 'late':
+ return (
+
+ )
+ case 'onTime':
+ return
+ case 'scheduled':
+ return
+ default:
+ return null
+ }
+}
+
+FormattedRealtimeStatusLabel.propTypes = {
+ status: PropTypes.string.isRequired
+}
+
+FormattedRealtimeStatusLabel.defaultProps = {
+ status: ''
+}
+
+export default FormattedRealtimeStatusLabel
diff --git a/lib/components/util/formatted-time-range.tsx b/lib/components/util/formatted-time-range.tsx
new file mode 100644
index 000000000..74177bed6
--- /dev/null
+++ b/lib/components/util/formatted-time-range.tsx
@@ -0,0 +1,25 @@
+import React from 'react'
+import moment from 'moment-timezone'
+import { FormattedMessage } from 'react-intl'
+
+/**
+ * Renders a time range e.g. 3:45pm-4:15pm according to the
+ * react-intl default time format for the ambient locale.
+ */
+export default function FormattedTimeRange({
+ endTime,
+ startTime
+}: {
+ endTime: number
+ startTime: number
+}): JSX.Element {
+ return (
+
+ )
+}
diff --git a/lib/components/util/formatted-transit-vehicle-status.js b/lib/components/util/formatted-transit-vehicle-status.js
index d43162782..26796fd8f 100644
--- a/lib/components/util/formatted-transit-vehicle-status.js
+++ b/lib/components/util/formatted-transit-vehicle-status.js
@@ -1,18 +1,34 @@
import { FormattedMessage } from 'react-intl'
import PropTypes from 'prop-types'
+import React from 'react'
/**
* Returns a FormattedMessage component for transit vehicle status msgs such that i18n IDs
* are hardcoded and can be kept track of by format.js CLI tools
-*/
-const FormattedTransitVehicleStatus = ({stop, stopStatus}) => {
+ */
+const FormattedTransitVehicleStatus = ({ stop, stopStatus }) => {
switch (stopStatus) {
case 'incoming_at':
- return
+ return (
+
+ )
case 'in_transit_to':
- return
+ return (
+
+ )
case 'stopped_at':
- return
+ return (
+
+ )
default:
return null
}
diff --git a/lib/components/util/formatted-validation-error.js b/lib/components/util/formatted-validation-error.js
index bf872a136..487e47c94 100644
--- a/lib/components/util/formatted-validation-error.js
+++ b/lib/components/util/formatted-validation-error.js
@@ -1,3 +1,4 @@
+import React from 'react'
import { FormattedMessage } from 'react-intl'
import PropTypes from 'prop-types'
@@ -5,18 +6,28 @@ import PropTypes from 'prop-types'
* Returns a FormattedMessage component for a yup validation error in order to
* keep react-intl message IDs hardcoded.
*/
-export default function FormattedValidationError ({type}) {
+export default function FormattedValidationError({ type }) {
switch (type) {
case 'invalid-address':
- return
+ return (
+
+ )
case 'invalid-name':
- return
+ return (
+
+ )
case 'placename-already-used':
- return
+ return (
+
+ )
case 'trip-name-already-used':
- return
+ return (
+
+ )
case 'trip-name-required':
- return
+ return (
+
+ )
default:
return null
}
diff --git a/lib/components/util/icon.js b/lib/components/util/icon.tsx
similarity index 60%
rename from lib/components/util/icon.js
rename to lib/components/util/icon.tsx
index 0662b2c94..fa1e86567 100644
--- a/lib/components/util/icon.js
+++ b/lib/components/util/icon.tsx
@@ -1,14 +1,21 @@
-import PropTypes from 'prop-types'
-import React from 'react'
import FontAwesome from 'react-fontawesome'
+import React from 'react'
import styled from 'styled-components'
+export type IconProps = {
+ className?: string
+ fixedWidth?: boolean
+ style?: Record
+ type: string
+ withSpace?: boolean
+}
+
/**
* A Font Awesome icon followed by a with a pseudo-element equivalent to a single space.
*/
const FontAwesomeWithSpace = styled(FontAwesome)`
&::after {
- content: "";
+ content: '';
margin: 0 0.125em;
}
`
@@ -19,23 +26,14 @@ const FontAwesomeWithSpace = styled(FontAwesome)`
* and that should work for both left-to-right and right-to-left layouts.
* Other props from FontAwesome are passed to that component.
*/
-const Icon = ({ fixedWidth = true, type, withSpace, ...props }) => {
- const FontComponent = withSpace
- ? FontAwesomeWithSpace
- : FontAwesome
- return (
-
- )
-}
-
-Icon.propTypes = {
- fixedWidth: PropTypes.bool,
- type: PropTypes.string.isRequired,
- withSpace: PropTypes.bool
+const Icon = ({
+ fixedWidth = true,
+ type,
+ withSpace = false,
+ ...props
+}: IconProps): JSX.Element => {
+ const FontComponent = withSpace ? FontAwesomeWithSpace : FontAwesome
+ return
}
export default Icon
diff --git a/lib/components/util/span-with-space.js b/lib/components/util/span-with-space.js
index 5d67d9c53..f34637c10 100644
--- a/lib/components/util/span-with-space.js
+++ b/lib/components/util/span-with-space.js
@@ -2,8 +2,8 @@ import styled from 'styled-components'
const SpanWithSpace = styled.span`
&::after {
- content: "";
- margin: 0 ${props => props.margin}em;
+ content: '';
+ margin: 0 ${(props) => props.margin}em;
}
`
export default SpanWithSpace
diff --git a/lib/components/util/strong-text.js b/lib/components/util/strong-text.js
index 7e498904a..959d124da 100644
--- a/lib/components/util/strong-text.js
+++ b/lib/components/util/strong-text.js
@@ -1,2 +1,3 @@
-const Strong = contents => {contents}
+import React from 'react'
+const Strong = (contents) => {contents}
export default Strong
diff --git a/lib/components/viewers/pattern-row.js b/lib/components/viewers/pattern-row.js
index 0f7de015c..52323b5e7 100644
--- a/lib/components/viewers/pattern-row.js
+++ b/lib/components/viewers/pattern-row.js
@@ -1,10 +1,12 @@
-import React, { Component } from 'react'
-import {FormattedMessage, injectIntl} from 'react-intl'
+// REMOVE THIS LINE BEFORE EDITING THIS FILE
+/* eslint-disable */
+import { FormattedMessage, injectIntl } from 'react-intl'
import { VelocityTransitionGroup } from 'velocity-react'
+import React, { Component } from 'react'
-import Strong from '../util/strong-text'
-import Icon from '../util/icon'
import { stopTimeComparator } from '../../util/viewer'
+import Icon from '../util/icon'
+import Strong from '../util/strong-text'
import RealtimeStatusLabel from './realtime-status-label'
import StopTimeCell from './stop-time-cell'
@@ -14,7 +16,7 @@ import StopTimeCell from './stop-time-cell'
* viewer.
*/
class PatternRow extends Component {
- constructor () {
+ constructor() {
super()
this.state = { expanded: false }
}
@@ -23,7 +25,7 @@ class PatternRow extends Component {
this.setState({ expanded: !this.state.expanded })
}
- render () {
+ render() {
const {
homeTimezone,
intl,
@@ -53,13 +55,13 @@ class PatternRow extends Component {
const routeName = route.shortName ? route.shortName : route.longName
return (
-
+
{/* header row */}
-
+
{/* route name */}
-
+
{/* next departure preview */}
{hasStopTimes && (
-
+
+