diff --git a/.github/workflows/percy.yml b/.github/workflows/percy.yml index c5e878b72..9684cb875 100644 --- a/.github/workflows/percy.yml +++ b/.github/workflows/percy.yml @@ -52,7 +52,7 @@ jobs: - name: Download OTP2 config file run: curl $PERCY_OTP2_CONFIG_URL --output /tmp/otp2config.yml env: - PERCY_OTP2_CONFIG_URL: ${{ secrets.PERCY_MOCK_OTP2_GEOCODER_CONFIG }} + PERCY_OTP2_CONFIG_URL: ${{ secrets.PERCY_MOCK_OTP2_GEOCODER_CALLTAKER_CONFIG }} - name: Build OTP-RR Calltaker # Calltaker has a separate config file, so another build should be produced. run: yarn build diff --git a/i18n/en-US.yml b/i18n/en-US.yml index 32c9b46f1..c9047adc0 100644 --- a/i18n/en-US.yml +++ b/i18n/en-US.yml @@ -52,6 +52,7 @@ actions: emailVerificationResent: The email verification message has been resent. genericError: "An error was encountered: {err}" itineraryExistenceCheckFailed: Error checking whether your selected trip is possible. + mustAcceptTermsToSavePlace: Please accept the Terms of Use (under My Account) to save locations. mustBeLoggedInToSavePlace: Please log in to save locations. placeRemembered: The settings for this place have been saved. preferencesSaved: Your preferences have been saved. @@ -677,6 +678,7 @@ components: notificationsDisabled: "Notifications: Disabled" TripTools: copyLink: Copy Link + header: Trip Tools linkCopied: Copied reportEmailSubject: Reporting an Issue with OpenTripPlanner reportEmailTemplate: > diff --git a/i18n/es.yml b/i18n/es.yml index 4df1c29df..be9f8314a 100644 --- a/i18n/es.yml +++ b/i18n/es.yml @@ -30,7 +30,8 @@ actions: No se puede guardar el plan: este plan no se pudo guardar debido a la falta de capacidad en uno o más vehículos. Por favor, vuelva a planificar su viaje. - maxTripRequestsExceeded: Número de solicitudes de viaje superadas sin resultados válidos + maxTripRequestsExceeded: Número de solicitudes de viaje superadas sin resultados + válidos saveItinerariesError: "No se pudieron guardar los itinerarios: {err}" setDateError: "Error al establecer la fecha:" setGroupSizeError: "No se pudo establecer el tamaño del grupo:" @@ -52,9 +53,13 @@ actions: authTokenError: Error al obtener un token de autorización. confirmDeleteMonitoredTrip: ¿Desea eliminar este viaje? confirmDeletePlace: ¿Quiere eliminar este lugar? - emailVerificationResent: El mensaje de verificación de correo electrónico ha sido reenviado. + emailVerificationResent: El mensaje de verificación de correo electrónico ha sido + reenviado. genericError: "Se ha encontrado un error: {err}" - itineraryExistenceCheckFailed: Comprobación de errores para ver si el viaje seleccionado es posible. + itineraryExistenceCheckFailed: Comprobación de errores para ver si el viaje seleccionado + es posible. + mustBeLoggedInToSavePlace: Por favor, inicia la sesión para guardar las ubicaciones. + placeRemembered: La configuración de este lugar se ha guardado. preferencesSaved: Sus preferencias se han guardado. smsInvalidCode: El código introducido no es válido. Por favor, inténtelo de nuevo. smsResendThrottled: >- @@ -63,8 +68,6 @@ actions: smsVerificationFailed: >- Su teléfono no ha podido ser verificado. Quizás el código que has introducido ha caducado. Solicita un nuevo código e inténtalo de nuevo. - mustBeLoggedInToSavePlace: Por favor, inicia la sesión para guardar las ubicaciones. - placeRemembered: La configuración de este lugar se ha guardado. common: coordinates: "{lat}; {lon}" dateExpressions: @@ -245,7 +248,8 @@ components: a incluir el transporte publico en la selección de modos. origin: origen planTripTooltip: Planificar viaje - validationMessage: "Por favor, defina los siguientes campos para planificar un viaje: {issues}" + validationMessage: "Por favor, defina los siguientes campos para planificar un + viaje: {issues}" BeforeSignInScreen: mainTitle: Iniciando sesión message: > @@ -487,11 +491,11 @@ components: tripsFound: Encontramos {count, plural, one {# opción} other {# opciones}} waiting: Espera... RouteDetails: + headsignTo: "{headsign} ({lastStop})" moreDetails: Más detalles operatedBy: Servicio operado por {agencyName} selectADirection: Seleccione una dirección… stopsTo: Hacia - headsignTo: '{headsign} ({lastStop})' RouteViewer: agencyFilter: Filtro de agencia allAgencies: Todas las agencias @@ -547,7 +551,8 @@ components: header: ¡La sesión está a punto de terminar! keepSession: Continuar sesión SimpleRealtimeAnnotation: - usingRealtimeInfo: Este viaje utiliza información de tráfico y retrasos en tiempo real + usingRealtimeInfo: Este viaje utiliza información de tráfico y retrasos en tiempo + real StackedPaneDisplay: savePreferences: Guardar preferencias StopScheduleTable: @@ -610,16 +615,19 @@ components: travelingAt: Viajando a {milesPerHour} vehicleName: Vehículo {vehicleNumber} TripBasicsPane: - checkingItineraryExistence: Comprobación de la existencia de itinerarios para cada día de la semana… + checkingItineraryExistence: Comprobación de la existencia de itinerarios para + cada día de la semana… selectAtLeastOneDay: Por favor, seleccione al menos un día para el seguimiento. tripDaysPrompt: ¿Qué días hace este viaje? - tripIsAvailableOnDaysIndicated: Su viaje está disponible en los días de la semana indicados anteriormente. + tripIsAvailableOnDaysIndicated: Su viaje está disponible en los días de la semana + indicados anteriormente. tripNamePrompt: "Por favor, indique un nombre para este viaje:" tripNotAvailableOnDay: El viaje no está disponible el {repeatedDay} unsavedChangesExistingTrip: >- Todavía no ha guardado su viaje. Si abandona la página, los cambios se perderán. - unsavedChangesNewTrip: Todavía no ha guardado su nuevo viaje. Si abandona la página, se perderá. + unsavedChangesNewTrip: Todavía no ha guardado su nuevo viaje. Si abandona la página, + se perderá. TripNotificationsPane: advancedSettings: Configuración avanzada altRouteRecommended: Se recomienda una ruta alternativa o un punto de transferencia @@ -700,9 +708,10 @@ components: notifications: >- Notificaciones: {leadTimeInMinutes} minutos antes de la salida programada - notificationsDisabled: "Notificaciones: Disabled" + notificationsDisabled: "Notificaciones: Desactivadas" TripTools: copyLink: Copiar enlace + header: Herramientas linkCopied: Copiado reportEmailSubject: Informar un problema con OpenTripPlanner reportEmailTemplate: > diff --git a/i18n/fr.yml b/i18n/fr.yml index 6e7c7d6ba..c854ba23b 100644 --- a/i18n/fr.yml +++ b/i18n/fr.yml @@ -58,6 +58,9 @@ actions: nouveau. genericError: "Une erreur s'est produite : {err}" itineraryExistenceCheckFailed: Erreur lors de la vérification de la validité du trajet choisi. + mustAcceptTermsToSavePlace: >- + Veuillez accepter les conditions d'utilisation (dans Mon compte) pour + enregistrer des lieux. mustBeLoggedInToSavePlace: Veuillez vous connecter pour enregistrer des lieux. placeRemembered: Les informations pour ce lieu ont été enregistrées. preferencesSaved: Vos préférences ont été enregistrées. @@ -703,6 +706,7 @@ components: notificationsDisabled: "Notifications : Désactivées" TripTools: copyLink: Copier le lien + header: Outils linkCopied: Copié reportEmailSubject: Signaler un problème avec OpenTripPlanner reportEmailTemplate: > diff --git a/i18n/ko.yml b/i18n/ko.yml index c36644127..53bfc40c3 100644 --- a/i18n/ko.yml +++ b/i18n/ko.yml @@ -22,7 +22,8 @@ actions: fetchFieldTripsError: "트립을 가져오는 중에 오류가 발생했습니다: {err}" fetchTripsForDateError: "필트 트립 날짜에 대한 트립을 가져오는 중에 오류가 발생했습니다: {err}" incompatibleTripDateError: 계획한 트립 날짜({tripDate})가 요청한 트립 날짜({requestDate})가 아닙니다 - itineraryCapacityError: "플랜을 저장할 수 없습니다: 하나 이상의 차량의 용량이 부족하여 이 플랜을 저장할 수 없습니다. 트립을 다시 계획하세요." + itineraryCapacityError: "플랜을 저장할 수 없습니다: 하나 이상의 차량의 용량이 부족하여 이 플랜을 저장할 수 없습니다. + 트립을 다시 계획하세요." maxTripRequestsExceeded: 유효한 결과없이 초과된 트립 요청의 개수 saveItinerariesError: "트립을 저장하는 데 실패했습니다: {err}" setDateError: "날짜를 설정하는 중에 오류가 발생했습니다:" @@ -504,6 +505,7 @@ components: notifyViaChannelWhen: "다음의 경우, {channel}을 통해 알려주세요:" oneHour: 1 시간 realtimeAlertFlagged: 내 트립 일정에 실시간 경고가 있습니다 + timeBefore: '{time} 전' TripStatus: alerts: "{alerts, plural, one {# 경고!} other {# 경고!}}" deleteTrip: 트립 삭제 @@ -561,6 +563,7 @@ components: notificationsDisabled: "알림: 비활성화됨" TripTools: copyLink: 링크 복사 + header: 도구 linkCopied: 복사됨 reportEmailSubject: OpenTripPlanner로 문제 보고 reportEmailTemplate: | @@ -640,5 +643,6 @@ util: networkUnavailable: 현재 {network} 네트워크를 사용할 수 없습니다. noTripFound: 트립을 찾을 수 없습니다. noTripFoundForMode: "{modes}의 트립을 찾을 수 없습니다." - noTripFoundReason: 지정된 최대 거리 내 또는 지정된 시간에 대중 교통 서비스가 없거나, 출발지 또는 도착지가 안전하게 접근가능하지 못할 수 있습니다. + noTripFoundReason: 지정된 최대 거리 내 또는 지정된 시간에 대중 교통 서비스가 없거나, 출발지 또는 도착지가 안전하게 접근가능하지 + 못할 수 있습니다. noTripFoundWithReason: "{noTripFound} {reason}" diff --git a/i18n/vi.yml b/i18n/vi.yml index e31f08271..471a3e7cb 100644 --- a/i18n/vi.yml +++ b/i18n/vi.yml @@ -602,6 +602,7 @@ components: notificationsDisabled: "Thông báo: Đã tắt" TripTools: copyLink: Sao chép URL + header: Công cụ linkCopied: Đã sao chép reportEmailSubject: Báo cáo sự cố với OpenTripPlanner reportEmailTemplate: | diff --git a/i18n/zh.yml b/i18n/zh.yml index 021e91a97..d487fa672 100644 --- a/i18n/zh.yml +++ b/i18n/zh.yml @@ -502,6 +502,7 @@ components: notifyViaChannelWhen: "通过 {channel} 通知我当:" oneHour: 1小时 realtimeAlertFlagged: 在我的行程中有一个实时警报标志着 + timeBefore: '{time} 前' TripStatus: alerts: "{alerts, plural, one {# 警报!} other {# 警报!}}" deleteTrip: 删除行程 @@ -559,6 +560,7 @@ components: notificationsDisabled: "通知: 已禁用" TripTools: copyLink: 复制链接 + header: 工具 linkCopied: 已复制 reportEmailSubject: 报告OpenTripPlanner的问题 reportEmailTemplate: | diff --git a/lib/actions/apiV2.js b/lib/actions/apiV2.js index b6ba5140f..d8de7a73a 100644 --- a/lib/actions/apiV2.js +++ b/lib/actions/apiV2.js @@ -14,8 +14,10 @@ import { getActiveItineraries, getActiveItinerary, isValidSubsequence, + getRouteOperator, queryIsValid } from '../util/state' +import { getRouteColorBasedOnSettings } from '../util/viewer' import { ItineraryView } from '../util/ui' import { @@ -698,6 +700,10 @@ export const findRoute = (params) => newRoute.patterns = routePatterns // TODO: avoid explicit behavior shift like this newRoute.v2 = true + newRoute.color = getRouteColorBasedOnSettings( + getRouteOperator(route, getState().otp.config.transitOperators), + route + ).split('#')[1] newRoute.mode = checkForRouteModeOverride( newRoute, getState().otp.config?.routeModeOverrides @@ -745,29 +751,27 @@ export function findRoutes() { const { config } = getState().otp // To initialize the route viewer, // convert the routes array to a dictionary indexed by route ids. - return routes.reduce( - ( - result, - { agency, color, id, longName, mode, shortName, type } - ) => { - result[id] = { - agencyId: agency.id, - agencyName: agency.name, - color, - id, - longName, - mode: checkForRouteModeOverride( - { id, mode }, - config?.routeModeOverrides - ), - shortName, - type, - v2: true - } - return result - }, - {} - ) + return routes.reduce((result, route) => { + const { agency, id, longName, mode, shortName, type } = route + result[id] = { + agencyId: agency.id, + agencyName: agency.name, + color: getRouteColorBasedOnSettings( + getRouteOperator(route, config.transitOperators), + route + ).split('#')[1], + id, + longName, + mode: checkForRouteModeOverride( + { id, mode }, + config?.routeModeOverrides + ), + shortName, + type, + v2: true + } + return result + }, {}) } } ) @@ -971,9 +975,27 @@ export function routingQuery(searchId = null, updateSearchInReducer) { const withCollapsedShortNames = filteredItineraries.map( (itin) => ({ ...itin, - legs: itin.legs?.map(convertGraphQLResponseToLegacy) - }) - ) + legs: itin.legs + ?.map((leg) => { + return { + ...leg, + route: { + ...leg.route, + color: getRouteColorBasedOnSettings( + getRouteOperator( + { + agencyId: leg?.agency?.id, + id: leg?.route?.id + }, + config.transitOperators + ), + { color: leg?.route?.color, mode: leg.mode } + ).split('#')?.[1] + } + } + }) + ?.map(convertGraphQLResponseToLegacy) + })) /* 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. diff --git a/lib/actions/user.js b/lib/actions/user.js index 4c5336bff..eb934817b 100644 --- a/lib/actions/user.js +++ b/lib/actions/user.js @@ -746,20 +746,29 @@ export function rememberPlace(placeTypeLocation, intl) { if (persistenceMode.isOtpMiddleware) { if (loggedInUser) { - // For middleware loggedInUsers, this method should only be triggered by the - // 'Save as home' or 'Save as work' links from OTP UI's EndPointOverlay/EndPoint. - const { location } = placeTypeLocation - if (isHomeOrWork(location)) { - // Find the index of the place in the loggedInUser.savedLocations - const placeIndex = loggedInUser.savedLocations.findIndex( - (loc) => loc.type === location.type - ) - if (placeIndex > -1) { - // Convert to loggedInUser saved place - return dispatch( - saveUserPlace(convertToPlace(location), placeIndex, intl) + if (loggedInUser.hasConsentedToTerms) { + // For middleware loggedInUsers who have accepted the terms of use, + // this method should only be triggered by the + // 'Save as home' or 'Save as work' links from OTP UI's EndPointOverlay/EndPoint. + const { location } = placeTypeLocation + if (isHomeOrWork(location)) { + // Find the index of the place in the loggedInUser.savedLocations + const placeIndex = loggedInUser.savedLocations.findIndex( + (loc) => loc.type === location.type ) + if (placeIndex > -1) { + // Convert to loggedInUser saved place + return dispatch( + saveUserPlace(convertToPlace(location), placeIndex, intl) + ) + } } + } else { + alert( + intl.formatMessage({ + id: 'actions.user.mustAcceptTermsToSavePlace' + }) + ) } } else { alert( diff --git a/lib/app.js b/lib/app.js index 4ea733227..fda84a81f 100644 --- a/lib/app.js +++ b/lib/app.js @@ -21,12 +21,7 @@ import { BatchResultsScreen, BatchRoutingPanel, BatchSearchScreen, - CallHistoryWindow, - CallTakerControls, - CallTakerPanel, DefaultItinerary, - FieldTripWindows, - MailablesWindow, ResponsiveWebapp } from './index' @@ -178,17 +173,9 @@ const components = { ItineraryBody: DefaultItinerary, - MainControls: isCallTakerModuleEnabled ? CallTakerControls : null, + MainPanel: BatchRoutingPanel, - MainPanel: isCallTakerModuleEnabled ? CallTakerPanel : BatchRoutingPanel, - - MapWindows: isCallTakerModuleEnabled ? ( - <> - - - - - ) : null, + MapWindows: isCallTakerModuleEnabled ? jsConfig.MapWindows : null, MobileResultsScreen: BatchResultsScreen, diff --git a/lib/components/app/batch-routing-panel.tsx b/lib/components/app/batch-routing-panel.tsx index 9115ecbb3..c1321091e 100644 --- a/lib/components/app/batch-routing-panel.tsx +++ b/lib/components/app/batch-routing-panel.tsx @@ -3,6 +3,7 @@ import { FormattedMessage, injectIntl, IntlShape } from 'react-intl' import React, { Component, FormEvent } from 'react' import { getActiveSearch, getShowUserSettings } from '../../util/state' +import { getPersistenceMode } from '../../util/user' import BatchSettings from '../form/batch-settings' import InvisibleA11yLabel from '../util/invisible-a11y-label' import LocationField from '../form/connected-location-field' @@ -108,7 +109,12 @@ class BatchRoutingPanel extends Component { // connect to the redux store const mapStateToProps = (state: any) => { - const showUserSettings = getShowUserSettings(state) + // Show the place shortcuts for OTP-middleware users who have accepted the terms of use + // and deployments using persistence to localStorage. Don't show shortcuts otherwise. + const showUserSettings = + getShowUserSettings(state) && + (state.user.loggedInUser?.hasConsentedToTerms || + getPersistenceMode(state.otp.config.persistence).isLocalStorage) return { activeSearch: getActiveSearch(state), showUserSettings diff --git a/lib/components/narrative/line-itin/connected-itinerary-body.js b/lib/components/narrative/line-itin/connected-itinerary-body.js index 81cdb475a..12c51897a 100644 --- a/lib/components/narrative/line-itin/connected-itinerary-body.js +++ b/lib/components/narrative/line-itin/connected-itinerary-body.js @@ -5,7 +5,8 @@ import { LegDescriptionHeadsignPrefix, PlaceName as PlaceNameWrapper, PlaceRowWrapper, - PlaceSubheader + PlaceSubheader, + TimeColumn } from '@opentripplanner/itinerary-body/lib/styled' import { PlaceName } from '@opentripplanner/itinerary-body/lib/otp-react-redux' import clone from 'clone' @@ -53,6 +54,9 @@ const StyledItineraryBody = styled(ItineraryBody)` ${PlaceRowWrapper} { max-width: inherit; } + ${TimeColumn} { + white-space: nowrap; + } ` class ConnectedItineraryBody extends Component { diff --git a/lib/components/narrative/metro/metro-itinerary.tsx b/lib/components/narrative/metro/metro-itinerary.tsx index cbd0f7194..6eff07f40 100644 --- a/lib/components/narrative/metro/metro-itinerary.tsx +++ b/lib/components/narrative/metro/metro-itinerary.tsx @@ -75,7 +75,6 @@ const ItineraryDetails = styled.ul` margin: 0; overflow: hidden; padding: 0; - width: 90%; ` const PrimaryInfo = styled.li` color: #000000cc; diff --git a/lib/components/narrative/trip-tools.js b/lib/components/narrative/trip-tools.js index 449e63f3c..ecd3518bc 100644 --- a/lib/components/narrative/trip-tools.js +++ b/lib/components/narrative/trip-tools.js @@ -185,7 +185,6 @@ LinkButton.propTypes = { const TripTools = ({ buttonTypes, - intl, location, popupTarget, reactRouterConfig, @@ -224,7 +223,6 @@ const TripTools = ({ location?.search ) buttonComponents.push( - // FIXME: The Spanish string does not fit in button width. } @@ -250,7 +248,9 @@ const TripTools = ({ return (
-

