Skip to content
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
41 changes: 41 additions & 0 deletions i18n/en-US.yml
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,47 @@ components:
noneSelect: Don't notify me
notificationChannelPrompt: How would you like to receive notifications?
notificationEmailDetail: "Notification emails will be sent to:"
OTP2ErrorRenderer:
LOCATION_NOT_FOUND:
body: >-
{inputFields} {inputFieldsCount, plural, =0 {} one {location is} other
{locations are}} not near any streets.
header: Location not accessible
NO_STOPS_IN_RANGE:
body: >-
{inputFields} {inputFieldsCount, plural, =0 {} one {location is} other
{locations are}} not near any transit stops.
header: No stops in range
NO_TRANSIT_CONNECTION:
body: >-
No transit connection was found between your origin and destination on
the selected day of service.
header: No transit connections
NO_TRANSIT_CONNECTION_IN_SEARCH_WINDOW:
body: >-
A transit connection was found, but it was outside the search window,
try adjusting your search window.
header: No transit connection in search window
OUTSIDE_BOUNDS:
body: >-
{inputFields} {inputFieldsCount, plural, =0 {} one {location is} other
{locations are}} not in the bounds of the trip planner.
header: Location outside bounds
OUTSIDE_SERVICE_PERIOD:
body: >-
The date specified is outside the range of data currently loaded into
the trip planner.
header: Outside service period
SYSTEM_ERROR:
body: An unknown error happened during the search.
header: Trip Planner Failure
WALKING_BETTER_THAN_TRANSIT:
body: >-
Avoiding transit on your trip will be faster than taking transit.
header: Transit isn't the fastest way to make this trip
inputFields:
FROM: Origin
TO: Destination
PhoneNumberEditor:
changeNumber: Change number
invalidCode: Please enter 6 digits for the validation code.
Expand Down
19 changes: 18 additions & 1 deletion lib/actions/apiV2.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import coreUtils from '@opentripplanner/core-utils'

import { checkForRouteModeOverride } from '../util/config'
import { convertToPlace, getPersistenceMode } from '../util/user'
import { getActiveItinerary, queryIsValid } from '../util/state'
import {
getActiveItineraries,
getActiveItinerary,
queryIsValid
} from '../util/state'

import {
createQueryAction,
Expand Down Expand Up @@ -883,6 +887,19 @@ export function routingQuery(searchId = null, updateSearchInReducer) {
...itin,
legs: itin.legs?.map(processLeg)
}))

