Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 35 additions & 32 deletions lib/components/user/existing-account-display.js
Original file line number Diff line number Diff line change
@@ -1,43 +1,46 @@
import React, { Component } from 'react'
import React from 'react'

import LinkButton from './link-button'
import StackedPaneDisplay from './stacked-pane-display'

/**
* This component handles the existing account display.
*/
class ExistingAccountDisplay extends Component {
render () {
const { onCancel, onComplete, panes } = this.props
const paneSequence = [
{
pane: () => <p><LinkButton to='/savedtrips'>Edit my trips</LinkButton></p>,
title: 'My trips'
},
{
pane: panes.terms,
props: { disableCheckTerms: true },
title: 'Terms'
},
{
pane: panes.notifications,
title: 'Notifications'
},
{
pane: panes.locations,
title: 'My locations'
}
]
const ExistingAccountDisplay = props => {
// The props include Formik props that provide access to the current user data
// and to its own blur/change/submit event handlers that automate the state.
// We forward the props to each pane so that their individual controls
// can be wired to be managed by Formik.
const { onCancel, panes } = props
const paneSequence = [
{
pane: () => <p><LinkButton to='/savedtrips'>Edit my trips</LinkButton></p>,
title: 'My trips'
},
{
pane: panes.terms,
props: { ...props, disableCheckTerms: true },
title: 'Terms'
},
{
pane: panes.notifications,
props,
title: 'Notifications'
},
{
pane: panes.locations,
props,
title: 'My locations'
}
]

return (
<StackedPaneDisplay
onCancel={onCancel}
onComplete={onComplete}
paneSequence={paneSequence}
title='My Account'
/>
)
}
return (
<StackedPaneDisplay
onCancel={onCancel}
paneSequence={paneSequence}
title='My Account'
/>
)
}

export default ExistingAccountDisplay
207 changes: 92 additions & 115 deletions lib/components/user/favorite-locations-pane.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import clone from 'lodash/cloneDeep'
import { Field, FieldArray } from 'formik'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm just realizing that the amount of files in the lib/components/user directory is getting quite large. Can some subfolders be created to help organize all these files?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Perhaps this? In this PR or a separate PR?

user/

