diff --git a/lib/components/form/date-time-modal.js b/lib/components/form/date-time-modal.js deleted file mode 100644 index 0936ebbe9..000000000 --- a/lib/components/form/date-time-modal.js +++ /dev/null @@ -1,76 +0,0 @@ -// TODO: TypeScript with props. -/* eslint-disable react/prop-types */ -import { connect } from 'react-redux' -import coreUtils from '@opentripplanner/core-utils' -import PropTypes from 'prop-types' -import React, { Component } from 'react' - -import { setQueryParam } from '../../actions/form' - -import { StyledDateTimeSelector } from './styled' - -class DateTimeModal extends Component { - static propTypes = { - setQueryParam: PropTypes.func - } - - render() { - const { - config, - date, - dateFormatLegacy, - departArrive, - setQueryParam, - time, - timeFormatLegacy - } = this.props - const { homeTimezone, isTouchScreenOnDesktop } = config - const touchClassName = isTouchScreenOnDesktop - ? 'with-desktop-touchscreen' - : '' - - return ( -
-
- `. - // These props are not relevant in modern browsers, - // where `` already - // formats the time|date according to the OS settings. - // eslint-disable-next-line react/jsx-sort-props - timeFormatLegacy={timeFormatLegacy} - timeZone={homeTimezone} - /> -
-
- ) - } -} - -const mapStateToProps = (state) => { - const { date, departArrive, time } = state.otp.currentQuery - const config = state.otp.config - return { - config, - date, - // This prop is for legacy browsers (see render method above). - dateFormatLegacy: coreUtils.time.getDateFormat(config), - departArrive, - time, - // This prop is for legacy browsers (see render method above). - timeFormatLegacy: coreUtils.time.getTimeFormat(config) - } -} - -const mapDispatchToProps = { - setQueryParam -} - -export default connect(mapStateToProps, mapDispatchToProps)(DateTimeModal) diff --git a/lib/components/form/date-time-modal.tsx b/lib/components/form/date-time-modal.tsx new file mode 100644 index 000000000..bf41b281a --- /dev/null +++ b/lib/components/form/date-time-modal.tsx @@ -0,0 +1,107 @@ +import { connect } from 'react-redux' +import { decodeQueryParams, StringParam } from 'serialize-query-params' +import coreUtils from '@opentripplanner/core-utils' +import React, { useCallback } from 'react' + +import { AppConfig } from '../../util/config-types' +import { AppReduxState } from '../../util/state-types' +import { setQueryParam } from '../../actions/form' + +import { StyledDateTimeSelector } from './styled' + +export type DepartArriveValue = 'NOW' | 'DEPART' | 'ARRIVE' + +type Props = { + config: AppConfig + date: any + dateFormatLegacy: string + departArrive: DepartArriveValue + setQueryParam: (params: any) => void + time: any + timeFormatLegacy: string +} + +function DateTimeModal({ + config, + date, + dateFormatLegacy, + departArrive, + setQueryParam, + time, + timeFormatLegacy +}: Props) { + /** + * Stores parameters in both the Redux `currentQuery` and URL + * @param params Params to store + */ + const _onSettingsUpdate = useCallback( + (params: any) => { + console.log('setting') + setQueryParam({ queryParamData: params, ...params }) + }, + [setQueryParam] + ) + + const { homeTimezone, isTouchScreenOnDesktop } = config + const touchClassName = isTouchScreenOnDesktop + ? 'with-desktop-touchscreen' + : '' + + return ( +
+
+ `. + // These props are not relevant in modern browsers, + // where `` already + // formats the time|date according to the OS settings. + // eslint-disable-next-line react/jsx-sort-props + timeFormatLegacy={timeFormatLegacy} + timeZone={homeTimezone} + /> +
+
+ ) +} + +const queryParamConfig = { + date: StringParam, + departArrive: StringParam, + time: StringParam +} + +const mapStateToProps = (state: AppReduxState) => { + const config = state.otp.config + const urlSearchParams = new URLSearchParams(state.router.location.search) + const { date, departArrive, time } = decodeQueryParams(queryParamConfig, { + date: urlSearchParams.get('date'), + departArrive: urlSearchParams.get('departArrive'), + time: urlSearchParams.get('time') + }) + + return { + config, + date: date || coreUtils.time.getCurrentDate(), + // This prop is for legacy browsers (see render method above). + // @ts-expect-error why do we have two config types? + dateFormatLegacy: coreUtils.time.getDateFormat(config), + departArrive: (departArrive as DepartArriveValue) || 'NOW', + time: time || coreUtils.time.getCurrentTime(), + // This prop is for legacy browsers (see render method above). + // @ts-expect-error why do we have two config types? + timeFormatLegacy: coreUtils.time.getTimeFormat(config) + } +} + +const mapDispatchToProps = { + setQueryParam +} + +export default connect(mapStateToProps, mapDispatchToProps)(DateTimeModal) diff --git a/lib/components/form/date-time-preview.tsx b/lib/components/form/date-time-preview.tsx index 55b19a837..4fa2faa95 100644 --- a/lib/components/form/date-time-preview.tsx +++ b/lib/components/form/date-time-preview.tsx @@ -1,13 +1,17 @@ import { CalendarAlt } from '@styled-icons/fa-solid/CalendarAlt' import { Clock } from '@styled-icons/fa-regular/Clock' import { connect } from 'react-redux' +import { decodeQueryParams, StringParam } from 'serialize-query-params' import { toDate } from 'date-fns-tz' +import coreUtils from '@opentripplanner/core-utils' import React from 'react' import { IconWithText } from '../util/styledIcon' import FormattedCalendarString from '../util/formatted-calendar-string' import FormattedDateTimePreview from '../util/formatted-date-time-preview' +import { DepartArriveValue } from './date-time-modal' + interface Props { date: string departArrive: string @@ -38,13 +42,24 @@ const DateTimePreview = ({ ) } +const queryParamConfig = { + date: StringParam, + departArrive: StringParam, + time: StringParam +} + const mapStateToProps = (state: any) => { - const { date, departArrive, time } = state.otp.currentQuery const { homeTimezone: timeZone } = state.otp.config + const urlSearchParams = new URLSearchParams(state.router.location.search) + const { date, departArrive, time } = decodeQueryParams(queryParamConfig, { + date: urlSearchParams.get('date'), + departArrive: urlSearchParams.get('departArrive'), + time: urlSearchParams.get('time') + }) return { - date, - departArrive, - time, + date: date || coreUtils.time.getCurrentDate(), + departArrive: (departArrive as DepartArriveValue) || 'NOW', + time: time || coreUtils.time.getCurrentTime(), timeZone } } diff --git a/lib/util/state-types.ts b/lib/util/state-types.ts index 3daba23c6..f393871fa 100644 --- a/lib/util/state-types.ts +++ b/lib/util/state-types.ts @@ -12,6 +12,7 @@ export interface OtpState { // TODO: Add other OTP states activeSearchId?: string config: AppConfig + currentQuery: any // TODO filter: { sort: { type: string diff --git a/percy/percy.test.js b/percy/percy.test.js index 4c46528c4..b7ef7586c 100644 --- a/percy/percy.test.js +++ b/percy/percy.test.js @@ -105,7 +105,7 @@ async function executeTest(page, isMobile, isCallTaker) { // Triggers mock.har graphql query #1 and #2 (bike-only query, twice). // FIXME: Opening a url with non-default mode params triggers the plan query twice. await page.goto( - `http://localhost:${MOCK_SERVER_PORT}/#/?ui_activeSearch=fg33svlbf&ui_activeItinerary=-1&fromPlace=South%20Prado%20Northeast%2C%20Atlanta%2C%20GA%2C%20USA%3A%3A33.78946214120528%2C-84.37663414886111&toPlace=1%20Copenhill%20Avenue%20NE%2C%20Atlanta%2C%20GA%2C%20USA%3A%3A33.767060728439574%2C-84.35749390533111&date=2023-08-09&time=17%3A56&arriveBy=false&mode=BICYCLE&showIntermediateStops=true&walkSpeed=1.34&ignoreRealtimeUpdates=true&numItineraries=3&otherThanPreferredRoutesPenalty=900&modeButtons=walk_bike` + `http://localhost:${MOCK_SERVER_PORT}/#/?ui_activeSearch=fg33svlbf&ui_activeItinerary=-1&fromPlace=South%20Prado%20Northeast%2C%20Atlanta%2C%20GA%2C%20USA%3A%3A33.78946214120528%2C-84.37663414886111&toPlace=1%20Copenhill%20Avenue%20NE%2C%20Atlanta%2C%20GA%2C%20USA%3A%3A33.767060728439574%2C-84.35749390533111&date=2023-08-09&time=17%3A56&departArrive=DEPART&mode=BICYCLE&showIntermediateStops=true&walkSpeed=1.34&ignoreRealtimeUpdates=true&numItineraries=3&otherThanPreferredRoutesPenalty=900&modeButtons=walk_bike` ) // FIXME: Network idle condition seems never met after navigating to above link. // await page.waitForNavigation({ waitUntil: 'networkidle2' })