Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mobility Profile #1090

Merged
merged 44 commits into from
Dec 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
937a709
refactor(user/types): Add MobilityProfile type.
binh-dam-ibigroup Nov 30, 2023
96c2681
refactor(dropdown-options): Extract common components.
binh-dam-ibigroup Nov 30, 2023
684fdc3
refactor(TripNotificationsPane): Extract SettingsList and fix dropdow…
binh-dam-ibigroup Dec 1, 2023
982c95b
feat(LimitationsPane): Add mobility limitations pane.
binh-dam-ibigroup Dec 1, 2023
45b11ec
feat(AssistiveDevicesPane): Add new pane for selecting assistive devi…
binh-dam-ibigroup Dec 1, 2023
e83d72f
refactor(MobilityProfile): Rename i18n strings
binh-dam-ibigroup Dec 1, 2023
6a2c683
refactor(LimitationsPane): Handle binding with user data default values.
binh-dam-ibigroup Dec 1, 2023
58cd7f9
refactor(AssistiveDevicesPane): Refactor and adjust styles
binh-dam-ibigroup Dec 1, 2023
48f9357
refactor(DropdownOptions): Fix types
binh-dam-ibigroup Dec 1, 2023
fb3e357
refactor(MobilityProfile): Update persisted user data on field change.
binh-dam-ibigroup Dec 4, 2023
9604240
refactor(NewAccountWizard): Get panes from config.
binh-dam-ibigroup Dec 5, 2023
e78f6a9
refactor(dropdown-options): Refactor types and comments.
binh-dam-ibigroup Dec 6, 2023
40aeaad
refactor(VisionLimitation): Use hyphen in 'legally-blind'.
binh-dam-ibigroup Dec 6, 2023
c633899
feat(webapp-routes): Add route for mobility profile wizard.
binh-dam-ibigroup Dec 6, 2023
22d7cc6
refactor(SequentialPaneDisplay): Move onNext from PaneProps to compon…
binh-dam-ibigroup Dec 6, 2023
4329513
refactor(NewAccountWizard): Support multiple page configs
binh-dam-ibigroup Dec 6, 2023
0078622
refactor(NewAccountWizard): Inline onCancel handler.
binh-dam-ibigroup Dec 6, 2023
8757513
refactor(NewAccountWizard): Remove basePath
binh-dam-ibigroup Dec 6, 2023
78a045b
refactor(Wizard): Introduce common wizard component.
binh-dam-ibigroup Dec 6, 2023
e476a3d
fix(Wizard): Fix compile errors
binh-dam-ibigroup Dec 6, 2023
ef08f0b
refactor(MobilityWizard): Remove unneeded props
binh-dam-ibigroup Dec 6, 2023
28a296c
feat(MobilityPane): Add mobility option to existing account display.
binh-dam-ibigroup Dec 6, 2023
4fe5a47
fix(SequentialPaneDisplay): Route to first pane if none specified.
binh-dam-ibigroup Dec 6, 2023
1ce8947
refactor(UserAccountScreen): Split out rendering to reduce props passed.
binh-dam-ibigroup Dec 6, 2023
cf74e15
fix(SequentialPaneDisplay): Push pane id if none provided.
binh-dam-ibigroup Dec 6, 2023
588992d
refactor(Wizard): Move routeTo functionality to SequentialPaneDisplay
binh-dam-ibigroup Dec 6, 2023
c01d138
refactor(Wizard): Refactor prop types
binh-dam-ibigroup Dec 6, 2023
dcb31e7
refactor(TripNotificationPane): Revert refactor of SettingsList
binh-dam-ibigroup Dec 6, 2023
a5019f4
refactor(TripNotificationPane): Unexport settings component.
binh-dam-ibigroup Dec 7, 2023
a744454
refactor(SequentialPaneDisplay): Merge wizard code and rename to wizard.
binh-dam-ibigroup Dec 7, 2023
b37a8c2
Revert renaming sequential-pane-display.
binh-dam-ibigroup Dec 7, 2023
a760285
refactor(standard-panes): Extract standard panes file
binh-dam-ibigroup Dec 7, 2023
4abd05f
refactor(SequentialPaneDisplay): Rename to wizard.
binh-dam-ibigroup Dec 7, 2023
86c73eb
refactor: Fix types
binh-dam-ibigroup Dec 7, 2023
545bfcd
chore(i18n): Add FR and i18n check exceptions
binh-dam-ibigroup Dec 7, 2023
ca4c50b
Merge branch 'dev' into mobility-profile
binh-dam-ibigroup Dec 7, 2023
918f2ed
refactor(UserAccountScreen): Rename isCreating to isWizard
binh-dam-ibigroup Dec 7, 2023
6023902
fix(AssistiveDevicesPane): Fix backend code of devices and adjust wor…
binh-dam-ibigroup Dec 8, 2023
b1ad318
refactor(StandardPanes): Rename mobility panes per PR feedback
binh-dam-ibigroup Dec 8, 2023
d680855
refactor(MobilityPane): Update button label: Define > Edit
binh-dam-ibigroup Dec 8, 2023
23edbc8
improvement(AssistiveDevicesPane): Fix checkbox spacing and introduce…
binh-dam-ibigroup Dec 8, 2023
a3a0a85
improvement(MobilityPane): Add summary of mobility choices per PR fee…
binh-dam-ibigroup Dec 11, 2023
f558897
refactor(MobilityPane): Fix types and text for no devices.
binh-dam-ibigroup Dec 11, 2023
0366dc9
Merge branch 'dev' into mobility-profile
binh-dam-ibigroup Dec 11, 2023
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
34 changes: 34 additions & 0 deletions i18n/en-US.yml
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,40 @@ components:
timeWalking: "{time} walking"
MobileOptions:
header: Set Search Options
MobilityProfile:
DevicesPane:
devices:
cane: Cane
crutches: Crutches
electric wheelchair: Electric wheelchair
manual walker: Manual walker
manual wheelchair: Manual/traditional wheelchair
mobility scooter: Mobility scooter
none: No assistive device
service animal: Service animal
stroller: Stroller
wheeled walker: Wheeled walker
white cane: White cane
prompt: Do you regularly use a mobility assistive device? (Check all that apply)
LimitationsPane:
mobilityPrompt: >-
Do you have any mobility limitations that cause you to walk more slowly
or more carefully than other people?
visionLimitations:
legally-blind: Legally blind
low-vision: Low-vision
none: None
visionPrompt: Do you have any vision limitations?
MobilityPane:
button: Edit your mobility profile
header: Mobility Profile
mobilityDevices: "Mobility devices: "
mobilityLimitations: "Mobility limitations: "
visionLimitations: "Vision limitations: "
intro: >-
Please answer a few questions to customize the trip planning experience to
your needs and preferences.
title: Define Your Mobility Profile
NarrativeItinerariesHeader:
changeSortDir: Change sort direction
howToFindResults: To view results, see the Itineraries Found heading below.
Expand Down
36 changes: 36 additions & 0 deletions i18n/fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,42 @@ components:
timeWalking: "{time} de marche"
MobileOptions:
header: Options de recherche
MobilityProfile:
DevicesPane:
devices:
cane: Canne
crutches: Béquilles
electric wheelchair: Fauteuil roulant électrique
manual walker: Déambulateur manuel
manual wheelchair: Fauteuil roulant manuel
mobility scooter: Scooter électrique
none: Aucun
service animal: Animal de service
stroller: Poussette
wheeled walker: Déambulateur à roues
white cane: Canne blanche
prompt: >-
Utilisez-vous habituellement l'aide d'un appareil pour vous déplacer ?
(Cochez tous les cas qui vous concernent)
LimitationsPane:
mobilityPrompt: >-
Avez-vous des handicaps moteurs qui vous font marcher plus lentement ou
plus prudemment que d'autres personnes ?
visionLimitations:
legally-blind: Non-voyant
low-vision: Vision basse
none: Aucune
visionPrompt: Avez-vous des handicaps visuels ?
MobilityPane:
button: Modifier votre profil mobilité
header: Profil mobilité
mobilityDevices: "Appareils d'aide : "
mobilityLimitations: "Handicaps moteurs : "
visionLimitations: "Handicaps visuels : "
intro: >-
Veuillez répondre a quelques questions pour personaliser vos recherches de
trajets selon vos besoins et préférences.
title: Spécifiez votre profil de mobilité
NarrativeItinerariesHeader:
changeSortDir: Changer l'ordre de tri
howToFindResults: Pour afficher les résultats, utilisez l'en-tête Trajets trouvés plus bas.
Expand Down
18 changes: 18 additions & 0 deletions i18n/i18n-exceptions.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,24 @@
"components.OTP2ErrorRenderer.inputFields.*": [
"FROM",
"TO"
],
"components.MobilityProfile.DevicesPane.devices.*": [
"cane",
"crutches",
"electric wheelchair",
"manual walker",
"manual wheelchair",
"mobility scooter",
"none",
"service animal",
"stroller",
"wheeled walker",
"white cane"
],
"components.MobilityProfile.LimitationsPane.visionLimitations.*": [
"none",
"low-vision",
"legally-blind"
]
}
}
140 changes: 140 additions & 0 deletions lib/components/user/common/dropdown-options.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { Field } from 'formik'
import { FormControl } from 'react-bootstrap'
import { IntlShape, useIntl } from 'react-intl'
import React, { ChangeEventHandler, ComponentType, ReactNode } from 'react'

