Skip to content

Commit ce2e609

Browse files
authored
Merge pull request #316 from opentripplanner/refactor-account-pages-qbd
Fixes to Save Trip routing, check terms
2 parents 905920c + 032070e commit ce2e609

File tree

8 files changed

+122
-106
lines changed

8 files changed

+122
-106
lines changed

Diff for: lib/actions/user.js

+25-11
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const API_MONITORED_TRIP_PATH = '/api/secure/monitoredtrip'
1717
const API_OTPUSER_PATH = '/api/secure/user'
1818
const API_OTPUSER_VERIFY_SMS_SUBPATH = '/verify_sms'
1919

20+
const setAccessToken = createAction('SET_ACCESS_TOKEN')
2021
const setCurrentUser = createAction('SET_CURRENT_USER')
2122
const setCurrentUserMonitoredTrips = createAction('SET_CURRENT_USER_MONITORED_TRIPS')
2223
const setLastPhoneSmsRequest = createAction('SET_LAST_PHONE_SMS_REQUEST')
@@ -68,14 +69,30 @@ export function fetchAuth0Token (auth0) {
6869
try {
6970
const accessToken = await auth0.getAccessTokenSilently()
7071

71-
dispatch(setCurrentUser({ accessToken }))
72+
dispatch(setAccessToken(accessToken))
7273
} catch (error) {
7374
// TODO: improve UI if there is an error.
7475
alert('Error obtaining an authorization token.')
7576
}
7677
}
7778
}
7879

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+
7996
/**
8097
* Attempts to fetch user preferences (or set initial values if the user is being created)
8198
* into the redux state, under state.user.
@@ -111,11 +128,9 @@ export function fetchOrInitializeUser (auth0User) {
111128

112129
const isNewAccount = status === 'error' || (user && user.result === 'ERR')
113130
const userData = isNewAccount ? createNewUser(auth0User) : user
114-
dispatch(setCurrentUser({ accessToken, user: userData }))
115-
// Also load monitored trips for existing users.
116-
if (!isNewAccount) {
117-
dispatch(fetchMonitoredTrips())
118-
}
131+
132+
// Set uset in redux state. (Fetch trips for existing users.)
133+
dispatch(setUser(userData, !isNewAccount))
119134
}
120135
}
121136

@@ -132,7 +147,8 @@ export function createOrUpdateUser (userData, silentOnSuccess = false) {
132147
let requestUrl, method
133148

134149
// Determine URL and method to use.
135-
if (isNewUser(loggedInUser)) {
150+
const isCreatingUser = isNewUser(loggedInUser)
151+
if (isCreatingUser) {
136152
requestUrl = `${apiBaseUrl}${API_OTPUSER_PATH}`
137153
method = 'POST'
138154
} else {
@@ -151,10 +167,8 @@ export function createOrUpdateUser (userData, silentOnSuccess = false) {
151167
}
152168

153169
// Update application state with the user entry as saved
154-
// (as returned) by the middleware.
155-
dispatch(setCurrentUser({ accessToken, user: data }))
156-
// Re-fetch user's monitored trips.
157-
dispatch(fetchMonitoredTrips())
170+
// (as returned) by the middleware. (Fetch trips if creating user.)
171+
dispatch(setUser(data, isCreatingUser))
158172
} else {
159173
alert(`An error was encountered:\n${JSON.stringify(message)}`)
160174
}

Diff for: lib/components/narrative/save-trip-button.js

+28-20
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import React from 'react'
22
import { OverlayTrigger, Tooltip } from 'react-bootstrap'
33
import { connect } from 'react-redux'
4+
import { LinkContainer } from 'react-router-bootstrap'
45

5-
import LinkButton from '../user/link-button'
6+
import { CREATE_TRIP_PATH } from '../../util/constants'
67
import { itineraryCanBeMonitored } from '../../util/itinerary'
78
import { getActiveItinerary } from '../../util/state'
89

@@ -13,7 +14,8 @@ import { getActiveItinerary } from '../../util/state'
1314
const SaveTripButton = ({
1415
itinerary,
1516
loggedInUser,
16-
persistence
17+
persistence,
18+
queryParams
1719
}) => {
1820
// We are dealing with the following states:
1921
// 1. Persistence disabled => just return null
@@ -41,30 +43,35 @@ const SaveTripButton = ({
4143
buttonText = 'Save trip'
4244
icon = 'fa fa-plus-circle'
4345
}
44-
46+
const button = (
47+
<button
48+
// Apply pull-right style class so that the element is flush right,
49+
// even with LineItineraries.
50+
className='clear-button-formatting pull-right'
51+
disabled={buttonDisabled}
52+
style={buttonDisabled ? { pointerEvents: 'none' } : {}}
53+
>
54+
<i className={icon} /> {buttonText}
55+
</button>
56+
)
57+
// Show tooltip with help text if button is disabled.
4558
if (buttonDisabled) {
46-
const tooltip = <Tooltip id='disabled-save-tooltip'>{tooltipText}</Tooltip>
47-
4859
return (
49-
// Apply disabled bootstrap button styles to a non-input element
50-
// to keep Tooltip and OverlayTrigger functional.
51-
// Also apply pull-right style class so that the element is flush right, even with LineItineraries.
52-
<OverlayTrigger placement='top' overlay={tooltip}>
53-
<span className='btn disabled clear-button-formatting pull-right'>
54-
<i className={icon} /> {buttonText}
55-
</span>
60+
<OverlayTrigger
61+
overlay={<Tooltip id='disabled-save-tooltip'>{tooltipText}</Tooltip>}
62+
placement='top'
63+
>
64+
<div style={{ cursor: 'not-allowed' }}>
65+
{button}
66+
</div>
5667
</OverlayTrigger>
5768
)
5869
}
5970

6071
return (
61-
<LinkButton
62-
// Also apply pull-right style class so that the element is flush right, even with LineItineraries.
63-
className='clear-button-formatting pull-right'
64-
to='/savetrip'
65-
>
66-
<i className={icon} /> {buttonText}
67-
</LinkButton>
72+
<LinkContainer to={{ pathname: CREATE_TRIP_PATH, search: queryParams }}>
73+
{button}
74+
</LinkContainer>
6875
)
6976
}
7077

@@ -75,7 +82,8 @@ const mapStateToProps = (state, ownProps) => {
7582
return {
7683
itinerary: getActiveItinerary(state.otp),
7784
loggedInUser: state.user.loggedInUser,
78-
persistence
85+
persistence,
86+
queryParams: state.router.location.search
7987
}
8088
}
8189

Diff for: lib/components/user/account-page.js

+58-16
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,68 @@
1-
import React from 'react'
1+
import { withAuthenticationRequired } from '@auth0/auth0-react'
2+
import React, { Component } from 'react'
23
import { Col, Row } from 'react-bootstrap'
4+
import { connect } from 'react-redux'
35

6+
import * as uiActions from '../../actions/ui'
7+
import { CREATE_ACCOUNT_PATH } from '../../util/constants'
8+
import { RETURN_TO_CURRENT_ROUTE } from '../../util/ui'
9+
import withLoggedInUserSupport from './with-logged-in-user-support'
410
import DesktopNav from '../app/desktop-nav'
511
import SubNav from './sub-nav'
612

713
/**
814
* This component contains common navigation elements and wrappers and should
915
* wrap any user account page (e.g., SavedTripList or account settings).
1016
*/
11-
const AccountPage = ({children, subnav = true}) => (
12-
<div className='otp'>
13-
{/* TODO: Do mobile view. */}
14-
<DesktopNav />
15-
{subnav && <SubNav />}
16-
<div className='container'>
17-
<Row xs={12}>
18-
<Col xs={12} sm={10} smOffset={1} md={8} mdOffset={2}>
19-
{children}
20-
</Col>
21-
</Row>
22-
</div>
23-
</div>
24-
)
17+
class AccountPage extends Component {
18+
componentDidMount () {
19+
const { loggedInUser, routeTo } = this.props
20+
21+
if (!loggedInUser.hasConsentedToTerms) {
22+
// If a user signed up in Auth0 and did not complete the New Account wizard
23+
// make the user finish set up their accounts first.
24+
// monitoredTrips should not be null otherwise.
25+
// NOTE: This check applies to any route that makes use of this component.
26+
routeTo(CREATE_ACCOUNT_PATH)
27+
}
28+
}
29+
30+
render () {
31+
const {children, subnav = true} = this.props
32+
return (
33+
<div className='otp'>
34+
{/* TODO: Do mobile view. */}
35+
<DesktopNav />
36+
{subnav && <SubNav />}
37+
<div className='container'>
38+
<Row xs={12}>
39+
<Col xs={12} sm={10} smOffset={1} md={8} mdOffset={2}>
40+
{children}
41+
</Col>
42+
</Row>
43+
</div>
44+
</div>
45+
)
46+
}
47+
}
48+
49+
// connect to the redux store
2550

