diff --git a/lib/actions/user.js b/lib/actions/user.js index aa8e8d095..83d54036c 100644 --- a/lib/actions/user.js +++ b/lib/actions/user.js @@ -5,14 +5,16 @@ import { secureFetch } from '../util/middleware' import { isNewUser } from '../util/user' // Middleware API paths. -const API_MONITORTRIP_PATH = '/api/secure/monitoredtrip' +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_SUBPATH = '/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 clearItineraryExistence = createAction('CLEAR_ITINERARY_EXISTENCE') +const setitineraryExistence = createAction('SET_ITINERARY_EXISTENCE') function createNewUser (auth0User) { return { @@ -160,7 +162,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 +184,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 +219,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 +245,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_SUBPATH}/${encodeURIComponent(newPhoneNumber)}` const { message, status } = await secureFetch(requestUrl, accessToken, apiKey, 'GET') @@ -273,7 +275,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_SUBPATH}/${code}` const { data, status } = await secureFetch(requestUrl, accessToken, apiKey, 'POST') @@ -293,3 +295,26 @@ export function verifyPhoneNumber (code) { } } } + +/** + * Check itinerary existence for the given monitored trip. + */ +export function checkItineraryExistence (trip) { + return async function (dispatch, getState) { + const { accessToken, apiBaseUrl, apiKey } = getMiddlewareVariables(getState()) + const requestUrl = `${apiBaseUrl}${API_MONITORED_TRIP_PATH}/checkitinerary` + + // Empty state before performing the checks. + dispatch(clearItineraryExistence()) + + const { data, status } = await secureFetch(requestUrl, accessToken, apiKey, 'POST', { + body: JSON.stringify(trip) + }) + + if (status === 'success' && data) { + dispatch(setitineraryExistence(data)) + } else { + alert('Error checking whether your selected trip is possible.') + } + } +} diff --git a/lib/components/narrative/narrative-itineraries.js b/lib/components/narrative/narrative-itineraries.js index 93b245fa6..56c7f9ee0 100644 --- a/lib/components/narrative/narrative-itineraries.js +++ b/lib/components/narrative/narrative-itineraries.js @@ -38,7 +38,7 @@ class NarrativeItineraries extends Component { static propTypes = { itineraries: PropTypes.array, itineraryClass: PropTypes.func, - pending: PropTypes.number, + pending: PropTypes.bool, activeItinerary: PropTypes.number, setActiveItinerary: PropTypes.func, setActiveLeg: PropTypes.func, diff --git a/lib/components/user/trip-basics-pane.js b/lib/components/user/trip-basics-pane.js index 5b1bbaa25..928f7b697 100644 --- a/lib/components/user/trip-basics-pane.js +++ b/lib/components/user/trip-basics-pane.js @@ -1,27 +1,32 @@ import { Field } from 'formik' -import React from 'react' +import React, { Component } from 'react' import { ControlLabel, FormControl, FormGroup, - HelpBlock + Glyphicon, + HelpBlock, + ProgressBar } from 'react-bootstrap' +import { connect } from 'react-redux' import styled from 'styled-components' -import { ALL_DAYS } from '../../util/monitored-trip' +import * as userActions from '../../actions/user' import TripSummary from './trip-summary' // Styles. -const StyledLabel = styled.label` +const TripDayLabel = styled.label` border: 1px solid #ccc; border-left: none; box-sizing: border-box; display: inline-block; font-weight: inherit; + float: left; + height: 3em; max-width: 150px; min-width: 14.28%; text-align: center; - & > span { + & > span:first-of-type { display: block; width: 100%; } @@ -31,73 +36,133 @@ const StyledLabel = styled.label` ` const allDays = [ - { name: 'monday', text: 'Mon.' }, - { name: 'tuesday', text: 'Tue.' }, - { name: 'wednesday', text: 'Wed.' }, - { name: 'thursday', text: 'Thu.' }, - { name: 'friday', text: 'Fri.' }, - { name: 'saturday', text: 'Sat.' }, - { name: 'sunday', text: 'Sun.' } + { name: 'monday', text: 'Mon.', fullText: 'Mondays' }, + { name: 'tuesday', text: 'Tue.', fullText: 'Tuesdays' }, + { name: 'wednesday', text: 'Wed.', fullText: 'Wednesdays' }, + { name: 'thursday', text: 'Thu.', fullText: 'Thursdays' }, + { name: 'friday', text: 'Fri.', fullText: 'Fridays' }, + { name: 'saturday', text: 'Sat.', fullText: 'Saturdays' }, + { name: 'sunday', text: 'Sun.', fullText: 'Sundays' } ] /** * This component shows summary information for a trip * and lets the user edit the trip name and day. */ -const TripBasicsPane = ({ errors, touched, values: monitoredTrip }) => { - const { itinerary } = monitoredTrip - - if (!itinerary) { - return