Skip to content

Commit bf20282

Browse files
authored
Merge pull request #318 from opentripplanner/dev
Feature release
2 parents 56e4243 + db9dc53 commit bf20282

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1543
-901
lines changed

Diff for: __tests__/components/viewers/__snapshots__/stop-viewer.js.snap

+65-65
Large diffs are not rendered by default.

Diff for: example.css

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
.otp .navbar {
22
border-radius: 0;
33
background-color: #444;
4+
margin: 0px;
45
}
56

67
.otp .navbar .container {

Diff for: lib/actions/user.js

+98-15
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
1+
import clone from 'clone'
2+
import moment from 'moment'
3+
import { planParamsToQuery } from '@opentripplanner/core-utils/lib/query'
4+
import { OTP_API_DATE_FORMAT } from '@opentripplanner/core-utils/lib/time'
5+
import qs from 'qs'
16
import { createAction } from 'redux-actions'
27

8+
import { routingQuery } from './api'
9+
import { setQueryParam } from './form'
310
import { routeTo } from './ui'
11+
import { TRIPS_PATH } from '../util/constants'
412
import { secureFetch } from '../util/middleware'
513
import { isNewUser } from '../util/user'
614

@@ -9,6 +17,7 @@ const API_MONITORED_TRIP_PATH = '/api/secure/monitoredtrip'
917
const API_OTPUSER_PATH = '/api/secure/user'
1018
const API_OTPUSER_VERIFY_SMS_SUBPATH = '/verify_sms'
1119

20+
const setAccessToken = createAction('SET_ACCESS_TOKEN')
1221
const setCurrentUser = createAction('SET_CURRENT_USER')
1322
const setCurrentUserMonitoredTrips = createAction('SET_CURRENT_USER_MONITORED_TRIPS')
1423
const setLastPhoneSmsRequest = createAction('SET_LAST_PHONE_SMS_REQUEST')
@@ -60,14 +69,30 @@ export function fetchAuth0Token (auth0) {
6069
try {
6170
const accessToken = await auth0.getAccessTokenSilently()
6271

63-
dispatch(setCurrentUser({ accessToken }))
72+
dispatch(setAccessToken(accessToken))
6473
} catch (error) {
6574
// TODO: improve UI if there is an error.
6675
alert('Error obtaining an authorization token.')
6776
}
6877
}
6978
}
7079

