You can receive notifications about trips you frequently take.
-
-
- How would you like to receive notifications?
-
-
- {allowedNotificationChannels.map(({ type, text }, index) => (
-
- {text}
-
- ))}
-
-
-
-
- {notificationChannel === 'email' && (
-
- Notification emails will be sent out to:
-
-
- )}
- {notificationChannel === 'sms' && (
-
- Enter your phone number for SMS notifications:
- {/* TODO: Add field validation. */}
-
-
- )}
-
-
- )
- }
+
+
+ How would you like to receive notifications?
+
+
+ {allowedNotificationChannels.map(({ type, text }, index) => (
+
+ {text}
+
+ ))}
+
+
+
+
+ {notificationChannel === 'email' && (
+
+ Notification emails will be sent out to:
+
+
+ )}
+ {notificationChannel === 'sms' && (
+ // FIXME: Merge the validation feedback upon approving PR #224.
+
+ Enter your phone number for SMS notifications:
+
+
+ {errors.phoneNumber && {errors.phoneNumber}}
+
+ )}
+
+
- You must agree to the terms of service to continue.
-
-
-
- {/* TODO: Implement the link */}
+const TermsOfUsePane = ({
+ disableCheckTerms,
+ handleBlur,
+ handleChange,
+ values: userData
+}) => {
+ const {
+ hasConsentedToTerms,
+ storeTripHistory
+ } = userData
+
+ return (
+
+ You must agree to the terms of service to continue.
+
+
+
+ {/* TODO: Implement the link */}
I have read and consent to the Terms of Service for using the Trip Planner.
-
-
-
-
-
- {/* TODO: Implement the link */}
+
+
+
+
+
+ {/* TODO: Implement the link */}
Optional: I consent to the Trip Planner storing my historical planned trips in order to
improve transit services in my area. More info...
-
-
-
- )
- }
+
+
+
+ )
+}
+
+TermsOfUsePane.propTypes = {
+ disableCheckTerms: PropTypes.bool,
+ handleBlur: PropTypes.func.isRequired,
+ handleChange: PropTypes.func.isRequired,
+ values: PropTypes.object.isRequired
+}
+
+TermsOfUsePane.defaultProps = {
+ disableCheckTerms: false
}
export default TermsOfUsePane
diff --git a/lib/components/user/user-account-screen.js b/lib/components/user/user-account-screen.js
index 0c77fa85d..46e5f51c1 100644
--- a/lib/components/user/user-account-screen.js
+++ b/lib/components/user/user-account-screen.js
@@ -1,7 +1,10 @@
-import clone from 'lodash/cloneDeep'
+import { Formik } from 'formik'
+import clone from 'clone'
import React, { Component } from 'react'
+import { Form } from 'react-bootstrap'
import { connect } from 'react-redux'
import { withLoginRequired } from 'use-auth0-hooks'
+import * as yup from 'yup'
import { routeTo } from '../../actions/ui'
import { createOrUpdateUser } from '../../actions/user'
@@ -17,6 +20,20 @@ import TermsOfUsePane from './terms-of-use-pane'
import VerifyEmailScreen from './verify-email-screen'
import withLoggedInUserSupport from './with-logged-in-user-support'
+// Regex for phone numbers from https://stackoverflow.com/questions/52483260/validate-phone-number-with-yup/53210158#53210158
+// https://www.oreilly.com/library/view/regular-expressions-cookbook/9781449327453/ch04s02.html
+// FIXME: On merging with PR #224, remember to strip the non-numbers out and add +1 if there are only 10 digits.
+const phoneRegExp = /^\(?([0-9]{3})\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$/ // /^((\\+[1-9]{1,4}[ \\-]*)|(\\([0-9]{2,3}\\)[ \\-]*)|([0-9]{2,4})[ \\-]*)*?[0-9]{3,4}?[ \\-]*[0-9]{3,4}?$/
+
+// The validation schema for the form fields.
+const validationSchema = yup.object({
+ email: yup.string().email(),
+ hasConsentedToTerms: yup.boolean().oneOf([true], 'You must agree to the terms to continue.'),
+ notificationChannel: yup.string().oneOf(['email', 'sms', 'none']),
+ phoneNumber: yup.string().matches(phoneRegExp, 'Phone number is not valid'),
+ storeTripHistory: yup.boolean()
+})
+
/**
* This screen handles creating/updating OTP user account settings.
*/
@@ -90,41 +107,62 @@ class UserAccountScreen extends Component {
render () {
const { auth, loggedInUser } = this.props
const { userData } = this.state
-
- let formContents
- if (isNewUser(loggedInUser)) {
- if (!auth.user.email_verified) {
- // Check and prompt for email verification first to avoid extra user wait.
- formContents =
- } else {
- // New users are shown "wizard" (step-by-step) mode
- // (includes when a "new" user clicks 'My Account' from the account menu in the nav bar).
- formContents = (
-
- )
- }
- } else {
- formContents = (
- // Existing users are shown all panes together.
-
- )
- }
+ const handleExit = this._handleExit
return (
{/* TODO: Do mobile view. */}
-
+
+ {
+ props => {
+ let formContents
+ if (isNewUser(loggedInUser)) {
+ if (!auth.user.email_verified) {
+ // Check and prompt for email verification first to avoid extra user wait.
+ formContents =
+ } else {
+ // New users are shown "wizard" (step-by-step) mode
+ // (includes when a "new" user clicks 'My Account' from the account menu in the nav bar).
+ formContents = (
+
+ )
+ }
+ } else {
+ formContents = (
+ // Existing users are shown all panes together.
+
+ )
+ }
+
+ return (
+
+ )
+ }
+ }
+
,
@@ -35,7 +35,6 @@ class ExistingAccountDisplay extends Component {
return (
diff --git a/lib/components/user/favorite-locations-pane.js b/lib/components/user/favorite-locations-pane.js
index e143a70e2..82a4df8c9 100644
--- a/lib/components/user/favorite-locations-pane.js
+++ b/lib/components/user/favorite-locations-pane.js
@@ -1,8 +1,8 @@
-import clone from 'lodash/cloneDeep'
-import memoize from 'lodash.memoize'
-import PropTypes from 'prop-types'
+import { Field, FieldArray } from 'formik'
+import clone from 'clone'
import React, { Component } from 'react'
import {
+ Button,
ControlLabel,
FormControl,
FormGroup,
@@ -43,73 +43,11 @@ const notHomeOrWork = loc => loc.type !== 'home' && loc.type !== 'work'
/**
* User's saved locations editor.
+ * TODO: Discuss and improve handling of location details (type, coordinates...).
*/
class FavoriteLocationsPane extends Component {
- static propTypes = {
- onUserDataChange: PropTypes.func.isRequired,
- userData: PropTypes.object.isRequired
- }
-
- _handleAddNewLocation = e => {
- const value = e.target.value || ''
- if (value.trim().length > 0) {
- const { userData, onUserDataChange } = this.props
- // FIXME: remove assigning [] when null.
- const { savedLocations = [] } = userData
-
- // Create a copy of savedLocations and add the new location to the copied array.
- const newLocations = clone(savedLocations)
- newLocations.push({
- address: value.trim(),
- icon: 'map-marker',
- type: 'custom'
- })
-
- // Event onChange will trigger after this and before rerender,
- // so DO empty the input box value so the user can enter their next location.
- e.target.value = null
-
- onUserDataChange({ savedLocations: newLocations })
- }
- }
-
- _handleAddressChange = memoize(
- location => e => {
- const { userData, onUserDataChange } = this.props
- // FIXME: remove assigning [] when null.
- const { savedLocations = [] } = userData
- const value = e.target.value
- const isValueEmpty = !value || value === ''
- const nonEmptyLocation = isValueEmpty ? null : location
-
- // Update location address, ohterwise it stalls the input box.
- location.address = value
-
- // Create a new array for savedLocations.
- let newLocations = []
-
- // Add home/work as first entries to the new state only if
- // - user edited home/work to non-empty, or
- // - user edited another location and home/work is in savedLocations.
- const homeLocation = (isHome(location) && nonEmptyLocation) || savedLocations.find(isHome)
- if (homeLocation) newLocations.push(homeLocation)
-
- const workLocation = (isWork(location) && nonEmptyLocation) || savedLocations.find(isWork)
- if (workLocation) newLocations.push(workLocation)
-
- // Add the rest if it is not home or work
- // and if the new address of this one is not null or empty.
- newLocations = newLocations.concat(savedLocations
- .filter(notHomeOrWork)
- .filter(loc => loc !== location || !isValueEmpty)
- )
-
- onUserDataChange({ savedLocations: newLocations })
- }
- )
-
render () {
- const { userData } = this.props
+ const { handleBlur, handleChange, values: userData } = this.props
// FIXME: remove assigning [] when null.
const { savedLocations = [] } = userData
@@ -118,12 +56,12 @@ class FavoriteLocationsPane extends Component {
// In theory there could be multiple home or work locations.
// Just pick the first one.
const homeLocation = savedLocations.find(isHome) || {
- address: null,
+ address: '',
icon: 'home',
type: 'home'
}
const workLocation = savedLocations.find(isWork) || {
- address: null,
+ address: '',
icon: 'briefcase',
type: 'work'
}
@@ -138,34 +76,91 @@ class FavoriteLocationsPane extends Component {
Add the places you frequent often to save time planning trips:
- {effectiveLocations.map((loc, index) => (
-
-
-
-
-
-
-
-
- ))}
-
- {/* For adding a location. */}
-
-
-
-
-
-
-
-
+ {
+ // If a home or work locations do not exist,
+ // add them to the Formik state so they are shown by default.
+ // This will also cause them to be saved in the database (without causing side-effects).
+ // Home is at position 0, work at 1.
+ let homeExists = savedLocations.find(isHome)
+ if (!homeExists) {
+ arrayHelpers.insert(0, {
+ address: '',
+ icon: 'home',
+ type: 'home'
+ })
+ homeExists = true
+ }
+ if (!savedLocations.find(isWork)) {
+ arrayHelpers.insert(homeExists ? 1 : 0, {
+ address: '',
+ icon: 'briefcase',
+ type: 'work'
+ })
+ }
+
+ // TODO: move home and work to the top of the list if they exist.
+
+ return (
+ <>
+ {savedLocations.map((loc, index) => (
+
+
+
+
+
+
+ {loc !== homeLocation && loc !== workLocation && (
+
+
+
+ )}
+
+
+ ))}
+
+ {/* For adding a location. */}
+
+
+
+
+
+ {
+ const value = (e.target.value || '').trim()
+ if (value.length > 0) {
+ arrayHelpers.push({
+ address: value,
+ icon: 'map-marker',
+ type: 'custom'
+ })
+
+ // Event onChange will trigger after this and before rerender,
+ // so DO empty the input box value so the user can enter their next location.
+ e.target.value = ''
+ }
+ }}
+ placeholder='Add another place'
+ type='text'
+ />
+
+
+ >
+ )
+ }}
+ />