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'
1
6
import { createAction } from 'redux-actions'
2
7
8
+ import { routingQuery } from './api'
9
+ import { setQueryParam } from './form'
3
10
import { routeTo } from './ui'
11
+ import { TRIPS_PATH } from '../util/constants'
4
12
import { secureFetch } from '../util/middleware'
5
13
import { isNewUser } from '../util/user'
6
14
@@ -9,6 +17,7 @@ const API_MONITORED_TRIP_PATH = '/api/secure/monitoredtrip'
9
17
const API_OTPUSER_PATH = '/api/secure/user'
10
18
const API_OTPUSER_VERIFY_SMS_SUBPATH = '/verify_sms'
11
19
20
+ const setAccessToken = createAction ( 'SET_ACCESS_TOKEN' )
12
21
const setCurrentUser = createAction ( 'SET_CURRENT_USER' )
13
22
const setCurrentUserMonitoredTrips = createAction ( 'SET_CURRENT_USER_MONITORED_TRIPS' )
14
23
const setLastPhoneSmsRequest = createAction ( 'SET_LAST_PHONE_SMS_REQUEST' )
@@ -60,14 +69,30 @@ export function fetchAuth0Token (auth0) {
60
69
try {
61
70
const accessToken = await auth0 . getAccessTokenSilently ( )
62
71
63
- dispatch ( setCurrentUser ( { accessToken } ) )
72
+ dispatch ( setAccessToken ( accessToken ) )
64
73
} catch ( error ) {
65
74
// TODO: improve UI if there is an error.
66
75
alert ( 'Error obtaining an authorization token.' )
67
76
}
68
77
}
69
78
}
70
79
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
+
71
96
/**
72
97
* Attempts to fetch user preferences (or set initial values if the user is being created)
73
98
* into the redux state, under state.user.
@@ -103,12 +128,9 @@ export function fetchOrInitializeUser (auth0User) {
103
128
104
129
const isNewAccount = status === 'error' || ( user && user . result === 'ERR' )
105
130
const userData = isNewAccount ? createNewUser ( auth0User ) : user
106
- dispatch ( setCurrentUser ( { accessToken, user : userData } ) )
107
131
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 ) )
112
134
}
113
135
}
114
136
@@ -125,7 +147,8 @@ export function createOrUpdateUser (userData, silentOnSuccess = false) {
125
147
let requestUrl , method
126
148
127
149
// Determine URL and method to use.
128
- if ( isNewUser ( loggedInUser ) ) {
150
+ const isCreatingUser = isNewUser ( loggedInUser )
151
+ if ( isCreatingUser ) {
129
152
requestUrl = `${ apiBaseUrl } ${ API_OTPUSER_PATH } `
130
153
method = 'POST'
131
154
} else {
@@ -144,8 +167,8 @@ export function createOrUpdateUser (userData, silentOnSuccess = false) {
144
167
}
145
168
146
169
// 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 ) )
149
172
} else {
150
173
alert ( `An error was encountered:\n${ JSON . stringify ( message ) } ` )
151
174
}
@@ -175,7 +198,7 @@ export function resendVerificationEmail () {
175
198
* We use the accessToken to fetch the data regardless of
176
199
* whether the process to populate state.user is completed or not.
177
200
*/
178
- export function fetchUserMonitoredTrips ( ) {
201
+ export function fetchMonitoredTrips ( ) {
179
202
return async function ( dispatch , getState ) {
180
203
const { accessToken, apiBaseUrl, apiKey } = getMiddlewareVariables ( getState ( ) )
181
204
const requestUrl = `${ apiBaseUrl } ${ API_MONITORED_TRIP_PATH } `
@@ -192,7 +215,12 @@ export function fetchUserMonitoredTrips () {
192
215
* then, if that was successful, alerts (optional)
193
216
* and refreshes the redux monitoredTrips with the updated trip.
194
217
*/
195
- export function createOrUpdateUserMonitoredTrip ( tripData , isNew , silentOnSuccess ) {
218
+ export function createOrUpdateUserMonitoredTrip (
219
+ tripData ,
220
+ isNew ,
221
+ silentOnSuccess ,
222
+ noRedirect
223
+ ) {
196
224
return async function ( dispatch , getState ) {
197
225
const { accessToken, apiBaseUrl, apiKey } = getMiddlewareVariables ( getState ( ) )
198
226
const { id } = tripData
@@ -218,29 +246,58 @@ export function createOrUpdateUserMonitoredTrip (tripData, isNew, silentOnSucces
218
246
}
219
247
220
248
// Reload user's monitored trips after add/update.
221
- await dispatch ( fetchUserMonitoredTrips ( ) )
249
+ await dispatch ( fetchMonitoredTrips ( ) )
250
+
251
+ if ( noRedirect ) return
222
252
223
253
// Finally, navigate to the saved trips page.
224
- dispatch ( routeTo ( '/savedtrips' ) )
254
+ dispatch ( routeTo ( TRIPS_PATH ) )
225
255
} else {
226
256
alert ( `An error was encountered:\n${ JSON . stringify ( message ) } ` )
227
257
}
228
258
}
229
259
}
230
260
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
+
231
287
/**
232
288
* Deletes a logged-in user's monitored trip,
233
289
* then, if that was successful, refreshes the redux monitoredTrips state.
234
290
*/
235
- export function deleteUserMonitoredTrip ( tripId ) {
291
+ export function confirmAndDeleteUserMonitoredTrip ( tripId ) {
236
292
return async function ( dispatch , getState ) {
293
+ if ( ! confirm ( 'Would you like to remove this trip?' ) ) return
237
294
const { accessToken, apiBaseUrl, apiKey } = getMiddlewareVariables ( getState ( ) )
238
295
const requestUrl = `${ apiBaseUrl } ${ API_MONITORED_TRIP_PATH } /${ tripId } `
239
296
240
297
const { message, status } = await secureFetch ( requestUrl , accessToken , apiKey , 'DELETE' )
241
298
if ( status === 'success' ) {
242
299
// Reload user's monitored trips after deletion.
243
- dispatch ( fetchUserMonitoredTrips ( ) )
300
+ dispatch ( fetchMonitoredTrips ( ) )
244
301
} else {
245
302
alert ( `An error was encountered:\n${ JSON . stringify ( message ) } ` )
246
303
}
@@ -334,3 +391,29 @@ export function checkItineraryExistence (trip) {
334
391
}
335
392
}
336
393
}
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
+ }
0 commit comments