diff --git a/i18n/en-US.yml b/i18n/en-US.yml index c9047adc0..c15a1b81b 100644 --- a/i18n/en-US.yml +++ b/i18n/en-US.yml @@ -363,6 +363,7 @@ components: description: The content you requested is not available. header: Content not found NotificationPrefsPane: + devicesRegistered: "{count, plural, one {# device} other {# devices}} registered" noDeviceForPush: Register your device using the mobile app to access push notifications. notificationChannelPrompt: "Receive notifications about your saved trips via:" OTP2ErrorRenderer: @@ -717,6 +718,11 @@ components: confirmDelete: >- Are you sure you would like to delete your user account? Once you do so, it cannot be recovered. + errorUpdatingProfile: Error updating profile. + fieldUpdated: This setting has been updated. + fields: + storeTripHistory: Store trip history + updating: Updating UserSettings: confirmDeletion: >- You have recent searches and/or places stored. Disabling storage of recent diff --git a/i18n/fr.yml b/i18n/fr.yml index c854ba23b..af03d9b33 100644 --- a/i18n/fr.yml +++ b/i18n/fr.yml @@ -378,6 +378,9 @@ components: description: Le contenu que vous avez demandé n'est pas disponible. header: Contenu introuvable NotificationPrefsPane: + devicesRegistered: >- + {count, plural, one {# appareil enregistré} other {# appareils + enregistrés}} noDeviceForPush: Inscrivez-vous avec l'application mobile pour accéder à ce paramètre. notificationChannelPrompt: "Recevoir des notifications sur vos trajets par :" OTP2ErrorRenderer: @@ -744,6 +747,11 @@ components: confirmDelete: >- Voulez-vous vraiment supprimer votre compte ? Cette action est irréversible. + errorUpdatingProfile: Erreur dans la mise à jour de votre profil. + fieldUpdated: Ce paramètre a été mis à jour. + fields: + storeTripHistory: Enregistrement des recherches + updating: Mise à jour UserSettings: confirmDeletion: >- Vous avez des recherches et/ou lieux récemment enregistrés qui vont être diff --git a/i18n/i18n-exceptions.json b/i18n/i18n-exceptions.json index 60f5855cd..0f3bd1185 100644 --- a/i18n/i18n-exceptions.json +++ b/i18n/i18n-exceptions.json @@ -5,6 +5,9 @@ "sms", "push" ], + "components.UserAccountScreen.fields.*": [ + "storeTripHistory" + ], "components.OTP2ErrorRenderer.*.body": [ "LOCATION_NOT_FOUND", "NO_STOPS_IN_RANGE", diff --git a/lib/actions/ui.js b/lib/actions/ui.js index 21856fd9b..9db265b8c 100644 --- a/lib/actions/ui.js +++ b/lib/actions/ui.js @@ -53,7 +53,7 @@ export const resetSessionTimeout = createAction('RESET_SESSION_TIMEOUT') * that preserves the current search or, if * replaceSearch is provided (including an empty string), replaces the search * when routing to a new URL path. - * @param {[type]} url path to route to + * @param {string} url path to route to * @param {string} [replaceSearch] optional search string to replace current one * @param {func} [routingMethod] the connected-react-router method to execute (defaults to push). */ diff --git a/lib/components/user/existing-account-display.tsx b/lib/components/user/existing-account-display.tsx index c253b2234..5e80d1fc3 100644 --- a/lib/components/user/existing-account-display.tsx +++ b/lib/components/user/existing-account-display.tsx @@ -1,30 +1,38 @@ import { connect } from 'react-redux' import { FormattedMessage, useIntl } from 'react-intl' +import { FormikProps } from 'formik' import React 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 A11yPrefs from './a11y-prefs' import BackToTripPlanner from './back-to-trip-planner' import DeleteUser from './delete-user' import FavoritePlaceList from './places/favorite-place-list' import NotificationPrefsPane from './notification-prefs-pane' -import StackedPaneDisplay from './stacked-pane-display' +import StackedPanes from './stacked-panes' import TermsOfUsePane from './terms-of-use-pane' +interface Props extends FormikProps { + wheelchairEnabled: boolean +} + /** * This component handles the existing account display. */ -const ExistingAccountDisplay = (props: { - onCancel: () => void - wheelchairEnabled: boolean -}) => { +const ExistingAccountDisplay = (props: 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, wheelchairEnabled } = props - const paneSequence = [ + + const { wheelchairEnabled } = props + const intl = useIntl() + + const panes = [ { pane: FavoritePlaceList, props, @@ -55,7 +63,6 @@ const ExistingAccountDisplay = (props: { } ] - const intl = useIntl() // Repeat text from the SubNav component in the title bar for brevity. const settings = intl.formatMessage({ id: 'components.SubNav.settings' @@ -67,9 +74,8 @@ const ExistingAccountDisplay = (props: {
- } @@ -77,15 +83,12 @@ const ExistingAccountDisplay = (props: {
) } -// TODO: state type -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const mapStateToProps = (state: any) => { - const { accessModes } = state.otp.config?.modes - const wheelchairEnabled = - accessModes && - accessModes.some( - (mode: { showWheelchairSetting: boolean }) => mode.showWheelchairSetting - ) + +const mapStateToProps = (state: AppReduxState) => { + const { accessModes } = state.otp.config.modes + const wheelchairEnabled = accessModes?.some( + (mode: TransitModeConfig) => mode.showWheelchairSetting + ) return { wheelchairEnabled } diff --git a/lib/components/user/monitored-trip/saved-trip-editor.tsx b/lib/components/user/monitored-trip/saved-trip-editor.tsx index 83d1af185..2dbd00a1c 100644 --- a/lib/components/user/monitored-trip/saved-trip-editor.tsx +++ b/lib/components/user/monitored-trip/saved-trip-editor.tsx @@ -3,7 +3,7 @@ import React, { ComponentType } from 'react' import BackLink from '../back-link' import PageTitle from '../../util/page-title' -import StackedPaneDisplay from '../stacked-pane-display' +import StackedPanesWithSave from '../stacked-panes-with-save' interface Props { isCreating: boolean @@ -50,9 +50,9 @@ const SavedTripEditor = (props: Props): JSX.Element => { <> - diff --git a/lib/components/user/new-account-wizard.tsx b/lib/components/user/new-account-wizard.tsx index 8676f5e9c..b0728af10 100644 --- a/lib/components/user/new-account-wizard.tsx +++ b/lib/components/user/new-account-wizard.tsx @@ -1,10 +1,11 @@ +import { Form, FormikProps } from 'formik' import { FormattedMessage, useIntl } from 'react-intl' -import { FormikProps } from 'formik' import React, { useCallback } from 'react' +import toast from 'react-hot-toast' import PageTitle from '../util/page-title' -import { User } from './types' +import { EditedUser } from './types' import AccountSetupFinishPane from './account-setup-finish-pane' import FavoritePlaceList from './places/favorite-place-list' import NotificationPrefsPane from './notification-prefs-pane' @@ -16,11 +17,12 @@ import VerifyEmailPane from './verify-email-pane' // and to its own blur/change/submit event handlers that automate the state. // We forward the props to each pane (via SequentialPaneDisplay) so that their individual controls // can be wired to be managed by Formik. -type FormikUserProps = FormikProps +type FormikUserProps = FormikProps interface Props extends FormikUserProps { activePaneId: string - onCreate: (value: User) => void + onCancel: () => void + onCreate: (value: EditedUser) => void } /** @@ -28,6 +30,7 @@ interface Props extends FormikUserProps { */ const NewAccountWizard = ({ activePaneId, + onCancel, // provided by UserAccountScreen onCreate, // provided by UserAccountScreen ...formikProps // provided by Formik }: Props): JSX.Element => { @@ -41,20 +44,28 @@ const NewAccountWizard = ({ } }, [onCreate, userData]) + const handleFinish = useCallback(() => { + // Display a toast to acknowledge saved changes + // (although in reality, changes quietly took effect in previous screens). + toast.success(intl.formatMessage({ id: 'actions.user.preferencesSaved' })) + + onCancel && onCancel() + }, [intl, onCancel]) + if (activePaneId === 'verify') { const verifyEmail = intl.formatMessage({ id: 'components.NewAccountWizard.verify' }) return ( - <> +

{verifyEmail}

- + ) } - const { hasConsentedToTerms, notificationChannel = 'email' } = userData + const { hasConsentedToTerms, notificationChannel = ['email'] } = userData const createNewAccount = intl.formatMessage({ id: 'components.NewAccountWizard.createNewAccount' }) @@ -72,7 +83,7 @@ const NewAccountWizard = ({ { id: 'notifications', invalid: - notificationChannel === 'sms' && + notificationChannel.includes('sms') && (!userData.phoneNumber || !userData.isPhoneNumberVerified), invalidMessage: intl.formatMessage({ id: 'components.PhoneNumberEditor.invalidPhone' @@ -93,14 +104,15 @@ const NewAccountWizard = ({ ] return ( - <> +
- + ) } diff --git a/lib/components/user/notification-prefs-pane.tsx b/lib/components/user/notification-prefs-pane.tsx index 67f3fb091..12eeee8f9 100644 --- a/lib/components/user/notification-prefs-pane.tsx +++ b/lib/components/user/notification-prefs-pane.tsx @@ -56,6 +56,7 @@ const NotificationOption = styled(ListGroupItem)` */ const NotificationPrefsPane = ({ allowedNotificationChannels, + handleChange, // Formik or custom handler onRequestPhoneVerificationCode, onSendPhoneVerificationCode, phoneFormatOptions, @@ -70,8 +71,6 @@ const NotificationPrefsPane = ({ {allowedNotificationChannels.map((type) => { - // TODO: If removing the Save/Cancel buttons on the account screen, - // persist changes immediately when onChange is triggered. const inputId = `notification-channel-${type}` const inputDescriptionId = `${inputId}-description` return ( @@ -79,10 +78,12 @@ const NotificationPrefsPane = ({ @@ -105,8 +106,12 @@ const NotificationPrefsPane = ({ ) : ( {pushDevices ? ( - // TODO: i18n - `${pushDevices} devices registered` + ) : ( )} diff --git a/lib/components/user/sequential-pane-display.tsx b/lib/components/user/sequential-pane-display.tsx index e41a953b7..9f2df97bf 100644 --- a/lib/components/user/sequential-pane-display.tsx +++ b/lib/components/user/sequential-pane-display.tsx @@ -21,6 +21,7 @@ export interface PaneProps { interface OwnProps { activePaneId: string + onFinish?: () => void panes: PaneProps[] } @@ -57,7 +58,7 @@ class SequentialPaneDisplay extends Component> { } _handleToNextPane = async (e: MouseEvent