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'
16import { createAction } from 'redux-actions'
27
8+ import { routingQuery } from './api'
9+ import { setQueryParam } from './form'
310import { routeTo } from './ui'
11+ import { TRIPS_PATH } from '../util/constants'
412import { secureFetch } from '../util/middleware'
513import { isNewUser } from '../util/user'
614
@@ -9,6 +17,7 @@ const API_MONITORED_TRIP_PATH = '/api/secure/monitoredtrip'
917const API_OTPUSER_PATH = '/api/secure/user'
1018const API_OTPUSER_VERIFY_SMS_SUBPATH = '/verify_sms'
1119
20+ const setAccessToken = createAction ( 'SET_ACCESS_TOKEN' )
1221const setCurrentUser = createAction ( 'SET_CURRENT_USER' )
1322const setCurrentUserMonitoredTrips = createAction ( 'SET_CURRENT_USER_MONITORED_TRIPS' )
1423const 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+ }
0 commit comments