diff --git a/__tests__/test-utils/mock-data/empty-string.js b/__tests__/test-utils/mock-data/empty-string.js new file mode 100644 index 000000000..b0c50903a --- /dev/null +++ b/__tests__/test-utils/mock-data/empty-string.js @@ -0,0 +1 @@ +module.exports = '' diff --git a/lib/components/map/default-map.tsx b/lib/components/map/default-map.tsx index 0aaaaaa9a..074a28b8f 100644 --- a/lib/components/map/default-map.tsx +++ b/lib/components/map/default-map.tsx @@ -2,9 +2,15 @@ // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-nocheck import { connect } from 'react-redux' +import { + findRequiredOptionsForTransportMode, + RequiredOptionsForTransportMode +} from '@opentripplanner/trip-form' import { GeolocateControl, NavigationControl } from 'react-map-gl/maplibre' import { getCurrentDate } from '@opentripplanner/core-utils/lib/time' import { injectIntl } from 'react-intl' +import { MapLocationActionArg } from '@opentripplanner/types' +import { QueryParamChangeEvent } from '@opentripplanner/trip-form/lib/types' import BaseMap from '@opentripplanner/base-map' import generateOTP2TileLayers from '@opentripplanner/otp2-tile-overlay' import React, { Component } from 'react' @@ -19,10 +25,17 @@ import { vehicleRentalQuery } from '../../actions/api' import { ComponentContext } from '../../util/contexts' +import { decodeQueryParams } from 'serialize-query-params' import { getActiveItinerary, getActiveSearch } from '../../util/state' import { getCurrentPosition } from '../../actions/location' import { MainPanelContent } from '../../actions/ui-constants' +import { + modesQueryParamConfig, + onSettingsUpdate, + setModeButton +} from '../form/util' import { setLocation, setMapPopupLocationAndGeocode } from '../../actions/map' +import { setQueryParam } from '../../actions/form' import { setViewedStop } from '../../actions/ui' import { updateOverlayVisibility } from '../../actions/config' import TransitOperatorIcons from '../util/connected-transit-operator-icons' @@ -145,6 +158,48 @@ function getLayerName(overlay, config, intl) { } } +/** + * Creates a location handler that enables rental vehicle query params when the user + * clicks "from here" on a rental vehicle in Nearby view. Ideally, this logic would take place + * at a lower level, but the location handler is passed into an OTP-UI component from here; this + * is the lowest level achievable in OTP-RR + */ +const createLocationHandler = ( + config, + enabledModeButtons, + setQueryParam, + setLocation, + overlays +): ((location: MapLocationActionArg) => void) => { + return (location: MapLocationActionArg) => { + const overlayTypes = overlays + ?.find((overlay) => overlay?.type === 'otp2') + ?.layers?.map((layer) => layer?.type) + if (overlayTypes && overlayTypes.includes('rentalVehicles')) { + const requiredOptions: RequiredOptionsForTransportMode = + findRequiredOptionsForTransportMode( + config.modes.modeButtons, + config.modes.modeSettingDefinitions, + { mode: 'SCOOTER', qualifier: 'RENT' } + ) + if (requiredOptions) { + const modeSetter = setModeButton( + enabledModeButtons, + onSettingsUpdate(setQueryParam) + ) + + modeSetter(requiredOptions.modeButton, true) + + if (requiredOptions.modeSetting) + onSettingsUpdate(setQueryParam)({ + [requiredOptions.modeSetting]: true + }) + } + } + setLocation(location) + } +} + class DefaultMap extends Component { static contextType = ComponentContext @@ -310,6 +365,7 @@ class DefaultMap extends Component { carRentalQuery, carRentalStations, config, + enabledModeButtons, feeds, getCurrentPosition, intl, @@ -319,6 +375,7 @@ class DefaultMap extends Component { nearbyViewActive, pending, setLocation, + setQueryParam, setViewedStop, vehicleRentalQuery, vehicleRentalStations, @@ -476,7 +533,13 @@ class DefaultMap extends Component { name: getLayerName(l, config, intl) || l.network || l.type })), vectorTilesEndpoint, - setLocation, + createLocationHandler( + config, + enabledModeButtons, + setQueryParam, + setLocation, + overlays + ), setViewedStop, viewedRouteStops, config.companies, @@ -526,12 +589,20 @@ const mapStateToProps = (state) => { ) ) : null + const urlSearchParams = new URLSearchParams(state.router.location.search) + const { modes } = state.otp.config return { activeNearbyFilters, bikeRentalStations: state.otp.overlay.bikeRental.stations, carRentalStations: state.otp.overlay.carRental.stations, config: state.otp.config, + enabledModeButtons: + decodeQueryParams(modesQueryParamConfig, { + modeButtons: urlSearchParams.get('modeButtons') + })?.modeButtons || + modes?.initialState?.enabledModeButtons || + {}, feeds: state.otp.transitIndex.feeds, itinerary: getActiveItinerary(state), mapConfig: state.otp.config.map, @@ -553,6 +624,7 @@ const mapDispatchToProps = { getCurrentPosition, setLocation, setMapPopupLocationAndGeocode, + setQueryParam, setViewedStop, updateOverlayVisibility, vehicleRentalQuery diff --git a/package.json b/package.json index 6e1c18f77..c168fec6f 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "@opentripplanner/transit-vehicle-overlay": "7.0.0", "@opentripplanner/transitive-overlay": "7.0.0", "@opentripplanner/trip-details": "^8.0.1", - "@opentripplanner/trip-form": "6.0.1", + "@opentripplanner/trip-form": "6.1.2", "@opentripplanner/trip-viewer-overlay": "5.0.0", "@opentripplanner/vehicle-rental-overlay": "5.0.1", "@styled-icons/fa-regular": "^10.34.0", @@ -210,6 +210,7 @@ "i18n/(.*)\\.yml$": "__tests__/test-utils/mock-data/empty-yml.js", "modeSettings.yml$": "__tests__/test-utils/mock-data/empty-yml.js", "i18n-loader": "__tests__/test-utils/mock-data/i18n-loader.js", + "\\.graphql$": "__tests__/test-utils/mock-data/empty-string.js", "\\.(jpg|ico|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "__tests__/test-utils/mock-data/fileMock.js" }, "transform": { diff --git a/yarn.lock b/yarn.lock index 6de8c96bd..0c216f263 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2743,6 +2743,24 @@ lodash.isequal "^4.5.0" qs "^6.9.1" +"@opentripplanner/core-utils@14.2.0": + version "14.2.0" + resolved "https://registry.yarnpkg.com/@opentripplanner/core-utils/-/core-utils-14.2.0.tgz#6a047b729ed0572b8ceb0ea0d46a6da64d434a1e" + integrity sha512-LR0QBVjf3WNZAL1qYoL+ovA7o2IK/Kn1XCv0z3ZAXSTDBj0bNhRN+i1yrm40KUMtqOikjSp1NUmtXURMUFkwNA== + dependencies: + "@conveyal/lonlat" "^1.4.1" + "@mapbox/polyline" "^1.1.1" + "@opentripplanner/geocoder" "3.0.5" + "@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@5.0.0": version "5.0.0" resolved "https://registry.yarnpkg.com/@opentripplanner/endpoints-overlay/-/endpoints-overlay-5.0.0.tgz#216ba195ef7cf9dd8f76b862480c018e7b96ffc6" @@ -2806,6 +2824,14 @@ "@opentripplanner/core-utils" "13.0.0" prop-types "^15.7.2" +"@opentripplanner/icons@4.1.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@opentripplanner/icons/-/icons-4.1.0.tgz#9f02b318efa9bf9ad26bfbe9ce900da31821f4a5" + integrity sha512-lAfyJ1xkOFb42Zy6mehLCEx96kzeEwLWqBotpFy/RJMlXS1jahd/VaPRBqy8dqTUrj8y6zkqWesusDQUXiYLIg== + dependencies: + "@opentripplanner/core-utils" "14.2.0" + prop-types "^15.7.2" + "@opentripplanner/itinerary-body@7.0.4": version "7.0.4" resolved "https://registry.yarnpkg.com/@opentripplanner/itinerary-body/-/itinerary-body-7.0.4.tgz#d4d2022707f776cb26108a228c09da839dc264fc" @@ -2956,15 +2982,15 @@ flat "^5.0.2" react-animate-height "^3.0.4" -"@opentripplanner/trip-form@6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/@opentripplanner/trip-form/-/trip-form-6.0.1.tgz#262079589e056bf49c285a5b38257c54c59f088f" - integrity sha512-7o8frpL7rhieQmkrSaAxj+kqp8ocHyxmyB2l2LH8jJmoGgGevtQ5VxEmXaVctwpGJ12Fi1P/yiqD08PqnPG49w== +"@opentripplanner/trip-form@6.1.2": + version "6.1.2" + resolved "https://registry.yarnpkg.com/@opentripplanner/trip-form/-/trip-form-6.1.2.tgz#e0628c350ae2ef8a9ee681dfefd0bc2b6618d87a" + integrity sha512-3KrkOsFUZ5Xavy5ddSiWyfF11UFYINOluKtfFp1t0y4ViPIMF7BTmVJ6JC6VBnRch2TO77r6jLnpgMSPJ2bXxg== dependencies: "@floating-ui/react" "^0.19.2" "@opentripplanner/building-blocks" "3.0.1" - "@opentripplanner/core-utils" "13.0.1" - "@opentripplanner/icons" "4.0.0" + "@opentripplanner/core-utils" "14.2.0" + "@opentripplanner/icons" "4.1.0" "@styled-icons/bootstrap" "^10.34.0" "@styled-icons/boxicons-regular" "^10.38.0" "@styled-icons/fa-regular" "^10.37.0"