Trip Tools

+

+ +

{buttonComponents.map((btn, i) => (
@@ -264,7 +264,6 @@ const TripTools = ({ TripTools.propTypes = { buttonTypes: PropTypes.arrayOf(PropTypes.string), - intl: PropTypes.object, location: PropTypes.object, popupTarget: PropTypes.string, reactRouterConfig: PropTypes.object, @@ -290,5 +289,5 @@ const mapDispatchToProps = { } export default withRouter( - connect(mapStateToProps, mapDispatchToProps)(injectIntl(TripTools)) + connect(mapStateToProps, mapDispatchToProps)(TripTools) ) diff --git a/lib/components/user/nav-login-button-auth0.tsx b/lib/components/user/nav-login-button-auth0.tsx index cdc60bf44..e4a44bf14 100644 --- a/lib/components/user/nav-login-button-auth0.tsx +++ b/lib/components/user/nav-login-button-auth0.tsx @@ -28,14 +28,18 @@ const NavLoginButtonAuth0 = ({ }: NavLoginButtonAuth0Props): JSX.Element => { const { isAuthenticated, loginWithRedirect, logout, user } = useAuth0() + // For Chinese (Chinese (Simplified)), we must pass 'zh-CN' to auth0. + // Unlike 'fr', 'zh' alone is not recognized and falls back to English. + const auth0Locale = locale === 'zh' ? 'zh-CN' : locale + // On login, preserve the current trip query if any. const handleLogin = useCallback( () => loginWithRedirect({ appState: { returnTo: getCurrentRoute() }, - ui_locales: locale + ui_locales: auth0Locale }), - [locale, loginWithRedirect] + [auth0Locale, loginWithRedirect] ) const handleLogout = useCallback( () => diff --git a/lib/index.js b/lib/index.js index 9b8180f8a..f7d142a80 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,9 +1,5 @@ /* eslint-disable prettier/prettier */ /* eslint-disable sort-imports-es6-autofix/sort-imports-es6 */ -import CallTakerControls from './components/admin/call-taker-controls' -import CallHistoryWindow from './components/admin/call-history-window' -import FieldTripWindows from './components/admin/field-trip-windows' -import MailablesWindow from './components/admin/mailables-window' import DateTimeModal from './components/form/date-time-modal' import DateTimePreview from './components/form/date-time-preview' import ErrorMessage from './components/form/error-message' @@ -29,7 +25,6 @@ import ViewStopButton from './components/viewers/view-stop-button' import ViewerContainer from './components/viewers/viewer-container' import ResponsiveWebapp from './components/app/responsive-webapp' import AppMenu from './components/app/app-menu' -import CallTakerPanel from './components/app/call-taker-panel' import DesktopNav from './components/app/desktop-nav' import BatchRoutingPanel from './components/app/batch-routing-panel' import BatchResultsScreen from './components/mobile/batch-results-screen' @@ -50,12 +45,6 @@ const MobileResultsScreen = BatchResultsScreen const MobileSearchScreen = BatchSearchScreen export { - // module components - CallHistoryWindow, - CallTakerControls, - FieldTripWindows, - MailablesWindow, - // form components DateTimeModal, DateTimePreview, @@ -96,7 +85,6 @@ export { // app components, ResponsiveWebapp, AppMenu, - CallTakerPanel, DesktopNav, // batch routing components diff --git a/lib/reducers/call-taker.js b/lib/reducers/call-taker.js index 202bc22c0..9953484ef 100644 --- a/lib/reducers/call-taker.js +++ b/lib/reducers/call-taker.js @@ -6,11 +6,17 @@ import { FETCH_STATUS } from '../util/constants' import { getISOLikeTimestamp } from '../util/state' import { getModuleConfig, Modules } from '../util/config' +function getCalltakerConfig(config) { + return getModuleConfig({ otp: { config } }, Modules.CALL_TAKER) +} + function createCallTakerReducer(config) { - const calltakerConfig = getModuleConfig( - { otp: { config } }, - Modules.CALL_TAKER - ) + const calltakerConfig = getCalltakerConfig(config) + if (!calltakerConfig) { + // Don't include the calltaker reducer at all if calltaker is not enabled in config. + return undefined + } + const initialState = { activeCall: null, callHistory: { diff --git a/lib/util/i18n.js b/lib/util/i18n.js index 9dfe53126..2674a781e 100644 --- a/lib/util/i18n.js +++ b/lib/util/i18n.js @@ -117,6 +117,7 @@ async function loadOtpUiLocaleData(matchedLocale) { */ export async function loadLocaleData(matchedLocale, customMessages) { let messages + let otpUiLocale = matchedLocale switch (matchedLocale) { case 'es': // Spanish translation is not specific to a region messages = await import('../../i18n/es.yml') @@ -130,15 +131,18 @@ export async function loadLocaleData(matchedLocale, customMessages) { case 'vi': // Vietnamese translation is not specific to a region messages = await import('../../i18n/vi.yml') break - case 'zh': // Chinese translation is not specific to a region + case 'zh': // Chinese (Simplified) translation is not specific to a region messages = await import('../../i18n/zh.yml') + // The OTP-UI files for Chinese (Simplified) are (correctly) named `zh_Hans`. + // TODO: Rename this repo's zh files to zh_Hans + otpUiLocale = 'zh_Hans' break default: messages = await import('../../i18n/en-US.yml') break } - const otpUiMessages = await loadOtpUiLocaleData(matchedLocale) + const otpUiMessages = await loadOtpUiLocaleData(otpUiLocale) // Merge custom strings into the standard language strings. const mergedMessages = { diff --git a/lib/util/state.js b/lib/util/state.js index eac0ef907..187ed0c77 100644 --- a/lib/util/state.js +++ b/lib/util/state.js @@ -245,8 +245,8 @@ function getActiveSearchRealtimeResponse(state) { * https://decembersoft.com/posts/error-selector-creators-expect-all-input-selectors-to-be-functions/ */ export const getActiveFieldTripRequest = createSelector( - (state) => state.callTaker?.fieldTrip.activeId, - (state) => state.callTaker?.fieldTrip.requests, + (state) => state.callTaker?.fieldTrip?.activeId, + (state) => state.callTaker?.fieldTrip?.requests, (activeId, requests) => { if (!activeId || !requests) return return requests.data.find((req) => req.id === activeId) diff --git a/lib/util/viewer.js b/lib/util/viewer.js index 9f1f9ef9d..b2e9df92e 100644 --- a/lib/util/viewer.js +++ b/lib/util/viewer.js @@ -383,7 +383,7 @@ export function getColorAndNameFromRoute(operator, route) { const modeColors = operator?.modeColors?.[getModeFromRoute(route)] const backgroundColor = `#${ - modeColors?.color || defaultRouteColor || route.color || '333333' + modeColors?.color || defaultRouteColor || route?.color || '333333' }` // NOTE: text color is not a part of short response route object, so there // is no way to determine from OTP what the text color should be if the diff --git a/package.json b/package.json index e25487124..75253a754 100644 --- a/package.json +++ b/package.json @@ -39,13 +39,13 @@ "@bugsnag/plugin-react": "^7.17.0", "@floating-ui/react": "^0.19.2", "@opentripplanner/base-map": "^3.0.15", - "@opentripplanner/core-utils": "^11.0.6", + "@opentripplanner/core-utils": "^11.1.2", "@opentripplanner/endpoints-overlay": "^2.0.9", "@opentripplanner/from-to-location-picker": "^2.1.9", "@opentripplanner/geocoder": "^1.4.2", "@opentripplanner/humanize-distance": "^1.2.0", "@opentripplanner/icons": "^2.0.6", - "@opentripplanner/itinerary-body": "^5.1.0", + "@opentripplanner/itinerary-body": "^5.1.1", "@opentripplanner/location-field": "^2.0.11", "@opentripplanner/location-icon": "^1.4.1", "@opentripplanner/map-popup": "^2.0.6", @@ -58,7 +58,7 @@ "@opentripplanner/transit-vehicle-overlay": "^4.0.6", "@opentripplanner/transitive-overlay": "^3.0.16", "@opentripplanner/trip-details": "^5.0.4", - "@opentripplanner/trip-form": "^3.3.4", + "@opentripplanner/trip-form": "^3.3.5", "@opentripplanner/trip-viewer-overlay": "^2.0.7", "@opentripplanner/vehicle-rental-overlay": "^2.1.3", "@styled-icons/fa-regular": "^10.34.0", diff --git a/percy/har-mock-config-call-taker.js b/percy/har-mock-config-call-taker.js index f1e892114..418f5f6c9 100644 --- a/percy/har-mock-config-call-taker.js +++ b/percy/har-mock-config-call-taker.js @@ -14,14 +14,14 @@ import React from 'react' import { BatchResultsScreen, BatchSearchScreen, - CallHistoryWindow, - CallTakerPanel, - FieldTripWindows, - MailablesWindow, MetroItinerary // Webpack sets this file to run from a subdirectory within otp-react-redux // ../lib points to the index file of otp-react-redux's source code } from '../lib' +import CallHistoryWindow from '../lib/components/admin/call-history-window' +import CallTakerPanel from '../lib/components/app/call-taker-panel' +import FieldTripWindows from '../lib/components/admin/field-trip-windows' +import MailablesWindow from '../lib/components/admin/mailables-window' /** * Custom itinerary footer for this deployment. diff --git a/yarn.lock b/yarn.lock index 61aeaa722..c7eee4146 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2353,7 +2353,7 @@ maplibre-gl "^2.1.9" react-map-gl "^7.0.15" -"@opentripplanner/core-utils@^11.0.2", "@opentripplanner/core-utils@^11.0.6": +"@opentripplanner/core-utils@^11.0.2": version "11.0.6" resolved "https://registry.yarnpkg.com/@opentripplanner/core-utils/-/core-utils-11.0.6.tgz#f8bd9796b4a9bc7490fb7fdca9ce661ed785bab1" integrity sha512-ullRWOhvx4TzCmNk97Fk3FefX5jVlk0oYaLUsSfZNJJSiO0WKQadHBaxXBHQ6JHv7pk9SPuEP7xXfjz8YV6vRA== @@ -2371,6 +2371,24 @@ lodash.isequal "^4.5.0" qs "^6.9.1" +"@opentripplanner/core-utils@^11.1.2": + version "11.1.2" + resolved "https://registry.yarnpkg.com/@opentripplanner/core-utils/-/core-utils-11.1.2.tgz#a99d1fa4fb1f587d58fae8fcfd70a3bfe4eed424" + integrity sha512-Rn1tBm5F+nt/A4/0cpq6cyNTprSsxfFslujMoZ4P4r6fZ7YTx0d25di+MZ/CZgSlCzHJIiGAfi8DsfJ7yStcTA== + dependencies: + "@conveyal/lonlat" "^1.4.1" + "@mapbox/polyline" "^1.1.0" + "@opentripplanner/geocoder" "^1.4.2" + "@styled-icons/foundation" "^10.34.0" + "@turf/along" "^6.0.1" + chroma-js "^2.4.2" + date-fns "^2.28.0" + date-fns-tz "^1.2.2" + graphql "^16.6.0" + lodash.clonedeep "^4.5.0" + lodash.isequal "^4.5.0" + qs "^6.9.1" + "@opentripplanner/endpoints-overlay@^2.0.9": version "2.0.9" resolved "https://registry.yarnpkg.com/@opentripplanner/endpoints-overlay/-/endpoints-overlay-2.0.9.tgz#74a5bab257686130dea768dc921ac197dc0a7c0d" @@ -2413,7 +2431,7 @@ "@opentripplanner/core-utils" "^11.0.2" prop-types "^15.7.2" -"@opentripplanner/itinerary-body@^5.0.2", "@opentripplanner/itinerary-body@^5.0.7", "@opentripplanner/itinerary-body@^5.1.0": +"@opentripplanner/itinerary-body@^5.0.2", "@opentripplanner/itinerary-body@^5.0.7": version "5.1.0" resolved "https://registry.yarnpkg.com/@opentripplanner/itinerary-body/-/itinerary-body-5.1.0.tgz#29bba91db379bb4f63d13ec95c1cb058248d0683" integrity sha512-DTS4KlbqokS/ZA+gL0QCuPFORoPq/GVcHlRv0s9YqKmr0aS5eBBLg4f4mOuv1CcNeHKyZc6lNb/Ro3epjshd5A== @@ -2431,6 +2449,24 @@ react-resize-detector "^4.2.1" string-similarity "^4.0.4" +"@opentripplanner/itinerary-body@^5.1.1": + version "5.1.1" + resolved "https://registry.yarnpkg.com/@opentripplanner/itinerary-body/-/itinerary-body-5.1.1.tgz#bcd76d6bc079c6407088223d021a5777358ab060" + integrity sha512-obsPBhgvpvVkJuTsgNhQFN+3QKpxlzrNU3FGUZT+PAkgUHQxFo/6FQ0LNGSC+dTBBS1t7EKDfC0czQz4KZPQiw== + dependencies: + "@opentripplanner/core-utils" "^11.0.2" + "@opentripplanner/humanize-distance" "^1.2.0" + "@opentripplanner/icons" "^2.0.5" + "@opentripplanner/location-icon" "^1.4.1" + "@styled-icons/fa-solid" "^10.34.0" + "@styled-icons/foundation" "^10.34.0" + date-fns "^2.28.0" + date-fns-tz "^1.2.2" + flat "^5.0.2" + react-animate-height "^3.0.4" + react-resize-detector "^4.2.1" + string-similarity "^4.0.4" + "@opentripplanner/location-field@^2.0.11": version "2.0.11" resolved "https://registry.yarnpkg.com/@opentripplanner/location-field/-/location-field-2.0.11.tgz#06a31ec2e62cf5b542d3392281bc28b9dd1ea416" @@ -2561,10 +2597,10 @@ flat "^5.0.2" react-animate-height "^3.0.4" -"@opentripplanner/trip-form@^3.3.4": - version "3.3.4" - resolved "https://registry.yarnpkg.com/@opentripplanner/trip-form/-/trip-form-3.3.4.tgz#12847736515aa11e1c69c8db627a54c1ad5e7e89" - integrity sha512-adEjAJ+2ygkc6vptiD7tHI3O+1tEIvoDsM3+/DFIgvtKxkSKuRzzTNXn67CI2pmEev75aiiTyT97PiWbDKRTJw== +"@opentripplanner/trip-form@^3.3.5": + version "3.3.5" + resolved "https://registry.yarnpkg.com/@opentripplanner/trip-form/-/trip-form-3.3.5.tgz#0c27191f6c3bfb491130d0f849a4086f199d9f20" + integrity sha512-rDfHWqaL9RTskgJPoV2acu7nRbxwtji5yposOVTzx4LK4GVOxczbpAD6KxXXLbx8nYwp+L6y2vqX97GmlogHKQ== dependencies: "@floating-ui/react" "^0.19.2" "@opentripplanner/core-utils" "^11.0.2"