interface SelectProps {
Control?: ComponentType
children: ReactNode
defaultValue?: string | number | boolean
label?: ReactNode
name: string
onChange?: ChangeEventHandler
}

/**
* A label followed by a dropdown control.
*/
export const Select = ({
Control = FormControl,
children,
defaultValue,
label,
name,
onChange
}: SelectProps): JSX.Element => (
// <Field> is kept outside of <label> to accommodate layout in table/grid cells.
<>
{label && <label htmlFor={name}>{label}</label>}
<Field
as={Control}
componentClass="select"
defaultValue={defaultValue}
id={name}
name={name}
onChange={onChange}
>
{children}
</Field>
</>
)

interface OptionsPropsBase<T> {
defaultValue?: T
hideDefaultIndication?: boolean
}

interface OptionsProps<T extends string | number> extends OptionsPropsBase<T> {
options: { text: string; value: T }[]
}

export function Options<T extends string | number>({
defaultValue,
hideDefaultIndication,
options
}: OptionsProps<T>): JSX.Element {
// <FormattedMessage> can't be used inside <option>.
const intl = useIntl()
return (
<>
{options.map(({ text, value }, i) => (
<option key={i} value={value}>
{!hideDefaultIndication && value === defaultValue
? intl.formatMessage(
{ id: 'common.forms.defaultValue' },
{ value: text }
)
: text}
</option>
))}
</>
)
}