80+
/**
81+
* Updates the redux state with the provided user data,
82+
* and also fetches monitored trips if requested, i.e. when
83+
* - initializing the user state with an existing persisted user, or
84+
* - POST-ing a user for the first time.
85+
*/
86+
function setUser (user, fetchTrips) {
87+
return function (dispatch, getState) {
88+
dispatch(setCurrentUser(user))
89+
90+
if (fetchTrips) {
91+
dispatch(fetchMonitoredTrips())
92+
}
93+
}
94+
}
95+
7196
/**
7297
* Attempts to fetch user preferences (or set initial values if the user is being created)
7398
* into the redux state, under state.user.
@@ -103,12 +128,9 @@ export function fetchOrInitializeUser (auth0User) {
103128

104129
const isNewAccount = status === 'error' || (user && user.result === 'ERR')
105130
const userData = isNewAccount ? createNewUser(auth0User) : user
106-
dispatch(setCurrentUser({ accessToken, user: userData }))
107131

108-
// Also load monitored trips for existing users.
109-
if (!isNewAccount) {
110-
dispatch(fetchUserMonitoredTrips())
111-
}
132+
// Set uset in redux state. (Fetch trips for existing users.)
133+
dispatch(setUser(userData, !isNewAccount))
112134
}
113135
}
114136

@@ -125,7 +147,8 @@ export function createOrUpdateUser (userData, silentOnSuccess = false) {
125147
let requestUrl, method
126148

127149
// Determine URL and method to use.
128-
if (isNewUser(loggedInUser)) {
150+
const isCreatingUser = isNewUser(loggedInUser)
151+
if (isCreatingUser) {
129152
requestUrl = `${apiBaseUrl}${API_OTPUSER_PATH}`
130153
method = 'POST'
131154
} else {
@@ -144,8 +167,8 @@ export function createOrUpdateUser (userData, silentOnSuccess = false) {
144167
}
145168

146169
// Update application state with the user entry as saved
147-
// (as returned) by the middleware.
148-
dispatch(setCurrentUser({ accessToken, user: data }))
170+
// (as returned) by the middleware. (Fetch trips if creating user.)
171+
dispatch(setUser(data, isCreatingUser))
149172
} else {
150173
alert(`An error was encountered:\n${JSON.stringify(message)}`)
151174
}
@@ -175,7 +198,7 @@ export function resendVerificationEmail () {
175198
* We use the accessToken to fetch the data regardless of
176199
* whether the process to populate state.user is completed or not.
177200
*/
178-
export function fetchUserMonitoredTrips () {
201+
export function fetchMonitoredTrips () {
179202
return async function (dispatch, getState) {
180203
const { accessToken, apiBaseUrl, apiKey } = getMiddlewareVariables(getState())
181204
const requestUrl = `${apiBaseUrl}${API_MONITORED_TRIP_PATH}`
@@ -192,7 +215,12 @@ export function fetchUserMonitoredTrips () {
192215
* then, if that was successful, alerts (optional)
193216
* and refreshes the redux monitoredTrips with the updated trip.
194217
*/
195-
export function createOrUpdateUserMonitoredTrip (tripData, isNew, silentOnSuccess) {
218+
export function createOrUpdateUserMonitoredTrip (
219+
tripData,
220+
isNew,
221+
silentOnSuccess,
222+
noRedirect
223+
) {
196224
return async function (dispatch, getState) {
197225
const { accessToken, apiBaseUrl, apiKey } = getMiddlewareVariables(getState())
198226
const { id } = tripData
@@ -218,29 +246,58 @@ export function createOrUpdateUserMonitoredTrip (tripData, isNew, silentOnSucces
218246
}
219247

220248
// Reload user's monitored trips after add/update.
221-
await dispatch(fetchUserMonitoredTrips())
249+
await dispatch(fetchMonitoredTrips())
250+
251+
if (noRedirect) return
222252

223253
// Finally, navigate to the saved trips page.
224-
dispatch(routeTo('/savedtrips'))
254+
dispatch(routeTo(TRIPS_PATH))
225255
} else {
226256
alert(`An error was encountered:\n${JSON.stringify(message)}`)
227257
}
228258
}
229259
}
230260

261+
/**
262+
* Toggles the isActive status of a monitored trip
263+
*/
264+
export function togglePauseTrip (trip) {
265+
return function (dispatch, getState) {
266+
const clonedTrip = clone(trip)
267+
clonedTrip.isActive = !clonedTrip.isActive
268+
269+
// Silent update of existing trip.
270+
dispatch(createOrUpdateUserMonitoredTrip(clonedTrip, false, true, true))
271+
}
272+
}
273+
274+
/**
275+
* Toggles the snoozed status of a monitored trip
276+
*/
277+
export function toggleSnoozeTrip (trip) {
278+
return function (dispatch, getState) {
279+
const newTrip = clone(trip)
280+
newTrip.snoozed = !newTrip.snoozed
281+
282+
// Silent update of existing trip.
283+
dispatch(createOrUpdateUserMonitoredTrip(newTrip, false, true, true))
284+
}
285+
}
286+
231287
/**
232288
* Deletes a logged-in user's monitored trip,
233289
* then, if that was successful, refreshes the redux monitoredTrips state.
234290
*/
235-
export function deleteUserMonitoredTrip (tripId) {
291+
export function confirmAndDeleteUserMonitoredTrip (tripId) {
236292
return async function (dispatch, getState) {
293+
if (!confirm('Would you like to remove this trip?')) return
237294
const { accessToken, apiBaseUrl, apiKey } = getMiddlewareVariables(getState())
238295
const requestUrl = `${apiBaseUrl}${API_MONITORED_TRIP_PATH}/${tripId}`
239296

240297
const { message, status } = await secureFetch(requestUrl, accessToken, apiKey, 'DELETE')
241298
if (status === 'success') {
242299
// Reload user's monitored trips after deletion.
243-
dispatch(fetchUserMonitoredTrips())
300+
dispatch(fetchMonitoredTrips())
244301
} else {
245302
alert(`An error was encountered:\n${JSON.stringify(message)}`)
246303
}
@@ -334,3 +391,29 @@ export function checkItineraryExistence (trip) {
334391
}
335392
}
336393
}
394+
395+
/**
396+
* Plans a new trip for the current date given the query parameters in the given
397+
* monitored trip
398+
*/
399+
export function planNewTripFromMonitoredTrip (monitoredTrip) {
400+
return function (dispatch, getState) {
401+
// update query params in store
402+
const newQuery = planParamsToQuery(qs.parse(monitoredTrip.queryParams))
403+
newQuery.date = moment().format(OTP_API_DATE_FORMAT)
404+
405+
dispatch(setQueryParam(newQuery))
406+
407+
dispatch(routeTo('/'))
408+
409+
// This prevents some kind of race condition whose origin I can't figure
410+
// out. Unless this is called after redux catches up with routing to the '/'
411+
// path, then the old path will be used and the screen won't change.
412+
// Therefore, this timeout occurs so that the view of the homepage has time
413+
// to render itself.
414+
// FIXME: remove hack
415+
setTimeout(() => {
416+
dispatch(routingQuery())
417+
}, 300)
418+
}
419+
}

Diff for: lib/components/app/call-taker-panel.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,7 @@ class CallTakerAdvancedOptions extends Component {
373373

374374
static contextType = ComponentContext
375375

376-
componentWillMount () {
376+
componentDidMount () {
377377
// Fetch routes for banned/preferred routes selectors.
378378
this.props.findRoutes()
379379
}

Diff for: lib/components/app/responsive-webapp.js

+33-54
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import coreUtils from '@opentripplanner/core-utils'
66
import PropTypes from 'prop-types'
77
import React, { Component } from 'react'
88
import { connect } from 'react-redux'
9-
import { Route, Switch, withRouter } from 'react-router'
9+
import { Redirect, Route, Switch, withRouter } from 'react-router'
1010

1111
import PrintLayout from './print-layout'
1212
import * as authActions from '../../actions/auth'
@@ -17,13 +17,21 @@ import * as locationActions from '../../actions/location'
1717
import * as mapActions from '../../actions/map'
1818
import * as uiActions from '../../actions/ui'
1919
import { getAuth0Config } from '../../util/auth'
20-
import { AUTH0_AUDIENCE, AUTH0_SCOPE, URL_ROOT } from '../../util/constants'
20+
import {
21+
ACCOUNT_PATH,
22+
AUTH0_AUDIENCE,
23+
AUTH0_SCOPE,
24+
ACCOUNT_SETTINGS_PATH,
25+
CREATE_ACCOUNT_PATH,
26+
TRIPS_PATH,
27+
URL_ROOT
28+
} from '../../util/constants'
2129
import { ComponentContext } from '../../util/contexts'
2230
import { getActiveItinerary, getTitle } from '../../util/state'
2331
import AfterSignInScreen from '../user/after-signin-screen'
2432
import BeforeSignInScreen from '../user/before-signin-screen'
25-
import SavedTripList from '../user/saved-trip-list'
26-
import SavedTripScreen from '../user/saved-trip-screen'
33+
import SavedTripList from '../user/monitored-trip/saved-trip-list'
34+
import SavedTripScreen from '../user/monitored-trip/saved-trip-screen'
2735
import UserAccountScreen from '../user/user-account-screen'
2836
import withLoggedInUserSupport from '../user/with-logged-in-user-support'
2937

@@ -137,12 +145,8 @@ class ResponsiveWebapp extends Component {
137145
}
138146

139147
render () {
140-
const { components, desktopView, mobileView } = this.props
141-
return (
142-
<ComponentContext.Provider value={components}>
143-
{isMobile() ? mobileView : desktopView}
144-
</ComponentContext.Provider>
145-
)
148+
const { desktopView, mobileView } = this.props
149+
return isMobile() ? mobileView : desktopView
146150
}
147151
}
148152

@@ -191,30 +195,21 @@ const WebappWithRouter = withRouter(
191195
* so that Auth0 services are available everywhere.
192196
*/
193197
class RouterWrapperWithAuth0 extends Component {
194-
/**
195-
* Combine the router props with the other props that get
196-
* passed to the exported component. This way, it is possible for
197-
* the PrintLayout, UserAccountScreen, and other components to
198-
* receive the LegIcon or other needed props.
199-
*/
200-
_combineProps = routerProps => {
201-
return { ...this.props, ...routerProps }
202-
}
203-
204198
render () {
205199
const {
206200
auth0Config,
201+
components,
207202
processSignIn,
208203
routerConfig,
209204
showAccessTokenError,
210205
showLoginError
211206
} = this.props
212207

213208
const router = (
214-
<ConnectedRouter
215-
basename={routerConfig && routerConfig.basename}
216-
history={history}>
217-
<div>
209+
<ComponentContext.Provider value={components}>
210+
<ConnectedRouter
211+
basename={routerConfig && routerConfig.basename}
212+
history={history}>
218213
<Switch>
219214
<Route
220215
exact
@@ -237,56 +232,40 @@ class RouterWrapperWithAuth0 extends Component {
237232
render={() => <WebappWithRouter {...this.props} />}
238233
/>
239234
<Route
240-
// This route lets new or existing users edit or set up their account.
241-
path={'/account'}
242-
component={routerProps => {
243-
const props = this._combineProps(routerProps)
244-
return <UserAccountScreen {...props} />
245-
}}
246-
/>
247-
<Route
248-
path={'/savetrip'}
249-
component={(routerProps) => {
250-
const props = this._combineProps(routerProps)
251-
return <SavedTripScreen isCreating {...props} />
252-
}}
235+
component={SavedTripScreen}
236+
path={`${TRIPS_PATH}/:id`}
253237
/>
238+
<Route exact path={ACCOUNT_PATH}>
239+
<Redirect to={TRIPS_PATH} />
240+
</Route>
254241
<Route
255-
path={'/savedtrips/:id'}
256-
component={(routerProps) => {
257-
const props = this._combineProps(routerProps)
258-
return <SavedTripScreen {...props} />
259-
}}
242+
// This route lets new or existing users edit or set up their account.
243+
component={UserAccountScreen}
244+
path={[CREATE_ACCOUNT_PATH, ACCOUNT_SETTINGS_PATH]}
260245
/>
261246
<Route
262-
path={'/savedtrips'}
263247
component={SavedTripList}
248+
path={TRIPS_PATH}
264249
/>
265250
<Route
266251
// This route is called immediately after login by Auth0
267252
// and by the onRedirectCallback function from /lib/util/auth.js.
268253
// For new users, it displays the account setup form.
269254
// For existing users, it takes the browser back to the itinerary search prior to login.
270-
path={'/signedin'}
271-
component={routerProps => {
272-
const props = this._combineProps(routerProps)
273-
return <AfterSignInScreen {...props} />
274-
}}
255+
component={AfterSignInScreen}
256+
path='/signedin'
275257
/>
276258
<Route
259+
component={PrintLayout}
277260
path='/print'
278-
component={routerProps => {
279-
const props = this._combineProps(routerProps)
280-
return <PrintLayout {...props} />
281-
}}
282261
/>
283262
{/* For any other route, simply return the web app. */}
284263
<Route
285264
render={() => <WebappWithRouter {...this.props} />}
286265
/>
287266
</Switch>
288-
</div>
289-
</ConnectedRouter>
267+
</ConnectedRouter>
268+
</ComponentContext.Provider>
290269
)
291270

292271
return (

Diff for: lib/components/form/date-time-preview.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class DateTimePreview extends Component {
1919
date: PropTypes.string,
2020
departArrive: PropTypes.string,
2121
editButtonText: PropTypes.element,
22-
hideButton: PropTypes.boolean,
22+
hideButton: PropTypes.bool,
2323
onClick: PropTypes.func,
2424
routingType: PropTypes.string,
2525
time: PropTypes.string

0 commit comments

Comments
 (0)