From 9e9b7cef76438b4fe760191e76870bc1b27c9f5b Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 2 Jun 2020 19:36:53 -0400 Subject: [PATCH 01/46] feat(saved trip): Add route and basic structure for saving trips. --- lib/components/app/responsive-webapp.js | 8 + lib/components/user/new-account-wizard.js | 1 + lib/components/user/save-trip-screen.js | 156 ++++++++++++++++++ lib/components/user/saved-trip-editor.js | 29 ++++ lib/components/user/saved-trip-wizard.js | 37 +++++ .../user/sequential-pane-display.js | 3 +- lib/components/user/trip-basics-pane.js | 5 + .../user/trip-notifications-pane.js | 5 + lib/components/user/trip-summary-pane.js | 5 + 9 files changed, 248 insertions(+), 1 deletion(-) create mode 100644 lib/components/user/save-trip-screen.js create mode 100644 lib/components/user/saved-trip-editor.js create mode 100644 lib/components/user/saved-trip-wizard.js create mode 100644 lib/components/user/trip-basics-pane.js create mode 100644 lib/components/user/trip-notifications-pane.js create mode 100644 lib/components/user/trip-summary-pane.js diff --git a/lib/components/app/responsive-webapp.js b/lib/components/app/responsive-webapp.js index d66d20621..b4f973f35 100644 --- a/lib/components/app/responsive-webapp.js +++ b/lib/components/app/responsive-webapp.js @@ -9,6 +9,7 @@ import { Route, Switch, withRouter } from 'react-router' import PrintLayout from './print-layout' import AfterSignInScreen from '../user/after-signin-screen' +import SaveTripScreen from '../user/save-trip-screen' import UserAccountScreen from '../user/user-account-screen' import withLoggedInUser from '../user/with-logged-in-user' import { setMapCenter, setMapZoom } from '../../actions/config' @@ -220,6 +221,13 @@ class RouterWrapper extends Component { return withLoggedInUser() }} /> + { + const props = this._combineProps(routerProps) + return withLoggedInUser() + }} + /> { return ( diff --git a/lib/components/user/save-trip-screen.js b/lib/components/user/save-trip-screen.js new file mode 100644 index 000000000..cf942f4dd --- /dev/null +++ b/lib/components/user/save-trip-screen.js @@ -0,0 +1,156 @@ +import PropTypes from 'prop-types' +import React, { Component } from 'react' +import { connect } from 'react-redux' +import { withLoginRequired } from 'use-auth0-hooks' + +import { addTrip, updateTrip } from '../../util/middleware' +import { routeTo } from '../../actions/ui' +import AppNav from '../app/app-nav' + +import SavedTripEditor from './saved-trip-editor' +import SavedTripWizard from './saved-trip-wizard' +import TripBasicsPane from './trip-basics-pane' +import TripNotificationsPane from './trip-notifications-pane' +import TripSummaryPane from './trip-summary-pane' + +/** + * This screen handles saving a trip from an OTP query for the logged-in user. + */ +class SaveTripScreen extends Component { + static propTypes = { + originalUrl: PropTypes.string + } + + static defaultProps = { + originalUrl: '/' + } + + constructor (props) { + super(props) + + this.state = { + // New trip: deep clone active trip to make edits. + monitoredTrip: {} + } + } + + _updateMonitoredTripState = newMonitoredTrip => { + const { monitoredTrip } = this.state + this.setState({ + monitoredTrip: { + ...monitoredTrip, + ...newMonitoredTrip + } + }) + } + + _updateMonitoredTrip = async () => { + const { auth, loggedInUser, persistence, wizard } = this.props + + if (persistence && persistence.otp_middleware) { + const { accessToken } = auth + const { id } = loggedInUser + const { monitoredTrip } = this.state + + // TODO: Change state of Save button. + + let result + if (wizard) { + result = await addTrip(persistence.otp_middleware, accessToken, monitoredTrip) + } else { + result = await updateTrip(persistence.otp_middleware, accessToken, monitoredTrip) + } + + // TODO: improve this. + if (result.status === 'success') { + alert('Your preferences have been saved.') + } else { + alert(`An error was encountered:\n${JSON.stringify(result)}`) + } + } + } + + _handleExit = () => { + const { originalUrl } = this.props + this.props.routeTo(originalUrl) + } + + _handleExitAndSave = async () => { + await this._updateMonitoredTrip() + this._handleExit() + } + + /** + * Hook monitoredTrip, onMonitoredTripChange on some panes upon rendering. + * This returns a new render function for the passed component + * that allows passing other props to it later if needed. + */ + _hookMonitoredTrip = Pane => props => { + const { monitoredTrip } = this.state + return ( + + ) + } + + // Make an index of pane components, so we don't render all panes at once on every render. + // Hook some panes to the monitoredTrip and onMonitoredTripChange props. + _panes = { + basics: this._hookMonitoredTrip(TripBasicsPane), + notifications: this._hookMonitoredTrip(TripNotificationsPane), + summary: this._hookMonitoredTrip(TripSummaryPane) + } + + // TODO: Update title bar during componentDidMount. + + render () { + const { monitoredTrip, wizard } = this.props + + let content + if (wizard) { + content = ( + + ) + } else { + content = ( + + ) + } + + return ( +
+ {/* TODO: Do mobile view. */} + +
+ {content} +
+
+ ) + } +} + +// connect to the redux store + +const mapStateToProps = (state, ownProps) => { + return { + loggedInUser: state.otp.user.loggedInUser, + persistence: state.otp.config.persistence + } +} + +const mapDispatchToProps = { + routeTo +} + +export default withLoginRequired(connect(mapStateToProps, mapDispatchToProps)(SaveTripScreen)) diff --git a/lib/components/user/saved-trip-editor.js b/lib/components/user/saved-trip-editor.js new file mode 100644 index 000000000..c97609aa6 --- /dev/null +++ b/lib/components/user/saved-trip-editor.js @@ -0,0 +1,29 @@ +import React from 'react' + +import StackedPaneDisplay from './stacked-pane-display' + +/** + * This component handles the existing account display. + */ +const SavedTripEditor = ({ onCancel, onComplete, panes }) => { + const paneSequence = [ + { + pane: panes.basics, + title: 'Trip information' + }, + { + pane: panes.notifications, + title: 'Trip notifications' + } + ] + + return ( + + ) +} + +export default SavedTripEditor diff --git a/lib/components/user/saved-trip-wizard.js b/lib/components/user/saved-trip-wizard.js new file mode 100644 index 000000000..a5ad2c5d4 --- /dev/null +++ b/lib/components/user/saved-trip-wizard.js @@ -0,0 +1,37 @@ +import React from 'react' + +import SequentialPaneDisplay from './sequential-pane-display' + +/** + * This component is the new account wizard. + */ +const SavedTripWizard = ({ onComplete, panes }) => { + const paneSequence = { + basics: { + nextId: 'notifications', + pane: panes.basics, + title: 'Trip information' + }, + notifications: { + nextId: 'summary', + pane: panes.notifications, + prevId: 'basics', + title: 'Trip notification preferences' + }, + summary: { + pane: panes.summary, + prevId: 'notifications', + title: 'Trip summary' + } + } + + return ( + + ) +} + +export default SavedTripWizard diff --git a/lib/components/user/sequential-pane-display.js b/lib/components/user/sequential-pane-display.js index 1de6b4d47..0caab78d5 100644 --- a/lib/components/user/sequential-pane-display.js +++ b/lib/components/user/sequential-pane-display.js @@ -15,6 +15,7 @@ const PaneContainer = styled.div` */ class SequentialPaneDisplay extends Component { static propTypes = { + initialPaneId: PropTypes.string.isRequired, onComplete: PropTypes.func.isRequired, paneSequence: PropTypes.object.isRequired } @@ -23,7 +24,7 @@ class SequentialPaneDisplay extends Component { super(props) this.state = { - activePaneId: 'terms' + activePaneId: props.initialPaneId } } diff --git a/lib/components/user/trip-basics-pane.js b/lib/components/user/trip-basics-pane.js new file mode 100644 index 000000000..056642320 --- /dev/null +++ b/lib/components/user/trip-basics-pane.js @@ -0,0 +1,5 @@ +import React from 'react' + +const TripBasicsPane = () =>
Trip basics!
+ +export default TripBasicsPane diff --git a/lib/components/user/trip-notifications-pane.js b/lib/components/user/trip-notifications-pane.js new file mode 100644 index 000000000..2e5183f71 --- /dev/null +++ b/lib/components/user/trip-notifications-pane.js @@ -0,0 +1,5 @@ +import React from 'react' + +const TripNotificationsPane = () =>
Trip notifications!
+ +export default TripNotificationsPane diff --git a/lib/components/user/trip-summary-pane.js b/lib/components/user/trip-summary-pane.js new file mode 100644 index 000000000..d99db4b31 --- /dev/null +++ b/lib/components/user/trip-summary-pane.js @@ -0,0 +1,5 @@ +import React from 'react' + +const TripSummaryPane = () =>
Trip summary!
+ +export default TripSummaryPane From 700bed21225d292650acad2d460ca6db59bedec5 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 5 Jun 2020 17:38:44 -0400 Subject: [PATCH 02/46] feat(SavedTripsWizard): Add content for SavedTripWizard --- .../narrative/default/default-itinerary.js | 2 + lib/components/user/trip-basics-pane.js | 95 ++++++++++++++++++- .../user/trip-notifications-pane.js | 83 +++++++++++++++- lib/components/user/trip-summary-pane.js | 65 ++++++++++++- lib/util/middleware.js | 5 +- 5 files changed, 239 insertions(+), 11 deletions(-) diff --git a/lib/components/narrative/default/default-itinerary.js b/lib/components/narrative/default/default-itinerary.js index efc740c3a..7a11185bd 100644 --- a/lib/components/narrative/default/default-itinerary.js +++ b/lib/components/narrative/default/default-itinerary.js @@ -6,6 +6,7 @@ import ItinerarySummary from './itinerary-summary' import ItineraryDetails from './itinerary-details' import TripDetails from '../connected-trip-details' import TripTools from '../trip-tools' +import BeginSaveTripButton from '../../user/begin-save-trip-button' const { formatDuration, formatTime } = coreUtils.time @@ -30,6 +31,7 @@ export default class DefaultItinerary extends NarrativeItinerary { Itinerary {index + 1}{' '} {formatDuration(itinerary.duration)}{' '} {formatTime(itinerary.startTime)}—{formatTime(itinerary.endTime)} + {' '} {(active || expanded) && diff --git a/lib/components/user/trip-basics-pane.js b/lib/components/user/trip-basics-pane.js index 056642320..f82b31120 100644 --- a/lib/components/user/trip-basics-pane.js +++ b/lib/components/user/trip-basics-pane.js @@ -1,5 +1,94 @@ -import React from 'react' +import coreUtils from '@opentripplanner/core-utils' +import React, { Component } from 'react' +import { connect } from 'react-redux' +import { ButtonToolbar, ControlLabel, FormControl, FormGroup, ToggleButton, ToggleButtonGroup } from 'react-bootstrap' -const TripBasicsPane = () =>
Trip basics!
+import { getActiveItineraries, getActiveSearch } from '../../util/state' +import ItinerarySummary from '../narrative/default/itinerary-summary' -export default TripBasicsPane +const { formatDuration, formatTime } = coreUtils.time + +class TripBasicsPane extends Component { + _handleTripNameChange = e => { + const { onMonitoredTripChange } = this.props + onMonitoredTripChange({ tripName: e.target.value }) + } + + render () { + const { activeItinerary, activeSearch, itinerary, /* monitoredTrip, */ pending } = this.props + const { from, to } = activeSearch.query + + if (pending) { + return
Loading...
+ } else if (itinerary) { + // TODO: use the modern itinerary summary built for trip comparison. + return ( +
+ Selected itinerary: +
From {from.name} to {to.name}
+
+ +
+ + + Please provide a name for this trip: + + + + + What days to you take this trip? + + + Weekdays + Monday + Tuesday + Wednesday + Thursday + Friday + Saturday + Sunday + Weekends + + + + +
+ ) + } else { + return
No itinerary to display.
+ } + } +} + +// connect to the redux store + +const mapStateToProps = (state, ownProps) => { + const activeSearch = getActiveSearch(state.otp) + const pending = activeSearch ? activeSearch.pending : false + const activeItinerary = activeSearch && activeSearch.activeItinerary + const itineraries = getActiveItineraries(state.otp) + return { + activeItinerary, + activeSearch, + itinerary: activeItinerary != null && itineraries[activeItinerary], + pending + } +} + +const mapDispatchToProps = { +} + +export default connect(mapStateToProps, mapDispatchToProps)(TripBasicsPane) diff --git a/lib/components/user/trip-notifications-pane.js b/lib/components/user/trip-notifications-pane.js index 2e5183f71..4efb8dcc6 100644 --- a/lib/components/user/trip-notifications-pane.js +++ b/lib/components/user/trip-notifications-pane.js @@ -1,5 +1,84 @@ -import React from 'react' +import React, { Component } from 'react' +import { Checkbox, ControlLabel, FormControl, FormGroup, Radio } from 'react-bootstrap' -const TripNotificationsPane = () =>
Trip notifications!
+class TripNotificationsPane extends Component { + render () { + // const { monitoredTrip } = this.props + + return ( +
+ + Would you like to receive notifications about this trip? + + Yes + + + No + + +

[small] Note: you will be notified by [email|SMS]. This can be changed in your account settings once the trip has been saved.

+
+ + + Would you like to disable notifications during US federal holidays? + + Yes + + + No + + +

[small] Note: you can always pause notifications for this trip once the trip has been saved.

+
+ + + When would you like to receive notifications about delays or disruptions to your trip? + + + Check for delays or disruptions: + + + + + + + + + + Notify me if: + A different route or transfer point is recommended + There is an alert for a route or stop that is part of my journey + + Your arrival or departure time changes by more than: + + + + + + + + + + +
+ ) + } +} export default TripNotificationsPane diff --git a/lib/components/user/trip-summary-pane.js b/lib/components/user/trip-summary-pane.js index d99db4b31..a0f8139d7 100644 --- a/lib/components/user/trip-summary-pane.js +++ b/lib/components/user/trip-summary-pane.js @@ -1,5 +1,64 @@ -import React from 'react' +import coreUtils from '@opentripplanner/core-utils' +import React, { Component } from 'react' +import { connect } from 'react-redux' +import { ControlLabel } from 'react-bootstrap' -const TripSummaryPane = () =>
Trip summary!
+import { getActiveItineraries, getActiveSearch } from '../../util/state' +import ItinerarySummary from '../narrative/default/itinerary-summary' -export default TripSummaryPane +const { formatDuration, formatTime } = coreUtils.time + +class TripSummaryPane extends Component { + render () { + const { activeItinerary, activeSearch, itinerary, /* monitoredTrip, */ pending } = this.props + const { from, to } = activeSearch.query + + if (pending) { + return
Loading...
+ } else if (itinerary) { + // TODO: use the modern itinerary summary built for trip comparison. + return ( +
+

Below is a summary of your trip and its notification preferences.

+ + [Trip Name] +
From {from.name} to {to.name}
+
+ +
+ +

Happens on: [days]

+

Notifications: [Enabled]

+

[Small] Exceptions:

+
+ ) + } else { + return
No itinerary to display.
+ } + } +} + +// connect to the redux store + +const mapStateToProps = (state, ownProps) => { + const activeSearch = getActiveSearch(state.otp) + const pending = activeSearch ? activeSearch.pending : false + const activeItinerary = activeSearch && activeSearch.activeItinerary + const itineraries = getActiveItineraries(state.otp) + return { + activeItinerary, + activeSearch, + itinerary: activeItinerary != null && itineraries[activeItinerary], + pending + } +} + +const mapDispatchToProps = { +} + +export default connect(mapStateToProps, mapDispatchToProps)(TripSummaryPane) diff --git a/lib/util/middleware.js b/lib/util/middleware.js index 3a7a280e5..a65bafe6d 100644 --- a/lib/util/middleware.js +++ b/lib/util/middleware.js @@ -1,7 +1,6 @@ if (typeof (fetch) === 'undefined') require('isomorphic-fetch') const API_USER_PATH = '/api/secure/user' -const API_AUTH0USER_PATH = '/api/secure/user/fromtoken' export async function secureFetch (url, accessToken, apiKey, method = 'get', options = {}) { const res = await fetch(url, { @@ -31,9 +30,9 @@ export async function secureFetch (url, accessToken, apiKey, method = 'get', opt } } -export async function fetchUser (middlewareConfig, token) { +export async function fetchUser (middlewareConfig, token, id) { const { apiBaseUrl, apiKey } = middlewareConfig - const requestUrl = `${apiBaseUrl}${API_AUTH0USER_PATH}` + const requestUrl = `${apiBaseUrl}${API_USER_PATH}/find/auth0UserId/${id}` return secureFetch(requestUrl, token, apiKey) } From c644cd79fee48b74ee4a1d6eeccec247e8ed1f1b Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 5 Jun 2020 18:46:51 -0400 Subject: [PATCH 03/46] improvement(SavedTripWizard): Add event handlers, build monitored trip instance. --- lib/components/app/responsive-webapp.js | 4 +- lib/components/user/begin-save-trip-button.js | 31 ++++++++ ...ve-trip-screen.js => saved-trip-screen.js} | 28 +++++-- lib/components/user/trip-basics-pane.js | 33 ++++---- .../user/trip-notifications-pane.js | 78 ++++++++++++------- lib/components/user/trip-summary-pane.js | 12 +-- lib/util/constants.js | 2 + 7 files changed, 132 insertions(+), 56 deletions(-) create mode 100644 lib/components/user/begin-save-trip-button.js rename lib/components/user/{save-trip-screen.js => saved-trip-screen.js} (84%) diff --git a/lib/components/app/responsive-webapp.js b/lib/components/app/responsive-webapp.js index b4f973f35..729df6f74 100644 --- a/lib/components/app/responsive-webapp.js +++ b/lib/components/app/responsive-webapp.js @@ -9,7 +9,7 @@ import { Route, Switch, withRouter } from 'react-router' import PrintLayout from './print-layout' import AfterSignInScreen from '../user/after-signin-screen' -import SaveTripScreen from '../user/save-trip-screen' +import SavedTripScreen from '../user/saved-trip-screen' import UserAccountScreen from '../user/user-account-screen' import withLoggedInUser from '../user/with-logged-in-user' import { setMapCenter, setMapZoom } from '../../actions/config' @@ -225,7 +225,7 @@ class RouterWrapper extends Component { path={'/savetrip'} component={(routerProps) => { const props = this._combineProps(routerProps) - return withLoggedInUser() + return withLoggedInUser() }} /> { + this.props.routeTo('/savetrip') + } + + render () { + return + } +} + +// connect to the redux store + +const mapStateToProps = (state, ownProps) => { + return {} +} + +const mapDispatchToProps = { + routeTo +} + +export default connect(mapStateToProps, mapDispatchToProps)(BeginSaveTripButton) diff --git a/lib/components/user/save-trip-screen.js b/lib/components/user/saved-trip-screen.js similarity index 84% rename from lib/components/user/save-trip-screen.js rename to lib/components/user/saved-trip-screen.js index cf942f4dd..2e126e27b 100644 --- a/lib/components/user/save-trip-screen.js +++ b/lib/components/user/saved-trip-screen.js @@ -6,6 +6,7 @@ import { withLoginRequired } from 'use-auth0-hooks' import { addTrip, updateTrip } from '../../util/middleware' import { routeTo } from '../../actions/ui' import AppNav from '../app/app-nav' +import { WEEKDAYS } from '../../util/constants' import SavedTripEditor from './saved-trip-editor' import SavedTripWizard from './saved-trip-wizard' @@ -13,10 +14,21 @@ import TripBasicsPane from './trip-basics-pane' import TripNotificationsPane from './trip-notifications-pane' import TripSummaryPane from './trip-summary-pane' +function getNewMonitoredTrip (loggedInUser) { + return { + days: WEEKDAYS, + excludeFederalHolidays: true, + isActive: true, + leadTimeInMinutes: 30, + tripName: '', + userId: loggedInUser.id // must provide to API. + } +} + /** * This screen handles saving a trip from an OTP query for the logged-in user. */ -class SaveTripScreen extends Component { +class SavedTripScreen extends Component { static propTypes = { originalUrl: PropTypes.string } @@ -28,9 +40,11 @@ class SaveTripScreen extends Component { constructor (props) { super(props) + const { loggedInUser, monitoredTrip, wizard } = props + const thisMonitoredTrip = wizard || !monitoredTrip ? getNewMonitoredTrip(loggedInUser) : monitoredTrip + this.state = { - // New trip: deep clone active trip to make edits. - monitoredTrip: {} + monitoredTrip: thisMonitoredTrip } } @@ -45,11 +59,10 @@ class SaveTripScreen extends Component { } _updateMonitoredTrip = async () => { - const { auth, loggedInUser, persistence, wizard } = this.props + const { auth, persistence, wizard } = this.props if (persistence && persistence.otp_middleware) { const { accessToken } = auth - const { id } = loggedInUser const { monitoredTrip } = this.state // TODO: Change state of Save button. @@ -107,7 +120,8 @@ class SaveTripScreen extends Component { // TODO: Update title bar during componentDidMount. render () { - const { monitoredTrip, wizard } = this.props + const { wizard } = this.props + const { monitoredTrip } = this.state let content if (wizard) { @@ -153,4 +167,4 @@ const mapDispatchToProps = { routeTo } -export default withLoginRequired(connect(mapStateToProps, mapDispatchToProps)(SaveTripScreen)) +export default withLoginRequired(connect(mapStateToProps, mapDispatchToProps)(SavedTripScreen)) diff --git a/lib/components/user/trip-basics-pane.js b/lib/components/user/trip-basics-pane.js index f82b31120..c96f0d016 100644 --- a/lib/components/user/trip-basics-pane.js +++ b/lib/components/user/trip-basics-pane.js @@ -3,19 +3,25 @@ import React, { Component } from 'react' import { connect } from 'react-redux' import { ButtonToolbar, ControlLabel, FormControl, FormGroup, ToggleButton, ToggleButtonGroup } from 'react-bootstrap' +import { WEEKDAYS } from '../../util/constants' import { getActiveItineraries, getActiveSearch } from '../../util/state' import ItinerarySummary from '../narrative/default/itinerary-summary' const { formatDuration, formatTime } = coreUtils.time class TripBasicsPane extends Component { + _handleTripDaysChange = e => { + const { onMonitoredTripChange } = this.props + onMonitoredTripChange({ days: e }) + } + _handleTripNameChange = e => { const { onMonitoredTripChange } = this.props onMonitoredTripChange({ tripName: e.target.value }) } render () { - const { activeItinerary, activeSearch, itinerary, /* monitoredTrip, */ pending } = this.props + const { activeItinerary, activeSearch, itinerary, monitoredTrip, pending } = this.props const { from, to } = activeSearch.query if (pending) { @@ -38,9 +44,9 @@ class TripBasicsPane extends Component { Please provide a name for this trip: @@ -48,19 +54,18 @@ class TripBasicsPane extends Component { What days to you take this trip? - Weekdays - Monday - Tuesday - Wednesday - Thursday - Friday - Saturday - Sunday - Weekends + Monday + Tuesday + Wednesday + Thursday + Friday + Saturday + Sunday diff --git a/lib/components/user/trip-notifications-pane.js b/lib/components/user/trip-notifications-pane.js index 4efb8dcc6..e9ff3798c 100644 --- a/lib/components/user/trip-notifications-pane.js +++ b/lib/components/user/trip-notifications-pane.js @@ -1,25 +1,40 @@ import React, { Component } from 'react' -import { Checkbox, ControlLabel, FormControl, FormGroup, Radio } from 'react-bootstrap' +import { Alert, Checkbox, ControlLabel, FormControl, FormGroup, Radio } from 'react-bootstrap' class TripNotificationsPane extends Component { + _handleIsActiveChange = e => { + const { onMonitoredTripChange } = this.props + onMonitoredTripChange({ isActive: e.target.value }) + } + + _handleExcludeFedHolidaysChange = e => { + const { onMonitoredTripChange } = this.props + onMonitoredTripChange({ excludeFederalHolidays: e.target.value }) + } + + _handleLeadTimeChange = e => { + const { onMonitoredTripChange } = this.props + onMonitoredTripChange({ leadTimeInMinutes: e.target.value }) + } + render () { - // const { monitoredTrip } = this.props + const { monitoredTrip } = this.props return (
Would you like to receive notifications about this trip? Yes No @@ -30,16 +45,16 @@ class TripNotificationsPane extends Component { Would you like to disable notifications during US federal holidays? Yes No @@ -52,7 +67,13 @@ class TripNotificationsPane extends Component { Check for delays or disruptions: - + @@ -60,20 +81,21 @@ class TripNotificationsPane extends Component { - - Notify me if: - A different route or transfer point is recommended - There is an alert for a route or stop that is part of my journey - - Your arrival or departure time changes by more than: - - - - - - - + + Under construction! + + Notify me if: + A different route or transfer point is recommended + There is an alert for a route or stop that is part of my journey + Your arrival or departure time changes by more than: + + + + + + +
diff --git a/lib/components/user/trip-summary-pane.js b/lib/components/user/trip-summary-pane.js index a0f8139d7..741887843 100644 --- a/lib/components/user/trip-summary-pane.js +++ b/lib/components/user/trip-summary-pane.js @@ -10,7 +10,7 @@ const { formatDuration, formatTime } = coreUtils.time class TripSummaryPane extends Component { render () { - const { activeItinerary, activeSearch, itinerary, /* monitoredTrip, */ pending } = this.props + const { activeItinerary, activeSearch, itinerary, monitoredTrip, pending } = this.props const { from, to } = activeSearch.query if (pending) { @@ -21,7 +21,7 @@ class TripSummaryPane extends Component {

Below is a summary of your trip and its notification preferences.

- [Trip Name] + {monitoredTrip.tripName}
From {from.name} to {to.name}
-

Happens on: [days]

-

Notifications: [Enabled]

-

[Small] Exceptions:

+

Happens on: {monitoredTrip.days.join(', ')}

+

Notifications: {monitoredTrip.isActive + ? `Enabled, ${monitoredTrip.leadTimeInMinutes} min. before scheduled departure` + : 'Disabled'}

+ {monitoredTrip.excludeFederalHolidays &&

Except for US federal holidays

}
) } else { diff --git a/lib/util/constants.js b/lib/util/constants.js index 742b4c092..1d28f33f3 100644 --- a/lib/util/constants.js +++ b/lib/util/constants.js @@ -3,3 +3,5 @@ export const PERSISTENCE_STRATEGY_OTP_MIDDLEWARE = 'otp_middleware' // Gets the root URL, e.g. https://otp-instance.example.com:8080/, computed once for all. export const URL_ROOT = `${window.location.protocol}//${window.location.host}` + +export const WEEKDAYS = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday'] From 3f980cfdcdb44972992a978fb6da9ae33cdc4c92 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 5 Jun 2020 19:09:39 -0400 Subject: [PATCH 04/46] improvement(SavedTripWizard): Implement save to middleware. --- .../user/trip-notifications-pane.js | 16 ++++++----- lib/util/middleware.js | 27 +++++++++++++++++++ 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/lib/components/user/trip-notifications-pane.js b/lib/components/user/trip-notifications-pane.js index e9ff3798c..a34e4f626 100644 --- a/lib/components/user/trip-notifications-pane.js +++ b/lib/components/user/trip-notifications-pane.js @@ -4,12 +4,12 @@ import { Alert, Checkbox, ControlLabel, FormControl, FormGroup, Radio } from 're class TripNotificationsPane extends Component { _handleIsActiveChange = e => { const { onMonitoredTripChange } = this.props - onMonitoredTripChange({ isActive: e.target.value }) + onMonitoredTripChange({ isActive: e.target.value === 'true' }) } _handleExcludeFedHolidaysChange = e => { const { onMonitoredTripChange } = this.props - onMonitoredTripChange({ excludeFederalHolidays: e.target.value }) + onMonitoredTripChange({ excludeFederalHolidays: e.target.value === 'true' }) } _handleLeadTimeChange = e => { @@ -25,16 +25,18 @@ class TripNotificationsPane extends Component { Would you like to receive notifications about this trip? Yes No @@ -45,16 +47,18 @@ class TripNotificationsPane extends Component { Would you like to disable notifications during US federal holidays? Yes No diff --git a/lib/util/middleware.js b/lib/util/middleware.js index a65bafe6d..5fa4afc7a 100644 --- a/lib/util/middleware.js +++ b/lib/util/middleware.js @@ -1,6 +1,7 @@ if (typeof (fetch) === 'undefined') require('isomorphic-fetch') const API_USER_PATH = '/api/secure/user' +const API_MONITORTRIP_PATH = '/api/secure/monitortrip' export async function secureFetch (url, accessToken, apiKey, method = 'get', options = {}) { const res = await fetch(url, { @@ -62,3 +63,29 @@ export async function updateUser (middlewareConfig, token, data) { } } } + +export async function addTrip (middlewareConfig, token, data) { + const { apiBaseUrl, apiKey } = middlewareConfig + const requestUrl = `${apiBaseUrl}${API_MONITORTRIP_PATH}` + + return secureFetch(requestUrl, token, apiKey, 'POST', { + body: JSON.stringify(data) + }) +} + +export async function updateTrip (middlewareConfig, token, data) { + const { apiBaseUrl, apiKey } = middlewareConfig + const { id } = data // Middleware ID, NOT auth0 (or similar) id. + const requestUrl = `${apiBaseUrl}${API_MONITORTRIP_PATH}/${id}` + + if (id) { + return secureFetch(requestUrl, token, apiKey, 'PUT', { + body: JSON.stringify(data) + }) + } else { + return { + status: 'error', + message: 'Corrupted state: User ID not available for exiting user.' + } + } +} From 80c442549bb424b3c714b133972cdd1e6acd62aa Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Mon, 8 Jun 2020 15:54:00 -0400 Subject: [PATCH 05/46] feat(SavedTripEditor): Add very basic saved trip editor. --- lib/components/app/responsive-webapp.js | 7 ++ .../user/existing-account-display.js | 76 ++++++++---- lib/components/user/saved-trip-editor.js | 115 +++++++++++++++--- lib/components/user/saved-trip-screen.js | 29 ++++- lib/components/user/stacked-pane-display.js | 4 +- lib/util/middleware.js | 26 +++- 6 files changed, 202 insertions(+), 55 deletions(-) diff --git a/lib/components/app/responsive-webapp.js b/lib/components/app/responsive-webapp.js index 729df6f74..9ffffdca2 100644 --- a/lib/components/app/responsive-webapp.js +++ b/lib/components/app/responsive-webapp.js @@ -228,6 +228,13 @@ class RouterWrapper extends Component { return withLoggedInUser() }} /> + { + const props = this._combineProps(routerProps) + return withLoggedInUser() + }} + /> { - const paneSequence = [ - { - pane: panes.terms, - props: { disableCheckTerms: true }, - title: 'Terms' - }, - { - pane: panes.notifications, - title: 'Notifications' - }, - { - pane: panes.locations, - title: 'My locations' - } - ] - - return ( - - ) +class ExistingAccountDisplay extends Component { + _handleViewMyTrips = () => { + this.props.routeTo('/savedtrips') + } + + render () { + const { onCancel, onComplete, panes } = this.props + const paneSequence = [ + { + pane: () => , + title: 'My trips' + }, + { + pane: panes.terms, + props: { disableCheckTerms: true }, + title: 'Terms' + }, + { + pane: panes.notifications, + title: 'Notifications' + }, + { + pane: panes.locations, + title: 'My locations' + } + ] + + return ( + + ) + } +} + +// connect to the redux store + +const mapStateToProps = (state, ownProps) => ({}) + +const mapDispatchToProps = { + routeTo } -export default ExistingAccountDisplay +export default connect(mapStateToProps, mapDispatchToProps)(ExistingAccountDisplay) diff --git a/lib/components/user/saved-trip-editor.js b/lib/components/user/saved-trip-editor.js index c97609aa6..66a252bb3 100644 --- a/lib/components/user/saved-trip-editor.js +++ b/lib/components/user/saved-trip-editor.js @@ -1,29 +1,104 @@ -import React from 'react' +import React, { Component } from 'react' +import { Button, Col, Nav, NavItem, Row } from 'react-bootstrap' +import { connect } from 'react-redux' + +import { deleteTrip, getTrips } from '../../util/middleware' import StackedPaneDisplay from './stacked-pane-display' /** * This component handles the existing account display. */ -const SavedTripEditor = ({ onCancel, onComplete, panes }) => { - const paneSequence = [ - { - pane: panes.basics, - title: 'Trip information' - }, - { - pane: panes.notifications, - title: 'Trip notifications' +class SavedTripEditor extends Component { + constructor (props) { + super(props) + + this.state = { + trips: [] + } + } + + _fetchTrips = async () => { + const { auth, persistence } = this.props + const fetchResult = await getTrips(persistence.otp_middleware, auth.accessToken) + + if (fetchResult.status === 'success') { + this.setState({ trips: fetchResult.data }) + if (fetchResult.data.length) { + this._handleMonitoredTripSelect(0) + } } - ] - - return ( - - ) + } + + _handleDeleteTrip = async () => { + if (confirm('This will delete the trip from your save trips.')) { + const { auth, monitoredTrip, persistence } = this.props + await deleteTrip(persistence.otp_middleware, auth.accessToken, monitoredTrip) + } + } + + _handleMonitoredTripSelect = selectedKey => { + const { onMonitoredTripSelect } = this.props + const { trips } = this.state + if (onMonitoredTripSelect) onMonitoredTripSelect(trips[selectedKey]) + } + + async componentDidMount () { + await this._fetchTrips() + } + + render () { + const { monitoredTrip, onCancel, onComplete, panes } = this.props + const { trips } = this.state + + const paneSequence = [ + { + pane: () => ( +
{monitoredTrip.queryParams}
+ ), + title: monitoredTrip.tripName + }, + { + pane: panes.notifications, + title: 'Trip notifications' + }, + { + pane: () => , + title: 'Danger zone' + } + ] + + return ( + + + + + + + + + ) + } +} + +// connect to the redux store + +const mapStateToProps = (state, ownProps) => { + return { + persistence: state.otp.config.persistence + } +} + +const mapDispatchToProps = { } -export default SavedTripEditor +export default connect(mapStateToProps, mapDispatchToProps)(SavedTripEditor) diff --git a/lib/components/user/saved-trip-screen.js b/lib/components/user/saved-trip-screen.js index 2e126e27b..9c663a754 100644 --- a/lib/components/user/saved-trip-screen.js +++ b/lib/components/user/saved-trip-screen.js @@ -7,6 +7,7 @@ import { addTrip, updateTrip } from '../../util/middleware' import { routeTo } from '../../actions/ui' import AppNav from '../app/app-nav' import { WEEKDAYS } from '../../util/constants' +import { getActiveItineraries, getActiveSearch } from '../../util/state' import SavedTripEditor from './saved-trip-editor' import SavedTripWizard from './saved-trip-wizard' @@ -14,12 +15,14 @@ import TripBasicsPane from './trip-basics-pane' import TripNotificationsPane from './trip-notifications-pane' import TripSummaryPane from './trip-summary-pane' -function getNewMonitoredTrip (loggedInUser) { +function getNewMonitoredTrip (loggedInUser, queryParams, itineraries) { return { days: WEEKDAYS, excludeFederalHolidays: true, isActive: true, + itinerary: itineraries, leadTimeInMinutes: 30, + queryParams, tripName: '', userId: loggedInUser.id // must provide to API. } @@ -40,8 +43,8 @@ class SavedTripScreen extends Component { constructor (props) { super(props) - const { loggedInUser, monitoredTrip, wizard } = props - const thisMonitoredTrip = wizard || !monitoredTrip ? getNewMonitoredTrip(loggedInUser) : monitoredTrip + const { itineraries, loggedInUser, monitoredTrip, queryParams, wizard } = props + const thisMonitoredTrip = (wizard || !monitoredTrip) ? getNewMonitoredTrip(loggedInUser, queryParams, itineraries) : monitoredTrip this.state = { monitoredTrip: thisMonitoredTrip @@ -93,6 +96,12 @@ class SavedTripScreen extends Component { this._handleExit() } + _handleMonitoredTripSelect = trip => { + this.setState({ + monitoredTrip: trip + }) + } + /** * Hook monitoredTrip, onMonitoredTripChange on some panes upon rendering. * This returns a new render function for the passed component @@ -120,7 +129,7 @@ class SavedTripScreen extends Component { // TODO: Update title bar during componentDidMount. render () { - const { wizard } = this.props + const { auth, wizard } = this.props const { monitoredTrip } = this.state let content @@ -134,9 +143,11 @@ class SavedTripScreen extends Component { } else { content = ( ) @@ -157,9 +168,15 @@ class SavedTripScreen extends Component { // connect to the redux store const mapStateToProps = (state, ownProps) => { + const activeSearch = getActiveSearch(state.otp) + const activeItinerary = activeSearch && activeSearch.activeItinerary + const itineraries = getActiveItineraries(state.otp) return { + activeItinerary, + itineraries, loggedInUser: state.otp.user.loggedInUser, - persistence: state.otp.config.persistence + persistence: state.otp.config.persistence, + queryParams: state.router.location.search } } diff --git a/lib/components/user/stacked-pane-display.js b/lib/components/user/stacked-pane-display.js index 1038abc09..da013f1ab 100644 --- a/lib/components/user/stacked-pane-display.js +++ b/lib/components/user/stacked-pane-display.js @@ -19,9 +19,9 @@ const PaneContainer = styled.div` /** * This component handles the flow between screens for new OTP user accounts. */ -const StackedPaneDisplay = ({ onCancel, onComplete, paneSequence }) => ( +const StackedPaneDisplay = ({ onCancel, onComplete, paneSequence, title }) => ( <> -

My Account

+

{title}

{ paneSequence.map(({ pane: Pane, props, title }, index) => ( diff --git a/lib/util/middleware.js b/lib/util/middleware.js index 5fa4afc7a..44c71b8d8 100644 --- a/lib/util/middleware.js +++ b/lib/util/middleware.js @@ -64,6 +64,13 @@ export async function updateUser (middlewareConfig, token, data) { } } +export async function getTrips (middlewareConfig, token) { + const { apiBaseUrl, apiKey } = middlewareConfig + const requestUrl = `${apiBaseUrl}${API_MONITORTRIP_PATH}` + + return secureFetch(requestUrl, token, apiKey, 'GET') +} + export async function addTrip (middlewareConfig, token, data) { const { apiBaseUrl, apiKey } = middlewareConfig const requestUrl = `${apiBaseUrl}${API_MONITORTRIP_PATH}` @@ -85,7 +92,24 @@ export async function updateTrip (middlewareConfig, token, data) { } else { return { status: 'error', - message: 'Corrupted state: User ID not available for exiting user.' + message: 'Corrupted state: Monitored Trip ID not available for exiting user.' + } + } +} + +export async function deleteTrip (middlewareConfig, token, data) { + const { apiBaseUrl, apiKey } = middlewareConfig + const { id } = data // Middleware ID, NOT auth0 (or similar) id. + const requestUrl = `${apiBaseUrl}${API_MONITORTRIP_PATH}/${id}` + + if (id) { + return secureFetch(requestUrl, token, apiKey, 'DELETE', { + body: JSON.stringify(data) + }) + } else { + return { + status: 'error', + message: 'Corrupted state: Monitored Trip ID not available for exiting user.' } } } From f4c98cd0fa2450b4dfb00e3dad430a4baf295d31 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Mon, 8 Jun 2020 21:50:24 -0400 Subject: [PATCH 06/46] fix(SavedTripEditor): Fix deleting trips. --- lib/components/user/saved-trip-editor.js | 134 +++++++------------- lib/components/user/saved-trip-screen.js | 77 ++++++++++- lib/components/user/stacked-pane-display.js | 2 +- lib/components/user/trip-basics-pane.js | 6 +- 4 files changed, 122 insertions(+), 97 deletions(-) diff --git a/lib/components/user/saved-trip-editor.js b/lib/components/user/saved-trip-editor.js index 66a252bb3..cb62b8633 100644 --- a/lib/components/user/saved-trip-editor.js +++ b/lib/components/user/saved-trip-editor.js @@ -1,104 +1,68 @@ import React, { Component } from 'react' import { Button, Col, Nav, NavItem, Row } from 'react-bootstrap' -import { connect } from 'react-redux' - -import { deleteTrip, getTrips } from '../../util/middleware' import StackedPaneDisplay from './stacked-pane-display' /** - * This component handles the existing account display. + * This component handles editing saved trips. */ class SavedTripEditor extends Component { - constructor (props) { - super(props) - - this.state = { - trips: [] - } - } - - _fetchTrips = async () => { - const { auth, persistence } = this.props - const fetchResult = await getTrips(persistence.otp_middleware, auth.accessToken) - - if (fetchResult.status === 'success') { - this.setState({ trips: fetchResult.data }) - if (fetchResult.data.length) { - this._handleMonitoredTripSelect(0) - } - } - } - - _handleDeleteTrip = async () => { - if (confirm('This will delete the trip from your save trips.')) { - const { auth, monitoredTrip, persistence } = this.props - await deleteTrip(persistence.otp_middleware, auth.accessToken, monitoredTrip) - } - } - _handleMonitoredTripSelect = selectedKey => { - const { onMonitoredTripSelect } = this.props - const { trips } = this.state + const { onMonitoredTripSelect, trips } = this.props if (onMonitoredTripSelect) onMonitoredTripSelect(trips[selectedKey]) } - async componentDidMount () { - await this._fetchTrips() - } - render () { - const { monitoredTrip, onCancel, onComplete, panes } = this.props - const { trips } = this.state - - const paneSequence = [ - { - pane: () => ( -
{monitoredTrip.queryParams}
- ), - title: monitoredTrip.tripName - }, - { - pane: panes.notifications, - title: 'Trip notifications' - }, - { - pane: () => , - title: 'Danger zone' - } - ] + const { monitoredTrip, onCancel, onComplete, onDeleteTrip, panes, trips } = this.props + const paneSequence = monitoredTrip + ? [ + { + pane: () => ( +
{monitoredTrip.queryParams}
+ ), + title: monitoredTrip.tripName + }, + { + pane: panes.notifications, + title: 'Trip notifications' + }, + { + pane: () => , + title: 'Danger zone' + } + ] + : [ + { + pane: () => ( +
Add trips from the map.
+ ), + title: 'You have no saved trips.' + } + ] return ( - - - - - - - - + <> +

My saved trips

+ + + + + + + + + + ) } } -// connect to the redux store - -const mapStateToProps = (state, ownProps) => { - return { - persistence: state.otp.config.persistence - } -} - -const mapDispatchToProps = { -} - -export default connect(mapStateToProps, mapDispatchToProps)(SavedTripEditor) +export default SavedTripEditor diff --git a/lib/components/user/saved-trip-screen.js b/lib/components/user/saved-trip-screen.js index 9c663a754..dcab292b3 100644 --- a/lib/components/user/saved-trip-screen.js +++ b/lib/components/user/saved-trip-screen.js @@ -3,12 +3,13 @@ import React, { Component } from 'react' import { connect } from 'react-redux' import { withLoginRequired } from 'use-auth0-hooks' -import { addTrip, updateTrip } from '../../util/middleware' +import { addTrip, deleteTrip, getTrips, updateTrip } from '../../util/middleware' import { routeTo } from '../../actions/ui' import AppNav from '../app/app-nav' import { WEEKDAYS } from '../../util/constants' import { getActiveItineraries, getActiveSearch } from '../../util/state' +import AwaitingScreen from './awaiting-screen' import SavedTripEditor from './saved-trip-editor' import SavedTripWizard from './saved-trip-wizard' import TripBasicsPane from './trip-basics-pane' @@ -28,6 +29,10 @@ function getNewMonitoredTrip (loggedInUser, queryParams, itineraries) { } } +function hasMaxTripCount (trips) { + return trips && trips.length >= 5 +} + /** * This screen handles saving a trip from an OTP query for the logged-in user. */ @@ -43,11 +48,37 @@ class SavedTripScreen extends Component { constructor (props) { super(props) - const { itineraries, loggedInUser, monitoredTrip, queryParams, wizard } = props - const thisMonitoredTrip = (wizard || !monitoredTrip) ? getNewMonitoredTrip(loggedInUser, queryParams, itineraries) : monitoredTrip + const { itineraries, loggedInUser, queryParams, wizard } = props + const thisMonitoredTrip = wizard ? getNewMonitoredTrip(loggedInUser, queryParams, itineraries) : null this.state = { - monitoredTrip: thisMonitoredTrip + monitoredTrip: thisMonitoredTrip, + trips: null + } + } + + _fetchTrips = async () => { + const { auth, persistence, routeTo, wizard } = this.props + const fetchResult = await getTrips(persistence.otp_middleware, auth.accessToken) + + if (fetchResult.status === 'success') { + const trips = fetchResult.data + this.setState({ trips }) + + // There is a middleware limit of 5 saved trips, + // so if that limit is already reached, alert, then show editing mode. + const maxTripCount = hasMaxTripCount(trips) + if (wizard && maxTripCount) { + alert('You already have reached the maximum of five saved trips.\n' + + 'Please remove unused trips from your saved trips, and try again.') + + routeTo('/savedtrips') + return + } + + if (!wizard && trips.length) { + this._handleMonitoredTripSelect(trips[0]) + } } } @@ -86,6 +117,25 @@ class SavedTripScreen extends Component { } } + _handleDeleteTrip = async () => { + if (confirm('Would you like to remove this trip?')) { + const { auth, persistence } = this.props + const { monitoredTrip, trips } = this.state + const deleteResult = await deleteTrip(persistence.otp_middleware, auth.accessToken, monitoredTrip) + + if (deleteResult.status === 'success') { + const removedIndex = trips.indexOf(monitoredTrip) + const newTrips = [].concat(trips) + newTrips.splice(removedIndex, 1) + const newIndex = Math.min(removedIndex, newTrips.length - 1) + this.setState({ + monitoredTrip: newTrips[newIndex], + trips: newTrips + }) + } + } + } + _handleExit = () => { const { originalUrl } = this.props this.props.routeTo(originalUrl) @@ -126,14 +176,25 @@ class SavedTripScreen extends Component { summary: this._hookMonitoredTrip(TripSummaryPane) } - // TODO: Update title bar during componentDidMount. + async componentDidMount () { + await this._fetchTrips() + + // TODO: Update title bar during componentDidMount. + } render () { const { auth, wizard } = this.props - const { monitoredTrip } = this.state + const { monitoredTrip, trips } = this.state + + if (!trips) { + return + } + + const maxTripCount = hasMaxTripCount(trips) let content - if (wizard) { + + if (wizard && !maxTripCount) { content = ( ) } diff --git a/lib/components/user/stacked-pane-display.js b/lib/components/user/stacked-pane-display.js index da013f1ab..aa5ded890 100644 --- a/lib/components/user/stacked-pane-display.js +++ b/lib/components/user/stacked-pane-display.js @@ -21,7 +21,7 @@ const PaneContainer = styled.div` */ const StackedPaneDisplay = ({ onCancel, onComplete, paneSequence, title }) => ( <> -

