Skip to content

Commit f6994a9

Browse files
authored
Merge pull request #213 from opentripplanner/commtrans-accessibility-release
Release: Accessibility, Itinerary Body Time Format (and add saved trips)
2 parents 96c689d + 72875cb commit f6994a9

28 files changed

+1009
-69
lines changed

Diff for: example.js

+13-7
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,10 @@ class OtpRRExample extends Component {
8585
<Grid>
8686
<Row className='main-row'>
8787
<Col sm={6} md={4} className='sidebar'>
88-
<DefaultMainPanel LegIcon={MyLegIcon} ModeIcon={MyModeIcon} />
88+
{/* <main> Needed for accessibility checks. TODO: Find a better place. */}
89+
<main>
90+
<DefaultMainPanel LegIcon={MyLegIcon} ModeIcon={MyModeIcon} />
91+
</main>
8992
</Col>
9093
<Col sm={6} md={8} className='map-container'>
9194
<Map />
@@ -97,12 +100,15 @@ class OtpRRExample extends Component {
97100

98101
/** mobile view **/
99102
const mobileView = (
100-
<MobileMain
101-
LegIcon={MyLegIcon}
102-
ModeIcon={MyModeIcon}
103-
map={<Map />}
104-
title={<div className='navbar-title'>OpenTripPlanner</div>}
105-
/>
103+
// <main> Needed for accessibility checks. TODO: Find a better place.
104+
<main>
105+
<MobileMain
106+
LegIcon={MyLegIcon}
107+
ModeIcon={MyModeIcon}
108+
map={<Map />}
109+
title={<div className='navbar-title'>OpenTripPlanner</div>}
110+
/>
111+
</main>
106112
)
107113

108114
/** the main webapp **/

Diff for: lib/actions/user.js

+96-18
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
11
import { createAction } from 'redux-actions'
22

3-
import { addUser, fetchUser, updateUser } from '../util/middleware'
3+
import {
4+
addTrip,
5+
addUser,
6+
deleteTrip,
7+
fetchUser,
8+
getTrips,
9+
updateTrip,
10+
updateUser
11+
} from '../util/middleware'
412
import { isNewUser } from '../util/user'
513

614
const setCurrentUser = createAction('SET_CURRENT_USER')
15+
const setCurrentUserMonitoredTrips = createAction('SET_CURRENT_USER_MONITORED_TRIPS')
716
export const setPathBeforeSignIn = createAction('SET_PATH_BEFORE_SIGNIN')
817

9-
function getStateForNewUser (auth0User) {
18+
function createNewUser (auth0User) {
1019
return {
1120
auth0UserId: auth0User.sub,
1221
email: auth0User.email,
@@ -18,19 +27,36 @@ function getStateForNewUser (auth0User) {
1827
}
1928
}
2029

30+
/**
31+
* Fetches the saved/monitored trips for a user.
32+
* We use the accessToken to fetch the data regardless of
33+
* whether the process to populate state.user is completed or not.
34+
*/
35+
export function fetchUserMonitoredTrips (accessToken) {
36+
return async function (dispatch, getState) {
37+
const { otp } = getState()
38+
const { otp_middleware: otpMiddleware = null } = otp.config.persistence
39+
40+
if (otpMiddleware) {
41+
const { data: trips, status: fetchStatus } = await getTrips(otpMiddleware, accessToken)
42+
if (fetchStatus === 'success') {
43+
dispatch(setCurrentUserMonitoredTrips(trips))
44+
}
45+
}
46+
}
47+
}
48+
2149
/**
2250
* Fetches user preferences to state.user, or set initial values under state.user if no user has been loaded.
2351
*/
2452
export function fetchOrInitializeUser (auth) {
2553
return async function (dispatch, getState) {
2654
const { otp } = getState()
27-
const { accessToken, user } = auth
55+
const { otp_middleware: otpMiddleware = null } = otp.config.persistence
2856

29-
try {
30-
const result = await fetchUser(
31-
otp.config.persistence.otp_middleware,
32-
accessToken
33-
)
57+
if (otpMiddleware) {
58+
const { accessToken, user: authUser } = auth
59+
const { data: user, status: fetchUserStatus } = await fetchUser(otpMiddleware, accessToken)
3460

3561
// Beware! On AWS API gateway, if a user is not found in the middleware
3662
// (e.g. they just created their Auth0 password but have not completed the account setup form yet),
@@ -53,19 +79,15 @@ export function fetchOrInitializeUser (auth) {
5379
// }
5480
// TODO: Improve AWS response.
5581

56-
const resultData = result.data
57-
const isNewAccount = result.status === 'error' || (resultData && resultData.result === 'ERR')
58-
82+
const isNewAccount = fetchUserStatus === 'error' || (user && user.result === 'ERR')
5983
if (!isNewAccount) {
60-
// TODO: Move next line somewhere else.
61-
if (resultData.savedLocations === null) resultData.savedLocations = []
62-
dispatch(setCurrentUser({ accessToken, user: resultData }))
84+
// Load user's monitored trips before setting the user state.
85+
await dispatch(fetchUserMonitoredTrips(accessToken))
86+
87+
dispatch(setCurrentUser({ accessToken, user }))
6388
} else {
64-
dispatch(setCurrentUser({ accessToken, user: getStateForNewUser(user) }))
89+
dispatch(setCurrentUser({ accessToken, user: createNewUser(authUser) }))
6590
}
66-
} catch (error) {
67-
// TODO: improve error handling.
68-
alert(`An error was encountered:\n${error}`)
6991
}
7092
}
7193
}
@@ -91,15 +113,71 @@ export function createOrUpdateUser (userData) {
91113

92114
// TODO: improve the UI feedback messages for this.
93115
if (result.status === 'success' && result.data) {
116+
alert('Your preferences have been saved.')
117+
94118
// Update application state with the user entry as saved
95119
// (as returned) by the middleware.
96120
const userData = result.data
97121
dispatch(setCurrentUser({ accessToken, user: userData }))
122+
} else {
123+
alert(`An error was encountered:\n${JSON.stringify(result)}`)
124+
}
125+
}
126+
}
127+
}
98128

129+
/**
130+
* Updates a logged-in user's monitored trip,
131+
* then, if that was successful, refreshes the redux monitoredTrips
132+
* with the updated trip.
133+
*/
134+
export function createOrUpdateUserMonitoredTrip (tripData, isNew) {
135+
return async function (dispatch, getState) {
136+
const { otp, user } = getState()
137+
const { otp_middleware: otpMiddleware = null } = otp.config.persistence
138+
139+
if (otpMiddleware) {
140+
const { accessToken } = user
141+
142+
let result
143+
if (isNew) {
144+
result = await addTrip(otpMiddleware, accessToken, tripData)
145+
} else {
146+
result = await updateTrip(otpMiddleware, accessToken, tripData)
147+
}
148+
149+
// TODO: improve the UI feedback messages for this.
150+
if (result.status === 'success' && result.data) {
99151
alert('Your preferences have been saved.')
152+
153+
// Reload user's monitored trips after add/update.
154+
await dispatch(fetchUserMonitoredTrips(accessToken))
100155
} else {
101156
alert(`An error was encountered:\n${JSON.stringify(result)}`)
102157
}
103158
}
104159
}
105160
}
161+
162+
/**
163+
* Deletes a logged-in user's monitored trip,
164+
* then, if that was successful, refreshes the redux monitoredTrips state.
165+
*/
166+
export function deleteUserMonitoredTrip (id) {
167+
return async function (dispatch, getState) {
168+
const { otp, user } = getState()
169+
const { otp_middleware: otpMiddleware = null } = otp.config.persistence
170+
171+
if (otpMiddleware) {
172+
const { accessToken } = user
173+
const deleteResult = await deleteTrip(otpMiddleware, accessToken, id)
174+
175+
if (deleteResult.status === 'success') {
176+
// Reload user's monitored trips after deletion.
177+
await dispatch(fetchUserMonitoredTrips(accessToken))
178+
} else {
179+
alert(`An error was encountered:\n${JSON.stringify(deleteResult)}`)
180+
}
181+
}
182+
}
183+
}

Diff for: lib/components/app/app-menu.js

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class AppMenu extends Component {
3333
return (
3434
<div className='app-menu'>
3535
<DropdownButton
36+
aria-label='Application Menu'
3637
title={(<Icon type='bars' />)}
3738
noCaret
3839
className='app-menu-button'

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

+20
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import { AUTH0_AUDIENCE, AUTH0_SCOPE, URL_ROOT } from '../../util/constants'
2020
import { getActiveItinerary, getTitle } from '../../util/state'
2121
import AfterSignInScreen from '../user/after-signin-screen'
2222
import BeforeSignInScreen from '../user/before-signin-screen'
23+
import SavedTripList from '../user/saved-trip-list'
24+
import SavedTripScreen from '../user/saved-trip-screen'
2325
import UserAccountScreen from '../user/user-account-screen'
2426
import withLoggedInUserSupport from '../user/with-logged-in-user-support'
2527

@@ -233,6 +235,24 @@ class RouterWrapperWithAuth0 extends Component {
233235
return <UserAccountScreen {...props} />
234236
}}
235237
/>
238+
<Route
239+
path={'/savetrip'}
240+
component={(routerProps) => {
241+
const props = this._combineProps(routerProps)
242+
return <SavedTripScreen isCreating {...props} />
243+
}}
244+
/>
245+
<Route
246+
path={'/savedtrips/:id'}
247+
component={(routerProps) => {
248+
const props = this._combineProps(routerProps)
249+
return <SavedTripScreen {...props} />
250+
}}
251+
/>
252+
<Route
253+
path={'/savedtrips'}
254+
component={SavedTripList}
255+
/>
236256
<Route
237257
// This route is called immediately after login by Auth0
238258
// and by the onRedirectCallback function from /lib/util/auth.js.

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

+4-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,10 @@ class DateTimePreview extends Component {
6565

6666
const button = (
6767
<div className='button-container'>
68-
<Button onClick={this.props.onClick}>
68+
<Button
69+
aria-label='Edit departure or arrival time'
70+
onClick={this.props.onClick}
71+
>
6972
{editButtonText}{caret && <span> <i className={`fa fa-caret-${caret}`} /></span>}
7073
</Button>
7174
</div>

Diff for: lib/components/form/settings-preview.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,10 @@ class SettingsPreview extends Component {
3535
let showDot = coreUtils.query.isNotDefaultQuery(query, config)
3636
const button = (
3737
<div className='button-container'>
38-
<Button onClick={this.props.onClick}>
38+
<Button
39+
aria-label={messages.label.replace('\n', ' ')}
40+
onClick={this.props.onClick}
41+
>
3942
{editButtonText}{caret && <span> <i className={`fa fa-caret-${caret}`} /></span>}
4043
</Button>
4144
{showDot && <div className='dot' />}

Diff for: lib/components/narrative/default/default-itinerary.js

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import ItinerarySummary from './itinerary-summary'
66
import ItineraryDetails from './itinerary-details'
77
import TripDetails from '../connected-trip-details'
88
import TripTools from '../trip-tools'
9+
import LinkButton from '../../user/link-button'
910

1011
const { formatDuration, formatTime } = coreUtils.time
1112

@@ -31,6 +32,7 @@ export default class DefaultItinerary extends NarrativeItinerary {
3132
<span className='title'>Itinerary {index + 1}</span>{' '}
3233
<span className='duration pull-right'>{formatDuration(itinerary.duration)}</span>{' '}
3334
<span className='arrivalTime'>{formatTime(itinerary.startTime)}{formatTime(itinerary.endTime)}</span>
35+
<span className='pull-right'><LinkButton to='/savetrip'>Save</LinkButton></span>{' '}
3436
<ItinerarySummary itinerary={itinerary} LegIcon={LegIcon} />
3537
</button>
3638
{(active || expanded) &&

Diff for: lib/components/narrative/line-itin/connected-itinerary-body.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ class ConnectedItineraryBody extends Component {
4141
LegIcon,
4242
setActiveLeg,
4343
setViewedTrip,
44-
showLegDiagram
44+
showLegDiagram,
45+
timeOptions
4546
} = this.props
4647

4748
return (
@@ -62,6 +63,7 @@ class ConnectedItineraryBody extends Component {
6263
showLegIcon
6364
showMapButtonColumn={false}
6465
showViewTripButton
66+
timeOptions={timeOptions}
6567
toRouteAbbreviation={noop}
6668
TransitLegSubheader={TransitLegSubheader}
6769
TransitLegSummary={TransitLegSummary}

Diff for: lib/components/narrative/line-itin/line-itinerary.js

+7-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import ItineraryBody from './connected-itinerary-body'
66
import ItinerarySummary from './itin-summary'
77
import NarrativeItinerary from '../narrative-itinerary'
88
import SimpleRealtimeAnnotation from '../simple-realtime-annotation'
9+
import LinkButton from '../../user/link-button'
910

1011
const { getLegModeLabel, getTimeZoneOffset, isTransit } = coreUtils.itinerary
1112

@@ -50,9 +51,9 @@ export default class LineItinerary extends NarrativeItinerary {
5051
itinerary,
5152
itineraryFooter,
5253
LegIcon,
54+
onClick,
5355
setActiveLeg,
5456
showRealtimeAnnotation,
55-
onClick,
5657
timeFormat
5758
} = this.props
5859

@@ -71,9 +72,12 @@ export default class LineItinerary extends NarrativeItinerary {
7172
companies={companies}
7273
itinerary={itinerary}
7374
LegIcon={LegIcon}
74-
timeOptions={timeOptions}
7575
onClick={onClick}
76+
timeOptions={timeOptions}
7677
/>
78+
79+
<span className='pull-right'><LinkButton to='/savetrip'>Save this option</LinkButton></span>
80+
7781
{showRealtimeAnnotation && <SimpleRealtimeAnnotation />}
7882
{active || expanded
7983
? <ItineraryBody
@@ -83,6 +87,7 @@ export default class LineItinerary extends NarrativeItinerary {
8387
// (will cause error when clicking on itinerary suymmary).
8488
// Use the one passed by NarrativeItineraries instead.
8589
setActiveLeg={setActiveLeg}
90+
timeOptions={timeOptions}
8691
/>
8792
: null}
8893
{itineraryFooter}

0 commit comments

Comments
 (0)