/* It is possible for a NO_TRANSIT_CONNECTION error to be
returned even if trips were returned, since it is on a mode-by-mode basis.
there is a chance for user confusion!

By checking if itineraries exist, we can hide this error when it is
not applicable. */
if (getActiveItineraries(getState()).length > 0) {
response.data.plan.routingErrors =
response.data.plan.routingErrors.filter(
(re) => re.code !== 'NO_TRANSIT_CONNECTION'
)
}
return {
response: {
plan: {
Expand Down
87 changes: 87 additions & 0 deletions lib/components/narrative/metro/OTP2ErrorRenderer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { ExclamationCircle } from '@styled-icons/fa-solid/ExclamationCircle'
import { FormattedMessage, useIntl } from 'react-intl'
import React from 'react'
import styled from 'styled-components'

import { Icon } from '../../util/styledIcon'

type Error = {
[code: string]: string[]
}

const List = styled.ul`
margin: 0;
padding: 0;
`
const Container = styled.li`
background: rgba(0, 0, 0, 0.1);
display: grid;
grid-template-columns: 1fr 3fr;
grid-template-rows: 1fr max-content;
list-style-type: none;
padding: 0 1em;
margin: 0;

h3 {
grid-column: 2;
grid-row: 1;
}

span {
grid-column: 1;
place-self: center;
grid-row: 1 / -1;
}

p {
grid-column: 2;
grid-row: 2;
padding-bottom: 10px;
}
`

const ErrorRenderer = ({ errors }: { errors: Error }): JSX.Element => {
const intl = useIntl()

return (
<List>
{Object.keys(errors).map((error: string) => {
const localizedInputFieldList = Array.from(errors[error])?.map(
(inputField) =>
intl.formatMessage({
id: `components.OTP2ErrorRenderer.inputFields.${inputField}`
})
)

// The search window is set by the client and can't be changed by the user.
// Do not tell them what's happening as they can't act on the issue.
if (error === 'NO_TRANSIT_CONNECTION_IN_SEARCH_WINDOW') {
return null
}

return (
<Container key={error}>
<Icon Icon={ExclamationCircle} size="3x" />
<h3>
<FormattedMessage
id={`components.OTP2ErrorRenderer.${error}.header`}
/>
</h3>
<p>
<FormattedMessage
id={`components.OTP2ErrorRenderer.${error}.body`}
values={{
inputFields: intl.formatList(localizedInputFieldList),
inputFieldsCount: localizedInputFieldList.length
}}
/>
</p>
</Container>
)
})}
</List>
)
}

export default ErrorRenderer
export type { Error }
42 changes: 35 additions & 7 deletions lib/components/narrative/narrative-itineraries.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { connect } from 'react-redux'
import { differenceInDays } from 'date-fns'
import { FormattedMessage, injectIntl, IntlShape } from 'react-intl'
import { Itinerary, Leg } from '@opentripplanner/types'
import clone from 'clone'
import coreUtils from '@opentripplanner/core-utils'
import React, { useContext, useState } from 'react'
Expand All @@ -17,8 +18,6 @@ import {
getVisibleItineraryIndex
} from '../../util/state'
import { getFirstLegStartTime, itinerariesAreEqual } from '../../util/itinerary'
import { Itinerary, Leg } from '@opentripplanner/types'

import {
setActiveItinerary,
setActiveLeg,
Expand All @@ -31,6 +30,7 @@ import PageTitle from '../util/page-title'

import * as S from './styled'
import { getItineraryDescription } from './default/itinerary-description'
import ErrorRenderer, { Error } from './metro/OTP2ErrorRenderer'
import Loading from './loading'
import NarrativeItinerariesErrors from './narrative-itineraries-errors'
import NarrativeItinerariesHeader from './narrative-itineraries-header'
Expand All @@ -50,6 +50,7 @@ type Props = {
customBatchUiBackground: any
errorMessages: any
errors: any
errorsOtp2: any
groupItineraries: boolean
groupTransitModes: boolean
hideFirstResultByDefault: any
Expand Down Expand Up @@ -89,6 +90,7 @@ const NarrativeItineraries = ({
customBatchUiBackground,
errorMessages,
errors,
errorsOtp2,
groupItineraries,
groupTransitModes,
hideFirstResultByDefault,
Expand Down Expand Up @@ -191,11 +193,16 @@ const NarrativeItineraries = ({
setShowingErrors(!showingErrors)
}

const _renderLoadingDivs = () => {
// If renderSkeletons is off, show standard spinner
const _renderLoadingSpinner = () => {
if (!renderSkeletons) {
return pending ? <Loading /> : null
}
}
const _renderLoadingDivs = () => {
// If renderSkeletons is off, don't render the skeleton-type loading divs
if (!renderSkeletons) {
return null
}

if (!pending || showingErrors) return null

Expand Down Expand Up @@ -405,7 +412,7 @@ const NarrativeItineraries = ({
overflowY: 'auto'
}}
>
{/* TODO PROPERLY RENDER */}
<ErrorRenderer errors={errorsOtp2} />
{showingErrors || mergedItineraries.length === 0 ? (
<NarrativeItinerariesErrors
errorMessages={errorMessages}
Expand Down Expand Up @@ -448,6 +455,7 @@ const NarrativeItineraries = ({
)}
</>
)}
{_renderLoadingSpinner()}
</div>
</S.NarrativeItinerariesContainer>
)
Expand All @@ -462,7 +470,7 @@ const mapStateToProps = (state: any) => {
activeSearch && activeSearch.activeItineraryTimeIndex
const { co2, errorMessages, modes } = state.otp.config
const { sort } = state.otp.filter
const pending = activeSearch ? Boolean(activeSearch.pending) : false
const pending = activeSearch?.pending > 0
const itineraries = getActiveItineraries(state)
const realtimeEffects = getRealtimeEffects(state)
const urlParams = coreUtils.query.getUrlParams()
Expand Down Expand Up @@ -498,7 +506,27 @@ const mapStateToProps = (state: any) => {
customBatchUiBackground,
errorMessages,
errors: getResponsesWithErrors(state),
// TODO: Destory otp1 errors and rename this
// TODO: Destroy otp1 errors and rename this
errorsOtp2: activeSearch?.response?.reduce(
// TODO: type
(acc: { [code: string]: Set<string | undefined> }, cur: any) => {
const { routingErrors } = cur?.plan
// code and inputfield
if (routingErrors) {
routingErrors.forEach(
(routingError: { code: string; inputField?: string }) => {
const { code, inputField } = routingError
if (!acc[code]) {
acc[code] = new Set()
}
acc[code].add(inputField)
}
)
}
return acc
},
{}
),
groupItineraries,
groupTransitModes,
hideFirstResultByDefault,
Expand Down