diff --git a/lib/actions/user.js b/lib/actions/user.js index ba265128c..e575adb05 100644 --- a/lib/actions/user.js +++ b/lib/actions/user.js @@ -5,14 +5,17 @@ import { secureFetch } from '../util/middleware' import { isNewUser } from '../util/user' // Middleware API paths. -const API_MONITORTRIP_PATH = '/api/secure/monitoredtrip' +const API_ITINERARY_CHECK_PATH = '/api/secure/itinerarycheck' +const API_MONITORED_TRIP_PATH = '/api/secure/monitoredtrip' const API_OTPUSER_PATH = '/api/secure/user' -const API_OTPUSER_VERIFYSMS_PATH = '/verify_sms' +const API_OTPUSER_VERIFY_SMS_PATH = '/verify_sms' const setCurrentUser = createAction('SET_CURRENT_USER') const setCurrentUserMonitoredTrips = createAction('SET_CURRENT_USER_MONITORED_TRIPS') const setLastPhoneSmsRequest = createAction('SET_LAST_PHONE_SMS_REQUEST') export const setPathBeforeSignIn = createAction('SET_PATH_BEFORE_SIGNIN') +export const clearItineraryAvailability = createAction('CLEAR_ITINERARY_AVAILABILITY') +const setItineraryAvailability = createAction('SET_ITINERARY_AVAILABILITY') function createNewUser (auth0User) { return { @@ -160,7 +163,7 @@ export function createOrUpdateUser (userData, silentOnSuccess = false) { export function fetchUserMonitoredTrips () { return async function (dispatch, getState) { const { accessToken, apiBaseUrl, apiKey } = getMiddlewareVariables(getState()) - const requestUrl = `${apiBaseUrl}${API_MONITORTRIP_PATH}` + const requestUrl = `${apiBaseUrl}${API_MONITORED_TRIP_PATH}` const { data: trips, status } = await secureFetch(requestUrl, accessToken, apiKey, 'GET') if (status === 'success') { @@ -182,10 +185,10 @@ export function createOrUpdateUserMonitoredTrip (tripData, isNew, silentOnSucces // Determine URL and method to use. if (isNew) { - requestUrl = `${apiBaseUrl}${API_MONITORTRIP_PATH}` + requestUrl = `${apiBaseUrl}${API_MONITORED_TRIP_PATH}` method = 'POST' } else { - requestUrl = `${apiBaseUrl}${API_MONITORTRIP_PATH}/${id}` + requestUrl = `${apiBaseUrl}${API_MONITORED_TRIP_PATH}/${id}` method = 'PUT' } @@ -217,7 +220,7 @@ export function createOrUpdateUserMonitoredTrip (tripData, isNew, silentOnSucces export function deleteUserMonitoredTrip (tripId) { return async function (dispatch, getState) { const { accessToken, apiBaseUrl, apiKey } = getMiddlewareVariables(getState()) - const requestUrl = `${apiBaseUrl}${API_MONITORTRIP_PATH}/${tripId}` + const requestUrl = `${apiBaseUrl}${API_MONITORED_TRIP_PATH}/${tripId}` const { message, status } = await secureFetch(requestUrl, accessToken, apiKey, 'DELETE') if (status === 'success') { @@ -243,7 +246,7 @@ export function requestPhoneVerificationSms (newPhoneNumber) { // TODO: Should throttling be handled in the middleware? if (number !== newPhoneNumber || (now - timestamp) >= 60000) { const { accessToken, apiBaseUrl, apiKey, loggedInUser } = getMiddlewareVariables(state) - const requestUrl = `${apiBaseUrl}${API_OTPUSER_PATH}/${loggedInUser.id}${API_OTPUSER_VERIFYSMS_PATH}/${encodeURIComponent(newPhoneNumber)}` + const requestUrl = `${apiBaseUrl}${API_OTPUSER_PATH}/${loggedInUser.id}${API_OTPUSER_VERIFY_SMS_PATH}/${encodeURIComponent(newPhoneNumber)}` const { message, status } = await secureFetch(requestUrl, accessToken, apiKey, 'GET') @@ -273,7 +276,7 @@ export function requestPhoneVerificationSms (newPhoneNumber) { export function verifyPhoneNumber (code) { return async function (dispatch, getState) { const { accessToken, apiBaseUrl, apiKey, loggedInUser } = getMiddlewareVariables(getState()) - const requestUrl = `${apiBaseUrl}${API_OTPUSER_PATH}/${loggedInUser.id}${API_OTPUSER_VERIFYSMS_PATH}/${code}` + const requestUrl = `${apiBaseUrl}${API_OTPUSER_PATH}/${loggedInUser.id}${API_OTPUSER_VERIFY_SMS_PATH}/${code}` const { data, status } = await secureFetch(requestUrl, accessToken, apiKey, 'POST') @@ -293,13 +296,26 @@ export function verifyPhoneNumber (code) { } } } -/* -export async function checkItinerary (middlewareConfig, token, monitoredTrip) { - const { apiBaseUrl, apiKey } = middlewareConfig - const requestUrl = `${apiBaseUrl}${API_ITINERARYCHECK_PATH}` - - return secureFetch(requestUrl, token, apiKey, 'POST', { - body: JSON.stringify(monitoredTrip) - }) + +/** + * Check itinerary availability (existence) for the given monitored trip. + */ +export function checkItineraryAvailability (trip) { + return async function (dispatch, getState) { + const { accessToken, apiBaseUrl, apiKey } = getMiddlewareVariables(getState()) + const requestUrl = `${apiBaseUrl}${API_ITINERARY_CHECK_PATH}` + + // Empty state before performing the checks. + dispatch(clearItineraryAvailability()) + + const { data, status } = await secureFetch(requestUrl, accessToken, apiKey, 'POST', { + body: JSON.stringify(trip) + }) + + if (status === 'success' && data) { + dispatch(setItineraryAvailability(data)) + } else { + alert('Error checking the availability of your selected trip.') + } + } } -*/ diff --git a/lib/components/user/trip-basics-pane.js b/lib/components/user/trip-basics-pane.js index c584dca19..ca2d42519 100644 --- a/lib/components/user/trip-basics-pane.js +++ b/lib/components/user/trip-basics-pane.js @@ -11,7 +11,7 @@ import { import { connect } from 'react-redux' import styled from 'styled-components' -import { checkItinerary } from '../../util/middleware' +import * as userActions from '../../actions/user' import { ALL_DAYS } from '../../util/monitored-trip' import TripSummary from './trip-summary' @@ -46,45 +46,47 @@ const allDays = [ { name: 'sunday', text: 'Sun.', fullText: 'Sundays' } ] +/** + * @returns true if there is a trip matching for the specified availability/existence check. + */ +function isDayAvailable (dayAvailability) { + return dayAvailability && dayAvailability.isValid +} + /** * This component shows summary information for a trip * and lets the user edit the trip name and day. */ class TripBasicsPane extends Component { - constructor () { - super() - - this.state = {} + componentDidMount () { + // Check itinerary availability (existence) for all days. + const { checkItineraryAvailability, values: monitoredTrip } = this.props + checkItineraryAvailability(monitoredTrip) } - async componentDidMount () { - // Check itinerary existence for all days. - const { accessToken, config, isCreating, setFieldValue, values } = this.props - - const itineraryCheckResult = await checkItinerary( - config.persistence.otp_middleware, - accessToken, - values - ) + componentDidUpdate (prevProps) { + const { isCreating, itineraryAvailability, setFieldValue } = this.props - this.setState({ itineraryCheckResult }) - - // For new trips only, - // update the Formik state to uncheck days for which the itinerary is not available. - if (isCreating && itineraryCheckResult && itineraryCheckResult.status === 'success') { - ALL_DAYS.forEach(day => { - const dayAvailability = itineraryCheckResult.data[day] - if (!dayAvailability || !dayAvailability.isValid) { - setFieldValue(day, false) - } - }) + if (itineraryAvailability !== prevProps.itineraryAvailability) { + // For new trips only, + // update the Formik state to uncheck days for which the itinerary is not available. + if (isCreating && itineraryAvailability) { + ALL_DAYS.forEach(day => { + if (!isDayAvailable(itineraryAvailability[day])) { + setFieldValue(day, false) + } + }) + } } } + componentWillUnmount () { + this.props.clearItineraryAvailability() + } + render () { - const { errors, isCreating, touched, values: monitoredTrip } = this.props + const { errors, isCreating, itineraryAvailability, touched, values: monitoredTrip } = this.props const { itinerary } = monitoredTrip - const { itineraryCheckResult } = this.state if (!itinerary) { return
No itinerary to display.
@@ -123,11 +125,7 @@ class TripBasicsPane extends Component { What days do you take this trip?
{allDays.map(({ name, fullText, text }, index) => { - const isDayDisabled = - itineraryCheckResult && - itineraryCheckResult.status === 'success' && - (!itineraryCheckResult.data[name] || !itineraryCheckResult.data[name].isValid) - + const isDayDisabled = itineraryAvailability && !isDayAvailable(itineraryAvailability[name]) const boxClass = isDayDisabled ? 'alert-danger' : (monitoredTrip[name] ? 'bg-primary' : '') const notAvailableText = isDayDisabled ? `Trip not available on ${fullText}` : null @@ -146,7 +144,7 @@ class TripBasicsPane extends Component {
- {itineraryCheckResult + {itineraryAvailability ? ( <>Your trip is available on the days of the week as indicated above. ) : ( @@ -164,13 +162,17 @@ class TripBasicsPane extends Component { // Connect to redux store const mapStateToProps = (state, ownProps) => { + const { accessToken, itineraryAvailability } = state.user return { + accessToken, config: state.otp.config, - accessToken: state.user.accessToken + itineraryAvailability } } const mapDispatchToProps = { + checkItineraryAvailability: userActions.checkItineraryAvailability, + clearItineraryAvailability: userActions.clearItineraryAvailability } export default connect(mapStateToProps, mapDispatchToProps)(TripBasicsPane) diff --git a/lib/reducers/create-user-reducer.js b/lib/reducers/create-user-reducer.js index eb5a6b430..b02021f80 100644 --- a/lib/reducers/create-user-reducer.js +++ b/lib/reducers/create-user-reducer.js @@ -4,6 +4,7 @@ import update from 'immutability-helper' function createUserReducer () { const initialState = { accessToken: null, + itineraryAvailability: null, lastPhoneSmsRequest: { number: null, status: null, @@ -41,6 +42,18 @@ function createUserReducer () { }) } + case 'SET_ITINERARY_AVAILABILITY': { + return update(state, { + itineraryAvailability: { $set: action.payload } + }) + } + + case 'CLEAR_ITINERARY_AVAILABILITY': { + return update(state, { + itineraryAvailability: { $set: null } + }) + } + default: return state }