26-
export default AccountPage
51+
const mapStateToProps = (state, ownProps) => {
52+
return {
53+
loggedInUser: state.user.loggedInUser,
54+
trips: state.user.loggedInUserMonitoredTrips
55+
}
56+
}
57+
58+
const mapDispatchToProps = {
59+
routeTo: uiActions.routeTo
60+
}
61+
62+
export default withLoggedInUserSupport(
63+
withAuthenticationRequired(
64+
connect(mapStateToProps, mapDispatchToProps)(AccountPage),
65+
RETURN_TO_CURRENT_ROUTE
66+
),
67+
true
68+
)

Diff for: lib/components/user/link-button.js

-53
This file was deleted.

Diff for: lib/components/user/monitored-trip/saved-trip-list.js

+1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ class SavedTripList extends Component {
5858

5959
const mapStateToProps = (state, ownProps) => {
6060
return {
61+
loggedInUser: state.user.loggedInUser,
6162
trips: state.user.loggedInUserMonitoredTrips
6263
}
6364
}

Diff for: lib/components/user/monitored-trip/saved-trip-screen.js

+3-4
Original file line numberDiff line numberDiff line change
@@ -101,12 +101,11 @@ class SavedTripScreen extends Component {
101101
summary: TripSummaryPane
102102
}
103103

104-
async componentDidMount () {
104+
componentDidMount () {
105105
const { isCreating, monitoredTrips } = this.props
106-
107-
// There is a middleware limit of 5 saved trips,
108-
// so if that limit is already reached, alert, then show editing mode.
109106
if (isCreating && hasMaxTripCount(monitoredTrips)) {
107+
// There is a middleware limit of 5 saved trips,
108+
// so if that limit is already reached, alert, then show editing mode.
110109
alert('You already have reached the maximum of five saved trips.\n' +
111110
'Please remove unused trips from your saved trips, and try again.')
112111

Diff for: lib/reducers/create-user-reducer.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,14 @@ function createUserReducer () {
1717

1818
return (state = initialState, action) => {
1919
switch (action.type) {
20+
case 'SET_ACCESS_TOKEN': {
21+
return update(state, {
22+
accessToken: { $set: action.payload }
23+
})
24+
}
2025
case 'SET_CURRENT_USER': {
2126
return update(state, {
22-
accessToken: { $set: action.payload.accessToken },
23-
loggedInUser: { $set: action.payload.user }
27+
loggedInUser: { $set: action.payload }
2428
})
2529
}
2630

Diff for: lib/util/constants.js

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export const ACCOUNT_PATH = '/account'
88
export const ACCOUNT_SETTINGS_PATH = `${ACCOUNT_PATH}/settings`
99
export const TRIPS_PATH = `${ACCOUNT_PATH}/trips`
1010
export const CREATE_ACCOUNT_PATH = `${ACCOUNT_PATH}/create`
11+
export const CREATE_TRIP_PATH = `${TRIPS_PATH}/new`
1112

1213
// Gets the root URL, e.g. https://otp-instance.example.com:8080, computed once for all.
1314
// TODO: support root URLs that involve paths or subfolders, as in https://otp-ui.example.com/path-to-ui/

0 commit comments

Comments
 (0)