From cee60f4fbb4105d48f66468a889abb92f0d20f3f Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Mon, 18 Nov 2024 10:25:51 -0500 Subject: [PATCH 01/29] feat(TripCompanionsPane): Add basic trip companions pane to trip settings --- .../mobility-profile/trip-companions-pane.tsx | 57 +++++++++++++++++++ .../user/monitored-trip/saved-trip-screen.js | 4 +- 2 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 lib/components/user/mobility-profile/trip-companions-pane.tsx diff --git a/lib/components/user/mobility-profile/trip-companions-pane.tsx b/lib/components/user/mobility-profile/trip-companions-pane.tsx new file mode 100644 index 000000000..ca3022811 --- /dev/null +++ b/lib/components/user/mobility-profile/trip-companions-pane.tsx @@ -0,0 +1,57 @@ +import { Ban } from '@styled-icons/fa-solid/Ban' +import { connect } from 'react-redux' +import { + ControlLabel, + FormControl, + FormGroup, + HelpBlock, + ProgressBar, + Radio +} from 'react-bootstrap' +import { Field, FormikProps } from 'formik' +import { FormattedMessage, useIntl } from 'react-intl' +import { Prompt } from 'react-router' +// @ts-expect-error FormikErrorFocus does not support TypeScript yet. +import FormikErrorFocus from 'formik-error-focus' +import React, { Component, FormEventHandler } from 'react' +import styled from 'styled-components' +import type { IntlShape, WrappedComponentProps } from 'react-intl' + +import * as userActions from '../../../actions/user' +import { AppReduxState } from '../../../util/state-types' +import { FieldSet } from '../styled' +import { getBaseColor, RED_ON_WHITE } from '../../util/colors' +import { getErrorStates } from '../../../util/ui' +import { ItineraryExistence, MonitoredTrip } from '../types' +import InvisibleA11yLabel from '../../util/invisible-a11y-label' + +type Props = WrappedComponentProps & + FormikProps & { + canceled: boolean + } + +/** + * Pane for showing/setting trip companions and observers. + */ +const TripCompanions = (props: Props): JSX.Element => { + console.log(props) + return ( +
+

+ Primary traveler: {props.primary ? props.primary.email : 'None set'} +

+

Companions: {props.companions ? props.companions.length : 'None'}

+

Observers: {props.observers ? props.observers.length : 'None'}