  • (name needed)
    • form-navigation-buttons
    • link-button`
    • link-menu-item
    • stacked-pane-display
    • sequential-pane-display
  • login
    • after-signin-screen
    • awaiting-screen
    • before-signin-screen
    • nav-login-button
    • nav-login-button-auth0
    • nav-login-button.css
    • with-loggedin-user-support
  • account
    • account-setup-finish-pane.js
    • existing-account-display.js
    • favorite-locations-pane.js
    • new-account-wizard.js
    • notification-prefs-pane.js
    • phone-verification-pane.js
    • terms-of-use-pane.js
    • user-account-screen.js
    • verify-email-screen.js
  • trip
    • saved-trip-editor.js
    • saved-trip-list.js
    • saved-trip-screen.js
    • trip-basics-pane.js
    • trip-notifications-pane.js
    • trip-summary-pane.js
    • trip-summary.js

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might as well do a separate PR. For the (name needed) I'd just keep those in the root /user folder.

import memoize from 'lodash.memoize'
import PropTypes from 'prop-types'
import React, { Component } from 'react'
import {
Button,
ControlLabel,
FormControl,
FormGroup,
Expand Down Expand Up @@ -37,135 +37,112 @@ const NewLocationFormControl = styled(FormControl)`
`

// Helper filter functions.
const isHome = loc => loc.type === 'home'
const isWork = loc => loc.type === 'work'
const notHomeOrWork = loc => loc.type !== 'home' && loc.type !== 'work'
export const isHome = loc => loc.type === 'home'
export const isWork = loc => loc.type === 'work'

/**
* User's saved locations editor.
* Helper function that adds a new address to the Formik state
* using the Formik-provided arrayHelpers object.
*/
class FavoriteLocationsPane extends Component {
static propTypes = {
onUserDataChange: PropTypes.func.isRequired,
userData: PropTypes.object.isRequired
function addNewAddress (arrayHelpers, e) {
const value = (e.target.value || '').trim()
if (value.length > 0) {
arrayHelpers.push({
address: value,
icon: 'map-marker',
type: 'custom'
})

// Empty the input box value so the user can enter their next location.
e.target.value = ''
}
}

_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 })
/**
* User's saved locations editor.
* TODO: Discuss and improve handling of location details (type, coordinates...).
*/
class FavoriteLocationsPane extends Component {
_handleNewAddressKeyDown = memoize(
arrayHelpers => e => {
if (e.keyCode === 13) {
// On the user pressing enter (keyCode 13) on the new location input,
// add new address to user's savedLocations...
addNewAddress(arrayHelpers, e)

// ... but don't submit the form.
e.preventDefault()
}
}
}

_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 })
_handleNewAddressBlur = memoize(
arrayHelpers => e => {
addNewAddress(arrayHelpers, e)
}
)

render () {
const { userData } = this.props
// FIXME: remove assigning [] when null.
const { savedLocations = [] } = userData

// Build an 'effective' list of locations for display,
// where at least one 'home' and one 'work', are always present even if blank.
// In theory there could be multiple home or work locations.
// Just pick the first one.
const homeLocation = savedLocations.find(isHome) || {
address: null,
icon: 'home',
type: 'home'
}
const workLocation = savedLocations.find(isWork) || {
address: null,
icon: 'briefcase',
type: 'work'
}

const effectiveLocations = [
homeLocation,
workLocation,
...savedLocations.filter(notHomeOrWork)
]
const { values: userData } = this.props
const { savedLocations } = userData
const homeLocation = savedLocations.find(isHome)
const workLocation = savedLocations.find(isWork)

return (
<div>
<ControlLabel>Add the places you frequent often to save time planning trips:</ControlLabel>

{effectiveLocations.map((loc, index) => (
<FormGroup key={index}>
<InputGroup>
<StyledAddon title={loc.type}>
<FontAwesome name={loc.icon} />
</StyledAddon>
<FormControl
onChange={this._handleAddressChange(loc)}
placeholder={`Add ${loc.type}`}
type='text'
value={loc.address} />
</InputGroup>
</FormGroup>
))}

{/* For adding a location. */}
<FormGroup>
<InputGroup>
<NewLocationAddon>
<FontAwesome name='plus' />
</NewLocationAddon>
<NewLocationFormControl
onBlur={this._handleAddNewLocation}
placeholder='Add another place'
type='text'
/>
</InputGroup>
</FormGroup>
<FieldArray
name='savedLocations'
render={arrayHelpers => (
<>
{savedLocations.map((loc, index) => {
const isHomeOrWork = loc === homeLocation || loc === workLocation
return (
<FormGroup key={index}>
<InputGroup>
<StyledAddon title={loc.type}>
<FontAwesome name={loc.icon} />
</StyledAddon>
<Field as={FormControl}
name={`savedLocations.${index}.address`}
placeholder={isHomeOrWork ? `Add ${loc.type}` : 'Enter a favorite address'}
// onBlur, onChange, and value are passed automatically.
/>
{!isHomeOrWork && (
<InputGroup.Button>
<Button
aria-label='Delete this location'
// Do not memoize this call, as this component's index will change.
onClick={() => arrayHelpers.remove(index)}
title='Delete this location'
>
<FontAwesome name='times' />
</Button>
</InputGroup.Button>
)}
</InputGroup>
</FormGroup>
)
})}

{/* For adding a new location. */}
<FormGroup>
<InputGroup>
<NewLocationAddon>
<FontAwesome name='plus' />
</NewLocationAddon>
<NewLocationFormControl
onBlur={this._handleNewAddressBlur(arrayHelpers)}
onKeyDown={this._handleNewAddressKeyDown(arrayHelpers)}
placeholder='Add another place'
type='text'
/>
</InputGroup>
</FormGroup>
</>
)}
/>
</div>
)
}
Expand Down
10 changes: 7 additions & 3 deletions lib/components/user/form-navigation-buttons.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,12 @@ const FormNavigationButtons = ({
okayButton
}) => (
<StyledFormGroup>
<nav aria-label='...'>
<nav aria-label='Form navigation'>
{backButton && (
<LeftButton
disabled={backButton.disabled}
onClick={backButton.onClick}
type='button'
>
{backButton.text}
</LeftButton>
Expand All @@ -36,6 +37,7 @@ const FormNavigationButtons = ({
bsStyle='primary'
disabled={okayButton.disabled}
onClick={okayButton.onClick}
type={okayButton.type || 'button'}
>
{okayButton.text}
</RightButton>
Expand All @@ -47,9 +49,11 @@ const FormNavigationButtons = ({
const buttonType = PropTypes.shape({
disabled: PropTypes.bool,
/** Triggered when the button is clicked. */
onClick: PropTypes.func.isRequired,
onClick: PropTypes.func,
/** The text to display on the button. */
text: PropTypes.string
text: PropTypes.string,
/** The HTML type of the button ('button', 'reset', 'submit'). */
type: PropTypes.string
})

FormNavigationButtons.propTypes = {
Expand Down
2 changes: 1 addition & 1 deletion lib/components/user/link-button.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import * as uiActions from '../../actions/ui'
class LinkButton extends Component {
static propTypes = {
className: PropTypes.string,
componentClass: PropTypes.string,
componentClass: PropTypes.elementType,
/** The destination url when clicking the button. */
to: PropTypes.string.isRequired
}
Expand Down
Loading