{title}

+ {title &&

{title}

} { paneSequence.map(({ pane: Pane, props, title }, index) => ( diff --git a/lib/components/user/trip-basics-pane.js b/lib/components/user/trip-basics-pane.js index c96f0d016..8dd5231a4 100644 --- a/lib/components/user/trip-basics-pane.js +++ b/lib/components/user/trip-basics-pane.js @@ -3,7 +3,6 @@ import React, { Component } from 'react' import { connect } from 'react-redux' import { ButtonToolbar, ControlLabel, FormControl, FormGroup, ToggleButton, ToggleButtonGroup } from 'react-bootstrap' -import { WEEKDAYS } from '../../util/constants' import { getActiveItineraries, getActiveSearch } from '../../util/state' import ItinerarySummary from '../narrative/default/itinerary-summary' @@ -44,9 +43,9 @@ class TripBasicsPane extends Component { Please provide a name for this trip: @@ -54,7 +53,6 @@ class TripBasicsPane extends Component { What days to you take this trip? Date: Thu, 11 Jun 2020 17:49:57 -0400 Subject: [PATCH 07/46] refactor(TripSummary): Extract trip summary component and related changes. --- lib/components/user/saved-trip-editor.js | 6 +-- lib/components/user/saved-trip-screen.js | 34 ++++++++------- lib/components/user/saved-trip-wizard.js | 3 +- lib/components/user/trip-basics-pane.js | 54 +++++------------------- lib/components/user/trip-summary-pane.js | 53 ++++------------------- lib/components/user/trip-summary.js | 31 ++++++++++++++ lib/util/query.js | 18 ++++++++ 7 files changed, 92 insertions(+), 107 deletions(-) create mode 100644 lib/components/user/trip-summary.js create mode 100644 lib/util/query.js diff --git a/lib/components/user/saved-trip-editor.js b/lib/components/user/saved-trip-editor.js index cb62b8633..d52948e18 100644 --- a/lib/components/user/saved-trip-editor.js +++ b/lib/components/user/saved-trip-editor.js @@ -2,6 +2,7 @@ import React, { Component } from 'react' import { Button, Col, Nav, NavItem, Row } from 'react-bootstrap' import StackedPaneDisplay from './stacked-pane-display' +import TripSummary from './trip-summary' /** * This component handles editing saved trips. @@ -17,9 +18,8 @@ class SavedTripEditor extends Component { const paneSequence = monitoredTrip ? [ { - pane: () => ( -
{monitoredTrip.queryParams}
- ), + pane: TripSummary, + props: { monitoredTrip }, title: monitoredTrip.tripName }, { diff --git a/lib/components/user/saved-trip-screen.js b/lib/components/user/saved-trip-screen.js index dcab292b3..9ad01b681 100644 --- a/lib/components/user/saved-trip-screen.js +++ b/lib/components/user/saved-trip-screen.js @@ -15,13 +15,14 @@ import SavedTripWizard from './saved-trip-wizard' import TripBasicsPane from './trip-basics-pane' import TripNotificationsPane from './trip-notifications-pane' import TripSummaryPane from './trip-summary-pane' +import withLoggedInUserSupport from './with-logged-in-user-support' -function getNewMonitoredTrip (loggedInUser, queryParams, itineraries) { +function getNewMonitoredTrip (loggedInUser, queryParams, itinerary) { return { days: WEEKDAYS, excludeFederalHolidays: true, isActive: true, - itinerary: itineraries, + itinerary, leadTimeInMinutes: 30, queryParams, tripName: '', @@ -48,8 +49,8 @@ class SavedTripScreen extends Component { constructor (props) { super(props) - const { itineraries, loggedInUser, queryParams, wizard } = props - const thisMonitoredTrip = wizard ? getNewMonitoredTrip(loggedInUser, queryParams, itineraries) : null + const { itinerary, loggedInUser, queryParams, wizard } = props + const thisMonitoredTrip = wizard ? getNewMonitoredTrip(loggedInUser, queryParams, itinerary) : null this.state = { monitoredTrip: thisMonitoredTrip, @@ -58,8 +59,8 @@ class SavedTripScreen extends Component { } _fetchTrips = async () => { - const { auth, persistence, routeTo, wizard } = this.props - const fetchResult = await getTrips(persistence.otp_middleware, auth.accessToken) + const { accessToken, persistence, routeTo, wizard } = this.props + const fetchResult = await getTrips(persistence.otp_middleware, accessToken) if (fetchResult.status === 'success') { const trips = fetchResult.data @@ -93,10 +94,9 @@ class SavedTripScreen extends Component { } _updateMonitoredTrip = async () => { - const { auth, persistence, wizard } = this.props + const { accessToken, persistence, wizard } = this.props if (persistence && persistence.otp_middleware) { - const { accessToken } = auth const { monitoredTrip } = this.state // TODO: Change state of Save button. @@ -119,9 +119,9 @@ class SavedTripScreen extends Component { _handleDeleteTrip = async () => { if (confirm('Would you like to remove this trip?')) { - const { auth, persistence } = this.props + const { accessToken, persistence } = this.props const { monitoredTrip, trips } = this.state - const deleteResult = await deleteTrip(persistence.otp_middleware, auth.accessToken, monitoredTrip) + const deleteResult = await deleteTrip(persistence.otp_middleware, accessToken, monitoredTrip) if (deleteResult.status === 'success') { const removedIndex = trips.indexOf(monitoredTrip) @@ -183,7 +183,7 @@ class SavedTripScreen extends Component { } render () { - const { auth, wizard } = this.props + const { wizard } = this.props const { monitoredTrip, trips } = this.state if (!trips) { @@ -197,6 +197,7 @@ class SavedTripScreen extends Component { if (wizard && !maxTripCount) { content = ( @@ -204,7 +205,6 @@ class SavedTripScreen extends Component { } else { content = ( { const itineraries = getActiveItineraries(state.otp) return { activeItinerary, - itineraries, - loggedInUser: state.otp.user.loggedInUser, + accessToken: state.user.accessToken, + itinerary: itineraries[activeItinerary], + loggedInUser: state.user.loggedInUser, persistence: state.otp.config.persistence, queryParams: state.router.location.search } @@ -247,4 +248,7 @@ const mapDispatchToProps = { routeTo } -export default withLoginRequired(connect(mapStateToProps, mapDispatchToProps)(SavedTripScreen)) +export default withLoggedInUserSupport( + withLoginRequired(connect(mapStateToProps, mapDispatchToProps)(SavedTripScreen)), + true +) diff --git a/lib/components/user/saved-trip-wizard.js b/lib/components/user/saved-trip-wizard.js index a5ad2c5d4..e1cf6f88e 100644 --- a/lib/components/user/saved-trip-wizard.js +++ b/lib/components/user/saved-trip-wizard.js @@ -5,9 +5,10 @@ import SequentialPaneDisplay from './sequential-pane-display' /** * This component is the new account wizard. */ -const SavedTripWizard = ({ onComplete, panes }) => { +const SavedTripWizard = ({ monitoredTrip, onComplete, panes }) => { const paneSequence = { basics: { + disableNext: !monitoredTrip.tripName, nextId: 'notifications', pane: panes.basics, title: 'Trip information' diff --git a/lib/components/user/trip-basics-pane.js b/lib/components/user/trip-basics-pane.js index 8dd5231a4..7b7fb48a0 100644 --- a/lib/components/user/trip-basics-pane.js +++ b/lib/components/user/trip-basics-pane.js @@ -1,12 +1,7 @@ -import coreUtils from '@opentripplanner/core-utils' import React, { Component } from 'react' -import { connect } from 'react-redux' import { ButtonToolbar, ControlLabel, FormControl, FormGroup, ToggleButton, ToggleButtonGroup } from 'react-bootstrap' -import { getActiveItineraries, getActiveSearch } from '../../util/state' -import ItinerarySummary from '../narrative/default/itinerary-summary' - -const { formatDuration, formatTime } = coreUtils.time +import TripSummary from './trip-summary' class TripBasicsPane extends Component { _handleTripDaysChange = e => { @@ -20,32 +15,23 @@ class TripBasicsPane extends Component { } render () { - const { activeItinerary, activeSearch, itinerary, monitoredTrip, pending } = this.props - const { from, to } = activeSearch.query + const { monitoredTrip } = this.props + const { itinerary } = monitoredTrip - if (pending) { - return
Loading...
- } else if (itinerary) { - // TODO: use the modern itinerary summary built for trip comparison. + if (!itinerary) { + return
No itinerary to display.
+ } else { return (
Selected itinerary: -
From {from.name} to {to.name}
-
- -
+ Please provide a name for this trip: @@ -70,28 +56,8 @@ class TripBasicsPane extends Component {
) - } else { - return
No itinerary to display.
} } } -// connect to the redux store - -const mapStateToProps = (state, ownProps) => { - const activeSearch = getActiveSearch(state.otp) - const pending = activeSearch ? activeSearch.pending : false - const activeItinerary = activeSearch && activeSearch.activeItinerary - const itineraries = getActiveItineraries(state.otp) - return { - activeItinerary, - activeSearch, - itinerary: activeItinerary != null && itineraries[activeItinerary], - pending - } -} - -const mapDispatchToProps = { -} - -export default connect(mapStateToProps, mapDispatchToProps)(TripBasicsPane) +export default TripBasicsPane diff --git a/lib/components/user/trip-summary-pane.js b/lib/components/user/trip-summary-pane.js index 741887843..374256d90 100644 --- a/lib/components/user/trip-summary-pane.js +++ b/lib/components/user/trip-summary-pane.js @@ -1,36 +1,21 @@ -import coreUtils from '@opentripplanner/core-utils' import React, { Component } from 'react' -import { connect } from 'react-redux' import { ControlLabel } from 'react-bootstrap' -import { getActiveItineraries, getActiveSearch } from '../../util/state' -import ItinerarySummary from '../narrative/default/itinerary-summary' - -const { formatDuration, formatTime } = coreUtils.time +import TripSummary from './trip-summary' class TripSummaryPane extends Component { render () { - const { activeItinerary, activeSearch, itinerary, monitoredTrip, pending } = this.props - const { from, to } = activeSearch.query + const { monitoredTrip } = this.props + const { itinerary } = monitoredTrip - if (pending) { - return
Loading...
- } else if (itinerary) { + if (!itinerary) { + return
No itinerary to display.
+ } else { // TODO: use the modern itinerary summary built for trip comparison. return (
-

Below is a summary of your trip and its notification preferences.

- - {monitoredTrip.tripName} -
From {from.name} to {to.name}
-
- -
+ Selected itinerary: +

Happens on: {monitoredTrip.days.join(', ')}

Notifications: {monitoredTrip.isActive @@ -39,28 +24,8 @@ class TripSummaryPane extends Component { {monitoredTrip.excludeFederalHolidays &&

Except for US federal holidays

}
) - } else { - return
No itinerary to display.
} } } -// connect to the redux store - -const mapStateToProps = (state, ownProps) => { - const activeSearch = getActiveSearch(state.otp) - const pending = activeSearch ? activeSearch.pending : false - const activeItinerary = activeSearch && activeSearch.activeItinerary - const itineraries = getActiveItineraries(state.otp) - return { - activeItinerary, - activeSearch, - itinerary: activeItinerary != null && itineraries[activeItinerary], - pending - } -} - -const mapDispatchToProps = { -} - -export default connect(mapStateToProps, mapDispatchToProps)(TripSummaryPane) +export default TripSummaryPane diff --git a/lib/components/user/trip-summary.js b/lib/components/user/trip-summary.js new file mode 100644 index 000000000..123c83941 --- /dev/null +++ b/lib/components/user/trip-summary.js @@ -0,0 +1,31 @@ +import coreUtils from '@opentripplanner/core-utils' +import { ClassicLegIcon } from '@opentripplanner/icons' +import React from 'react' + +import { getQueryParamsFromQueryString } from '../../util/query' +import ItinerarySummary from '../narrative/default/itinerary-summary' + +const { formatDuration, formatTime } = coreUtils.time + +const TripSummary = ({ monitoredTrip }) => { + const { itinerary, queryParams } = monitoredTrip + const queryObj = getQueryParamsFromQueryString(queryParams) + const { from, to } = queryObj + + // TODO: use the modern itinerary summary built for trip comparison. + return ( +
+
From {from.name} to {to.name}
+
+
+ Itinerary{' '} + {formatDuration(itinerary.duration)}{' '} + {formatTime(itinerary.startTime)}—{formatTime(itinerary.endTime)} + +
+
+
+ ) +} + +export default TripSummary diff --git a/lib/util/query.js b/lib/util/query.js new file mode 100644 index 000000000..3f6bd4f6c --- /dev/null +++ b/lib/util/query.js @@ -0,0 +1,18 @@ +import coreUtils from '@opentripplanner/core-utils' +import qs from 'qs' + +const { planParamsToQuery } = coreUtils.query + +/** Extracts a query object for a monitored trip. */ +export function getQueryParamsFromQueryString (queryString, config) { + // Note: monitoredTrip.queryParams starts with '?'. + const queryObj = qs.parse(queryString.split('?')[1]) + + // Filter out the OTP (i.e. non-UI) params. + const planParams = {} + Object.keys(queryObj).forEach(key => { + if (!key.startsWith('ui_')) planParams[key] = queryObj[key] + }) + + return planParamsToQuery(planParams, config) +} From cafe68c7d1121ce7370d9be174215a75cbb5200f Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Thu, 11 Jun 2020 18:52:20 -0400 Subject: [PATCH 08/46] refactor(query): Reuse getQueryParamsFromQueryObj --- lib/actions/form.js | 17 ++++++----------- lib/util/query.js | 14 +++++++++----- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/lib/actions/form.js b/lib/actions/form.js index abaa4ff00..c536a8387 100644 --- a/lib/actions/form.js +++ b/lib/actions/form.js @@ -5,6 +5,7 @@ import coreUtils from '@opentripplanner/core-utils' import { createAction } from 'redux-actions' import { queryIsValid } from '../util/state' +import { getQueryParamsFromQueryObj } from '../util/query' import { MobileScreens, setMainPanelContent, @@ -16,8 +17,7 @@ import { routingQuery } from './api' const { getDefaultQuery, getTripOptionsFromQuery, - getUrlParams, - planParamsToQuery + getUrlParams } = coreUtils.query export const settingQueryParam = createAction('SET_QUERY_PARAM') @@ -63,19 +63,14 @@ export function setQueryParam (payload, searchId) { export function parseUrlQueryString (params = getUrlParams()) { return function (dispatch, getState) { - // Filter out the OTP (i.e. non-UI) params and set the initial query - const planParams = {} - Object.keys(params).forEach(key => { - if (!key.startsWith('ui_')) planParams[key] = params[key] - }) const searchId = params.ui_activeSearch || coreUtils.storage.randId() + // Convert strings to numbers/objects and dispatch + const queryParams = getQueryParamsFromQueryObj(params, getState().otp.config) + dispatch( setQueryParam( - planParamsToQuery( - planParams, - getState().otp.config - ), + queryParams, searchId ) ) diff --git a/lib/util/query.js b/lib/util/query.js index 3f6bd4f6c..1d540a91d 100644 --- a/lib/util/query.js +++ b/lib/util/query.js @@ -3,16 +3,20 @@ import qs from 'qs' const { planParamsToQuery } = coreUtils.query -/** Extracts a query object for a monitored trip. */ -export function getQueryParamsFromQueryString (queryString, config) { - // Note: monitoredTrip.queryParams starts with '?'. - const queryObj = qs.parse(queryString.split('?')[1]) - +/** Extracts a query object from a query object, using an optional OTP configuration. */ +export function getQueryParamsFromQueryObj (queryObj, config) { // Filter out the OTP (i.e. non-UI) params. const planParams = {} Object.keys(queryObj).forEach(key => { if (!key.startsWith('ui_')) planParams[key] = queryObj[key] }) + // Fully-format query object. return planParamsToQuery(planParams, config) } + +/** Extracts a query object from a query string, using an optional OTP configuration. */ +export function getQueryParamsFromQueryString (queryString, config) { + const queryObj = qs.parse(queryString.split('?')[1]) + return getQueryParamsFromQueryObj(queryObj, config) +} From 27a60ce66fd5fa128e0e5efb861a549bee6fcb88 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Thu, 11 Jun 2020 19:09:04 -0400 Subject: [PATCH 09/46] improvement(TripNotificationPane): Add style for info text. --- lib/components/user/existing-account-display.js | 2 +- lib/components/user/trip-notifications-pane.js | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/components/user/existing-account-display.js b/lib/components/user/existing-account-display.js index 68a8b22e7..7e90c955e 100644 --- a/lib/components/user/existing-account-display.js +++ b/lib/components/user/existing-account-display.js @@ -18,7 +18,7 @@ class ExistingAccountDisplay extends Component { const { onCancel, onComplete, panes } = this.props const paneSequence = [ { - pane: () => , + pane: () =>

, title: 'My trips' }, { diff --git a/lib/components/user/trip-notifications-pane.js b/lib/components/user/trip-notifications-pane.js index a34e4f626..b291a26bc 100644 --- a/lib/components/user/trip-notifications-pane.js +++ b/lib/components/user/trip-notifications-pane.js @@ -1,5 +1,10 @@ import React, { Component } from 'react' import { Alert, Checkbox, ControlLabel, FormControl, FormGroup, Radio } from 'react-bootstrap' +import styled from 'styled-components' + +const SmallInfoText = styled.p` + font-size: 80%; +` class TripNotificationsPane extends Component { _handleIsActiveChange = e => { @@ -41,7 +46,9 @@ class TripNotificationsPane extends Component { No -

[small] Note: you will be notified by [email|SMS]. This can be changed in your account settings once the trip has been saved.

+ + Note: you will be notified by [email|SMS]. This can be changed in your account settings once the trip has been saved. +
@@ -63,7 +70,10 @@ class TripNotificationsPane extends Component { No -

[small] Note: you can always pause notifications for this trip once the trip has been saved.

+ + Note: you can always pause notifications for this trip once the trip has been saved. + +
From ae2331c36d71fe32aace255a5b6baa8e1d96accd Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 12 Jun 2020 14:56:02 -0400 Subject: [PATCH 10/46] fix(monitoredtrip API): Change monitored trip URL to match API. --- lib/components/user/saved-trip-screen.js | 3 +-- lib/util/middleware.js | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/components/user/saved-trip-screen.js b/lib/components/user/saved-trip-screen.js index 9ad01b681..03756e119 100644 --- a/lib/components/user/saved-trip-screen.js +++ b/lib/components/user/saved-trip-screen.js @@ -233,9 +233,8 @@ class SavedTripScreen extends Component { const mapStateToProps = (state, ownProps) => { const activeSearch = getActiveSearch(state.otp) const activeItinerary = activeSearch && activeSearch.activeItinerary - const itineraries = getActiveItineraries(state.otp) + const itineraries = getActiveItineraries(state.otp) || [] return { - activeItinerary, accessToken: state.user.accessToken, itinerary: itineraries[activeItinerary], loggedInUser: state.user.loggedInUser, diff --git a/lib/util/middleware.js b/lib/util/middleware.js index f92148287..fb8f70111 100644 --- a/lib/util/middleware.js +++ b/lib/util/middleware.js @@ -1,7 +1,7 @@ if (typeof (fetch) === 'undefined') require('isomorphic-fetch') const API_USER_PATH = '/api/secure/user' -const API_MONITORTRIP_PATH = '/api/secure/monitortrip' +const API_MONITORTRIP_PATH = '/api/secure/monitoredtrip' /** * This method builds the options object for call to the fetch method. From 9ab83bf28ba703bd531589ee5a485763b7ce4abf Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 15 Jul 2020 17:59:23 -0400 Subject: [PATCH 11/46] refactor(SavedTripScreen): Finish merging changed from dev. --- lib/components/user/saved-trip-screen.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/components/user/saved-trip-screen.js b/lib/components/user/saved-trip-screen.js index 03756e119..d34a50bb6 100644 --- a/lib/components/user/saved-trip-screen.js +++ b/lib/components/user/saved-trip-screen.js @@ -5,7 +5,7 @@ import { withLoginRequired } from 'use-auth0-hooks' import { addTrip, deleteTrip, getTrips, updateTrip } from '../../util/middleware' import { routeTo } from '../../actions/ui' -import AppNav from '../app/app-nav' +import DesktopNav from '../app/desktop-nav' import { WEEKDAYS } from '../../util/constants' import { getActiveItineraries, getActiveSearch } from '../../util/state' From 0dde27d02446da4152d41795921e50978410ff9d Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Thu, 16 Jul 2020 10:54:22 -0400 Subject: [PATCH 12/46] refactor(SavedTripScreen): Rename AppNav -> DesktopNav --- lib/components/user/saved-trip-screen.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/components/user/saved-trip-screen.js b/lib/components/user/saved-trip-screen.js index d34a50bb6..0aebce6fd 100644 --- a/lib/components/user/saved-trip-screen.js +++ b/lib/components/user/saved-trip-screen.js @@ -219,7 +219,7 @@ class SavedTripScreen extends Component { return (
{/* TODO: Do mobile view. */} - +
{content}
From 4f05fb1c697a8973acf11296c08c8f9e0aac6ef5 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Thu, 16 Jul 2020 11:12:44 -0400 Subject: [PATCH 13/46] refactor(actions/form): Add unmerged file. --- lib/actions/form.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/actions/form.js b/lib/actions/form.js index edeaafcd7..023e5ff17 100644 --- a/lib/actions/form.js +++ b/lib/actions/form.js @@ -5,7 +5,6 @@ import coreUtils from '@opentripplanner/core-utils' import { createAction } from 'redux-actions' import { queryIsValid } from '../util/state' -import { getQueryParamsFromQueryObj } from '../util/query' import { MobileScreens, setMainPanelContent, @@ -17,7 +16,8 @@ import { routingQuery } from './api' const { getDefaultQuery, getTripOptionsFromQuery, - getUrlParams + getUrlParams, + planParamsToQuery } = coreUtils.query export const settingQueryParam = createAction('SET_QUERY_PARAM') @@ -63,8 +63,12 @@ export function setQueryParam (payload, searchId) { export function parseUrlQueryString (params = getUrlParams()) { return function (dispatch, getState) { + // Filter out the OTP (i.e. non-UI) params and set the initial query + const planParams = {} + Object.keys(params).forEach(key => { + if (!key.startsWith('ui_')) planParams[key] = params[key] + }) const searchId = params.ui_activeSearch || coreUtils.storage.randId() - // Convert strings to numbers/objects and dispatch planParamsToQuery(planParams, getState().otp.config) .then(query => dispatch(setQueryParam(query, searchId))) From 2f0c2ba2c2b51c642b0509388adfdd86182d9f30 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Thu, 16 Jul 2020 12:51:24 -0400 Subject: [PATCH 14/46] refactor(util/query): Make query methods async per OTP-UI --- lib/components/user/trip-summary.js | 71 +++++++++++++++++++++-------- lib/util/query.js | 5 +- 2 files changed, 55 insertions(+), 21 deletions(-) diff --git a/lib/components/user/trip-summary.js b/lib/components/user/trip-summary.js index 123c83941..f0df9f967 100644 --- a/lib/components/user/trip-summary.js +++ b/lib/components/user/trip-summary.js @@ -1,31 +1,64 @@ import coreUtils from '@opentripplanner/core-utils' import { ClassicLegIcon } from '@opentripplanner/icons' -import React from 'react' +import React, { Component } from 'react' +import { connect } from 'react-redux' import { getQueryParamsFromQueryString } from '../../util/query' import ItinerarySummary from '../narrative/default/itinerary-summary' const { formatDuration, formatTime } = coreUtils.time -const TripSummary = ({ monitoredTrip }) => { - const { itinerary, queryParams } = monitoredTrip - const queryObj = getQueryParamsFromQueryString(queryParams) - const { from, to } = queryObj - - // TODO: use the modern itinerary summary built for trip comparison. - return ( -
-
From {from.name} to {to.name}
-
-
- Itinerary{' '} - {formatDuration(itinerary.duration)}{' '} - {formatTime(itinerary.startTime)}—{formatTime(itinerary.endTime)} - +class TripSummary extends Component { + constructor () { + super() + + this.state = { + // Hold a default state for query location names while being fetched. + queryObj: { + from: {}, + to: {} + } + } + } + + async componentDidMount () { + const { monitoredTrip, config } = this.props + const { queryParams } = monitoredTrip + this.setState({ + queryObj: await getQueryParamsFromQueryString(queryParams, config) + }) + } + + render () { + const { itinerary } = this.props.monitoredTrip + const { from, to } = this.state.queryObj + + // TODO: use the modern itinerary summary built for trip comparison. + return ( +
+
From {from.name} to {to.name}
+
+
+ Itinerary{' '} + {formatDuration(itinerary.duration)}{' '} + {formatTime(itinerary.startTime)}—{formatTime(itinerary.endTime)} + +
-
- ) + ) + } +} + +// connect to the redux store + +const mapStateToProps = (state, ownProps) => { + return { + config: state.otp.config + } +} + +const mapDispatchToProps = { } -export default TripSummary +export default connect(mapStateToProps, mapDispatchToProps)(TripSummary) diff --git a/lib/util/query.js b/lib/util/query.js index 1d540a91d..c28ba333c 100644 --- a/lib/util/query.js +++ b/lib/util/query.js @@ -1,10 +1,11 @@ import coreUtils from '@opentripplanner/core-utils' import qs from 'qs' +// FIXME: Change to planParamsToQueryAsync with latest OTP-UI release. const { planParamsToQuery } = coreUtils.query /** Extracts a query object from a query object, using an optional OTP configuration. */ -export function getQueryParamsFromQueryObj (queryObj, config) { +export async function getQueryParamsFromQueryObj (queryObj, config) { // Filter out the OTP (i.e. non-UI) params. const planParams = {} Object.keys(queryObj).forEach(key => { @@ -16,7 +17,7 @@ export function getQueryParamsFromQueryObj (queryObj, config) { } /** Extracts a query object from a query string, using an optional OTP configuration. */ -export function getQueryParamsFromQueryString (queryString, config) { +export async function getQueryParamsFromQueryString (queryString, config) { const queryObj = qs.parse(queryString.split('?')[1]) return getQueryParamsFromQueryObj(queryObj, config) } From 5c8f06206e08d9e1f4522c9ab0bcbb007bdad283 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Thu, 16 Jul 2020 15:31:32 -0400 Subject: [PATCH 15/46] refactor(saved-trip-screen): Convert monitoredTrip.days to fields for each day. --- lib/components/user/saved-trip-screen.js | 8 +++++--- lib/components/user/trip-basics-pane.js | 5 +++-- lib/components/user/trip-summary-pane.js | 9 ++++++++- lib/components/user/trip-summary.js | 16 ++++++++++++++-- lib/util/constants.js | 2 -- lib/util/monitored-trip.js | 22 ++++++++++++++++++++++ 6 files changed, 52 insertions(+), 10 deletions(-) create mode 100644 lib/util/monitored-trip.js diff --git a/lib/components/user/saved-trip-screen.js b/lib/components/user/saved-trip-screen.js index 0aebce6fd..fd1d968b0 100644 --- a/lib/components/user/saved-trip-screen.js +++ b/lib/components/user/saved-trip-screen.js @@ -6,9 +6,8 @@ import { withLoginRequired } from 'use-auth0-hooks' import { addTrip, deleteTrip, getTrips, updateTrip } from '../../util/middleware' import { routeTo } from '../../actions/ui' import DesktopNav from '../app/desktop-nav' -import { WEEKDAYS } from '../../util/constants' +import { arrayToDayFields, WEEKDAYS } from '../../util/monitored-trip' import { getActiveItineraries, getActiveSearch } from '../../util/state' - import AwaitingScreen from './awaiting-screen' import SavedTripEditor from './saved-trip-editor' import SavedTripWizard from './saved-trip-wizard' @@ -17,9 +16,12 @@ import TripNotificationsPane from './trip-notifications-pane' import TripSummaryPane from './trip-summary-pane' import withLoggedInUserSupport from './with-logged-in-user-support' +/** + * Initializes a monitored trip object from the given query. + */ function getNewMonitoredTrip (loggedInUser, queryParams, itinerary) { return { - days: WEEKDAYS, + ...arrayToDayFields(WEEKDAYS), excludeFederalHolidays: true, isActive: true, itinerary, diff --git a/lib/components/user/trip-basics-pane.js b/lib/components/user/trip-basics-pane.js index 7b7fb48a0..f4df37298 100644 --- a/lib/components/user/trip-basics-pane.js +++ b/lib/components/user/trip-basics-pane.js @@ -1,12 +1,13 @@ import React, { Component } from 'react' import { ButtonToolbar, ControlLabel, FormControl, FormGroup, ToggleButton, ToggleButtonGroup } from 'react-bootstrap' +import { arrayToDayFields, dayFieldsToArray } from '../../util/monitored-trip' import TripSummary from './trip-summary' class TripBasicsPane extends Component { _handleTripDaysChange = e => { const { onMonitoredTripChange } = this.props - onMonitoredTripChange({ days: e }) + onMonitoredTripChange(arrayToDayFields(e)) } _handleTripNameChange = e => { @@ -41,7 +42,7 @@ class TripBasicsPane extends Component { Monday Tuesday diff --git a/lib/components/user/trip-summary-pane.js b/lib/components/user/trip-summary-pane.js index 374256d90..33d987cf8 100644 --- a/lib/components/user/trip-summary-pane.js +++ b/lib/components/user/trip-summary-pane.js @@ -1,6 +1,7 @@ import React, { Component } from 'react' import { ControlLabel } from 'react-bootstrap' +import { dayFieldsToArray } from '../../util/monitored-trip' import TripSummary from './trip-summary' class TripSummaryPane extends Component { @@ -12,12 +13,18 @@ class TripSummaryPane extends Component { return
No itinerary to display.
} else { // TODO: use the modern itinerary summary built for trip comparison. + + // For now, just capitalize the day fields from monitoredTrip. + const capitalizedDays = dayFieldsToArray(monitoredTrip).map( + day => `${day.charAt(0).toUpperCase()}${day.substr(1)}` + ) + return (
Selected itinerary: -

Happens on: {monitoredTrip.days.join(', ')}

+

Happens on: {capitalizedDays.join(', ')}

Notifications: {monitoredTrip.isActive ? `Enabled, ${monitoredTrip.leadTimeInMinutes} min. before scheduled departure` : 'Disabled'}

diff --git a/lib/components/user/trip-summary.js b/lib/components/user/trip-summary.js index f0df9f967..3fb763abd 100644 --- a/lib/components/user/trip-summary.js +++ b/lib/components/user/trip-summary.js @@ -13,7 +13,8 @@ class TripSummary extends Component { super() this.state = { - // Hold a default state for query location names while being fetched. + // Hold a default state for query location names + // while it is being fetched in updateQueryState. queryObj: { from: {}, to: {} @@ -21,7 +22,7 @@ class TripSummary extends Component { } } - async componentDidMount () { + updateQueryState = async () => { const { monitoredTrip, config } = this.props const { queryParams } = monitoredTrip this.setState({ @@ -29,6 +30,17 @@ class TripSummary extends Component { }) } + async componentDidMount () { + this.updateQueryState() + } + + async componentDidUpdate (prevProps) { + // Update the state the monitoredTrip prop has changed. + if (this.props.monitoredTrip !== prevProps.monitoredTrip) { + this.updateQueryState() + } + } + render () { const { itinerary } = this.props.monitoredTrip const { from, to } = this.state.queryObj diff --git a/lib/util/constants.js b/lib/util/constants.js index 99e26bcf6..3f16df29e 100644 --- a/lib/util/constants.js +++ b/lib/util/constants.js @@ -5,5 +5,3 @@ export const PERSISTENCE_STRATEGY_OTP_MIDDLEWARE = 'otp_middleware' // Gets the root URL, e.g. https://otp-instance.example.com:8080, computed once for all. export const URL_ROOT = `${window.location.protocol}//${window.location.host}` - -export const WEEKDAYS = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday'] diff --git a/lib/util/monitored-trip.js b/lib/util/monitored-trip.js new file mode 100644 index 000000000..5250a64b0 --- /dev/null +++ b/lib/util/monitored-trip.js @@ -0,0 +1,22 @@ +export const WEEKDAYS = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday'] +const ALL_DAYS = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'] + +/** + * Extracts the day of week fields of an object to an array. + * Example: { monday: truthy, tuesday: falsy, wednesday: truthy ... } => ['monday', 'wednesday' ...] + */ +export function dayFieldsToArray (obj) { + return ALL_DAYS.filter(day => obj[day]) +} + +/** + * Converts an array of day of week values into an object with those fields. + * Example: ['monday', 'wednesday' ...] => { monday: true, tuesday: false, wednesday: true ... } + */ +export function arrayToDayFields (array) { + const result = {} + ALL_DAYS.forEach(day => { + result[day] = array.includes(day) + }) + return result +} From c3dcf9fe02aace326c60695a7b12734bb6eb6203 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Thu, 16 Jul 2020 15:47:40 -0400 Subject: [PATCH 16/46] improvement(TripSummaryPane): Tweak text in TripSummaryPane --- lib/components/user/trip-summary-pane.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/components/user/trip-summary-pane.js b/lib/components/user/trip-summary-pane.js index 33d987cf8..85e4abf0e 100644 --- a/lib/components/user/trip-summary-pane.js +++ b/lib/components/user/trip-summary-pane.js @@ -1,5 +1,4 @@ import React, { Component } from 'react' -import { ControlLabel } from 'react-bootstrap' import { dayFieldsToArray } from '../../util/monitored-trip' import TripSummary from './trip-summary' @@ -21,14 +20,18 @@ class TripSummaryPane extends Component { return (
- Selected itinerary: +

{monitoredTrip.tripName}

-

Happens on: {capitalizedDays.join(', ')}

-

Notifications: {monitoredTrip.isActive - ? `Enabled, ${monitoredTrip.leadTimeInMinutes} min. before scheduled departure` - : 'Disabled'}

- {monitoredTrip.excludeFederalHolidays &&

Except for US federal holidays

} +

+ Happens on: {capitalizedDays.join(', ')} +

+

+ Notifications: {monitoredTrip.isActive + ? `${monitoredTrip.leadTimeInMinutes} min. before scheduled departure` + : 'Disabled'} + {monitoredTrip.isActive && monitoredTrip.excludeFederalHolidays && ' (except federal holidays)'} +

) } From 9ecf01750fecb85f1a98ad12c56fabbb54414f3c Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Thu, 16 Jul 2020 17:27:38 -0400 Subject: [PATCH 17/46] improvement(SavedTripScreen): Improve button flow for trip editing. --- lib/components/user/saved-trip-editor.js | 91 +++++++++++++----------- lib/components/user/saved-trip-screen.js | 32 ++++++--- 2 files changed, 70 insertions(+), 53 deletions(-) diff --git a/lib/components/user/saved-trip-editor.js b/lib/components/user/saved-trip-editor.js index d52948e18..c3102fbcf 100644 --- a/lib/components/user/saved-trip-editor.js +++ b/lib/components/user/saved-trip-editor.js @@ -1,26 +1,29 @@ import React, { Component } from 'react' -import { Button, Col, Nav, NavItem, Row } from 'react-bootstrap' +import { Button, ButtonGroup } from 'react-bootstrap' import StackedPaneDisplay from './stacked-pane-display' -import TripSummary from './trip-summary' +import TripSummaryPane from './trip-summary-pane' /** - * This component handles editing saved trips. + * This component handles displaying and editing saved trips. */ class SavedTripEditor extends Component { - _handleMonitoredTripSelect = selectedKey => { - const { onMonitoredTripSelect, trips } = this.props - if (onMonitoredTripSelect) onMonitoredTripSelect(trips[selectedKey]) + _handleTripSelect = index => () => { + const { onMonitoredTripSelect } = this.props + if (onMonitoredTripSelect) onMonitoredTripSelect(index) + + // TODO: Add URL handling so URL shows as #/savedtrips/trip-id-123 } render () { - const { monitoredTrip, onCancel, onComplete, onDeleteTrip, panes, trips } = this.props - const paneSequence = monitoredTrip - ? [ + const { monitoredTrip, onComplete, onDeleteTrip, panes, trips } = this.props + + let content + if (monitoredTrip) { + const paneSequence = [ { - pane: TripSummary, - props: { monitoredTrip }, - title: monitoredTrip.tripName + pane: panes.basics, + title: 'Trip overview' }, { pane: panes.notifications, @@ -31,37 +34,41 @@ class SavedTripEditor extends Component { title: 'Danger zone' } ] - : [ - { - pane: () => ( -
Add trips from the map.
- ), - title: 'You have no saved trips.' - } - ] - - return ( - <> -

My saved trips

+ content = ( + <> +

{monitoredTrip.tripName}

+ + + ) + } else if (trips.length === 0) { + content = ( + <> +

You have no saved trips

+

Perform a trip search from the map first.

+ + ) + } else { + // Stack the saved trip summaries. When the user clicks on one, they can edit that trip. + content = ( + <> +

My saved trips

+

Click on a saved trip below to modify it.

+ + {trips.map((trip, index) => ( + + ))} + + + ) + } - - - - - - - - - - ) + return content } } diff --git a/lib/components/user/saved-trip-screen.js b/lib/components/user/saved-trip-screen.js index fd1d968b0..2633c0a58 100644 --- a/lib/components/user/saved-trip-screen.js +++ b/lib/components/user/saved-trip-screen.js @@ -76,11 +76,6 @@ class SavedTripScreen extends Component { 'Please remove unused trips from your saved trips, and try again.') routeTo('/savedtrips') - return - } - - if (!wizard && trips.length) { - this._handleMonitoredTripSelect(trips[0]) } } } @@ -129,9 +124,9 @@ class SavedTripScreen extends Component { const removedIndex = trips.indexOf(monitoredTrip) const newTrips = [].concat(trips) newTrips.splice(removedIndex, 1) - const newIndex = Math.min(removedIndex, newTrips.length - 1) this.setState({ - monitoredTrip: newTrips[newIndex], + monitoredTrip: null, // select nothing + tripIndex: -1, // select nothing trips: newTrips }) } @@ -148,9 +143,25 @@ class SavedTripScreen extends Component { this._handleExit() } - _handleMonitoredTripSelect = trip => { + _handleMonitoredTripSelect = tripIndex => { + this.setState({ + monitoredTrip: this.state.trips[tripIndex], + tripIndex + }) + } + + _handleSaveTripEdits = async () => { + const { monitoredTrip, tripIndex } = this.state + const newTrips = [].concat(this.state.trips) + + // Need to update the state trips with the modified copy so far. + newTrips[tripIndex] = monitoredTrip + + await this._updateMonitoredTrip() this.setState({ - monitoredTrip: trip + monitoredTrip: null, // select nothing + tripIndex: -1, // select nothing + trips: newTrips }) } @@ -208,8 +219,7 @@ class SavedTripScreen extends Component { content = ( Date: Thu, 16 Jul 2020 17:33:26 -0400 Subject: [PATCH 18/46] chore(deps): Upgrade to @opentripplanner/core-utils 1.2.1 --- package.json | 2 +- yarn.lock | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index debb9d095..6f8d3264d 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "homepage": "https://github.com/opentripplanner/otp-react-redux#readme", "dependencies": { "@opentripplanner/base-map": "^1.0.1", - "@opentripplanner/core-utils": "^1.2.0", + "@opentripplanner/core-utils": "^1.2.1", "@opentripplanner/endpoints-overlay": "^1.0.1", "@opentripplanner/from-to-location-picker": "^1.0.1", "@opentripplanner/geocoder": "^1.0.2", diff --git a/yarn.lock b/yarn.lock index 053318dec..53af8ccb2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1368,6 +1368,20 @@ prop-types "^15.7.2" qs "^6.9.1" +"@opentripplanner/core-utils@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@opentripplanner/core-utils/-/core-utils-1.2.1.tgz#e21f1c6825a0922bd0fe1326fb5a7bbdac2db7f6" + integrity sha512-RHSPrGyu/sV9krVSTsuW1R8fTqj+l+CeHCTK8XLj5cYC4HbqkKLtnOXXwvWGGh21Cpt+jkVBiPcYtfluIX1qMQ== + dependencies: + "@mapbox/polyline" "^1.1.0" + "@turf/along" "^6.0.1" + bowser "^2.7.0" + lodash.isequal "^4.5.0" + moment "^2.24.0" + moment-timezone "^0.5.27" + prop-types "^15.7.2" + qs "^6.9.1" + "@opentripplanner/endpoints-overlay@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@opentripplanner/endpoints-overlay/-/endpoints-overlay-1.0.1.tgz#d95f0bbfddc9382b593845799b963340eece2742" From f0d0346f63134fe70fcd63a9ba339abd8bc92411 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Thu, 16 Jul 2020 17:58:46 -0400 Subject: [PATCH 19/46] refactor(util/query): Use planParamsToQueryAsync from OTP-UI/core-utils 1.2.1 --- lib/util/query.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/util/query.js b/lib/util/query.js index c28ba333c..612d8da2d 100644 --- a/lib/util/query.js +++ b/lib/util/query.js @@ -1,8 +1,7 @@ import coreUtils from '@opentripplanner/core-utils' import qs from 'qs' -// FIXME: Change to planParamsToQueryAsync with latest OTP-UI release. -const { planParamsToQuery } = coreUtils.query +const { planParamsToQueryAsync } = coreUtils.query /** Extracts a query object from a query object, using an optional OTP configuration. */ export async function getQueryParamsFromQueryObj (queryObj, config) { @@ -13,7 +12,7 @@ export async function getQueryParamsFromQueryObj (queryObj, config) { }) // Fully-format query object. - return planParamsToQuery(planParams, config) + return planParamsToQueryAsync(planParams, config) } /** Extracts a query object from a query string, using an optional OTP configuration. */ From 04ee40fbfb45820ab74b40a82f5ab39a1463e10c Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Mon, 20 Jul 2020 17:02:21 -0400 Subject: [PATCH 20/46] refactor(saved-trip-* components): Refactors and comment tweaks per PR comments. --- lib/components/user/saved-trip-editor.js | 9 ++---- lib/components/user/saved-trip-screen.js | 29 ++++++++++--------- lib/components/user/saved-trip-wizard.js | 2 +- .../user/trip-notifications-pane.js | 1 + lib/components/user/trip-summary-pane.js | 8 +++-- lib/util/middleware.js | 4 +-- 6 files changed, 27 insertions(+), 26 deletions(-) diff --git a/lib/components/user/saved-trip-editor.js b/lib/components/user/saved-trip-editor.js index c3102fbcf..df98d735f 100644 --- a/lib/components/user/saved-trip-editor.js +++ b/lib/components/user/saved-trip-editor.js @@ -18,7 +18,6 @@ class SavedTripEditor extends Component { render () { const { monitoredTrip, onComplete, onDeleteTrip, panes, trips } = this.props - let content if (monitoredTrip) { const paneSequence = [ { @@ -34,7 +33,7 @@ class SavedTripEditor extends Component { title: 'Danger zone' } ] - content = ( + return ( <>

{monitoredTrip.tripName}

) } else if (trips.length === 0) { - content = ( + return ( <>

You have no saved trips

Perform a trip search from the map first.

@@ -53,7 +52,7 @@ class SavedTripEditor extends Component { ) } else { // Stack the saved trip summaries. When the user clicks on one, they can edit that trip. - content = ( + return ( <>

My saved trips

Click on a saved trip below to modify it.

@@ -67,8 +66,6 @@ class SavedTripEditor extends Component { ) } - - return content } } diff --git a/lib/components/user/saved-trip-screen.js b/lib/components/user/saved-trip-screen.js index 2633c0a58..edd0f8768 100644 --- a/lib/components/user/saved-trip-screen.js +++ b/lib/components/user/saved-trip-screen.js @@ -19,7 +19,7 @@ import withLoggedInUserSupport from './with-logged-in-user-support' /** * Initializes a monitored trip object from the given query. */ -function getNewMonitoredTrip (loggedInUser, queryParams, itinerary) { +function createMonitoredTrip (loggedInUser, queryParams, itinerary) { return { ...arrayToDayFields(WEEKDAYS), excludeFederalHolidays: true, @@ -32,12 +32,17 @@ function getNewMonitoredTrip (loggedInUser, queryParams, itinerary) { } } +/** + * Checks that the maximum allowed number of saved trips has not been reached. + */ function hasMaxTripCount (trips) { + // TODO: Obtain the maximum number from a query to middleware (it is currently hard coded there too). return trips && trips.length >= 5 } /** - * This screen handles saving a trip from an OTP query for the logged-in user. + * This screen handles saving a trip from an OTP query, or editing saved trips + * for the currently logged-in user. */ class SavedTripScreen extends Component { static propTypes = { @@ -51,8 +56,8 @@ class SavedTripScreen extends Component { constructor (props) { super(props) - const { itinerary, loggedInUser, queryParams, wizard } = props - const thisMonitoredTrip = wizard ? getNewMonitoredTrip(loggedInUser, queryParams, itinerary) : null + const { isCreating, itinerary, loggedInUser, queryParams } = props + const thisMonitoredTrip = isCreating ? createMonitoredTrip(loggedInUser, queryParams, itinerary) : null this.state = { monitoredTrip: thisMonitoredTrip, @@ -61,7 +66,7 @@ class SavedTripScreen extends Component { } _fetchTrips = async () => { - const { accessToken, persistence, routeTo, wizard } = this.props + const { accessToken, isCreating, persistence, routeTo } = this.props const fetchResult = await getTrips(persistence.otp_middleware, accessToken) if (fetchResult.status === 'success') { @@ -70,8 +75,7 @@ class SavedTripScreen extends Component { // There is a middleware limit of 5 saved trips, // so if that limit is already reached, alert, then show editing mode. - const maxTripCount = hasMaxTripCount(trips) - if (wizard && maxTripCount) { + if (isCreating && hasMaxTripCount(trips)) { alert('You already have reached the maximum of five saved trips.\n' + 'Please remove unused trips from your saved trips, and try again.') @@ -91,7 +95,7 @@ class SavedTripScreen extends Component { } _updateMonitoredTrip = async () => { - const { accessToken, persistence, wizard } = this.props + const { accessToken, isCreating, persistence } = this.props if (persistence && persistence.otp_middleware) { const { monitoredTrip } = this.state @@ -99,7 +103,7 @@ class SavedTripScreen extends Component { // TODO: Change state of Save button. let result - if (wizard) { + if (isCreating) { result = await addTrip(persistence.otp_middleware, accessToken, monitoredTrip) } else { result = await updateTrip(persistence.otp_middleware, accessToken, monitoredTrip) @@ -196,18 +200,15 @@ class SavedTripScreen extends Component { } render () { - const { wizard } = this.props + const { isCreating } = this.props const { monitoredTrip, trips } = this.state if (!trips) { return } - const maxTripCount = hasMaxTripCount(trips) - let content - - if (wizard && !maxTripCount) { + if (isCreating && !hasMaxTripCount(trips)) { content = ( { const paneSequence = { diff --git a/lib/components/user/trip-notifications-pane.js b/lib/components/user/trip-notifications-pane.js index b291a26bc..ef9c49f91 100644 --- a/lib/components/user/trip-notifications-pane.js +++ b/lib/components/user/trip-notifications-pane.js @@ -47,6 +47,7 @@ class TripNotificationsPane extends Component { + {/* TODO: populate either email or SMS below */} Note: you will be notified by [email|SMS]. This can be changed in your account settings once the trip has been saved. diff --git a/lib/components/user/trip-summary-pane.js b/lib/components/user/trip-summary-pane.js index 85e4abf0e..a94d602aa 100644 --- a/lib/components/user/trip-summary-pane.js +++ b/lib/components/user/trip-summary-pane.js @@ -27,10 +27,12 @@ class TripSummaryPane extends Component { Happens on: {capitalizedDays.join(', ')}

- Notifications: {monitoredTrip.isActive + Notifications: + + {monitoredTrip.isActive ? `${monitoredTrip.leadTimeInMinutes} min. before scheduled departure` - : 'Disabled'} - {monitoredTrip.isActive && monitoredTrip.excludeFederalHolidays && ' (except federal holidays)'} + : 'Disabled'} +

) diff --git a/lib/util/middleware.js b/lib/util/middleware.js index caa8d8cd2..e5b670bfc 100644 --- a/lib/util/middleware.js +++ b/lib/util/middleware.js @@ -111,7 +111,7 @@ export async function addTrip (middlewareConfig, token, data) { export async function updateTrip (middlewareConfig, token, data) { const { apiBaseUrl, apiKey } = middlewareConfig - const { id } = data // Middleware ID, NOT auth0 (or similar) id. + const { id } = data const requestUrl = `${apiBaseUrl}${API_MONITORTRIP_PATH}/${id}` if (id) { @@ -128,7 +128,7 @@ export async function updateTrip (middlewareConfig, token, data) { export async function deleteTrip (middlewareConfig, token, data) { const { apiBaseUrl, apiKey } = middlewareConfig - const { id } = data // Middleware ID, NOT auth0 (or similar) id. + const { id } = data const requestUrl = `${apiBaseUrl}${API_MONITORTRIP_PATH}/${id}` if (id) { From 8541c0526faf51ea3a714be0a606eb28779e853a Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Mon, 20 Jul 2020 17:23:51 -0400 Subject: [PATCH 21/46] refactor(Responsive): Finish rename prop. --- lib/components/app/responsive-webapp.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/components/app/responsive-webapp.js b/lib/components/app/responsive-webapp.js index 1021d0294..5c6388b5d 100644 --- a/lib/components/app/responsive-webapp.js +++ b/lib/components/app/responsive-webapp.js @@ -238,7 +238,7 @@ class RouterWrapperWithAuth0 extends Component { path={'/savetrip'} component={(routerProps) => { const props = this._combineProps(routerProps) - return + return }} /> Date: Mon, 20 Jul 2020 17:24:19 -0400 Subject: [PATCH 22/46] refactor(TripSummary): Remove unused async. --- lib/components/user/trip-summary.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/components/user/trip-summary.js b/lib/components/user/trip-summary.js index 3fb763abd..fd63138a2 100644 --- a/lib/components/user/trip-summary.js +++ b/lib/components/user/trip-summary.js @@ -30,11 +30,11 @@ class TripSummary extends Component { }) } - async componentDidMount () { + componentDidMount () { this.updateQueryState() } - async componentDidUpdate (prevProps) { + componentDidUpdate (prevProps) { // Update the state the monitoredTrip prop has changed. if (this.props.monitoredTrip !== prevProps.monitoredTrip) { this.updateQueryState() From 563000c0f31529c3aaeafb858379a701b2243774 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 21 Jul 2020 09:56:55 -0400 Subject: [PATCH 23/46] style(TripSummaryPane): Fix lint --- lib/components/user/trip-summary-pane.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/components/user/trip-summary-pane.js b/lib/components/user/trip-summary-pane.js index a94d602aa..b563fd844 100644 --- a/lib/components/user/trip-summary-pane.js +++ b/lib/components/user/trip-summary-pane.js @@ -30,8 +30,8 @@ class TripSummaryPane extends Component { Notifications: {monitoredTrip.isActive - ? `${monitoredTrip.leadTimeInMinutes} min. before scheduled departure` - : 'Disabled'} + ? `${monitoredTrip.leadTimeInMinutes} min. before scheduled departure` + : 'Disabled'}

From 9592581707b6610f5a26f3c7992b72171b9b11da Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 21 Jul 2020 11:37:45 -0400 Subject: [PATCH 24/46] refactor(LinkButton): Make BeginSaveTripButton more generic. --- .../narrative/default/default-itinerary.js | 4 +- lib/components/user/begin-save-trip-button.js | 31 ---------------- .../user/existing-account-display.js | 21 ++--------- lib/components/user/link-button.js | 37 +++++++++++++++++++ 4 files changed, 42 insertions(+), 51 deletions(-) delete mode 100644 lib/components/user/begin-save-trip-button.js create mode 100644 lib/components/user/link-button.js diff --git a/lib/components/narrative/default/default-itinerary.js b/lib/components/narrative/default/default-itinerary.js index 7e8ae2fc2..62a6292cd 100644 --- a/lib/components/narrative/default/default-itinerary.js +++ b/lib/components/narrative/default/default-itinerary.js @@ -6,7 +6,7 @@ import ItinerarySummary from './itinerary-summary' import ItineraryDetails from './itinerary-details' import TripDetails from '../connected-trip-details' import TripTools from '../trip-tools' -import BeginSaveTripButton from '../../user/begin-save-trip-button' +import LinkButton from '../../user/link-button' const { formatDuration, formatTime } = coreUtils.time @@ -32,7 +32,7 @@ export default class DefaultItinerary extends NarrativeItinerary { Itinerary {index + 1}{' '} {formatDuration(itinerary.duration)}{' '} {formatTime(itinerary.startTime)}—{formatTime(itinerary.endTime)} - {' '} + Save{' '} {(active || expanded) && diff --git a/lib/components/user/begin-save-trip-button.js b/lib/components/user/begin-save-trip-button.js deleted file mode 100644 index b1624eba5..000000000 --- a/lib/components/user/begin-save-trip-button.js +++ /dev/null @@ -1,31 +0,0 @@ -import React, { Component } from 'react' -import { connect } from 'react-redux' -import { Button } from 'react-bootstrap' - -import { routeTo } from '../../actions/ui' - -/** - * TODO? Embed _handleClick inside itinerary summary. - * This button redirects to /savetrip to let a user define a monitored trip. - */ -class BeginSaveTripButton extends Component { - _handleClick = () => { - this.props.routeTo('/savetrip') - } - - render () { - return - } -} - -// connect to the redux store - -const mapStateToProps = (state, ownProps) => { - return {} -} - -const mapDispatchToProps = { - routeTo -} - -export default connect(mapStateToProps, mapDispatchToProps)(BeginSaveTripButton) diff --git a/lib/components/user/existing-account-display.js b/lib/components/user/existing-account-display.js index 7e90c955e..0513914f4 100644 --- a/lib/components/user/existing-account-display.js +++ b/lib/components/user/existing-account-display.js @@ -1,24 +1,17 @@ import React, { Component } from 'react' -import { Button } from 'react-bootstrap' -import { connect } from 'react-redux' - -import { routeTo } from '../../actions/ui' +import LinkButton from './link-button' import StackedPaneDisplay from './stacked-pane-display' /** * This component handles the existing account display. */ class ExistingAccountDisplay extends Component { - _handleViewMyTrips = () => { - this.props.routeTo('/savedtrips') - } - render () { const { onCancel, onComplete, panes } = this.props const paneSequence = [ { - pane: () =>

, + pane: () =>

Edit my trips

, title: 'My trips' }, { @@ -47,12 +40,4 @@ class ExistingAccountDisplay extends Component { } } -// connect to the redux store - -const mapStateToProps = (state, ownProps) => ({}) - -const mapDispatchToProps = { - routeTo -} - -export default connect(mapStateToProps, mapDispatchToProps)(ExistingAccountDisplay) +export default ExistingAccountDisplay diff --git a/lib/components/user/link-button.js b/lib/components/user/link-button.js new file mode 100644 index 000000000..58b406601 --- /dev/null +++ b/lib/components/user/link-button.js @@ -0,0 +1,37 @@ +import PropTypes from 'prop-types' +import React, { Component } from 'react' +import { connect } from 'react-redux' +import { Button } from 'react-bootstrap' + +import { routeTo } from '../../actions/ui' + +/** + * This button provides basic redirecting functionality. + * FIXME: Replace this component with Link (react-router-dom) or LinkContainer (react-router-bootstrap). + */ +class LinkButton extends Component { + static propTypes = { + /** The destination url when clicking the button. */ + to: PropTypes.string.isRequired + } + + _handleClick = () => { + this.props.routeTo(this.props.to) + } + + render () { + return + } +} + +// connect to the redux store + +const mapStateToProps = (state, ownProps) => { + return {} +} + +const mapDispatchToProps = { + routeTo +} + +export default connect(mapStateToProps, mapDispatchToProps)(LinkButton) From 0ce31e2b7ff4192b8d46132b76d6a452414697ee Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 21 Jul 2020 12:43:35 -0400 Subject: [PATCH 25/46] refactor(SavedTripScreen): Cleanup monitored trip state handling. --- lib/components/user/saved-trip-screen.js | 52 +++++++++++++++--------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/lib/components/user/saved-trip-screen.js b/lib/components/user/saved-trip-screen.js index edd0f8768..eac1782ed 100644 --- a/lib/components/user/saved-trip-screen.js +++ b/lib/components/user/saved-trip-screen.js @@ -28,7 +28,9 @@ function createMonitoredTrip (loggedInUser, queryParams, itinerary) { leadTimeInMinutes: 30, queryParams, tripName: '', - userId: loggedInUser.id // must provide to API. + // FIXME: Handle populating/checking userID from middleware too, + // so that providing this field is no longer needed. + userId: loggedInUser.id } } @@ -67,10 +69,9 @@ class SavedTripScreen extends Component { _fetchTrips = async () => { const { accessToken, isCreating, persistence, routeTo } = this.props - const fetchResult = await getTrips(persistence.otp_middleware, accessToken) + const { data: trips, status } = await getTrips(persistence.otp_middleware, accessToken) - if (fetchResult.status === 'success') { - const trips = fetchResult.data + if (status === 'success') { this.setState({ trips }) // There is a middleware limit of 5 saved trips, @@ -109,15 +110,32 @@ class SavedTripScreen extends Component { result = await updateTrip(persistence.otp_middleware, accessToken, monitoredTrip) } - // TODO: improve this. + // TODO: improve the confirmation messages. if (result.status === 'success') { + // Update our monitoredTrip variable with what gets returned from server. + // (there are updated attributes such as dates, auth0 etc.) + this._updateMonitoredTripState(result.data) + alert('Your preferences have been saved.') } else { alert(`An error was encountered:\n${JSON.stringify(result)}`) } + + return result } } + /** + * Updates the trip list and removes all selection. + */ + _updateTrips = newTrips => { + this.setState({ + monitoredTrip: null, // select nothing + tripIndex: -1, // select nothing + trips: newTrips + }) + } + _handleDeleteTrip = async () => { if (confirm('Would you like to remove this trip?')) { const { accessToken, persistence } = this.props @@ -128,11 +146,7 @@ class SavedTripScreen extends Component { const removedIndex = trips.indexOf(monitoredTrip) const newTrips = [].concat(trips) newTrips.splice(removedIndex, 1) - this.setState({ - monitoredTrip: null, // select nothing - tripIndex: -1, // select nothing - trips: newTrips - }) + this._updateTrips(newTrips) } } } @@ -155,18 +169,16 @@ class SavedTripScreen extends Component { } _handleSaveTripEdits = async () => { - const { monitoredTrip, tripIndex } = this.state - const newTrips = [].concat(this.state.trips) + const { data: newTrip, status } = await this._updateMonitoredTrip() - // Need to update the state trips with the modified copy so far. - newTrips[tripIndex] = monitoredTrip + // If update was successful, update the state trips with the modified copy so far. + if (status === 'success') { + const { tripIndex } = this.state + const newTrips = [].concat(this.state.trips) - await this._updateMonitoredTrip() - this.setState({ - monitoredTrip: null, // select nothing - tripIndex: -1, // select nothing - trips: newTrips - }) + newTrips[tripIndex] = newTrip + this._updateTrips(newTrips) + } } /** From 9749c71e75cc43b18002c787739037c2e7838026 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 21 Jul 2020 12:44:34 -0400 Subject: [PATCH 26/46] fix(TripSummary): Fix component unmount warning. --- lib/components/user/trip-summary.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/components/user/trip-summary.js b/lib/components/user/trip-summary.js index fd63138a2..2534c0b26 100644 --- a/lib/components/user/trip-summary.js +++ b/lib/components/user/trip-summary.js @@ -25,9 +25,12 @@ class TripSummary extends Component { updateQueryState = async () => { const { monitoredTrip, config } = this.props const { queryParams } = monitoredTrip - this.setState({ - queryObj: await getQueryParamsFromQueryString(queryParams, config) - }) + const queryObj = await getQueryParamsFromQueryString(queryParams, config) + + // Prevent a state update if componentWillUnmount happened while await was executing. + if (!this.state.willUnmount) { + this.setState({ queryObj }) + } } componentDidMount () { @@ -41,6 +44,12 @@ class TripSummary extends Component { } } + componentWillMount () { + this.setState({ + willUnmount: true + }) + } + render () { const { itinerary } = this.props.monitoredTrip const { from, to } = this.state.queryObj From fb2ffe29720f1b81648c6019dbcfcdd3dc08acb8 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 21 Jul 2020 12:48:50 -0400 Subject: [PATCH 27/46] fix(TripNotificationsPane): Fix uncontrolled component warning. --- lib/components/user/trip-notifications-pane.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/components/user/trip-notifications-pane.js b/lib/components/user/trip-notifications-pane.js index ef9c49f91..8e75226f4 100644 --- a/lib/components/user/trip-notifications-pane.js +++ b/lib/components/user/trip-notifications-pane.js @@ -84,7 +84,6 @@ class TripNotificationsPane extends Component { Check for delays or disruptions: Date: Tue, 21 Jul 2020 13:02:55 -0400 Subject: [PATCH 28/46] refactor(SavedTripScreen): Add links out of the saved trip screens. --- lib/components/user/saved-trip-editor.js | 6 ++++++ lib/components/user/saved-trip-wizard.js | 15 ++++++++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/lib/components/user/saved-trip-editor.js b/lib/components/user/saved-trip-editor.js index df98d735f..d54e30b32 100644 --- a/lib/components/user/saved-trip-editor.js +++ b/lib/components/user/saved-trip-editor.js @@ -1,6 +1,7 @@ import React, { Component } from 'react' import { Button, ButtonGroup } from 'react-bootstrap' +import LinkButton from './link-button' import StackedPaneDisplay from './stacked-pane-display' import TripSummaryPane from './trip-summary-pane' @@ -18,6 +19,9 @@ class SavedTripEditor extends Component { render () { const { monitoredTrip, onComplete, onDeleteTrip, panes, trips } = this.props + // TODO: Improve navigation. + const accountLink =

Back to My Account

+ if (monitoredTrip) { const paneSequence = [ { @@ -46,6 +50,7 @@ class SavedTripEditor extends Component { } else if (trips.length === 0) { return ( <> + {accountLink}

You have no saved trips

Perform a trip search from the map first.

@@ -54,6 +59,7 @@ class SavedTripEditor extends Component { // Stack the saved trip summaries. When the user clicks on one, they can edit that trip. return ( <> + {accountLink}

My saved trips

Click on a saved trip below to modify it.

diff --git a/lib/components/user/saved-trip-wizard.js b/lib/components/user/saved-trip-wizard.js index 5feabf18f..bd38c0a7a 100644 --- a/lib/components/user/saved-trip-wizard.js +++ b/lib/components/user/saved-trip-wizard.js @@ -1,5 +1,6 @@ import React from 'react' +import LinkButton from './link-button' import SequentialPaneDisplay from './sequential-pane-display' /** @@ -27,11 +28,15 @@ const SavedTripWizard = ({ monitoredTrip, onComplete, panes }) => { } return ( - +
+ {/* TODO: Improve navigation. */} +

Back to Trip Planner

+ +
) } From 1f4ef522bc0f04d776bf38bcd5fc8f221cbae1bf Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 21 Jul 2020 18:12:42 -0400 Subject: [PATCH 29/46] refactor(TripNotificationPane): Add missing blank space. --- lib/components/user/trip-summary-pane.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/components/user/trip-summary-pane.js b/lib/components/user/trip-summary-pane.js index b563fd844..eea9ba233 100644 --- a/lib/components/user/trip-summary-pane.js +++ b/lib/components/user/trip-summary-pane.js @@ -27,7 +27,7 @@ class TripSummaryPane extends Component { Happens on: {capitalizedDays.join(', ')}

- Notifications: + Notifications:{' '} {monitoredTrip.isActive ? `${monitoredTrip.leadTimeInMinutes} min. before scheduled departure` From 1dda92a192e447be6e43d87bef4ed361b4925e5c Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 21 Jul 2020 18:13:55 -0400 Subject: [PATCH 30/46] refactor(LineItinerary): Extend Trip save button to LineItinerary. --- lib/components/narrative/line-itin/line-itinerary.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/components/narrative/line-itin/line-itinerary.js b/lib/components/narrative/line-itin/line-itinerary.js index 872d8df99..d4d6cda31 100644 --- a/lib/components/narrative/line-itin/line-itinerary.js +++ b/lib/components/narrative/line-itin/line-itinerary.js @@ -6,6 +6,7 @@ import ItineraryBody from './connected-itinerary-body' import ItinerarySummary from './itin-summary' import NarrativeItinerary from '../narrative-itinerary' import SimpleRealtimeAnnotation from '../simple-realtime-annotation' +import LinkButton from '../../user/link-button' const { getLegModeLabel, getTimeZoneOffset, isTransit } = coreUtils.itinerary @@ -74,6 +75,9 @@ export default class LineItinerary extends NarrativeItinerary { timeOptions={timeOptions} onClick={onClick} /> + + Save this option + {showRealtimeAnnotation && } {active || expanded ? Date: Fri, 24 Jul 2020 14:37:43 -0400 Subject: [PATCH 31/46] build(yarn.lock): Deduplicate yarn.lock. --- yarn.lock | 390 ++++++------------------------------------------------ 1 file changed, 42 insertions(+), 348 deletions(-) diff --git a/yarn.lock b/yarn.lock index 53af8ccb2..949c1ec44 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15,14 +15,7 @@ promise-polyfill "^8.1.3" unfetch "^4.1.0" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.5.5": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.5.5.tgz#bc0782f6d69f7b7d49531219699b988f669a8f9d" - integrity sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw== - dependencies: - "@babel/highlight" "^7.0.0" - -"@babel/code-frame@^7.8.3": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.5.5", "@babel/code-frame@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e" integrity sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g== @@ -49,18 +42,7 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/generator@^7.1.3", "@babel/generator@^7.4.0", "@babel/generator@^7.5.5": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.5.5.tgz#873a7f936a3c89491b43536d12245b626664e3cf" - integrity sha512-ETI/4vyTSxTzGnU2c49XHv2zhExkv9JHLTwDAFz85kmcwuShvYG2H08FwgIguQf4JC75CBnXAUM5PqeF4fj0nQ== - dependencies: - "@babel/types" "^7.5.5" - jsesc "^2.5.1" - lodash "^4.17.13" - source-map "^0.5.0" - trim-right "^1.0.1" - -"@babel/generator@^7.8.6": +"@babel/generator@^7.1.3", "@babel/generator@^7.4.0", "@babel/generator@^7.5.5", "@babel/generator@^7.8.6": version "7.8.6" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.8.6.tgz#57adf96d370c9a63c241cd719f9111468578537a" integrity sha512-4bpOR5ZBz+wWcMeVtcf7FbjcFzCp+817z2/gHNncIRcM9MmKzUhtWCYAq27RAfUrAFwb+OCG1s9WEaVxfi6cjg== @@ -131,16 +113,7 @@ "@babel/traverse" "^7.1.0" "@babel/types" "^7.0.0" -"@babel/helper-function-name@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz#a0ceb01685f73355d4360c1247f582bfafc8ff53" - integrity sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw== - dependencies: - "@babel/helper-get-function-arity" "^7.0.0" - "@babel/template" "^7.1.0" - "@babel/types" "^7.0.0" - -"@babel/helper-function-name@^7.8.3": +"@babel/helper-function-name@^7.1.0", "@babel/helper-function-name@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz#eeeb665a01b1f11068e9fb86ad56a1cb1a824cca" integrity sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA== @@ -149,14 +122,7 @@ "@babel/template" "^7.8.3" "@babel/types" "^7.8.3" -"@babel/helper-get-function-arity@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz#83572d4320e2a4657263734113c42868b64e49c3" - integrity sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ== - dependencies: - "@babel/types" "^7.0.0" - -"@babel/helper-get-function-arity@^7.8.3": +"@babel/helper-get-function-arity@^7.0.0", "@babel/helper-get-function-arity@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz#b894b947bd004381ce63ea1db9f08547e920abd5" integrity sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA== @@ -244,14 +210,7 @@ "@babel/template" "^7.1.0" "@babel/types" "^7.0.0" -"@babel/helper-split-export-declaration@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz#ff94894a340be78f53f06af038b205c49d993677" - integrity sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q== - dependencies: - "@babel/types" "^7.4.4" - -"@babel/helper-split-export-declaration@^7.8.3": +"@babel/helper-split-export-declaration@^7.4.4", "@babel/helper-split-export-declaration@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz#31a9f30070f91368a7182cf05f831781065fc7a9" integrity sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA== @@ -277,16 +236,7 @@ "@babel/traverse" "^7.5.5" "@babel/types" "^7.5.5" -"@babel/highlight@^7.0.0": - version "7.5.0" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.5.0.tgz#56d11312bd9248fa619591d02472be6e8cb32540" - integrity sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ== - dependencies: - chalk "^2.0.0" - esutils "^2.0.2" - js-tokens "^4.0.0" - -"@babel/highlight@^7.8.3": +"@babel/highlight@^7.0.0", "@babel/highlight@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.8.3.tgz#28f173d04223eaaa59bc1d439a3836e6d1265797" integrity sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg== @@ -300,12 +250,7 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.1.3.tgz#2c92469bac2b7fbff810b67fca07bd138b48af77" integrity sha512-gqmspPZOMW3MIRb9HlrnbZHXI1/KHTOroBwN1NcLL6pWxzqzEKGvRTq0W/PxS45OtQGbaFikSQpkS5zbnsQm2w== -"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.4.3", "@babel/parser@^7.4.4", "@babel/parser@^7.5.5": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.5.5.tgz#02f077ac8817d3df4a832ef59de67565e71cca4b" - integrity sha512-E5BN68cqR7dhKan1SfqgPGhQ178bkVKpXTPEXnFJBrEt8/DKRZlybmy+IgYLTeN7tp1R5Ccmbm2rBk17sHYU3g== - -"@babel/parser@^7.8.6": +"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.4.3", "@babel/parser@^7.4.4", "@babel/parser@^7.5.5", "@babel/parser@^7.8.6": version "7.8.6" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.8.6.tgz#ba5c9910cddb77685a008e3c587af8d27b67962c" integrity sha512-trGNYSfwq5s0SgM1BMEB8hX3NDmO7EP2wsDGDexiaKMB92BaRpS+qZfpkMqUBhcsOTBwNy9B/jieo4ad/t/z2g== @@ -987,30 +932,14 @@ dependencies: regenerator-runtime "^0.12.0" -"@babel/runtime@^7.1.2", "@babel/runtime@^7.2.0", "@babel/runtime@^7.4.0", "@babel/runtime@^7.4.5": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.5.5.tgz#74fba56d35efbeca444091c7850ccd494fd2f132" - integrity sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ== - dependencies: - regenerator-runtime "^0.13.2" - -"@babel/runtime@^7.7.6": +"@babel/runtime@^7.1.2", "@babel/runtime@^7.2.0", "@babel/runtime@^7.4.0", "@babel/runtime@^7.4.5", "@babel/runtime@^7.7.6": version "7.8.4" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.8.4.tgz#d79f5a2040f7caa24d53e563aad49cbc05581308" integrity sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ== dependencies: regenerator-runtime "^0.13.2" -"@babel/template@^7.1.0", "@babel/template@^7.4.0", "@babel/template@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.4.4.tgz#f4b88d1225689a08f5bc3a17483545be9e4ed237" - integrity sha512-CiGzLN9KgAvgZsnivND7rkA+AeJ9JB0ciPOD4U59GKbQP2iQl+olF1l76kJOupqidozfZ32ghwBEJDhnk9MEcw== - dependencies: - "@babel/code-frame" "^7.0.0" - "@babel/parser" "^7.4.4" - "@babel/types" "^7.4.4" - -"@babel/template@^7.8.3": +"@babel/template@^7.1.0", "@babel/template@^7.4.0", "@babel/template@^7.4.4", "@babel/template@^7.8.3": version "7.8.6" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.6.tgz#86b22af15f828dfb086474f964dcc3e39c43ce2b" integrity sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg== @@ -1019,22 +948,7 @@ "@babel/parser" "^7.8.6" "@babel/types" "^7.8.6" -"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.1.4", "@babel/traverse@^7.4.3", "@babel/traverse@^7.4.4", "@babel/traverse@^7.5.5": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.5.5.tgz#f664f8f368ed32988cd648da9f72d5ca70f165bb" - integrity sha512-MqB0782whsfffYfSjH4TM+LMjrJnhCNEDMDIjeTpl+ASaUvxcjoiVCo/sM1GhS1pHOXYfWVCYneLjMckuUxDaQ== - dependencies: - "@babel/code-frame" "^7.5.5" - "@babel/generator" "^7.5.5" - "@babel/helper-function-name" "^7.1.0" - "@babel/helper-split-export-declaration" "^7.4.4" - "@babel/parser" "^7.5.5" - "@babel/types" "^7.5.5" - debug "^4.1.0" - globals "^11.1.0" - lodash "^4.17.13" - -"@babel/traverse@^7.4.5": +"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.1.4", "@babel/traverse@^7.4.3", "@babel/traverse@^7.4.4", "@babel/traverse@^7.4.5", "@babel/traverse@^7.5.5": version "7.8.6" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.8.6.tgz#acfe0c64e1cd991b3e32eae813a6eb564954b5ff" integrity sha512-2B8l0db/DPi8iinITKuo7cbPznLCEk0kCxDoB9/N6gGNg/gxOXiR/IcymAFPiBwk5w6TtQ27w4wpElgp9btR9A== @@ -1049,16 +963,7 @@ globals "^11.1.0" lodash "^4.17.13" -"@babel/types@^7.0.0", "@babel/types@^7.0.0-beta.49", "@babel/types@^7.1.3", "@babel/types@^7.2.0", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.5.5": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.5.5.tgz#97b9f728e182785909aa4ab56264f090a028d18a" - integrity sha512-s63F9nJioLqOlW3UkyMd+BYhXt44YuaFm/VV0VwuteqjYwRrObkU7ra9pY4wAJR3oXi8hJrMcrcJdO/HH33vtw== - dependencies: - esutils "^2.0.2" - lodash "^4.17.13" - to-fast-properties "^2.0.0" - -"@babel/types@^7.8.3", "@babel/types@^7.8.6": +"@babel/types@^7.0.0", "@babel/types@^7.0.0-beta.49", "@babel/types@^7.1.3", "@babel/types@^7.2.0", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.5.5", "@babel/types@^7.8.3", "@babel/types@^7.8.6": version "7.8.6" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.8.6.tgz#629ecc33c2557fcde7126e58053127afdb3e6d01" integrity sha512-wqz7pgWMIrht3gquyEFPVXeXCti72Rm8ep9b5tQKz9Yg9LzJA3HxosF1SB3Kc81KD1A3XBkkVYtJvCKS2Z/QrA== @@ -1093,14 +998,7 @@ resolved "https://registry.yarnpkg.com/@csstools/convert-colors/-/convert-colors-1.4.0.tgz#ad495dc41b12e75d588c6db8b9834f08fa131eb7" integrity sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw== -"@emotion/is-prop-valid@^0.8.3": - version "0.8.7" - resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.7.tgz#803449993f436f9a6c67752251ea3fc492a1044c" - integrity sha512-OPkKzUeiid0vEKjZqnGcy2mzxjIlCffin+L2C02pdz/bVlt5zZZE2VzO0D3XOPnH0NEeF21QNKSXiZphjr4xiQ== - dependencies: - "@emotion/memoize" "0.7.4" - -"@emotion/is-prop-valid@^0.8.6": +"@emotion/is-prop-valid@^0.8.3", "@emotion/is-prop-valid@^0.8.6": version "0.8.8" resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a" integrity sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA== @@ -1354,21 +1252,7 @@ "@opentripplanner/core-utils" "^1.2.0" prop-types "^15.7.2" -"@opentripplanner/core-utils@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@opentripplanner/core-utils/-/core-utils-1.2.0.tgz#000eaa57605b73c522ad6243fc27bbb1f182dd7b" - integrity sha512-8FeJFywuuWlLnNHqLIu4ZejApVwVKFTJHX2KHMw2Cv+tmNUMvNfmESqZXQ5cMyEjSCdFtdT980/MB5GND+Fy8Q== - dependencies: - "@mapbox/polyline" "^1.1.0" - "@turf/along" "^6.0.1" - bowser "^2.7.0" - lodash.isequal "^4.5.0" - moment "^2.24.0" - moment-timezone "^0.5.27" - prop-types "^15.7.2" - qs "^6.9.1" - -"@opentripplanner/core-utils@^1.2.1": +"@opentripplanner/core-utils@^1.2.0", "@opentripplanner/core-utils@^1.2.1": version "1.2.1" resolved "https://registry.yarnpkg.com/@opentripplanner/core-utils/-/core-utils-1.2.1.tgz#e21f1c6825a0922bd0fe1326fb5a7bbdac2db7f6" integrity sha512-RHSPrGyu/sV9krVSTsuW1R8fTqj+l+CeHCTK8XLj5cYC4HbqkKLtnOXXwvWGGh21Cpt+jkVBiPcYtfluIX1qMQ== @@ -3904,7 +3788,7 @@ bytes@1: resolved "https://registry.yarnpkg.com/bytes/-/bytes-1.0.0.tgz#3569ede8ba34315fab99c3e92cb04c7220de1fa8" integrity sha1-NWnt6Lo0MV+rmcPpLLBMciDeH6g= -cacache@^12.0.0: +cacache@^12.0.0, cacache@^12.0.2, cacache@^12.0.3: version "12.0.4" resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.4.tgz#668bcbd105aeb5f1d92fe25570ec9525c8faa40c" integrity sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ== @@ -3925,27 +3809,6 @@ cacache@^12.0.0: unique-filename "^1.1.1" y18n "^4.0.0" -cacache@^12.0.2, cacache@^12.0.3: - version "12.0.3" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.3.tgz#be99abba4e1bf5df461cd5a2c1071fc432573390" - integrity sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw== - dependencies: - bluebird "^3.5.5" - chownr "^1.1.1" - figgy-pudding "^3.5.1" - glob "^7.1.4" - graceful-fs "^4.1.15" - infer-owner "^1.0.3" - lru-cache "^5.1.1" - mississippi "^3.0.0" - mkdirp "^0.5.1" - move-concurrently "^1.0.1" - promise-inflight "^1.0.1" - rimraf "^2.6.3" - ssri "^6.0.1" - unique-filename "^1.1.1" - y18n "^4.0.0" - cache-base@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" @@ -4009,16 +3872,7 @@ camelcase-keys@^4.0.0: map-obj "^2.0.0" quick-lru "^1.0.0" -camelcase-keys@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-6.0.0.tgz#12a07a6f50189213c3e8626c4069e28b997d01d1" - integrity sha512-NW1C7M9/uDZlfDP0+pWv0yAtgni7AZ9bYKtWgIfJylNXUFfis2BxsX3lCVuZE12wtRePEfJjnG6T9CnMohEybw== - dependencies: - camelcase "^5.3.1" - map-obj "^4.0.0" - quick-lru "^4.0.1" - -camelcase-keys@^6.2.2: +camelcase-keys@^6.0.0, camelcase-keys@^6.2.2: version "6.2.2" resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-6.2.2.tgz#5e755d6ba51aa223ec7d3d52f25778210f9dc3c0" integrity sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg== @@ -4245,16 +4099,11 @@ chokidar@^2.0.2, chokidar@^2.0.4, chokidar@^2.1.1, chokidar@^2.1.2: optionalDependencies: fsevents "^1.2.7" -chownr@^1.1.1: +chownr@^1.1.1, chownr@^1.1.2, chownr@^1.1.3: version "1.1.4" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== -chownr@^1.1.2, chownr@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.3.tgz#42d837d5239688d55f303003a508230fa6727142" - integrity sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw== - ci-info@^1.5.0: version "1.6.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.6.0.tgz#2ca20dbb9ceb32d4524a683303313f0304b1e497" @@ -4564,12 +4413,7 @@ comma-separated-tokens@^1.0.1: resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.7.tgz#419cd7fb3258b1ed838dc0953167a25e152f5b59" integrity sha512-Jrx3xsP4pPv4AwJUDWY9wOXGtwPXARej6Xd99h4TUGotmf8APuquKMpK+dnD3UgyxK7OEWaisjZz+3b5jtL6xQ== -commander@^2.11.0, commander@^2.19.0: - version "2.20.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" - integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ== - -commander@~2.20.3: +commander@^2.11.0, commander@^2.19.0, commander@~2.20.3: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== @@ -4765,15 +4609,7 @@ conventional-commit-types@^2.0.0: resolved "https://registry.yarnpkg.com/conventional-commit-types/-/conventional-commit-types-2.1.1.tgz#352eb53f56fbc7c1a6c1ba059c2b6670c90b2a8a" integrity sha512-0Ts+fEdmjqYDOQ1yZ+LNgdSPO335XZw9qC10M7CxtLP3nIMGmeMhmkM8Taffa4+MXN13bRPlp0CtH+QfOzKTzw== -conventional-commits-filter@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/conventional-commits-filter/-/conventional-commits-filter-2.0.1.tgz#55a135de1802f6510b6758e0a6aa9e0b28618db3" - integrity sha512-92OU8pz/977udhBjgPEbg3sbYzIxMDFTlQT97w7KdhR9igNqdJvy8smmedAAgn4tPiqseFloKkrVfbXCVd+E7A== - dependencies: - is-subset "^0.1.1" - modify-values "^1.0.0" - -conventional-commits-filter@^2.0.2: +conventional-commits-filter@^2.0.0, conventional-commits-filter@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/conventional-commits-filter/-/conventional-commits-filter-2.0.2.tgz#f122f89fbcd5bb81e2af2fcac0254d062d1039c1" integrity sha512-WpGKsMeXfs21m1zIw4s9H5sys2+9JccTzpN6toXtxhpw2VNF2JUXwIakthKBy+LN4DvJm+TzWhxOMWOs1OFCFQ== @@ -4854,12 +4690,7 @@ core-js@^1.0.0: resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY= -core-js@^3.1.4: - version "3.1.4" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.1.4.tgz#3a2837fc48e582e1ae25907afcd6cf03b0cc7a07" - integrity sha512-YNZN8lt82XIMLnLirj9MhKDFZHalwzzrL9YLt6eb0T5D0EDl4IQ90IGkua8mHbnxNrkj1d8hbdizMc0Qmg1WnQ== - -core-js@^3.6.4: +core-js@^3.1.4, core-js@^3.6.4: version "3.6.5" resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.5.tgz#7395dc273af37fb2e50e9bd3d9fe841285231d1a" integrity sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA== @@ -5246,16 +5077,7 @@ cssstyle@^1.0.0: dependencies: cssom "0.3.x" -currency-formatter@^1.4.2: - version "1.5.4" - resolved "https://registry.yarnpkg.com/currency-formatter/-/currency-formatter-1.5.4.tgz#115ebd438a3f94ab335516ce27ef9511a5e05c02" - integrity sha512-8qyGfrJO2ulabrlP4AidcV4kXpuTMqWJ61NW16QK3u+1L/9R0x7XM4f4hQ8HVlJyzBcxJV4SZMYx7zYoAhpArA== - dependencies: - accounting "^0.4.1" - locale-currency "0.0.2" - object-assign "^4.1.1" - -currency-formatter@^1.5.5: +currency-formatter@^1.4.2, currency-formatter@^1.5.5: version "1.5.5" resolved "https://registry.yarnpkg.com/currency-formatter/-/currency-formatter-1.5.5.tgz#907790bb0b7f129c4a64d2924e0d7fa36db0cf52" integrity sha512-PEsZ9fK2AwPBYgzWTtqpSckam7hFDkT8ZKFAOrsooR0XbydZEKuFioUzcc3DoT2mCDkscjf1XdT6Qq53ababZQ== @@ -6109,16 +5931,7 @@ es-cookie@^1.3.2: resolved "https://registry.yarnpkg.com/es-cookie/-/es-cookie-1.3.2.tgz#80e831597f72a25721701bdcb21d990319acd831" integrity sha512-UTlYYhXGLOy05P/vKVT2Ui7WtC7NiRzGtJyAKKn32g5Gvcjn7KAClLPWlipCtxIus934dFg9o9jXiBL0nP+t9Q== -es-to-primitive@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.0.tgz#edf72478033456e8dda8ef09e00ad9650707f377" - integrity sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg== - dependencies: - is-callable "^1.1.4" - is-date-object "^1.0.1" - is-symbol "^1.0.2" - -es-to-primitive@^1.2.1: +es-to-primitive@^1.2.0, es-to-primitive@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== @@ -7375,12 +7188,7 @@ got@^6.7.1: unzip-response "^2.0.1" url-parse-lax "^1.0.0" -graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.4, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" - integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== - -graceful-fs@^4.1.15, graceful-fs@^4.1.2: +graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.4, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.3: version "4.2.4" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== @@ -7591,20 +7399,13 @@ hmac-drbg@^1.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.3.1: +hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== dependencies: react-is "^16.7.0" -hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz#b09178f0122184fb95acf525daaecb4d8f45958b" - integrity sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA== - dependencies: - react-is "^16.7.0" - home-or-tmp@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8" @@ -7625,12 +7426,7 @@ hook-std@^2.0.0: resolved "https://registry.yarnpkg.com/hook-std/-/hook-std-2.0.0.tgz#ff9aafdebb6a989a354f729bb6445cf4a3a7077c" integrity sha512-zZ6T5WcuBMIUVh49iPQS9t977t7C0l7OtHrpeMb5uk48JdflRX0NSFvCekfYNmGQETnLq9W/isMyHl69kxGi8g== -hosted-git-info@^2.1.4, hosted-git-info@^2.8.5: - version "2.8.5" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.5.tgz#759cfcf2c4d156ade59b0b2dfabddc42a6b9c70c" - integrity sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg== - -hosted-git-info@^2.7.1: +hosted-git-info@^2.1.4, hosted-git-info@^2.7.1, hosted-git-info@^2.8.5: version "2.8.8" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== @@ -9194,12 +8990,7 @@ kind-of@^5.0.0: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== -kind-of@^6.0.0, kind-of@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" - integrity sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA== - -kind-of@^6.0.3: +kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== @@ -9748,12 +9539,7 @@ lodash@4.17.14: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.14.tgz#9ce487ae66c96254fe20b599f21b6816028078ba" integrity sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw== -lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1: - version "4.17.15" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" - integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== - -lodash@^4.17.15: +lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1: version "4.17.19" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== @@ -10388,12 +10174,12 @@ minimist@0.0.8: resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= -minimist@1.2.0, minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0: +minimist@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= -minimist@^1.2.5: +minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== @@ -10442,14 +10228,7 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" -mkdirp@^0.5.0, mkdirp@~0.5.0, mkdirp@~0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" - integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= - dependencies: - minimist "0.0.8" - -mkdirp@^0.5.1: +mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== @@ -10510,26 +10289,14 @@ mold-source-map@~0.4.0: convert-source-map "^1.1.0" through "~2.2.7" -moment-timezone@^0.5.23: - version "0.5.26" - resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.26.tgz#c0267ca09ae84631aa3dc33f65bedbe6e8e0d772" - integrity sha512-sFP4cgEKTCymBBKgoxZjYzlSovC20Y6J7y3nanDc5RoBIXKlZhoYwBoZGe3flwU6A372AcRwScH8KiwV6zjy1g== - dependencies: - moment ">= 2.9.0" - -moment-timezone@^0.5.27: +moment-timezone@^0.5.23, moment-timezone@^0.5.27: version "0.5.31" resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.31.tgz#9c40d8c5026f0c7ab46eda3d63e49c155148de05" integrity sha512-+GgHNg8xRhMXfEbv81iDtrVeTcWt0kWmTEY1XQK14dICTXnWJnT0dxdlPspwqF3keKMVPXwayEsk1DI0AA/jdA== dependencies: moment ">= 2.9.0" -"moment@>= 2.9.0", moment@>=1.6.0, moment@^2.17.1: - version "2.24.0" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" - integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg== - -moment@^2.24.0: +"moment@>= 2.9.0", moment@>=1.6.0, moment@^2.17.1, moment@^2.24.0: version "2.27.0" resolved "https://registry.yarnpkg.com/moment/-/moment-2.27.0.tgz#8bff4e3e26a236220dfe3e36de756b6ebaa0105d" integrity sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ== @@ -11130,12 +10897,7 @@ object-hash@^1.3.1: resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.3.1.tgz#fde452098a951cb145f039bb7d455449ddc126df" integrity sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA== -object-inspect@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.6.0.tgz#c70b6cbf72f274aab4c34c0c82f5167bf82cf15b" - integrity sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ== - -object-inspect@^1.7.0: +object-inspect@^1.6.0, object-inspect@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== @@ -11406,20 +11168,13 @@ p-limit@^1.1.0: dependencies: p-try "^1.0.0" -p-limit@^2.0.0: +p-limit@^2.0.0, p-limit@^2.2.0: version "2.2.1" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.1.tgz#aa07a788cc3151c939b5131f63570f0dd2009537" integrity sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg== dependencies: p-try "^2.0.0" -p-limit@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.0.tgz#417c9941e6027a9abcba5092dd2904e255b5fbc2" - integrity sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ== - dependencies: - p-try "^2.0.0" - p-locate@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" @@ -12716,12 +12471,7 @@ postcss-value-parser@^3.0.0, postcss-value-parser@^3.0.1, postcss-value-parser@^ resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== -postcss-value-parser@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.0.0.tgz#99a983d365f7b2ad8d0f9b8c3094926eab4b936d" - integrity sha512-ESPktioptiSUchCKgggAkzdmkgzKfmp0EU8jXH+5kbIUB+unr0Y4CY9SRMvibuvYUBjNh1ACLbxqYNpdTQOteQ== - -postcss-value-parser@^4.0.2: +postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2: version "4.0.3" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.0.3.tgz#651ff4593aa9eda8d5d0d66593a2417aeaeb325d" integrity sha512-N7h4pG+Nnu5BEIzyeaaIYWs0LI5XC40OrRh5L60z0QjFsqGWcHcbkBvpe1WYpcIS9yQ8sOi/vIPt1ejQCrMVrg== @@ -13001,16 +12751,11 @@ pseudomap@^1.0.2: resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= -psl@^1.1.24: +psl@^1.1.24, psl@^1.1.28: version "1.6.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.6.0.tgz#60557582ee23b6c43719d9890fb4170ecd91e110" integrity sha512-SYKKmVel98NCOYXpkwUqZqh0ahZeeKfmisiLIcEZdsb+WbLv02g/dI5BUmZnIyOe7RzZtLax81nnb2HbvC2tzA== -psl@^1.1.28: - version "1.2.0" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.2.0.tgz#df12b5b1b3a30f51c329eacbdef98f3a6e136dc6" - integrity sha512-GEn74ZffufCmkDDLNcl3uuyF/aSD6exEyh1v/ZSdAomB82t6G9hzJVRx0jBmLDW+VfZqks3aScmMw9DszwUalA== - public-encrypt@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" @@ -13073,12 +12818,7 @@ qrcode-terminal@^0.12.0: resolved "https://registry.yarnpkg.com/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz#bb5b699ef7f9f0505092a3748be4464fe71b5819" integrity sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ== -qs@^6.3.0, qs@^6.4.0, qs@^6.5.1: - version "6.7.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" - integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== - -qs@^6.9.1: +qs@^6.3.0, qs@^6.4.0, qs@^6.5.1, qs@^6.9.1: version "6.9.4" resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.4.tgz#9090b290d1f91728d3c22e54843ca44aea5ab687" integrity sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ== @@ -13096,16 +12836,7 @@ query-string@^4.1.0, query-string@^4.2.3: object-assign "^4.1.0" strict-uri-encode "^1.0.0" -query-string@^6.8.2: - version "6.9.0" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.9.0.tgz#1c3b727c370cf00f177c99f328fda2108f8fa3dd" - integrity sha512-KG4bhCFYapExLsUHrFt+kQVEegF2agm4cpF/VNc6pZVthIfCc/GK8t8VyNIE3nyXG9DK3Tf2EGkxjR6/uRdYsA== - dependencies: - decode-uri-component "^0.2.0" - split-on-first "^1.0.0" - strict-uri-encode "^2.0.0" - -query-string@^6.8.3: +query-string@^6.8.2, query-string@^6.8.3: version "6.12.1" resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.12.1.tgz#2ae4d272db4fba267141665374e49a1de09e8a7c" integrity sha512-OHj+zzfRMyj3rmo/6G8a5Ifvw3AleL/EbcHMD27YA31Q+cO5lfmQxECkImuNVjcskLcvBRVHNAB3w6udMs1eAA== @@ -13266,16 +12997,11 @@ react-fontawesome@^1.5.0: dependencies: prop-types "^15.5.6" -react-is@^16.3.2, react-is@^16.6.0, react-is@^16.9.0: +react-is@^16.3.2, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6, react-is@^16.9.0: version "16.9.0" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.9.0.tgz#21ca9561399aad0ff1a7701c01683e8ca981edcb" integrity sha512-tJBzzzIgnnRfEm046qRcURvwQnZVXmuCbscxUO5RWrGTXpon2d4c8mI0D8WE6ydVIm29JiLB6+RslkIvym9Rjw== -react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6: - version "16.8.6" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16" - integrity sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA== - react-leaflet@^2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/react-leaflet/-/react-leaflet-2.6.1.tgz#e5d6514d2358c51a28e614b8a0ddf8dca56e7179" @@ -13584,7 +13310,7 @@ read@1, read@~1.0.1, read@~1.0.7: dependencies: mute-stream "~0.0.4" -"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.6, readable-stream@~2.3.6: +"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -13616,19 +13342,6 @@ read@1, read@~1.0.1, read@~1.0.7: isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.3.3, readable-stream@^2.3.5: - version "2.3.6" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" - integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - readable-stream@~1.1.10: version "1.1.14" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" @@ -14215,14 +13928,7 @@ resolve@1.1.7: resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= -resolve@^1.1.3, resolve@^1.1.4, resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.1, resolve@^1.11.0, resolve@^1.3.2, resolve@^1.4.0, resolve@^1.5.0, resolve@^1.8.1: - version "1.11.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.11.1.tgz#ea10d8110376982fef578df8fc30b9ac30a07a3e" - integrity sha512-vIpgF6wfuJOZI7KKKSP+HmiKggadPQAdsp5HiC1mvqnfp0gF1vdwgBWZIdrVft9pgqoMFQN+R7BSWZiBxx+BBw== - dependencies: - path-parse "^1.0.6" - -resolve@^1.10.0: +resolve@^1.1.3, resolve@^1.1.4, resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.11.0, resolve@^1.3.2, resolve@^1.4.0, resolve@^1.5.0, resolve@^1.8.1: version "1.13.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.13.1.tgz#be0aa4c06acd53083505abb35f4d66932ab35d16" integrity sha512-CxqObCX8K8YtAhOBRg+lrcdn+LK+WYOS8tSjqSFbjtrI5PnS63QPhZl4+yKfrU9tdsbMu9Anr/amegT87M9Z6w== @@ -15110,14 +14816,7 @@ string_decoder@0.10, string_decoder@~0.10.x: resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ= -string_decoder@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.2.0.tgz#fe86e738b19544afe70469243b2a1ee9240eae8d" - integrity sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w== - dependencies: - safe-buffer "~5.1.0" - -string_decoder@^1.1.1: +string_decoder@^1.0.0, string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== @@ -15738,12 +15437,7 @@ trough@^1.0.0: resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.4.tgz#3b52b1f13924f460c3fbfd0df69b587dbcbc762e" integrity sha512-tdzBRDGWcI1OpPVmChbdSKhvSVurznZ8X36AYURAcl+0o2ldlCY2XPzyXNNxwJwwyIU+rIglTCG4kxtNKBQH7Q== -tslib@^1.9.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" - integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== - -tslib@^1.9.3: +tslib@^1.9.0, tslib@^1.9.3: version "1.13.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q== From 9b10f860f97e108b181ac5c915cee1e0fead09b3 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 24 Jul 2020 14:39:47 -0400 Subject: [PATCH 32/46] build(yarn.lock): Cleanup yarn.lock after yarn install using deduplicated yarn.lock. --- yarn.lock | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/yarn.lock b/yarn.lock index 949c1ec44..0e730c292 100644 --- a/yarn.lock +++ b/yarn.lock @@ -236,7 +236,7 @@ "@babel/traverse" "^7.5.5" "@babel/types" "^7.5.5" -"@babel/highlight@^7.0.0", "@babel/highlight@^7.8.3": +"@babel/highlight@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.8.3.tgz#28f173d04223eaaa59bc1d439a3836e6d1265797" integrity sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg== @@ -250,7 +250,7 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.1.3.tgz#2c92469bac2b7fbff810b67fca07bd138b48af77" integrity sha512-gqmspPZOMW3MIRb9HlrnbZHXI1/KHTOroBwN1NcLL6pWxzqzEKGvRTq0W/PxS45OtQGbaFikSQpkS5zbnsQm2w== -"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.4.3", "@babel/parser@^7.4.4", "@babel/parser@^7.5.5", "@babel/parser@^7.8.6": +"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.4.3", "@babel/parser@^7.5.5", "@babel/parser@^7.8.6": version "7.8.6" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.8.6.tgz#ba5c9910cddb77685a008e3c587af8d27b67962c" integrity sha512-trGNYSfwq5s0SgM1BMEB8hX3NDmO7EP2wsDGDexiaKMB92BaRpS+qZfpkMqUBhcsOTBwNy9B/jieo4ad/t/z2g== @@ -10169,11 +10169,6 @@ minimist@0.0.5: resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.5.tgz#d7aa327bcecf518f9106ac6b8f003fa3bcea8566" integrity sha1-16oye87PUY+RBqxrjwA/o7zqhWY= -minimist@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" - integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= - minimist@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" From b4e99d7f6239a443ded667203754be15572ebf80 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 24 Jul 2020 15:18:06 -0400 Subject: [PATCH 33/46] refactor: Light reformatting from PR comments. --- lib/components/user/link-button.js | 6 ++--- lib/components/user/saved-trip-screen.js | 6 ++++- lib/components/user/trip-basics-pane.js | 13 +++++++++- .../user/trip-notifications-pane.js | 25 ------------------- 4 files changed, 20 insertions(+), 30 deletions(-) diff --git a/lib/components/user/link-button.js b/lib/components/user/link-button.js index 58b406601..9231cb3be 100644 --- a/lib/components/user/link-button.js +++ b/lib/components/user/link-button.js @@ -1,9 +1,9 @@ import PropTypes from 'prop-types' import React, { Component } from 'react' -import { connect } from 'react-redux' import { Button } from 'react-bootstrap' +import { connect } from 'react-redux' -import { routeTo } from '../../actions/ui' +import * as uiActions from '../../actions/ui' /** * This button provides basic redirecting functionality. @@ -31,7 +31,7 @@ const mapStateToProps = (state, ownProps) => { } const mapDispatchToProps = { - routeTo + routeTo: uiActions.routeTo } export default connect(mapStateToProps, mapDispatchToProps)(LinkButton) diff --git a/lib/components/user/saved-trip-screen.js b/lib/components/user/saved-trip-screen.js index eac1782ed..4e46fd7eb 100644 --- a/lib/components/user/saved-trip-screen.js +++ b/lib/components/user/saved-trip-screen.js @@ -1,3 +1,4 @@ +import clone from 'lodash/cloneDeep' import PropTypes from 'prop-types' import React, { Component } from 'react' import { connect } from 'react-redux' @@ -144,7 +145,10 @@ class SavedTripScreen extends Component { if (deleteResult.status === 'success') { const removedIndex = trips.indexOf(monitoredTrip) - const newTrips = [].concat(trips) + + // Cleanly update this.state.trips by + // making a clone of the trips array and removing the deleted trip. + const newTrips = clone(trips) newTrips.splice(removedIndex, 1) this._updateTrips(newTrips) } diff --git a/lib/components/user/trip-basics-pane.js b/lib/components/user/trip-basics-pane.js index f4df37298..e64df89da 100644 --- a/lib/components/user/trip-basics-pane.js +++ b/lib/components/user/trip-basics-pane.js @@ -1,9 +1,20 @@ import React, { Component } from 'react' -import { ButtonToolbar, ControlLabel, FormControl, FormGroup, ToggleButton, ToggleButtonGroup } from 'react-bootstrap' +import { + ButtonToolbar, + ControlLabel, + FormControl, + FormGroup, + ToggleButton, + ToggleButtonGroup +} from 'react-bootstrap' import { arrayToDayFields, dayFieldsToArray } from '../../util/monitored-trip' import TripSummary from './trip-summary' +/** + * This component shows summary information for a trip + * and lets the user edit the trip name and day. + */ class TripBasicsPane extends Component { _handleTripDaysChange = e => { const { onMonitoredTripChange } = this.props diff --git a/lib/components/user/trip-notifications-pane.js b/lib/components/user/trip-notifications-pane.js index 8e75226f4..cccff2229 100644 --- a/lib/components/user/trip-notifications-pane.js +++ b/lib/components/user/trip-notifications-pane.js @@ -52,31 +52,6 @@ class TripNotificationsPane extends Component { - - Would you like to disable notifications during US federal holidays? - - Yes - - - No - - - - Note: you can always pause notifications for this trip once the trip has been saved. - - - - When would you like to receive notifications about delays or disruptions to your trip? From ad8e678e36fec4268c8bdbbb4c29f585aedf4c75 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 24 Jul 2020 16:15:29 -0400 Subject: [PATCH 34/46] refactor(TripSummary): Improve state management. --- lib/components/user/trip-summary.js | 95 +++++++++++------------------ 1 file changed, 36 insertions(+), 59 deletions(-) diff --git a/lib/components/user/trip-summary.js b/lib/components/user/trip-summary.js index 2534c0b26..5c3bd7a5c 100644 --- a/lib/components/user/trip-summary.js +++ b/lib/components/user/trip-summary.js @@ -1,6 +1,6 @@ import coreUtils from '@opentripplanner/core-utils' import { ClassicLegIcon } from '@opentripplanner/icons' -import React, { Component } from 'react' +import React, { useEffect, useState } from 'react' import { connect } from 'react-redux' import { getQueryParamsFromQueryString } from '../../util/query' @@ -8,67 +8,44 @@ import ItinerarySummary from '../narrative/default/itinerary-summary' const { formatDuration, formatTime } = coreUtils.time -class TripSummary extends Component { - constructor () { - super() - - this.state = { - // Hold a default state for query location names - // while it is being fetched in updateQueryState. - queryObj: { - from: {}, - to: {} - } - } - } - - updateQueryState = async () => { - const { monitoredTrip, config } = this.props - const { queryParams } = monitoredTrip - const queryObj = await getQueryParamsFromQueryString(queryParams, config) - - // Prevent a state update if componentWillUnmount happened while await was executing. - if (!this.state.willUnmount) { - this.setState({ queryObj }) - } - } - - componentDidMount () { - this.updateQueryState() - } - - componentDidUpdate (prevProps) { - // Update the state the monitoredTrip prop has changed. - if (this.props.monitoredTrip !== prevProps.monitoredTrip) { - this.updateQueryState() +const TripSummary = ({ config, monitoredTrip }) => { + // State, state setter, and intital state. + const [{ from, to }, setQueryObj] = useState({ from: {}, to: {} }) + const { itinerary, queryParams } = monitoredTrip + + useEffect(() => { + // Will be set to false on component unload. + let isComponentMounted = true + + getQueryParamsFromQueryString(queryParams, config) + .then(queryObj => { + // Update the state with the query object while component is mounted. + if (isComponentMounted) { + setQueryObj(queryObj) + } + }) + + // Gets called when component is unmounted. + // Turn off flag to prevent state update. + return () => { + isComponentMounted = false } - } - - componentWillMount () { - this.setState({ - willUnmount: true - }) - } - - render () { - const { itinerary } = this.props.monitoredTrip - const { from, to } = this.state.queryObj - - // TODO: use the modern itinerary summary built for trip comparison. - return ( -

-
From {from.name} to {to.name}
-
-
- Itinerary{' '} - {formatDuration(itinerary.duration)}{' '} - {formatTime(itinerary.startTime)}—{formatTime(itinerary.endTime)} - -
+ }, []) + + // TODO: use the modern itinerary summary built for trip comparison. + return ( +
+
From {from.name} to {to.name}
+
+
+ Itinerary{' '} + {formatDuration(itinerary.duration)}{' '} + {formatTime(itinerary.startTime)}—{formatTime(itinerary.endTime)} +
- ) - } +
+ ) } // connect to the redux store From 0048026d23c42c221eea06e5f72b8322635c0d1f Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 24 Jul 2020 18:57:52 -0400 Subject: [PATCH 35/46] refactor(actions/user): Move monitored trip middleware calls to actions/user. --- lib/actions/user.js | 115 +++++++++++++++++--- lib/components/user/saved-trip-editor.js | 2 +- lib/components/user/saved-trip-screen.js | 132 +++++++---------------- lib/reducers/create-user-reducer.js | 7 ++ lib/util/middleware.js | 7 +- 5 files changed, 148 insertions(+), 115 deletions(-) diff --git a/lib/actions/user.js b/lib/actions/user.js index f306af6ae..804761285 100644 --- a/lib/actions/user.js +++ b/lib/actions/user.js @@ -1,9 +1,19 @@ +import clone from 'lodash/cloneDeep' import { createAction } from 'redux-actions' -import { addUser, fetchUser, updateUser } from '../util/middleware' +import { + addTrip, + addUser, + deleteTrip, + fetchUser, + getTrips, + updateTrip, + updateUser +} from '../util/middleware' import { isNewUser } from '../util/user' const setCurrentUser = createAction('SET_CURRENT_USER') +const setCurrentUserMonitoredTrips = createAction('SET_CURRENT_USER_MONITORED_TRIPS') export const setPathBeforeSignIn = createAction('SET_PATH_BEFORE_SIGNIN') function getStateForNewUser (auth0User) { @@ -26,13 +36,11 @@ function getStateForNewUser (auth0User) { export function fetchOrInitializeUser (auth) { return async function (dispatch, getState) { const { otp } = getState() - const { accessToken, user } = auth + const { otp_middleware: otpMiddleware = null } = otp.config.persistence - try { - const result = await fetchUser( - otp.config.persistence.otp_middleware, - accessToken - ) + if (otpMiddleware) { + const { accessToken, user: authUser } = auth + const { data: user, status: fetchUserStatus } = await fetchUser(otpMiddleware, accessToken) // Beware! On AWS API gateway, if a user is not found in the middleware // (e.g. they just created their Auth0 password but have not completed the account setup form yet), @@ -55,19 +63,20 @@ export function fetchOrInitializeUser (auth) { // } // TODO: Improve AWS response. - const resultData = result.data - const isNewAccount = result.status === 'error' || (resultData && resultData.result === 'ERR') - + const isNewAccount = fetchUserStatus === 'error' || (user && user.result === 'ERR') if (!isNewAccount) { // TODO: Move next line somewhere else. - if (resultData.savedLocations === null) resultData.savedLocations = [] - dispatch(setCurrentUser({ accessToken, user: resultData })) + if (user.savedLocations === null) user.savedLocations = [] + dispatch(setCurrentUser({ accessToken, user })) + + // Load user's monitored trips. + const { data: trips, status: tripFetchStatus } = await getTrips(otpMiddleware, accessToken) + if (tripFetchStatus === 'success') { + dispatch(setCurrentUserMonitoredTrips(trips)) + } } else { - dispatch(setCurrentUser({ accessToken, user: getStateForNewUser(user) })) + dispatch(setCurrentUser({ accessToken, user: getStateForNewUser(authUser) })) } - } catch (error) { - // TODO: improve error handling. - alert(`An error was encountered:\n${error}`) } } } @@ -93,10 +102,57 @@ export function createOrUpdateUser (userData) { // TODO: improve the UI feedback messages for this. if (result.status === 'success' && result.data) { + alert('Your preferences have been saved.') + // Update application state with the user entry as saved // (as returned) by the middleware. const userData = result.data dispatch(setCurrentUser({ accessToken, user: userData })) + } else { + alert(`An error was encountered:\n${JSON.stringify(result)}`) + } + } + } +} + +/** + * Updates a logged-in user's monitored trip, + * then, if that was successful, refreshes the redux monitoredTrips + * with the updated trip. + */ +export function createOrUpdateUserMonitoredTrip (tripData, isNew) { + return async function (dispatch, getState) { + const { otp, user } = getState() + const { otp_middleware: otpMiddleware = null } = otp.config.persistence + + if (otpMiddleware) { + const { accessToken, loggedInUserMonitoredTrips: trips } = user + + let result + if (isNew) { + result = await addTrip(otpMiddleware, accessToken, tripData) + } else { + result = await updateTrip(otpMiddleware, accessToken, tripData) + } + + // TODO: improve the UI feedback messages for this. + if (result.status === 'success' && result.data) { + const newTrip = result.data + + // For updating the monitoredTrips state, + // make a clone of the trips array, + // find the index of the trip being updated (using trip.id), + // and assign the trip returned from the middleware at that index + // (or append to array if index was not found). + const newTrips = trips ? clone(trips) : [] + const tripIndex = newTrips.findIndex(trip => trip.id === newTrip.id) + if (tripIndex !== -1) { + newTrips[tripIndex] = newTrip + } else { + newTrips.push(newTrip) + } + + dispatch(setCurrentUserMonitoredTrips(newTrips)) alert('Your preferences have been saved.') } else { @@ -105,3 +161,30 @@ export function createOrUpdateUser (userData) { } } } + +/** + * Deletes a logged-in user's monitored trip, + * then, if that was successful, refreshes the redux monitoredTrips state. + */ +export function deleteUserMonitoredTrip (id) { + return async function (dispatch, getState) { + const { otp, user } = getState() + const { otp_middleware: otpMiddleware = null } = otp.config.persistence + + if (otpMiddleware) { + const { accessToken, loggedInUserMonitoredTrips: trips } = user + const deleteResult = await deleteTrip(otpMiddleware, accessToken, id) + + if (deleteResult.status === 'success') { + // For updating the monitoredTrips state, + // keep the trips whose id is not that of the deleted trip. + if (trips) { + const newTrips = trips.filter(trip => trip.id !== id) + dispatch(setCurrentUserMonitoredTrips(newTrips)) + } + } else { + alert(`An error was encountered:\n${JSON.stringify(deleteResult)}`) + } + } + } +} diff --git a/lib/components/user/saved-trip-editor.js b/lib/components/user/saved-trip-editor.js index d54e30b32..e1a323712 100644 --- a/lib/components/user/saved-trip-editor.js +++ b/lib/components/user/saved-trip-editor.js @@ -47,7 +47,7 @@ class SavedTripEditor extends Component { /> ) - } else if (trips.length === 0) { + } else if (!trips || trips.length === 0) { return ( <> {accountLink} diff --git a/lib/components/user/saved-trip-screen.js b/lib/components/user/saved-trip-screen.js index 4e46fd7eb..f92e1e5ad 100644 --- a/lib/components/user/saved-trip-screen.js +++ b/lib/components/user/saved-trip-screen.js @@ -1,15 +1,13 @@ -import clone from 'lodash/cloneDeep' import PropTypes from 'prop-types' import React, { Component } from 'react' import { connect } from 'react-redux' import { withLoginRequired } from 'use-auth0-hooks' -import { addTrip, deleteTrip, getTrips, updateTrip } from '../../util/middleware' -import { routeTo } from '../../actions/ui' +import * as uiActions from '../../actions/ui' +import * as userActions from '../../actions/user' import DesktopNav from '../app/desktop-nav' import { arrayToDayFields, WEEKDAYS } from '../../util/monitored-trip' import { getActiveItineraries, getActiveSearch } from '../../util/state' -import AwaitingScreen from './awaiting-screen' import SavedTripEditor from './saved-trip-editor' import SavedTripWizard from './saved-trip-wizard' import TripBasicsPane from './trip-basics-pane' @@ -63,26 +61,7 @@ class SavedTripScreen extends Component { const thisMonitoredTrip = isCreating ? createMonitoredTrip(loggedInUser, queryParams, itinerary) : null this.state = { - monitoredTrip: thisMonitoredTrip, - trips: null - } - } - - _fetchTrips = async () => { - const { accessToken, isCreating, persistence, routeTo } = this.props - const { data: trips, status } = await getTrips(persistence.otp_middleware, accessToken) - - if (status === 'success') { - this.setState({ trips }) - - // There is a middleware limit of 5 saved trips, - // so if that limit is already reached, alert, then show editing mode. - if (isCreating && hasMaxTripCount(trips)) { - alert('You already have reached the maximum of five saved trips.\n' + - 'Please remove unused trips from your saved trips, and try again.') - - routeTo('/savedtrips') - } + monitoredTrip: thisMonitoredTrip } } @@ -96,62 +75,29 @@ class SavedTripScreen extends Component { }) } - _updateMonitoredTrip = async () => { - const { accessToken, isCreating, persistence } = this.props - - if (persistence && persistence.otp_middleware) { - const { monitoredTrip } = this.state - - // TODO: Change state of Save button. - - let result - if (isCreating) { - result = await addTrip(persistence.otp_middleware, accessToken, monitoredTrip) - } else { - result = await updateTrip(persistence.otp_middleware, accessToken, monitoredTrip) - } - - // TODO: improve the confirmation messages. - if (result.status === 'success') { - // Update our monitoredTrip variable with what gets returned from server. - // (there are updated attributes such as dates, auth0 etc.) - this._updateMonitoredTripState(result.data) - - alert('Your preferences have been saved.') - } else { - alert(`An error was encountered:\n${JSON.stringify(result)}`) - } - - return result - } - } - /** - * Updates the trip list and removes all selection. + * Selects no trip. */ - _updateTrips = newTrips => { + _selectNoTrip = () => { this.setState({ - monitoredTrip: null, // select nothing - tripIndex: -1, // select nothing - trips: newTrips + monitoredTrip: null, + tripIndex: -1 }) } + _updateMonitoredTrip = async () => { + const { isCreating, createOrUpdateUserMonitoredTrip } = this.props + const { monitoredTrip } = this.state + await createOrUpdateUserMonitoredTrip(monitoredTrip, isCreating) + } + _handleDeleteTrip = async () => { if (confirm('Would you like to remove this trip?')) { - const { accessToken, persistence } = this.props - const { monitoredTrip, trips } = this.state - const deleteResult = await deleteTrip(persistence.otp_middleware, accessToken, monitoredTrip) - - if (deleteResult.status === 'success') { - const removedIndex = trips.indexOf(monitoredTrip) + const { deleteUserMonitoredTrip } = this.props + const { monitoredTrip } = this.state + await deleteUserMonitoredTrip(monitoredTrip.id) - // Cleanly update this.state.trips by - // making a clone of the trips array and removing the deleted trip. - const newTrips = clone(trips) - newTrips.splice(removedIndex, 1) - this._updateTrips(newTrips) - } + this._selectNoTrip() } } @@ -167,22 +113,14 @@ class SavedTripScreen extends Component { _handleMonitoredTripSelect = tripIndex => { this.setState({ - monitoredTrip: this.state.trips[tripIndex], + monitoredTrip: this.props.monitoredTrips[tripIndex], tripIndex }) } _handleSaveTripEdits = async () => { - const { data: newTrip, status } = await this._updateMonitoredTrip() - - // If update was successful, update the state trips with the modified copy so far. - if (status === 'success') { - const { tripIndex } = this.state - const newTrips = [].concat(this.state.trips) - - newTrips[tripIndex] = newTrip - this._updateTrips(newTrips) - } + await this._updateMonitoredTrip() + this._selectNoTrip() } /** @@ -209,22 +147,27 @@ class SavedTripScreen extends Component { summary: this._hookMonitoredTrip(TripSummaryPane) } - async componentDidMount () { - await this._fetchTrips() + componentDidMount () { + const { isCreating, monitoredTrips, routeTo } = this.props + + // There is a middleware limit of 5 saved trips, + // so if that limit is already reached, alert, then show editing mode. + if (isCreating && hasMaxTripCount(monitoredTrips)) { + alert('You already have reached the maximum of five saved trips.\n' + + 'Please remove unused trips from your saved trips, and try again.') + + routeTo('/savedtrips') + } // TODO: Update title bar during componentDidMount. } render () { - const { isCreating } = this.props - const { monitoredTrip, trips } = this.state - - if (!trips) { - return - } + const { isCreating, monitoredTrips } = this.props + const { monitoredTrip } = this.state let content - if (isCreating && !hasMaxTripCount(trips)) { + if (isCreating && !hasMaxTripCount(monitoredTrips)) { content = ( ) } @@ -267,13 +210,16 @@ const mapStateToProps = (state, ownProps) => { accessToken: state.user.accessToken, itinerary: itineraries[activeItinerary], loggedInUser: state.user.loggedInUser, + monitoredTrips: state.user.loggedInUserMonitoredTrips, persistence: state.otp.config.persistence, queryParams: state.router.location.search } } const mapDispatchToProps = { - routeTo + createOrUpdateUserMonitoredTrip: userActions.createOrUpdateUserMonitoredTrip, + deleteUserMonitoredTrip: userActions.deleteUserMonitoredTrip, + routeTo: uiActions.routeTo } export default withLoggedInUserSupport( diff --git a/lib/reducers/create-user-reducer.js b/lib/reducers/create-user-reducer.js index 6551312dc..2dc9afbb6 100644 --- a/lib/reducers/create-user-reducer.js +++ b/lib/reducers/create-user-reducer.js @@ -3,6 +3,7 @@ import update from 'immutability-helper' // TODO: port user-specific code from the otp reducer. function createUserReducer () { const initialState = {} + return (state = initialState, action) => { switch (action.type) { case 'SET_CURRENT_USER': { @@ -12,6 +13,12 @@ function createUserReducer () { }) } + case 'SET_CURRENT_USER_MONITORED_TRIPS': { + return update(state, { + loggedInUserMonitoredTrips: { $set: action.payload } + }) + } + case 'SET_PATH_BEFORE_SIGNIN': { return update(state, { pathBeforeSignIn: { $set: action.payload } diff --git a/lib/util/middleware.js b/lib/util/middleware.js index e5b670bfc..10403b465 100644 --- a/lib/util/middleware.js +++ b/lib/util/middleware.js @@ -126,15 +126,12 @@ export async function updateTrip (middlewareConfig, token, data) { } } -export async function deleteTrip (middlewareConfig, token, data) { +export async function deleteTrip (middlewareConfig, token, id) { const { apiBaseUrl, apiKey } = middlewareConfig - const { id } = data const requestUrl = `${apiBaseUrl}${API_MONITORTRIP_PATH}/${id}` if (id) { - return secureFetch(requestUrl, token, apiKey, 'DELETE', { - body: JSON.stringify(data) - }) + return secureFetch(requestUrl, token, apiKey, 'DELETE') } else { return { status: 'error', From 9f18270ce661696bf7ae37f3de69147762a7efbe Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Mon, 27 Jul 2020 12:42:18 -0400 Subject: [PATCH 36/46] refactor(SavedTripList): Separate SavedTripList, enable savedtrips/:id urls. --- lib/actions/user.js | 5 +- lib/components/app/responsive-webapp.js | 7 +- lib/components/user/saved-trip-editor.js | 110 ++++++++------------- lib/components/user/saved-trip-list.js | 85 ++++++++++++++++ lib/components/user/saved-trip-screen.js | 119 +++++++++++++---------- 5 files changed, 206 insertions(+), 120 deletions(-) create mode 100644 lib/components/user/saved-trip-list.js diff --git a/lib/actions/user.js b/lib/actions/user.js index 804761285..be5009921 100644 --- a/lib/actions/user.js +++ b/lib/actions/user.js @@ -67,13 +67,14 @@ export function fetchOrInitializeUser (auth) { if (!isNewAccount) { // TODO: Move next line somewhere else. if (user.savedLocations === null) user.savedLocations = [] - dispatch(setCurrentUser({ accessToken, user })) - // Load user's monitored trips. + // Load user's monitored trips before setting the user state. const { data: trips, status: tripFetchStatus } = await getTrips(otpMiddleware, accessToken) if (tripFetchStatus === 'success') { dispatch(setCurrentUserMonitoredTrips(trips)) } + + dispatch(setCurrentUser({ accessToken, user })) } else { dispatch(setCurrentUser({ accessToken, user: getStateForNewUser(authUser) })) } diff --git a/lib/components/app/responsive-webapp.js b/lib/components/app/responsive-webapp.js index 5c6388b5d..7dff8032d 100644 --- a/lib/components/app/responsive-webapp.js +++ b/lib/components/app/responsive-webapp.js @@ -20,6 +20,7 @@ import { AUTH0_AUDIENCE, AUTH0_SCOPE, URL_ROOT } from '../../util/constants' import { getActiveItinerary, getTitle } from '../../util/state' import AfterSignInScreen from '../user/after-signin-screen' import BeforeSignInScreen from '../user/before-signin-screen' +import SavedTripList from '../user/saved-trip-list' import SavedTripScreen from '../user/saved-trip-screen' import UserAccountScreen from '../user/user-account-screen' import withLoggedInUserSupport from '../user/with-logged-in-user-support' @@ -242,12 +243,16 @@ class RouterWrapperWithAuth0 extends Component { }} /> { const props = this._combineProps(routerProps) return }} /> + () => { - const { onMonitoredTripSelect } = this.props - if (onMonitoredTripSelect) onMonitoredTripSelect(index) - - // TODO: Add URL handling so URL shows as #/savedtrips/trip-id-123 +const SavedTripEditor = ({ + monitoredTrip, + onCancel, + onComplete, + onDeleteTrip, + panes +}) => { + if (monitoredTrip) { + const paneSequence = [ + { + pane: panes.basics, + title: 'Trip overview' + }, + { + pane: panes.notifications, + title: 'Trip notifications' + }, + { + // TODO: Find a better place for this. + pane: () => , + title: 'Danger zone' + } + ] + return ( + <> +

{monitoredTrip.tripName}

+ + + ) } - render () { - const { monitoredTrip, onComplete, onDeleteTrip, panes, trips } = this.props - - // TODO: Improve navigation. - const accountLink =

Back to My Account

- - if (monitoredTrip) { - const paneSequence = [ - { - pane: panes.basics, - title: 'Trip overview' - }, - { - pane: panes.notifications, - title: 'Trip notifications' - }, - { - pane: () => , - title: 'Danger zone' - } - ] - return ( - <> -

{monitoredTrip.tripName}

- - - ) - } else if (!trips || trips.length === 0) { - return ( - <> - {accountLink} -

You have no saved trips

-

Perform a trip search from the map first.

- - ) - } else { - // Stack the saved trip summaries. When the user clicks on one, they can edit that trip. - return ( - <> - {accountLink} -

My saved trips

-

Click on a saved trip below to modify it.

- - {trips.map((trip, index) => ( - - ))} - - - ) - } - } + return ( + <> +

Trip Not Found

+

Sorry, the requested trip was not found.

+ + ) } export default SavedTripEditor diff --git a/lib/components/user/saved-trip-list.js b/lib/components/user/saved-trip-list.js new file mode 100644 index 000000000..9969a5d3c --- /dev/null +++ b/lib/components/user/saved-trip-list.js @@ -0,0 +1,85 @@ +import React, { Component } from 'react' +import { Button, ButtonGroup } from 'react-bootstrap' +import { connect } from 'react-redux' +import { withLoginRequired } from 'use-auth0-hooks' + +import * as uiActions from '../../actions/ui' +import DesktopNav from '../app/desktop-nav' +import LinkButton from './link-button' +import TripSummaryPane from './trip-summary-pane' +import withLoggedInUserSupport from './with-logged-in-user-support' + +/** + * This component displays the list of saved trips for the logged-in user. + */ +class SavedTripList extends Component { + /** + * Navigate to the saved trip's URL #/savedtrips/trip-id-123. + * (There shouldn't be a need to encode the ids from Mongo.) + */ + _handleTripSelect = trip => () => { + const { id } = trip + this.props.routeTo(`/savedtrips/${id}`) + } + + render () { + const { trips } = this.props + + // TODO: Improve navigation. + const accountLink =

Back to My Account

+ let content + + if (!trips || trips.length === 0) { + content = ( + <> + {accountLink} +

You have no saved trips

+

Perform a trip search from the map first.

+ + ) + } else { + // Stack the saved trip summaries. When the user clicks on one, they can edit that trip. + content = ( + <> + {accountLink} +

My saved trips

+

Click on a saved trip below to modify it.

+ + {trips.map((trip, index) => ( + + ))} + + + ) + } + + return ( +
+ {/* TODO: Do mobile view. */} + +
+ {content} +
+
+ ) + } +} + +// connect to the redux store + +const mapStateToProps = (state, ownProps) => { + return { + trips: state.user.loggedInUserMonitoredTrips + } +} + +const mapDispatchToProps = { + routeTo: uiActions.routeTo +} + +export default withLoggedInUserSupport( + withLoginRequired(connect(mapStateToProps, mapDispatchToProps)(SavedTripList)), + true +) diff --git a/lib/components/user/saved-trip-screen.js b/lib/components/user/saved-trip-screen.js index f92e1e5ad..3c1ecf8e1 100644 --- a/lib/components/user/saved-trip-screen.js +++ b/lib/components/user/saved-trip-screen.js @@ -1,4 +1,3 @@ -import PropTypes from 'prop-types' import React, { Component } from 'react' import { connect } from 'react-redux' import { withLoginRequired } from 'use-auth0-hooks' @@ -6,13 +5,13 @@ import { withLoginRequired } from 'use-auth0-hooks' import * as uiActions from '../../actions/ui' import * as userActions from '../../actions/user' import DesktopNav from '../app/desktop-nav' -import { arrayToDayFields, WEEKDAYS } from '../../util/monitored-trip' -import { getActiveItineraries, getActiveSearch } from '../../util/state' import SavedTripEditor from './saved-trip-editor' import SavedTripWizard from './saved-trip-wizard' import TripBasicsPane from './trip-basics-pane' import TripNotificationsPane from './trip-notifications-pane' import TripSummaryPane from './trip-summary-pane' +import { arrayToDayFields, WEEKDAYS } from '../../util/monitored-trip' +import { getActiveItineraries, getActiveSearch } from '../../util/state' import withLoggedInUserSupport from './with-logged-in-user-support' /** @@ -42,29 +41,20 @@ function hasMaxTripCount (trips) { } /** - * This screen handles saving a trip from an OTP query, or editing saved trips + * This screen handles saving a trip from an OTP query, or editing an existing saved trip * for the currently logged-in user. */ class SavedTripScreen extends Component { - static propTypes = { - originalUrl: PropTypes.string - } - - static defaultProps = { - originalUrl: '/' - } - constructor (props) { super(props) - const { isCreating, itinerary, loggedInUser, queryParams } = props - const thisMonitoredTrip = isCreating ? createMonitoredTrip(loggedInUser, queryParams, itinerary) : null - - this.state = { - monitoredTrip: thisMonitoredTrip - } + const monitoredTrip = this._getTripToEdit(props) + this.state = { monitoredTrip } } + /** + * Handles editing events on from all panes. + */ _updateMonitoredTripState = newMonitoredTrip => { const { monitoredTrip } = this.state this.setState({ @@ -76,51 +66,49 @@ class SavedTripScreen extends Component { } /** - * Selects no trip. + * Persists changes to edited trip. */ - _selectNoTrip = () => { - this.setState({ - monitoredTrip: null, - tripIndex: -1 - }) - } - _updateMonitoredTrip = async () => { const { isCreating, createOrUpdateUserMonitoredTrip } = this.props const { monitoredTrip } = this.state await createOrUpdateUserMonitoredTrip(monitoredTrip, isCreating) } + /** + * Navigates to the trip planner (for new trips). + */ + _goToTripPlanner = () => { + this.props.routeTo('/') + } + + /** + * Navigates to saved trips screen. + */ + _goToSavedTrips = () => { + this.props.routeTo('/savedtrips') + } + + /** + * Deletes a trip from persistence and returns to the saved trips screen. + */ _handleDeleteTrip = async () => { if (confirm('Would you like to remove this trip?')) { const { deleteUserMonitoredTrip } = this.props const { monitoredTrip } = this.state await deleteUserMonitoredTrip(monitoredTrip.id) - this._selectNoTrip() + this._goToSavedTrips() } } - _handleExit = () => { - const { originalUrl } = this.props - this.props.routeTo(originalUrl) - } - - _handleExitAndSave = async () => { + _handleSaveNewTrip = async () => { await this._updateMonitoredTrip() - this._handleExit() - } - - _handleMonitoredTripSelect = tripIndex => { - this.setState({ - monitoredTrip: this.props.monitoredTrips[tripIndex], - tripIndex - }) + this._goToTripPlanner() } _handleSaveTripEdits = async () => { await this._updateMonitoredTrip() - this._selectNoTrip() + this._goToSavedTrips() } /** @@ -148,7 +136,7 @@ class SavedTripScreen extends Component { } componentDidMount () { - const { isCreating, monitoredTrips, routeTo } = this.props + const { isCreating, monitoredTrips } = this.props // There is a middleware limit of 5 saved trips, // so if that limit is already reached, alert, then show editing mode. @@ -156,12 +144,48 @@ class SavedTripScreen extends Component { alert('You already have reached the maximum of five saved trips.\n' + 'Please remove unused trips from your saved trips, and try again.') - routeTo('/savedtrips') + this._goToSavedTrips() } // TODO: Update title bar during componentDidMount. } + /** + * Gets the trip to edit from the props. + * Optionally saves the state. + */ + _getTripToEdit = (props, saveState) => { + let monitoredTrip + + if (props.isCreating) { + const { itinerary, loggedInUser, queryParams } = props + monitoredTrip = createMonitoredTrip(loggedInUser, queryParams, itinerary) + } else { + const { match, monitoredTrips } = props + const { path, url } = match + if (monitoredTrips && path === '/savedtrips/:id') { + // Trip id is the portion of url after the second (the last) slash. + const tripId = url.split('/')[2] + monitoredTrip = monitoredTrips.find(trip => trip.id === tripId) + } else { + monitoredTrip = null + } + } + + if (saveState) { + this.setState({ monitoredTrip }) + } + + return monitoredTrip + } + + componentDidUpdate (prevProps) { + // Update the monitored trip from the new props if the url has changed. + if (prevProps.match.url !== this.props.match.url) { + this._getTripToEdit(this.props, true) + } + } + render () { const { isCreating, monitoredTrips } = this.props const { monitoredTrip } = this.state @@ -171,7 +195,7 @@ class SavedTripScreen extends Component { content = ( ) @@ -179,11 +203,10 @@ class SavedTripScreen extends Component { content = ( ) } @@ -207,11 +230,9 @@ const mapStateToProps = (state, ownProps) => { const activeItinerary = activeSearch && activeSearch.activeItinerary const itineraries = getActiveItineraries(state.otp) || [] return { - accessToken: state.user.accessToken, itinerary: itineraries[activeItinerary], loggedInUser: state.user.loggedInUser, monitoredTrips: state.user.loggedInUserMonitoredTrips, - persistence: state.otp.config.persistence, queryParams: state.router.location.search } } From 277d868571d617c7be128c0ca73c2ede622ff465 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Thu, 30 Jul 2020 15:54:19 -0400 Subject: [PATCH 37/46] refactor(FavoriteLocationsPane): Address comment for setting a default value for user.savedLocations --- lib/actions/user.js | 3 --- lib/components/user/favorite-locations-pane.js | 12 ++++++------ 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/lib/actions/user.js b/lib/actions/user.js index be5009921..796a7cf6f 100644 --- a/lib/actions/user.js +++ b/lib/actions/user.js @@ -65,9 +65,6 @@ export function fetchOrInitializeUser (auth) { const isNewAccount = fetchUserStatus === 'error' || (user && user.result === 'ERR') if (!isNewAccount) { - // TODO: Move next line somewhere else. - if (user.savedLocations === null) user.savedLocations = [] - // Load user's monitored trips before setting the user state. const { data: trips, status: tripFetchStatus } = await getTrips(otpMiddleware, accessToken) if (tripFetchStatus === 'success') { diff --git a/lib/components/user/favorite-locations-pane.js b/lib/components/user/favorite-locations-pane.js index d4c699d26..2d8146955 100644 --- a/lib/components/user/favorite-locations-pane.js +++ b/lib/components/user/favorite-locations-pane.js @@ -1,3 +1,4 @@ +import clone from 'lodash/cloneDeep' import memoize from 'lodash.memoize' import PropTypes from 'prop-types' import React, { Component } from 'react' @@ -53,11 +54,10 @@ class FavoriteLocationsPane extends Component { const value = e.target.value || '' if (value.trim().length > 0) { const { userData, onUserDataChange } = this.props - const { savedLocations } = userData - - // Create a new array for savedLocations. - const newLocations = [].concat(savedLocations) + const { savedLocations = [] } = userData + // Create a copy of savedLocations and add the new location to the copied array. + const newLocations = clone(savedLocations) newLocations.push({ address: value.trim(), icon: 'map-marker', @@ -75,7 +75,7 @@ class FavoriteLocationsPane extends Component { _handleAddressChange = memoize( location => e => { const { userData, onUserDataChange } = this.props - const { savedLocations } = userData + const { savedLocations = [] } = userData const value = e.target.value const isValueEmpty = !value || value === '' const nonEmptyLocation = isValueEmpty ? null : location @@ -108,7 +108,7 @@ class FavoriteLocationsPane extends Component { render () { const { userData } = this.props - const { savedLocations } = userData + const { savedLocations = [] } = userData // Build an 'effective' list of locations for display, // where at least one 'home' and one 'work', are always present even if blank. From b0395b10a66ab3639f243d25250007599aaf70dd Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Thu, 30 Jul 2020 16:34:47 -0400 Subject: [PATCH 38/46] refactor(actions/user): Extract fetchUserMonitoredTrips func, rename createNewUser func. --- lib/actions/user.js | 61 +++++++++++++++++++++------------------------ 1 file changed, 29 insertions(+), 32 deletions(-) diff --git a/lib/actions/user.js b/lib/actions/user.js index 796a7cf6f..5216a5590 100644 --- a/lib/actions/user.js +++ b/lib/actions/user.js @@ -1,4 +1,3 @@ -import clone from 'lodash/cloneDeep' import { createAction } from 'redux-actions' import { @@ -16,7 +15,7 @@ const setCurrentUser = createAction('SET_CURRENT_USER') const setCurrentUserMonitoredTrips = createAction('SET_CURRENT_USER_MONITORED_TRIPS') export const setPathBeforeSignIn = createAction('SET_PATH_BEFORE_SIGNIN') -function getStateForNewUser (auth0User) { +function createNewUser (auth0User) { return { auth0UserId: auth0User.sub, email: auth0User.email, @@ -30,6 +29,25 @@ function getStateForNewUser (auth0User) { } } +/** + * Fetches the saved/monitored trips for a user. + * We use the accessToken to fetch the data regardless of + * whether the process to populate state.user is completed or not. + */ +export function fetchUserMonitoredTrips (accessToken) { + return async function (dispatch, getState) { + const { otp } = getState() + const { otp_middleware: otpMiddleware = null } = otp.config.persistence + + if (otpMiddleware) { + const { data: trips, status: fetchStatus } = await getTrips(otpMiddleware, accessToken) + if (fetchStatus === 'success') { + dispatch(setCurrentUserMonitoredTrips(trips)) + } + } + } +} + /** * Fetches user preferences to state.user, or set initial values under state.user if no user has been loaded. */ @@ -66,14 +84,11 @@ export function fetchOrInitializeUser (auth) { const isNewAccount = fetchUserStatus === 'error' || (user && user.result === 'ERR') if (!isNewAccount) { // Load user's monitored trips before setting the user state. - const { data: trips, status: tripFetchStatus } = await getTrips(otpMiddleware, accessToken) - if (tripFetchStatus === 'success') { - dispatch(setCurrentUserMonitoredTrips(trips)) - } + await dispatch(fetchUserMonitoredTrips(accessToken)) dispatch(setCurrentUser({ accessToken, user })) } else { - dispatch(setCurrentUser({ accessToken, user: getStateForNewUser(authUser) })) + dispatch(setCurrentUser({ accessToken, user: createNewUser(authUser) })) } } } @@ -124,7 +139,7 @@ export function createOrUpdateUserMonitoredTrip (tripData, isNew) { const { otp_middleware: otpMiddleware = null } = otp.config.persistence if (otpMiddleware) { - const { accessToken, loggedInUserMonitoredTrips: trips } = user + const { accessToken } = user let result if (isNew) { @@ -135,24 +150,10 @@ export function createOrUpdateUserMonitoredTrip (tripData, isNew) { // TODO: improve the UI feedback messages for this. if (result.status === 'success' && result.data) { - const newTrip = result.data - - // For updating the monitoredTrips state, - // make a clone of the trips array, - // find the index of the trip being updated (using trip.id), - // and assign the trip returned from the middleware at that index - // (or append to array if index was not found). - const newTrips = trips ? clone(trips) : [] - const tripIndex = newTrips.findIndex(trip => trip.id === newTrip.id) - if (tripIndex !== -1) { - newTrips[tripIndex] = newTrip - } else { - newTrips.push(newTrip) - } - - dispatch(setCurrentUserMonitoredTrips(newTrips)) - alert('Your preferences have been saved.') + + // Reload user's monitored trips after add/update. + await dispatch(fetchUserMonitoredTrips(accessToken)) } else { alert(`An error was encountered:\n${JSON.stringify(result)}`) } @@ -170,16 +171,12 @@ export function deleteUserMonitoredTrip (id) { const { otp_middleware: otpMiddleware = null } = otp.config.persistence if (otpMiddleware) { - const { accessToken, loggedInUserMonitoredTrips: trips } = user + const { accessToken } = user const deleteResult = await deleteTrip(otpMiddleware, accessToken, id) if (deleteResult.status === 'success') { - // For updating the monitoredTrips state, - // keep the trips whose id is not that of the deleted trip. - if (trips) { - const newTrips = trips.filter(trip => trip.id !== id) - dispatch(setCurrentUserMonitoredTrips(newTrips)) - } + // Reload user's monitored trips after deletion. + await dispatch(fetchUserMonitoredTrips(accessToken)) } else { alert(`An error was encountered:\n${JSON.stringify(deleteResult)}`) } From 0281f40e7a248fcbcacc4a553176d550baaef995 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Thu, 30 Jul 2020 16:59:11 -0400 Subject: [PATCH 39/46] refactor(TripSummary): Obtain from/to names directly from itinerary. --- lib/components/user/trip-summary.js | 51 +++++++---------------------- 1 file changed, 12 insertions(+), 39 deletions(-) diff --git a/lib/components/user/trip-summary.js b/lib/components/user/trip-summary.js index 5c3bd7a5c..48f9be328 100644 --- a/lib/components/user/trip-summary.js +++ b/lib/components/user/trip-summary.js @@ -1,36 +1,20 @@ import coreUtils from '@opentripplanner/core-utils' import { ClassicLegIcon } from '@opentripplanner/icons' -import React, { useEffect, useState } from 'react' -import { connect } from 'react-redux' +import React from 'react' -import { getQueryParamsFromQueryString } from '../../util/query' import ItinerarySummary from '../narrative/default/itinerary-summary' const { formatDuration, formatTime } = coreUtils.time -const TripSummary = ({ config, monitoredTrip }) => { - // State, state setter, and intital state. - const [{ from, to }, setQueryObj] = useState({ from: {}, to: {} }) - const { itinerary, queryParams } = monitoredTrip +const TripSummary = ({ monitoredTrip }) => { + const { itinerary } = monitoredTrip + const { duration, endTime, legs, startTime } = itinerary - useEffect(() => { - // Will be set to false on component unload. - let isComponentMounted = true - - getQueryParamsFromQueryString(queryParams, config) - .then(queryObj => { - // Update the state with the query object while component is mounted. - if (isComponentMounted) { - setQueryObj(queryObj) - } - }) - - // Gets called when component is unmounted. - // Turn off flag to prevent state update. - return () => { - isComponentMounted = false - } - }, []) + // Assuming the monitored itinerary has at least one leg: + // - get the 'from' location of the first leg, + // - get the 'to' location of the last leg. + const from = legs[0].from + const to = legs[legs.length - 1].to // TODO: use the modern itinerary summary built for trip comparison. return ( @@ -39,8 +23,8 @@ const TripSummary = ({ config, monitoredTrip }) => {
Itinerary{' '} - {formatDuration(itinerary.duration)}{' '} - {formatTime(itinerary.startTime)}—{formatTime(itinerary.endTime)} + {formatDuration(duration)}{' '} + {formatTime(startTime)}—{formatTime(endTime)}
@@ -48,15 +32,4 @@ const TripSummary = ({ config, monitoredTrip }) => { ) } -// connect to the redux store - -const mapStateToProps = (state, ownProps) => { - return { - config: state.otp.config - } -} - -const mapDispatchToProps = { -} - -export default connect(mapStateToProps, mapDispatchToProps)(TripSummary) +export default TripSummary From 7ace866bd5c4a0f542f8f05ab10a24e217178e93 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 7 Aug 2020 18:40:39 -0400 Subject: [PATCH 40/46] fix(LineItinerary): Pass timeOptions prop to ItineraryBody. --- .../narrative/line-itin/connected-itinerary-body.js | 4 +++- lib/components/narrative/line-itin/line-itinerary.js | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/components/narrative/line-itin/connected-itinerary-body.js b/lib/components/narrative/line-itin/connected-itinerary-body.js index d1cef245a..5c6ba0148 100644 --- a/lib/components/narrative/line-itin/connected-itinerary-body.js +++ b/lib/components/narrative/line-itin/connected-itinerary-body.js @@ -41,7 +41,8 @@ class ConnectedItineraryBody extends Component { LegIcon, setActiveLeg, setViewedTrip, - showLegDiagram + showLegDiagram, + timeOptions } = this.props return ( @@ -62,6 +63,7 @@ class ConnectedItineraryBody extends Component { showLegIcon showMapButtonColumn={false} showViewTripButton + timeOptions={timeOptions} toRouteAbbreviation={noop} TransitLegSubheader={TransitLegSubheader} TransitLegSummary={TransitLegSummary} diff --git a/lib/components/narrative/line-itin/line-itinerary.js b/lib/components/narrative/line-itin/line-itinerary.js index 872d8df99..14d3ef282 100644 --- a/lib/components/narrative/line-itin/line-itinerary.js +++ b/lib/components/narrative/line-itin/line-itinerary.js @@ -83,6 +83,7 @@ export default class LineItinerary extends NarrativeItinerary { // (will cause error when clicking on itinerary suymmary). // Use the one passed by NarrativeItineraries instead. setActiveLeg={setActiveLeg} + timeOptions={timeOptions} /> : null} {itineraryFooter} From d04b5646c7b98ad6bb0fd3c978bfe667f2f4cc4e Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 7 Aug 2020 18:42:14 -0400 Subject: [PATCH 41/46] refactor(LineItinerary): Alphabetize --- lib/components/narrative/line-itin/line-itinerary.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/components/narrative/line-itin/line-itinerary.js b/lib/components/narrative/line-itin/line-itinerary.js index 14d3ef282..aa0643710 100644 --- a/lib/components/narrative/line-itin/line-itinerary.js +++ b/lib/components/narrative/line-itin/line-itinerary.js @@ -50,9 +50,9 @@ export default class LineItinerary extends NarrativeItinerary { itinerary, itineraryFooter, LegIcon, + onClick, setActiveLeg, showRealtimeAnnotation, - onClick, timeFormat } = this.props @@ -71,8 +71,8 @@ export default class LineItinerary extends NarrativeItinerary { companies={companies} itinerary={itinerary} LegIcon={LegIcon} - timeOptions={timeOptions} onClick={onClick} + timeOptions={timeOptions} /> {showRealtimeAnnotation && } {active || expanded From d77e617082a85151315a22e478befc5629fd8ba7 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 11 Aug 2020 12:45:50 -0400 Subject: [PATCH 42/46] refactor(util/monitored-trip): Rename vars. Remove unused file. --- lib/util/monitored-trip.js | 10 +++++----- lib/util/query.js | 22 ---------------------- 2 files changed, 5 insertions(+), 27 deletions(-) delete mode 100644 lib/util/query.js diff --git a/lib/util/monitored-trip.js b/lib/util/monitored-trip.js index 5250a64b0..f800d98c0 100644 --- a/lib/util/monitored-trip.js +++ b/lib/util/monitored-trip.js @@ -2,21 +2,21 @@ export const WEEKDAYS = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday'] const ALL_DAYS = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'] /** - * Extracts the day of week fields of an object to an array. + * Extracts the day of week fields of an object (e.g. a monitoredTrip) to an array. * Example: { monday: truthy, tuesday: falsy, wednesday: truthy ... } => ['monday', 'wednesday' ...] */ -export function dayFieldsToArray (obj) { - return ALL_DAYS.filter(day => obj[day]) +export function dayFieldsToArray (monitoredTrip) { + return ALL_DAYS.filter(day => monitoredTrip[day]) } /** * Converts an array of day of week values into an object with those fields. * Example: ['monday', 'wednesday' ...] => { monday: true, tuesday: false, wednesday: true ... } */ -export function arrayToDayFields (array) { +export function arrayToDayFields (arrayOfDayTypes) { const result = {} ALL_DAYS.forEach(day => { - result[day] = array.includes(day) + result[day] = arrayOfDayTypes.includes(day) }) return result } diff --git a/lib/util/query.js b/lib/util/query.js deleted file mode 100644 index 612d8da2d..000000000 --- a/lib/util/query.js +++ /dev/null @@ -1,22 +0,0 @@ -import coreUtils from '@opentripplanner/core-utils' -import qs from 'qs' - -const { planParamsToQueryAsync } = coreUtils.query - -/** Extracts a query object from a query object, using an optional OTP configuration. */ -export async function getQueryParamsFromQueryObj (queryObj, config) { - // Filter out the OTP (i.e. non-UI) params. - const planParams = {} - Object.keys(queryObj).forEach(key => { - if (!key.startsWith('ui_')) planParams[key] = queryObj[key] - }) - - // Fully-format query object. - return planParamsToQueryAsync(planParams, config) -} - -/** Extracts a query object from a query string, using an optional OTP configuration. */ -export async function getQueryParamsFromQueryString (queryString, config) { - const queryObj = qs.parse(queryString.split('?')[1]) - return getQueryParamsFromQueryObj(queryObj, config) -} From 982a8777c8b36e2392de2ee5348bd5ee4d0a140d Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 11 Aug 2020 12:58:04 -0400 Subject: [PATCH 43/46] docs(FavoriteLocationsPane): Add FIXMEs for initializing user.savedLocations. --- lib/components/user/favorite-locations-pane.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/components/user/favorite-locations-pane.js b/lib/components/user/favorite-locations-pane.js index 2d8146955..e143a70e2 100644 --- a/lib/components/user/favorite-locations-pane.js +++ b/lib/components/user/favorite-locations-pane.js @@ -54,6 +54,7 @@ class FavoriteLocationsPane extends Component { const value = e.target.value || '' if (value.trim().length > 0) { const { userData, onUserDataChange } = this.props + // FIXME: remove assigning [] when null. const { savedLocations = [] } = userData // Create a copy of savedLocations and add the new location to the copied array. @@ -75,6 +76,7 @@ class FavoriteLocationsPane extends Component { _handleAddressChange = memoize( location => e => { const { userData, onUserDataChange } = this.props + // FIXME: remove assigning [] when null. const { savedLocations = [] } = userData const value = e.target.value const isValueEmpty = !value || value === '' @@ -108,6 +110,7 @@ class FavoriteLocationsPane extends Component { render () { const { userData } = this.props + // FIXME: remove assigning [] when null. const { savedLocations = [] } = userData // Build an 'effective' list of locations for display, From ae58931d7104731332caa9d150ad84d77f505625 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 12 Aug 2020 15:52:29 -0400 Subject: [PATCH 44/46] improvement(a11y): Add markup and props for Lighthouse scorecard. --- example.js | 20 +++++++++++++------- lib/components/app/app-menu.js | 1 + 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/example.js b/example.js index 41142d151..93548ea9b 100644 --- a/example.js +++ b/example.js @@ -85,7 +85,10 @@ class OtpRRExample extends Component { - + {/*
Needed for accessibility checks. TODO: Find a better place. */} +
+ +
@@ -97,12 +100,15 @@ class OtpRRExample extends Component { /** mobile view **/ const mobileView = ( - } - title={
OpenTripPlanner
} - /> + //
Needed for accessibility checks. TODO: Find a better place. +
+ } + title={
OpenTripPlanner
} + /> +
) /** the main webapp **/ diff --git a/lib/components/app/app-menu.js b/lib/components/app/app-menu.js index 9093e6da8..6bf4d9374 100644 --- a/lib/components/app/app-menu.js +++ b/lib/components/app/app-menu.js @@ -33,6 +33,7 @@ class AppMenu extends Component { return (
)} noCaret className='app-menu-button' From 4cc23b40cc3407785ab0e6fcde2d6c9d072f7576 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 18 Aug 2020 14:57:49 -0400 Subject: [PATCH 45/46] fix(a11y): Fix some a11y issues (landing page only, desktop and mobile). --- lib/components/form/date-time-preview.js | 5 ++++- lib/components/form/settings-preview.js | 5 ++++- package.json | 4 ++-- yarn.lock | 24 +++++++++++++++++++----- 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/lib/components/form/date-time-preview.js b/lib/components/form/date-time-preview.js index 846c8b974..865cbbf84 100644 --- a/lib/components/form/date-time-preview.js +++ b/lib/components/form/date-time-preview.js @@ -65,7 +65,10 @@ class DateTimePreview extends Component { const button = (
-
diff --git a/lib/components/form/settings-preview.js b/lib/components/form/settings-preview.js index 5398024df..612aec4db 100644 --- a/lib/components/form/settings-preview.js +++ b/lib/components/form/settings-preview.js @@ -35,7 +35,10 @@ class SettingsPreview extends Component { let showDot = coreUtils.query.isNotDefaultQuery(query, config) const button = (
- {showDot &&
} diff --git a/package.json b/package.json index 527561bb0..63cdc9338 100644 --- a/package.json +++ b/package.json @@ -28,14 +28,14 @@ "homepage": "https://github.com/opentripplanner/otp-react-redux#readme", "dependencies": { "@opentripplanner/base-map": "^1.0.1", - "@opentripplanner/core-utils": "^1.2.1", + "@opentripplanner/core-utils": "^2.1.0", "@opentripplanner/endpoints-overlay": "^1.0.1", "@opentripplanner/from-to-location-picker": "^1.0.1", "@opentripplanner/geocoder": "^1.0.2", "@opentripplanner/humanize-distance": "^0.0.22", "@opentripplanner/icons": "^1.0.1", "@opentripplanner/itinerary-body": "^1.0.2", - "@opentripplanner/location-field": "^1.0.1", + "@opentripplanner/location-field": "^1.0.2", "@opentripplanner/location-icon": "^1.0.0", "@opentripplanner/park-and-ride-overlay": "^1.0.1", "@opentripplanner/printable-itinerary": "^1.0.0", diff --git a/yarn.lock b/yarn.lock index 051fc2355..517f5470a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1275,6 +1275,20 @@ prop-types "^15.7.2" qs "^6.9.1" +"@opentripplanner/core-utils@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@opentripplanner/core-utils/-/core-utils-2.1.0.tgz#ab7271a2b560168b21b0dd26c78532f24fd031f5" + integrity sha512-sQ3oiZB7f01kCVuTj5SzqsPe4uchupNbla+onLWH+aO3wH+OLZNuLRZZ/7oFFFnvpcFBakyg4TePPxyyEyJlMA== + dependencies: + "@mapbox/polyline" "^1.1.0" + "@turf/along" "^6.0.1" + bowser "^2.7.0" + lodash.isequal "^4.5.0" + moment "^2.24.0" + moment-timezone "^0.5.27" + prop-types "^15.7.2" + qs "^6.9.1" + "@opentripplanner/endpoints-overlay@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@opentripplanner/endpoints-overlay/-/endpoints-overlay-1.0.1.tgz#d95f0bbfddc9382b593845799b963340eece2742" @@ -1332,12 +1346,12 @@ react-resize-detector "^4.2.1" velocity-react "^1.4.3" -"@opentripplanner/location-field@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@opentripplanner/location-field/-/location-field-1.0.1.tgz#9db5622208739db06d707bfb5b443291598d803e" - integrity sha512-V2i1BDQPzQ8OJ1F1JQkH++LNLQzHR3w0nGvIwY31iTEdukDeePSa5raSPFwAXy90uqNtk2Xt6NPRetMII1L//w== +"@opentripplanner/location-field@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@opentripplanner/location-field/-/location-field-1.0.2.tgz#16daeac83be8f10b157fc8c51f32e2a8eb0a8db8" + integrity sha512-+YIXXltw6ZuHyvCzbDL97Wfhq+C/FaT2utmWAf1O1g6BGpkF6dHjh8+ZHoH0al/4/t6AyhSVlPMXOEyaj2xERA== dependencies: - "@opentripplanner/core-utils" "^1.2.0" + "@opentripplanner/core-utils" "^2.1.0" "@opentripplanner/geocoder" "^1.0.2" "@opentripplanner/humanize-distance" "^0.0.22" "@opentripplanner/location-icon" "^1.0.0" From 4b258e5aea605d19c46d5850491a4accf2792736 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 18 Aug 2020 15:33:51 -0400 Subject: [PATCH 46/46] fix(a11y): Fix dependendency. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 63cdc9338..cf7d1992a 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "homepage": "https://github.com/opentripplanner/otp-react-redux#readme", "dependencies": { "@opentripplanner/base-map": "^1.0.1", - "@opentripplanner/core-utils": "^2.1.0", + "@opentripplanner/core-utils": "^1.2.1", "@opentripplanner/endpoints-overlay": "^1.0.1", "@opentripplanner/from-to-location-picker": "^1.0.1", "@opentripplanner/geocoder": "^1.0.2",