+
+ ) +} + +// Connect to redux store + +const mapStateToProps = (state: AppReduxState) => { + return {} +} + +const mapDispatchToProps = {} + +export default connect(mapStateToProps, mapDispatchToProps)(TripCompanions) diff --git a/lib/components/user/monitored-trip/saved-trip-screen.js b/lib/components/user/monitored-trip/saved-trip-screen.js index 5e66539ab..12c47b3e0 100644 --- a/lib/components/user/monitored-trip/saved-trip-screen.js +++ b/lib/components/user/monitored-trip/saved-trip-screen.js @@ -20,8 +20,8 @@ import { RETURN_TO_CURRENT_ROUTE } from '../../../util/ui' import { TRIPS_PATH } from '../../../util/constants' import AccountPage from '../account-page' import AwaitingScreen from '../awaiting-screen' -import CompanionsPane from '../mobility-profile/companions-pane' import InvisibleA11yLabel from '../../util/invisible-a11y-label' +import TripCompanionsPane from '../mobility-profile/trip-companions-pane' import withLoggedInUserSupport from '../with-logged-in-user-support' import SavedTripEditor from './saved-trip-editor' @@ -122,7 +122,7 @@ class SavedTripScreen extends Component { basics: TripBasicsPane, notifications: TripNotificationsPane, summary: TripSummaryPane, - travelCompanions: CompanionsPane + travelCompanions: TripCompanionsPane } componentDidMount() { From 354d41ba84fefd37c526d60468afa826f45364ca Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Mon, 18 Nov 2024 10:47:41 -0500 Subject: [PATCH 02/29] refactor(user/types): Add trip companion types --- lib/components/user/types.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/components/user/types.ts b/lib/components/user/types.ts index be05872cd..26f257faa 100644 --- a/lib/components/user/types.ts +++ b/lib/components/user/types.ts @@ -89,8 +89,11 @@ export type MonitoredTrip = Record & { itineraryExistence?: ItineraryExistence journeyState?: JourneyState leadTimeInMinutes: number + observers?: CompanionInfo[] otp2QueryParams: Record + primary?: CompanionInfo[] queryParams: Record + secondary?: CompanionInfo tripName: string userId: string } From 01ec9dceae5de87d33bf6fb8b5be83de3dfbccad Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Mon, 18 Nov 2024 20:21:28 -0500 Subject: [PATCH 03/29] feat(TripCompanionsPane): Support assigning companions --- .../mobility-profile/companion-selector.tsx | 70 ++++++++++++++ .../mobility-profile/trip-companions-pane.tsx | 92 ++++++++++--------- 2 files changed, 119 insertions(+), 43 deletions(-) create mode 100644 lib/components/user/mobility-profile/companion-selector.tsx diff --git a/lib/components/user/mobility-profile/companion-selector.tsx b/lib/components/user/mobility-profile/companion-selector.tsx new file mode 100644 index 000000000..a735c0f5c --- /dev/null +++ b/lib/components/user/mobility-profile/companion-selector.tsx @@ -0,0 +1,70 @@ +import { connect } from 'react-redux' +import { QueryParamChangeEvent } from '@opentripplanner/trip-form/lib/types' +import React, { lazy, Suspense } from 'react' + +import { AppReduxState } from '../../util/state-types' +import { CompanionInfo, User } from '../types' + +export interface Option { + label: string + value: CompanionInfo +} + +const Select = lazy(() => import('react-select')) + +function notNull(item) { + return !!item +} + +function makeOption(companion: CompanionInfo) { + return { + label: companion?.nickname || companion?.email, + value: companion + } +} + +const CompanionSelector = ({ + excludedUsers = [], + loggedInUser, + multi = false, + onChange, + selectedCompanions +}: { + excludedUsers?: CompanionInfo[] + loggedInUser?: User + multi?: boolean + onChange: (e: QueryParamChangeEvent) => void + selectedCompanions?: CompanionInfo | CompanionInfo[] +}): JSX.Element => { + const excludedEmails = excludedUsers.filter(notNull).map(({ email }) => email) + const companionOptions = (loggedInUser?.relatedUsers || []) + .filter(notNull) + .filter(({ status = '' }) => status === 'CONFIRMED') + .filter(({ email }) => !excludedEmails.includes(email)) + .map(makeOption) + const companionValues = multi + ? selectedCompanions?.filter(notNull).map(makeOption) + : selectedCompanions !== null + ? makeOption(selectedCompanions) + : null + + return ( + ...}> + function optionValue(option: Option) { + if (!option) return null return option?.value } From 9b2e61a5a4690912ddf2ab5203f677a7ee287714 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 19 Nov 2024 17:24:51 -0500 Subject: [PATCH 06/29] refactor(TripCompanionsPane): Disable companion selection if trip was planned by other --- .../user/mobility-profile/companion-selector.tsx | 3 +++ .../user/mobility-profile/trip-companions-pane.tsx | 7 ++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/components/user/mobility-profile/companion-selector.tsx b/lib/components/user/mobility-profile/companion-selector.tsx index e605e84fc..342da5055 100644 --- a/lib/components/user/mobility-profile/companion-selector.tsx +++ b/lib/components/user/mobility-profile/companion-selector.tsx @@ -24,12 +24,14 @@ function makeOption(companion: CompanionInfo) { } const CompanionSelector = ({ + disabled, excludedUsers = [], loggedInUser, multi = false, onChange, selectedCompanions }: { + disabled?: boolean excludedUsers?: CompanionInfo[] loggedInUser?: User multi?: boolean @@ -55,6 +57,7 @@ const CompanionSelector = ({ ...}> } @@ -132,6 +134,7 @@ class TripNotificationsPane extends Component {
  • } diff --git a/lib/components/user/monitored-trip/trip-status.js b/lib/components/user/monitored-trip/trip-status.js index 27ffcfb8f..03bf2b30a 100644 --- a/lib/components/user/monitored-trip/trip-status.js +++ b/lib/components/user/monitored-trip/trip-status.js @@ -70,6 +70,7 @@ function MonitoredTripAlerts({ alerts }) { */ function TripStatus({ confirmAndDeleteUserMonitoredTrip, + isReadOnly, planNewTripFromMonitoredTrip, renderData, togglePauseTrip, @@ -89,37 +90,39 @@ function TripStatus({ )} {/* Footer buttons */} - - {renderData.shouldRenderToggleSnoozeTripButton && ( - - )} - {renderData.shouldRenderTogglePauseTripButton && ( - - )} - {renderData.shouldRenderDeleteTripButton && ( - } - /> - )} - {renderData.shouldRenderPlanNewTripButton && ( - } - /> - )} - + {!isReadOnly && ( + + {renderData.shouldRenderToggleSnoozeTripButton && ( + + )} + {renderData.shouldRenderTogglePauseTripButton && ( + + )} + {renderData.shouldRenderDeleteTripButton && ( + } + /> + )} + {renderData.shouldRenderPlanNewTripButton && ( + } + /> + )} + + )} ) } From 5af66eeb6a68cfd22a05d75aca31fcb06dffc465 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Thu, 21 Nov 2024 15:35:12 -0500 Subject: [PATCH 16/29] improvement(TripCompanionsPane): Display dependent info for trip where I'm a companion. --- .../form/advanced-settings-panel.tsx | 3 +- .../mobility-profile/trip-companions-pane.tsx | 49 ++++++++++++++++--- lib/util/user.js | 5 ++ 3 files changed, 49 insertions(+), 8 deletions(-) diff --git a/lib/components/form/advanced-settings-panel.tsx b/lib/components/form/advanced-settings-panel.tsx index df69d1194..7d425f1f9 100644 --- a/lib/components/form/advanced-settings-panel.tsx +++ b/lib/components/form/advanced-settings-panel.tsx @@ -33,6 +33,7 @@ import { AppReduxState } from '../../util/state-types' import { blue, getBaseColor } from '../util/colors' import { ComponentContext } from '../../util/contexts' import { generateModeSettingValues } from '../../util/api' +import { getDependentName } from '../../util/user' import { User } from '../user/types' import Link from '../util/link' @@ -321,7 +322,7 @@ const AdvancedSettingsPanel = ({ value: loggedInUser?.email }, ...(loggedInUser?.dependentsInfo?.map((user) => ({ - text: user.name || user.email, + text: getDependentName(user), value: user.email })) || []) ]} diff --git a/lib/components/user/mobility-profile/trip-companions-pane.tsx b/lib/components/user/mobility-profile/trip-companions-pane.tsx index ce7260dd7..92aaa16a8 100644 --- a/lib/components/user/mobility-profile/trip-companions-pane.tsx +++ b/lib/components/user/mobility-profile/trip-companions-pane.tsx @@ -1,14 +1,20 @@ +import { connect } from 'react-redux' import { FormikProps } from 'formik' -import React, { useCallback } from 'react' -import type { WrappedComponentProps } from 'react-intl' +import { useIntl, WrappedComponentProps } from 'react-intl' +import React, { useCallback, useEffect } from 'react' -import { MonitoredTrip } from '../types' +import * as userActions from '../../../actions/user' +import { AppReduxState } from '../../../util/state-types' +import { DependentInfo, MonitoredTrip, User } from '../types' +import { getDependentName } from '../../../util/user' import CompanionSelector, { Option } from './companion-selector' type Props = WrappedComponentProps & FormikProps & { + getDependentUserInfo: (args: string[]) => DependentInfo[] isReadOnly: boolean + loggedInUser: User } function optionValue(option: Option) { @@ -20,7 +26,9 @@ function optionValue(option: Option) { * Pane for showing/setting trip companions and observers. */ const TripCompanions = ({ + getDependentUserInfo, isReadOnly, + loggedInUser, setFieldValue, values: trip }: Props): JSX.Element => { @@ -38,15 +46,32 @@ const TripCompanions = ({ [setFieldValue] ) + const intl = useIntl() + + useEffect(() => { + if (loggedInUser?.dependents.length > 0) { + getDependentUserInfo(loggedInUser?.dependents, intl) + } + }, [loggedInUser.dependents, getDependentUserInfo, intl]) + const { companion, observers, primary } = trip - const iAmThePrimaryTraveler = !primary + const iAmThePrimaryTraveler = + (!primary && trip.userId === loggedInUser?.id) || + primary?.userId === loggedInUser?.id + + const primaryTraveler = iAmThePrimaryTraveler + ? 'Myself' + : primary + ? primary.email + : getDependentName( + loggedInUser?.dependentsInfo?.find((d) => d.userId === trip.userId) + ) return (

    - Primary traveler:{' '} - {iAmThePrimaryTraveler ? 'Myself' : primary.email} + Primary traveler: {primaryTraveler}

    {/* TODO: a11y label */} @@ -73,4 +98,14 @@ const TripCompanions = ({ ) } -export default TripCompanions +// connect to the redux store + +const mapStateToProps = (state: AppReduxState) => ({ + loggedInUser: state.user.loggedInUser +}) + +const mapDispatchToProps = { + getDependentUserInfo: userActions.getDependentUserInfo +} + +export default connect(mapStateToProps, mapDispatchToProps)(TripCompanions) diff --git a/lib/util/user.js b/lib/util/user.js index b6b864ed4..e909be808 100644 --- a/lib/util/user.js +++ b/lib/util/user.js @@ -269,3 +269,8 @@ export function getPlaceMainText(place, intl) { export function getUserWithEmail(users, email) { return users?.find((user) => user.email === email) } + +/** Helper for displaying a dependent user name with fallback on email. */ +export function getDependentName(dependent) { + return dependent?.name || dependent?.email +} From a8cba12d17844c678343c673482546b7cd0d16bc Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Thu, 21 Nov 2024 16:06:46 -0500 Subject: [PATCH 17/29] refactor(TripCompanionsPane): Implement i18n --- i18n/en-US.yml | 4 ++++ i18n/fr.yml | 4 ++++ .../user/mobility-profile/trip-companions-pane.tsx | 13 ++++++------- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/i18n/en-US.yml b/i18n/en-US.yml index c44db0bd3..6cf4994cd 100644 --- a/i18n/en-US.yml +++ b/i18n/en-US.yml @@ -638,6 +638,10 @@ components: tripNotAvailableOnDay: Trip not available on {repeatedDay} unsavedChangesExistingTrip: You haven't saved your trip yet. If you leave, changes will be lost. unsavedChangesNewTrip: You haven't saved your new trip yet. If you leave, it will be lost. + TripCompanionsPane: + companionLabel: "Companion on this trip:" + observersLabel: "Observers watching this trip:" + primaryLabel: "Primary traveler: " TripNotificationsPane: advancedSettings: Advanced settings altRouteRecommended: An alternative route or transfer point is recommended diff --git a/i18n/fr.yml b/i18n/fr.yml index 9719c7b86..f24f6da71 100644 --- a/i18n/fr.yml +++ b/i18n/fr.yml @@ -670,6 +670,10 @@ components: unsavedChangesNewTrip: >- Vous n'avez pas encore enregistré votre nouveau trajet. Si vous annulez, ce trajet sera perdu. + TripCompanionsPane: + companionLabel: "Accompagnateurs sur ce trajet :" + observersLabel: "Observateurs suivant ce trajet :" + primaryLabel: "Voyageur principal : " TripNotificationsPane: advancedSettings: Paramètres avancés altRouteRecommended: Un·e autre trajet ou correspondance est conseillé·e diff --git a/lib/components/user/mobility-profile/trip-companions-pane.tsx b/lib/components/user/mobility-profile/trip-companions-pane.tsx index 92aaa16a8..9057623c6 100644 --- a/lib/components/user/mobility-profile/trip-companions-pane.tsx +++ b/lib/components/user/mobility-profile/trip-companions-pane.tsx @@ -1,6 +1,6 @@ import { connect } from 'react-redux' +import { FormattedMessage, useIntl, WrappedComponentProps } from 'react-intl' import { FormikProps } from 'formik' -import { useIntl, WrappedComponentProps } from 'react-intl' import React, { useCallback, useEffect } from 'react' import * as userActions from '../../../actions/user' @@ -61,7 +61,7 @@ const TripCompanions = ({ primary?.userId === loggedInUser?.id const primaryTraveler = iAmThePrimaryTraveler - ? 'Myself' + ? intl.formatMessage({ id: 'components.MobilityProfile.myself' }) : primary ? primary.email : getDependentName( @@ -71,11 +71,11 @@ const TripCompanions = ({ return (

    - Primary traveler: {primaryTraveler} + + {primaryTraveler}

    - {/* TODO: a11y label */} - Companion on this trip: +

    - {/* TODO: a11y label */} - Observers: + Date: Thu, 21 Nov 2024 16:18:03 -0500 Subject: [PATCH 18/29] improvement(SavedTripList): Hide pause button on read-only trips --- .../user/monitored-trip/saved-trip-list.tsx | 5 ++- .../user/monitored-trip/trip-summary-pane.tsx | 33 +++++++++++-------- lib/components/user/types.ts | 1 + 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/lib/components/user/monitored-trip/saved-trip-list.tsx b/lib/components/user/monitored-trip/saved-trip-list.tsx index 03e33f80f..618c736c8 100644 --- a/lib/components/user/monitored-trip/saved-trip-list.tsx +++ b/lib/components/user/monitored-trip/saved-trip-list.tsx @@ -40,6 +40,7 @@ interface ItemOwnProps { interface ItemProps extends ItemOwnProps { intl: IntlShape + isReadOnly: boolean renderData: any togglePauseTrip: (trip: MonitoredTrip, intl: IntlShape) => void } @@ -97,7 +98,7 @@ class TripListItem extends Component { } render() { - const { intl, renderData, trip } = this.props + const { intl, isReadOnly, renderData, trip } = this.props const { itinerary } = trip const { legs } = itinerary const { alerts, shouldRenderAlerts } = renderData @@ -146,6 +147,7 @@ class TripListItem extends Component { { // connect to the redux store const itemMapStateToProps = (state: AppReduxState, { trip }: ItemOwnProps) => { return { + isReadOnly: trip.userId !== state.user.loggedInUser.id, renderData: getRenderData({ monitoredTrip: trip }) diff --git a/lib/components/user/monitored-trip/trip-summary-pane.tsx b/lib/components/user/monitored-trip/trip-summary-pane.tsx index 3e1ccef51..5ce8b904b 100644 --- a/lib/components/user/monitored-trip/trip-summary-pane.tsx +++ b/lib/components/user/monitored-trip/trip-summary-pane.tsx @@ -114,6 +114,7 @@ const ToggleNotificationButton = styled.button` const TripSummaryPane = ({ from, handleTogglePauseMonitoring, + isReadOnly, monitoredTrip, pendingRequest, to @@ -224,20 +225,24 @@ const TripSummaryPane = ({ values={{ leadTimeInMinutes }} /> )} -
    - - {pendingRequest === 'pause' ? ( - /* Make loader fit */ - - ) : monitoredTrip.isActive ? ( - - ) : ( - - )} - + {!isReadOnly && ( + <> +
    + + {pendingRequest === 'pause' ? ( + /* Make loader fit */ + + ) : monitoredTrip.isActive ? ( + + ) : ( + + )} + + + )} diff --git a/lib/components/user/types.ts b/lib/components/user/types.ts index 4cc921176..3f5f210a3 100644 --- a/lib/components/user/types.ts +++ b/lib/components/user/types.ts @@ -102,6 +102,7 @@ export type MonitoredTrip = Record & { export interface MonitoredTripProps { from?: Place handleTogglePauseMonitoring?: () => void + isReadOnly?: booleean monitoredTrip: MonitoredTrip pendingRequest?: boolean | string to?: Place From 8f33c04a1d464326a59a85d7f4f58a00cb2c582d Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 22 Nov 2024 11:59:34 -0500 Subject: [PATCH 19/29] refactor: Fix types --- .../mobility-profile/companion-selector.tsx | 18 +++++------ .../mobility-profile/trip-companions-pane.tsx | 32 ++++++++++++------- .../user/stacked-panes-with-save.tsx | 32 ++++++++++--------- lib/components/user/types.ts | 2 +- 4 files changed, 48 insertions(+), 36 deletions(-) diff --git a/lib/components/user/mobility-profile/companion-selector.tsx b/lib/components/user/mobility-profile/companion-selector.tsx index 342da5055..b6de4a08d 100644 --- a/lib/components/user/mobility-profile/companion-selector.tsx +++ b/lib/components/user/mobility-profile/companion-selector.tsx @@ -1,8 +1,7 @@ import { connect } from 'react-redux' -import { QueryParamChangeEvent } from '@opentripplanner/trip-form/lib/types' import React, { lazy, Suspense, useCallback } from 'react' -import { AppReduxState } from '../../util/state-types' +import { AppReduxState } from '../../../util/state-types' import { CompanionInfo, User } from '../types' export interface Option { @@ -10,13 +9,14 @@ export interface Option { value: CompanionInfo } +// @ts-expect-error: No types for react-select. const Select = lazy(() => import('react-select')) -function notNull(item) { +function notNull(item: unknown) { return !!item } -function makeOption(companion: CompanionInfo) { +function makeOption(companion?: CompanionInfo) { return { label: companion?.nickname || companion?.email, value: companion @@ -32,11 +32,11 @@ const CompanionSelector = ({ selectedCompanions }: { disabled?: boolean - excludedUsers?: CompanionInfo[] + excludedUsers?: (CompanionInfo | undefined)[] loggedInUser?: User multi?: boolean - onChange: (e: QueryParamChangeEvent) => void - selectedCompanions?: CompanionInfo | CompanionInfo[] + onChange: (e: Option | Option[]) => void + selectedCompanions?: (CompanionInfo | undefined)[] }): JSX.Element => { const companionOptions = (loggedInUser?.relatedUsers || []) .filter(notNull) @@ -44,8 +44,8 @@ const CompanionSelector = ({ .map(makeOption) const companionValues = multi ? selectedCompanions?.filter(notNull).map(makeOption) - : selectedCompanions !== null - ? makeOption(selectedCompanions) + : selectedCompanions?.[0] + ? makeOption(selectedCompanions[0]) : null const isOptionDisabled = useCallback( diff --git a/lib/components/user/mobility-profile/trip-companions-pane.tsx b/lib/components/user/mobility-profile/trip-companions-pane.tsx index 9057623c6..8681adfa3 100644 --- a/lib/components/user/mobility-profile/trip-companions-pane.tsx +++ b/lib/components/user/mobility-profile/trip-companions-pane.tsx @@ -1,18 +1,23 @@ import { connect } from 'react-redux' -import { FormattedMessage, useIntl, WrappedComponentProps } from 'react-intl' +import { + FormattedMessage, + IntlShape, + useIntl, + WrappedComponentProps +} from 'react-intl' import { FormikProps } from 'formik' import React, { useCallback, useEffect } from 'react' import * as userActions from '../../../actions/user' import { AppReduxState } from '../../../util/state-types' -import { DependentInfo, MonitoredTrip, User } from '../types' import { getDependentName } from '../../../util/user' +import { MonitoredTrip, User } from '../types' import CompanionSelector, { Option } from './companion-selector' type Props = WrappedComponentProps & FormikProps & { - getDependentUserInfo: (args: string[]) => DependentInfo[] + getDependentUserInfo: (userIds: string[], intl: IntlShape) => void isReadOnly: boolean loggedInUser: User } @@ -33,26 +38,31 @@ const TripCompanions = ({ values: trip }: Props): JSX.Element => { const handleCompanionChange = useCallback( - (option: Option) => { - setFieldValue('companion', optionValue(option)) + (option: Option | Option[]) => { + if ('label' in option) { + setFieldValue('companion', optionValue(option)) + } }, [setFieldValue] ) const handleObserversChange = useCallback( - (options: Option[]) => { - setFieldValue('observers', (options || []).map(optionValue)) + (options: Option | Option[]) => { + if ('length' in options) { + setFieldValue('observers', (options || []).map(optionValue)) + } }, [setFieldValue] ) const intl = useIntl() + const dependents = loggedInUser?.dependents useEffect(() => { - if (loggedInUser?.dependents.length > 0) { - getDependentUserInfo(loggedInUser?.dependents, intl) + if (dependents && dependents.length > 0) { + getDependentUserInfo(dependents, intl) } - }, [loggedInUser.dependents, getDependentUserInfo, intl]) + }, [dependents, getDependentUserInfo, intl]) const { companion, observers, primary } = trip @@ -80,7 +90,7 @@ const TripCompanions = ({ disabled={isReadOnly || !iAmThePrimaryTraveler} excludedUsers={observers} onChange={handleCompanionChange} - selectedCompanions={companion} + selectedCompanions={[companion]} />

    diff --git a/lib/components/user/stacked-panes-with-save.tsx b/lib/components/user/stacked-panes-with-save.tsx index 018a25e47..5304a01ff 100644 --- a/lib/components/user/stacked-panes-with-save.tsx +++ b/lib/components/user/stacked-panes-with-save.tsx @@ -51,22 +51,24 @@ const StackedPanesWithSave = ({ ) }} - extraButton={!isReadOnly && extraButton} + extraButton={isReadOnly ? undefined : extraButton} okayButton={ - !isReadOnly && { - disabled: buttonClicked === 'okay', - onClick: () => { - // Some browsers need this to happen after the formik action finishes firing - setTimeout(() => setButtonClicked('okay'), 10) - }, - text: - buttonClicked === 'okay' ? ( - - ) : ( - - ), - type: 'submit' - } + isReadOnly + ? undefined + : { + disabled: buttonClicked === 'okay', + onClick: () => { + // Some browsers need this to happen after the formik action finishes firing + setTimeout(() => setButtonClicked('okay'), 10) + }, + text: + buttonClicked === 'okay' ? ( + + ) : ( + + ), + type: 'submit' + } } /> diff --git a/lib/components/user/types.ts b/lib/components/user/types.ts index 3f5f210a3..6babd41f7 100644 --- a/lib/components/user/types.ts +++ b/lib/components/user/types.ts @@ -102,7 +102,7 @@ export type MonitoredTrip = Record & { export interface MonitoredTripProps { from?: Place handleTogglePauseMonitoring?: () => void - isReadOnly?: booleean + isReadOnly?: boolean monitoredTrip: MonitoredTrip pendingRequest?: boolean | string to?: Place From bca35c5432c9ba94823291c5bf0d72075f59e722 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 26 Nov 2024 15:01:21 -0500 Subject: [PATCH 20/29] fix(TripCompanionsPane): Handle deleting companions. --- .../user/mobility-profile/trip-companions-pane.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/components/user/mobility-profile/trip-companions-pane.tsx b/lib/components/user/mobility-profile/trip-companions-pane.tsx index 8681adfa3..46200de71 100644 --- a/lib/components/user/mobility-profile/trip-companions-pane.tsx +++ b/lib/components/user/mobility-profile/trip-companions-pane.tsx @@ -22,7 +22,7 @@ type Props = WrappedComponentProps & loggedInUser: User } -function optionValue(option: Option) { +function optionValue(option: Option | null) { if (!option) return null return option?.value } @@ -38,8 +38,8 @@ const TripCompanions = ({ values: trip }: Props): JSX.Element => { const handleCompanionChange = useCallback( - (option: Option | Option[]) => { - if ('label' in option) { + (option: Option | Option[] | null) => { + if (!option || 'label' in option) { setFieldValue('companion', optionValue(option)) } }, @@ -47,8 +47,8 @@ const TripCompanions = ({ ) const handleObserversChange = useCallback( - (options: Option | Option[]) => { - if ('length' in options) { + (options: Option | Option[] | null) => { + if (!options || 'length' in options) { setFieldValue('observers', (options || []).map(optionValue)) } }, From cc4b7a7cce0b248107f0c0dc1b0f213028770c7c Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 11 Dec 2024 11:40:38 -0500 Subject: [PATCH 21/29] refactor(CompanionRefactor): Format invalid companion/observer references --- .../mobility-profile/companion-selector.tsx | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/components/user/mobility-profile/companion-selector.tsx b/lib/components/user/mobility-profile/companion-selector.tsx index b6de4a08d..fbd5bb415 100644 --- a/lib/components/user/mobility-profile/companion-selector.tsx +++ b/lib/components/user/mobility-profile/companion-selector.tsx @@ -3,6 +3,7 @@ import React, { lazy, Suspense, useCallback } from 'react' import { AppReduxState } from '../../../util/state-types' import { CompanionInfo, User } from '../types' +import StatusBadge from '../../util/status-badge' export interface Option { label: string @@ -23,6 +24,22 @@ function makeOption(companion?: CompanionInfo) { } } +function isConfirmed({ status = '' }: CompanionInfo) { + return status === 'CONFIRMED' +} + +function formatOptionLabel(option: Option) { + if (!isConfirmed(option.value)) { + return ( + <> + {option.label} + + ) + } else { + return option.label + } +} + const CompanionSelector = ({ disabled, excludedUsers = [], @@ -40,7 +57,7 @@ const CompanionSelector = ({ }): JSX.Element => { const companionOptions = (loggedInUser?.relatedUsers || []) .filter(notNull) - .filter(({ status = '' }) => status === 'CONFIRMED') + .filter(isConfirmed) .map(makeOption) const companionValues = multi ? selectedCompanions?.filter(notNull).map(makeOption) @@ -56,6 +73,7 @@ const CompanionSelector = ({ return ( ...}> } @@ -134,7 +133,6 @@ class TripNotificationsPane extends Component {

  • } From 5cc84f0c028b92c4a96d04d85b457f7d972a4d30 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 20 Dec 2024 12:27:56 -0500 Subject: [PATCH 25/29] improvement(SavedTripEditor): Add a read-only banner. --- i18n/en-US.yml | 3 + i18n/fr.yml | 3 + .../mobility-profile/trip-companions-pane.tsx | 2 +- .../mobility-profile/trip-readonly-pane.tsx | 56 +++++++++++++++++++ .../user/monitored-trip/saved-trip-editor.tsx | 8 ++- .../user/monitored-trip/saved-trip-screen.js | 2 + 6 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 lib/components/user/mobility-profile/trip-readonly-pane.tsx diff --git a/i18n/en-US.yml b/i18n/en-US.yml index ea4b74aef..4b20cf3cf 100644 --- a/i18n/en-US.yml +++ b/i18n/en-US.yml @@ -533,6 +533,9 @@ components: SavedTripEditor: deleteSavedTrip: Delete saved trip editSavedTrip: Edit saved trip + readOnlyBanner: >- + {creator} created and added you to this trip. Therefore, you cannot make + changes. saveNewTrip: Save new trip travelCompanions: Travel companions tripInformation: Trip information diff --git a/i18n/fr.yml b/i18n/fr.yml index 6e3fa0b0e..ebaf3953d 100644 --- a/i18n/fr.yml +++ b/i18n/fr.yml @@ -554,6 +554,9 @@ components: SavedTripEditor: deleteSavedTrip: Supprimer ce trajet editSavedTrip: Modifier un trajet enregistré + readOnlyBanner: >- + {creator} a créé et vous a ajouté sur ce trajet. Vous ne pouvez donc pas + faire de modifications. saveNewTrip: Enregistrer un nouveau trajet travelCompanions: Accompagnateurs tripInformation: Informations sur le trajet diff --git a/lib/components/user/mobility-profile/trip-companions-pane.tsx b/lib/components/user/mobility-profile/trip-companions-pane.tsx index 46200de71..41cf19f6a 100644 --- a/lib/components/user/mobility-profile/trip-companions-pane.tsx +++ b/lib/components/user/mobility-profile/trip-companions-pane.tsx @@ -73,7 +73,7 @@ const TripCompanions = ({ const primaryTraveler = iAmThePrimaryTraveler ? intl.formatMessage({ id: 'components.MobilityProfile.myself' }) : primary - ? primary.email + ? primary.name || primary.email : getDependentName( loggedInUser?.dependentsInfo?.find((d) => d.userId === trip.userId) ) diff --git a/lib/components/user/mobility-profile/trip-readonly-pane.tsx b/lib/components/user/mobility-profile/trip-readonly-pane.tsx new file mode 100644 index 000000000..af477a96c --- /dev/null +++ b/lib/components/user/mobility-profile/trip-readonly-pane.tsx @@ -0,0 +1,56 @@ +import { Alert } from 'react-bootstrap' +import { connect } from 'react-redux' +import { FormattedMessage } from 'react-intl' +import { FormikProps } from 'formik' +import React from 'react' + +import { AppReduxState } from '../../../util/state-types' +import { getDependentName } from '../../../util/user' +import { MonitoredTrip, User } from '../types' + +type Props = FormikProps & { + isReadOnly: boolean + loggedInUser: User +} + +/** + * Displays a banner for read-only state. + */ +const TripReadOnlyPane = ({ + isReadOnly, + loggedInUser, + values: trip +}: Props) => { + if (!isReadOnly) return null + + const { companion, primary } = trip + + const iAmThePrimaryTraveler = + (!primary && trip.userId === loggedInUser?.id) || + primary?.userId === loggedInUser?.id + + const creator = iAmThePrimaryTraveler + ? companion?.nickname || companion?.email + : primary + ? primary.name || primary.email + : getDependentName( + loggedInUser?.dependentsInfo?.find((d) => d.userId === trip.userId) + ) + + return ( + + + + ) +} + +// connect to the redux store + +const mapStateToProps = (state: AppReduxState) => ({ + loggedInUser: state.user.loggedInUser +}) + +export default connect(mapStateToProps)(TripReadOnlyPane) diff --git a/lib/components/user/monitored-trip/saved-trip-editor.tsx b/lib/components/user/monitored-trip/saved-trip-editor.tsx index 183f2cf34..91ab0fa90 100644 --- a/lib/components/user/monitored-trip/saved-trip-editor.tsx +++ b/lib/components/user/monitored-trip/saved-trip-editor.tsx @@ -34,6 +34,10 @@ const SavedTripEditor = (props: Props): JSX.Element => { if (monitoredTrip) { const paneSequence: PaneAttributes[] = [ + { + pane: panes.readOnlyAlert, + props + }, { pane: panes.basics, props, @@ -61,7 +65,9 @@ const SavedTripEditor = (props: Props): JSX.Element => { }) } - const title = isCreating + const title = props.isReadOnly + ? intl.formatMessage({ id: 'otpUi.TripDetails.title' }) + : isCreating ? intl.formatMessage({ id: 'components.SavedTripEditor.saveNewTrip' }) : intl.formatMessage({ id: 'components.SavedTripEditor.editSavedTrip' }) diff --git a/lib/components/user/monitored-trip/saved-trip-screen.js b/lib/components/user/monitored-trip/saved-trip-screen.js index a4b07ab0b..ac1985635 100644 --- a/lib/components/user/monitored-trip/saved-trip-screen.js +++ b/lib/components/user/monitored-trip/saved-trip-screen.js @@ -23,6 +23,7 @@ import AccountPage from '../account-page' import AwaitingScreen from '../awaiting-screen' import InvisibleA11yLabel from '../../util/invisible-a11y-label' import TripCompanionsPane from '../mobility-profile/trip-companions-pane' +import TripReadonlyPane from '../mobility-profile/trip-readonly-pane' import withLoggedInUserSupport from '../with-logged-in-user-support' import SavedTripEditor from './saved-trip-editor' @@ -134,6 +135,7 @@ class SavedTripScreen extends Component { _panes = { basics: TripBasicsPane, notifications: TripNotificationsPane, + readOnlyAlert: TripReadonlyPane, summary: TripSummaryPane, travelCompanions: TripCompanionsPane } From 10551d8fc40922bee4d301d45eee33af8f8d3e56 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 20 Dec 2024 12:55:58 -0500 Subject: [PATCH 26/29] chore(i18n): Add exception for reused OTP-UI string --- i18n/i18n-exceptions.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/i18n/i18n-exceptions.json b/i18n/i18n-exceptions.json index 9cb9f516a..a837e2e23 100644 --- a/i18n/i18n-exceptions.json +++ b/i18n/i18n-exceptions.json @@ -52,5 +52,6 @@ "low-vision", "legally-blind" ] - } + }, + "ignoredIds": ["otpUi.TripDetails.title"] } \ No newline at end of file From 6b8e1775ea625b318892f2ef6571a994eedabf19 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 20 Dec 2024 13:21:15 -0500 Subject: [PATCH 27/29] refactor(DropdownOptions): Delete unused disabled prop --- lib/components/user/common/dropdown-options.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/components/user/common/dropdown-options.tsx b/lib/components/user/common/dropdown-options.tsx index 839570cde..70667915b 100644 --- a/lib/components/user/common/dropdown-options.tsx +++ b/lib/components/user/common/dropdown-options.tsx @@ -7,7 +7,6 @@ interface SelectProps { Control?: ComponentType children: ReactNode defaultValue?: string | number | boolean - disabled?: boolean label?: ReactNode name: string onChange?: ChangeEventHandler @@ -20,7 +19,6 @@ export const Select = ({ Control = FormControl, children, defaultValue, - disabled, label, name, onChange @@ -29,7 +27,6 @@ export const Select = ({ as: Control, componentClass: 'select', defaultValue, - disabled, id: name, name, onChange From e70e5562d3d8ac753399e9fe051a38b5af20bdd1 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 20 Dec 2024 13:27:31 -0500 Subject: [PATCH 28/29] refactor(TripCompanionsPane): Remove unneeded types --- .../mobility-profile/trip-companions-pane.tsx | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/lib/components/user/mobility-profile/trip-companions-pane.tsx b/lib/components/user/mobility-profile/trip-companions-pane.tsx index 41cf19f6a..9dcf51535 100644 --- a/lib/components/user/mobility-profile/trip-companions-pane.tsx +++ b/lib/components/user/mobility-profile/trip-companions-pane.tsx @@ -1,10 +1,5 @@ import { connect } from 'react-redux' -import { - FormattedMessage, - IntlShape, - useIntl, - WrappedComponentProps -} from 'react-intl' +import { FormattedMessage, IntlShape, useIntl } from 'react-intl' import { FormikProps } from 'formik' import React, { useCallback, useEffect } from 'react' @@ -15,12 +10,11 @@ import { MonitoredTrip, User } from '../types' import CompanionSelector, { Option } from './companion-selector' -type Props = WrappedComponentProps & - FormikProps & { - getDependentUserInfo: (userIds: string[], intl: IntlShape) => void - isReadOnly: boolean - loggedInUser: User - } +type Props = FormikProps & { + getDependentUserInfo: (userIds: string[], intl: IntlShape) => void + isReadOnly: boolean + loggedInUser: User +} function optionValue(option: Option | null) { if (!option) return null From 55ed3b7b3d2078d4b4d04a25321067f689924fb4 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Mon, 23 Dec 2024 16:29:50 -0500 Subject: [PATCH 29/29] refactor(TripReadonlyPane): Tweak margins --- lib/components/user/mobility-profile/trip-readonly-pane.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/components/user/mobility-profile/trip-readonly-pane.tsx b/lib/components/user/mobility-profile/trip-readonly-pane.tsx index af477a96c..125818bfe 100644 --- a/lib/components/user/mobility-profile/trip-readonly-pane.tsx +++ b/lib/components/user/mobility-profile/trip-readonly-pane.tsx @@ -38,7 +38,7 @@ const TripReadOnlyPane = ({ ) return ( - +