const basicYesNoOptions = [
{
id: 'yes',
value: 'true'
},
{
id: 'no',
value: 'false'
}
]

/**
* Produces a yes/no list of options with the specified
* default value (true for yes, false for no).
*/
export function YesNoOptions({
defaultValue,
hideDefaultIndication
}: OptionsPropsBase<boolean>): JSX.Element {
// <FormattedMessage> can't be used inside <option>.
const intl = useIntl()
const options = basicYesNoOptions.map(({ id, value }) => ({
text: intl.formatMessage({ id: `common.forms.${id}` }),
value
}))
return (
<Options
defaultValue={(defaultValue || false).toString()}
hideDefaultIndication={hideDefaultIndication}
options={options}
/>
)
}

interface DurationOptionsProps extends OptionsPropsBase<number> {
decoratorFunc?: (text: string, intl: IntlShape) => string
minuteOptions: number[]
}

/**
* Produces a list of duration options with the specified default value.
*/
export function DurationOptions({
decoratorFunc,
defaultValue,
minuteOptions
}: DurationOptionsProps): JSX.Element {
// <FormattedMessage> can't be used inside <option>.
const intl = useIntl()
const localizedMinutes = minuteOptions.map((minutes) => ({
text:
minutes === 60
? intl.formatMessage({ id: 'components.TripNotificationsPane.oneHour' })
: intl.formatMessage(
{ id: 'common.time.tripDurationFormat' },
{ hours: 0, minutes, seconds: 0 }
),
value: minutes
}))
const options = decoratorFunc
? localizedMinutes.map(({ text, value }) => ({
text: decoratorFunc(text, intl),
value
}))
: localizedMinutes
return <Options defaultValue={defaultValue} options={options} />
}
25 changes: 21 additions & 4 deletions lib/components/user/existing-account-display.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
import { connect } from 'react-redux'
import { FormattedMessage, useIntl } from 'react-intl'
import { FormikProps } from 'formik'
import React from 'react'
import React, { FormEventHandler } from 'react'

import { AppReduxState } from '../../util/state-types'
import { TransitModeConfig } from '../../util/config-types'
import PageTitle from '../util/page-title'

import { EditedUser } from './types'
import { PhoneCodeRequestHandler } from './phone-number-editor'
import { PhoneVerificationSubmitHandler } from './phone-verification-form'
import A11yPrefs from './a11y-prefs'
import BackToTripPlanner from './back-to-trip-planner'
import DeleteUser from './delete-user'
import FavoritePlaceList from './places/favorite-place-list'
import MobilityPane from './mobility-profile/mobility-pane'
import NotificationPrefsPane from './notification-prefs-pane'
import StackedPanes from './stacked-panes'
import TermsOfUsePane from './terms-of-use-pane'

interface Props extends FormikProps<EditedUser> {
mobilityProfileEnabled: boolean
onDelete: FormEventHandler
onRequestPhoneVerificationCode: PhoneCodeRequestHandler
onSendPhoneVerificationCode: PhoneVerificationSubmitHandler
wheelchairEnabled: boolean
}

Expand All @@ -29,7 +36,7 @@ const ExistingAccountDisplay = (props: Props) => {
// We forward the props to each pane so that their individual controls
// can be wired to be managed by Formik.

const { wheelchairEnabled } = props
const { mobilityProfileEnabled, wheelchairEnabled } = props
const intl = useIntl()

const panes = [
Expand All @@ -38,6 +45,14 @@ const ExistingAccountDisplay = (props: Props) => {
props,
title: <FormattedMessage id="components.ExistingAccountDisplay.places" />
},
{
hidden: !mobilityProfileEnabled,
pane: MobilityPane,
props,
title: (
<FormattedMessage id="components.MobilityProfile.MobilityPane.header" />
)
},
{
pane: NotificationPrefsPane,
props,
Expand Down Expand Up @@ -85,11 +100,13 @@ const ExistingAccountDisplay = (props: Props) => {
}

const mapStateToProps = (state: AppReduxState) => {
const { accessModes } = state.otp.config.modes
const wheelchairEnabled = accessModes?.some(
const { mobilityProfile: mobilityProfileEnabled = false, modes } =
state.otp.config
const wheelchairEnabled = modes.accessModes?.some(
(mode: TransitModeConfig) => mode.showWheelchairSetting
)
return {
mobilityProfileEnabled,
wheelchairEnabled
}
}
Expand Down
Loading
Loading