diff --git a/i18n/en-US.yml b/i18n/en-US.yml
index 197c0baad..ea4b74aef 100644
--- a/i18n/en-US.yml
+++ b/i18n/en-US.yml
@@ -639,6 +639,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 041b2e578..6e3fa0b0e 100644
--- a/i18n/fr.yml
+++ b/i18n/fr.yml
@@ -358,8 +358,7 @@ components:
mobilityLimitations: "Handicaps moteurs : "
planTripDescription: >-
Vous pouvez rechercher des trajets adaptés au profil mobilité des
- personnes que vous accompagnez. Pour ajouter des personnes
- accompagnatrices, allez dans Préférences.
+ personnes que vous accompagnez.
visionLimitations: "Handicaps visuels : "
dropdownLabel: "Profil à utiliser :"
intro: >-
@@ -672,6 +671,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/actions/apiV2.js b/lib/actions/apiV2.js
index 15d3f5d77..e4d5ab1a2 100644
--- a/lib/actions/apiV2.js
+++ b/lib/actions/apiV2.js
@@ -9,7 +9,11 @@ import clone from 'clone'
import coreUtils from '@opentripplanner/core-utils'
import { checkForRouteModeOverride } from '../util/config'
-import { convertToPlace, getPersistenceMode } from '../util/user'
+import {
+ convertToPlace,
+ getPersistenceMode,
+ getUserWithEmail
+} from '../util/user'
import { FETCH_STATUS } from '../util/constants'
import {
generateModeSettingValues,
@@ -1026,10 +1030,10 @@ export function routingQuery(searchId = null, updateSearchInReducer) {
...currentQuery,
numItineraries: numItineraries || getDefaultNumItineraries(config)
}
- if (config.mobilityProfile) {
+ if (config.mobilityProfile && loggedInUser) {
baseQuery.mobilityProfile =
- currentQuery.mobilityProfile ||
- loggedInUser?.mobilityProfile?.mobilityMode
+ getUserWithEmail(loggedInUser.dependentsInfo, currentQuery.forEmail)
+ ?.mobilityMode || loggedInUser.mobilityProfile?.mobilityMode
}
// Generate combinations if the modes for query are not specified in the query
// FIXME: BICYCLE_RENT does not appear in this list unless TRANSIT is also enabled.
diff --git a/lib/components/form/advanced-settings-panel.tsx b/lib/components/form/advanced-settings-panel.tsx
index bae3d224a..2510c3b88 100644
--- a/lib/components/form/advanced-settings-panel.tsx
+++ b/lib/components/form/advanced-settings-panel.tsx
@@ -33,8 +33,8 @@ 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'
import {
addCustomSettingLabels,
@@ -178,11 +178,7 @@ const AdvancedSettingsPanel = ({
const intl = useIntl()
const [closingBySave, setClosingBySave] = useState(false)
const [selectedMobilityProfile, setSelectedMobilityProfile] =
- useState(
- currentQuery.mobilityProfile ||
- loggedInUser?.mobilityProfile?.mobilityMode ||
- ''
- )
+ useState(currentQuery.forEmail || loggedInUser?.email)
const dependents = useMemo(
() => loggedInUser?.dependents || [],
[loggedInUser]
@@ -263,10 +259,10 @@ const AdvancedSettingsPanel = ({
const onMobilityProfileChange = useCallback(
(evt: QueryParamChangeEvent) => {
- const value = evt.mobilityProfile
+ const value = evt.forEmail
setSelectedMobilityProfile(value as string)
setQueryParam({
- mobilityProfile: value
+ forEmail: value
})
},
[setSelectedMobilityProfile, setQueryParam]
@@ -309,18 +305,18 @@ const AdvancedSettingsPanel = ({
label={intl.formatMessage({
id: 'components.MobilityProfile.dropdownLabel'
})}
- name="mobilityProfile"
+ name="forEmail"
onChange={onMobilityProfileChange}
options={[
{
text: intl.formatMessage({
id: 'components.MobilityProfile.myself'
}),
- value: loggedInUser.mobilityProfile?.mobilityMode || ''
+ value: loggedInUser?.email
},
- ...(loggedInUser.dependentsInfo?.map((user) => ({
- text: user.name || user.email,
- value: user.mobilityMode || ''
+ ...(loggedInUser?.dependentsInfo?.map((user) => ({
+ text: getDependentName(user),
+ value: user.email
})) || [])
]}
value={selectedMobilityProfile}
diff --git a/lib/components/user/common/dropdown-options.tsx b/lib/components/user/common/dropdown-options.tsx
index 70667915b..839570cde 100644
--- a/lib/components/user/common/dropdown-options.tsx
+++ b/lib/components/user/common/dropdown-options.tsx
@@ -7,6 +7,7 @@ interface SelectProps {
Control?: ComponentType
children: ReactNode
defaultValue?: string | number | boolean
+ disabled?: boolean
label?: ReactNode
name: string
onChange?: ChangeEventHandler
@@ -19,6 +20,7 @@ export const Select = ({
Control = FormControl,
children,
defaultValue,
+ disabled,
label,
name,
onChange
@@ -27,6 +29,7 @@ export const Select = ({
as: Control,
componentClass: 'select',
defaultValue,
+ disabled,
id: name,
name,
onChange
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..4b909b89b
--- /dev/null
+++ b/lib/components/user/mobility-profile/companion-selector.tsx
@@ -0,0 +1,95 @@
+import { connect } from 'react-redux'
+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
+ value: CompanionInfo
+}
+
+// @ts-expect-error: No types for react-select.
+const Select = lazy(() => import('react-select'))
+
+function notNull(item: unknown) {
+ return !!item
+}
+
+function makeOption(companion?: CompanionInfo) {
+ return {
+ label: companion?.nickname || companion?.email,
+ value: companion
+ }
+}
+
+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 = [],
+ loggedInUser,
+ multi = false,
+ onChange,
+ selectedCompanions
+}: {
+ disabled?: boolean
+ excludedUsers?: (CompanionInfo | undefined)[]
+ loggedInUser?: User
+ multi?: boolean
+ onChange: (e: Option | Option[]) => void
+ selectedCompanions?: (CompanionInfo | undefined)[]
+}): JSX.Element => {
+ const companionOptions = (loggedInUser?.relatedUsers || [])
+ .filter(notNull)
+ .filter(isConfirmed)
+ .map(makeOption)
+ const companionValues = multi
+ ? selectedCompanions?.filter(notNull).map(makeOption)
+ : selectedCompanions?.[0]
+ ? makeOption(selectedCompanions[0])
+ : null
+
+ const isOptionDisabled = useCallback(
+ (option: Option) => excludedUsers.includes(option?.value),
+ [excludedUsers]
+ )
+
+ return (
+ ...}>
+
+
+ )
+}
+
+const mapStateToProps = (state: AppReduxState) => {
+ return {
+ loggedInUser: state.user.loggedInUser
+ }
+}
+
+export default connect(mapStateToProps)(CompanionSelector)
diff --git a/lib/components/user/mobility-profile/companions-pane.tsx b/lib/components/user/mobility-profile/companions-pane.tsx
index d189b36eb..51a1a6e95 100644
--- a/lib/components/user/mobility-profile/companions-pane.tsx
+++ b/lib/components/user/mobility-profile/companions-pane.tsx
@@ -7,6 +7,7 @@ import React, { useCallback, useState } from 'react'
import styled from 'styled-components'
import { CompanionInfo, User } from '../types'
+import { getUserWithEmail } from '../../../util/user'
import { StyledIconWrapper } from '../../util/styledIcon'
import { UnstyledButton } from '../../util/unstyled-button'
import AddEmailForm from '../common/add-email-form'
@@ -57,7 +58,7 @@ const CompanionRow = ({
window.confirm(
intl.formatMessage(
{ id: 'components.CompanionsPane.confirmDeleteCompanion' },
- { email: email }
+ { email }
)
)
) {
@@ -127,7 +128,7 @@ const CompanionsPane = ({
const handleAddNewEmail = useCallback(
async ({ newEmail }, { resetForm }) => {
// Submit the new email if it is not already listed
- if (!companions.find((comp) => comp.email === newEmail)) {
+ if (!getUserWithEmail(companions, newEmail)) {
await updateCompanions([
...companions,
{
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..46200de71
--- /dev/null
+++ b/lib/components/user/mobility-profile/trip-companions-pane.tsx
@@ -0,0 +1,120 @@
+import { connect } from 'react-redux'
+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 { getDependentName } from '../../../util/user'
+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
+ }
+
+function optionValue(option: Option | null) {
+ if (!option) return null
+ return option?.value
+}
+
+/**
+ * Pane for showing/setting trip companions and observers.
+ */
+const TripCompanions = ({
+ getDependentUserInfo,
+ isReadOnly,
+ loggedInUser,
+ setFieldValue,
+ values: trip
+}: Props): JSX.Element => {
+ const handleCompanionChange = useCallback(
+ (option: Option | Option[] | null) => {
+ if (!option || 'label' in option) {
+ setFieldValue('companion', optionValue(option))
+ }
+ },
+ [setFieldValue]
+ )
+
+ const handleObserversChange = useCallback(
+ (options: Option | Option[] | null) => {
+ if (!options || 'length' in options) {
+ setFieldValue('observers', (options || []).map(optionValue))
+ }
+ },
+ [setFieldValue]
+ )
+
+ const intl = useIntl()
+ const dependents = loggedInUser?.dependents
+
+ useEffect(() => {
+ if (dependents && dependents.length > 0) {
+ getDependentUserInfo(dependents, intl)
+ }
+ }, [dependents, getDependentUserInfo, intl])
+
+ const { companion, observers, primary } = trip
+
+ const iAmThePrimaryTraveler =
+ (!primary && trip.userId === loggedInUser?.id) ||
+ primary?.userId === loggedInUser?.id
+
+ const primaryTraveler = iAmThePrimaryTraveler
+ ? intl.formatMessage({ id: 'components.MobilityProfile.myself' })
+ : primary
+ ? primary.email
+ : getDependentName(
+ loggedInUser?.dependentsInfo?.find((d) => d.userId === trip.userId)
+ )
+
+ return (
+
+
+
+ {primaryTraveler}
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+// 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/components/user/monitored-trip/saved-trip-editor.tsx b/lib/components/user/monitored-trip/saved-trip-editor.tsx
index 55e235878..183f2cf34 100644
--- a/lib/components/user/monitored-trip/saved-trip-editor.tsx
+++ b/lib/components/user/monitored-trip/saved-trip-editor.tsx
@@ -15,6 +15,7 @@ import TripNotFound from './trip-not-found'
interface Props {
hasMobilityProfile: boolean
isCreating: boolean
+ isReadOnly: boolean
onCancel: () => void
panes: Record
values: MonitoredTrip
@@ -78,6 +79,7 @@ const SavedTripEditor = (props: Props): JSX.Element => {
? { content: }
: undefined
}
+ isReadOnly={props.isReadOnly}
onCancel={onCancel}
panes={paneSequence}
title={title}
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/saved-trip-screen.js b/lib/components/user/monitored-trip/saved-trip-screen.js
index 76206baa9..a4b07ab0b 100644
--- a/lib/components/user/monitored-trip/saved-trip-screen.js
+++ b/lib/components/user/monitored-trip/saved-trip-screen.js
@@ -16,12 +16,13 @@ import {
getItineraryDefaultMonitoredDays
} from '../../../util/itinerary'
import { getActiveItineraries, getActiveSearch } from '../../../util/state'
+import { getUserWithEmail } from '../../../util/user'
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'
@@ -59,21 +60,33 @@ class SavedTripScreen extends Component {
* Initializes a monitored trip object from the props.
*/
_createMonitoredTrip = () => {
- const { homeTimezone, itinerary, loggedInUser, queryParams } = this.props
+ const { currentQuery, homeTimezone, itinerary, loggedInUser, queryParams } =
+ this.props
const monitoredDays = getItineraryDefaultMonitoredDays(
itinerary,
homeTimezone
)
const { otp2QueryParams, ...otherItineraryProps } = itinerary
+
+ const primaryTraveler = getUserWithEmail(
+ loggedInUser.dependentsInfo,
+ currentQuery.forEmail
+ )
+
return {
...arrayToDayFields(monitoredDays),
arrivalVarianceMinutesThreshold: 5,
+ // If someone else is the primary traveler, then I am the companion.
+ companion: primaryTraveler
+ ? { email: loggedInUser.email, status: 'CONFIRMED' }
+ : null,
departureVarianceMinutesThreshold: 5,
excludeFederalHolidays: true,
isActive: true,
itinerary: copyAndRemoveRouteModeOverrides(otherItineraryProps),
leadTimeInMinutes: 30,
otp2QueryParams,
+ primary: primaryTraveler,
// when creating a monitored trip, the query params will be changed on the
// backend so that the modes parameter will reflect the modes seen in the
// itinerary
@@ -122,7 +135,7 @@ class SavedTripScreen extends Component {
basics: TripBasicsPane,
notifications: TripNotificationsPane,
summary: TripSummaryPane,
- travelCompanions: CompanionsPane
+ travelCompanions: TripCompanionsPane
}
componentDidMount() {
@@ -244,6 +257,7 @@ class SavedTripScreen extends Component {
{...props}
hasMobilityProfile={hasMobilityProfile}
isCreating={isCreating}
+ isReadOnly={monitoredTrip?.userId !== loggedInUser.id}
notificationChannel={loggedInUser.notificationChannel}
onCancel={
isCreating ? this._goToTripPlanner : this._goToSavedTrips
@@ -283,6 +297,7 @@ const mapStateToProps = (state, ownProps) => {
const tripId = ownProps.match.params.id
const { disableSingleItineraryDays } = state.otp.config
return {
+ currentQuery: state.otp.currentQuery,
disableSingleItineraryDays,
hasMobilityProfile: state.otp.config.mobilityProfile,
homeTimezone: state.otp.config.homeTimezone,
diff --git a/lib/components/user/monitored-trip/trip-basics-pane.tsx b/lib/components/user/monitored-trip/trip-basics-pane.tsx
index e3685dcec..a238cb18b 100644
--- a/lib/components/user/monitored-trip/trip-basics-pane.tsx
+++ b/lib/components/user/monitored-trip/trip-basics-pane.tsx
@@ -34,7 +34,7 @@ import FormattedDayOfWeekCompact from '../../util/formatted-day-of-week-compact'
import FormattedValidationError from '../../util/formatted-validation-error'
import InvisibleA11yLabel from '../../util/invisible-a11y-label'
-import { MonitoredDayCircle } from './trip-monitored-days'
+import MonitoredDays, { MonitoredDayCircle } from './trip-monitored-days'
import TripStatus from './trip-status'
import TripSummary from './trip-duration-summary'
@@ -48,6 +48,7 @@ type TripBasicsProps = WrappedComponentProps &
clearItineraryExistence: () => void
disableSingleItineraryDays?: boolean
isCreating: boolean
+ isReadOnly: boolean
itineraryExistence?: ItineraryExistence
}
@@ -138,16 +139,23 @@ const RenderAvailableDays = ({
errorSelectingDays,
finalItineraryExistence,
isCreating,
+ isReadOnly,
monitoredTrip
}: {
errorCheckingTrip: boolean
errorSelectingDays?: 'error' | null
finalItineraryExistence?: ItineraryExistence
isCreating: boolean
+ isReadOnly: boolean
monitoredTrip: MonitoredTrip
}) => {
const intl = useIntl()
const baseColor = getBaseColor()
+
+ if (isReadOnly) {
+ return
+ }
+
return (
<>
{errorCheckingTrip && (
@@ -315,6 +323,7 @@ class TripBasicsPane extends Component {
errors,
intl,
isCreating,
+ isReadOnly,
isSubmitting,
itineraryExistence,
values: monitoredTrip
@@ -360,7 +369,9 @@ class TripBasicsPane extends Component {
{/* Do not show trip status when saving trip for the first time
(it doesn't exist in backend yet). */}
- {!isCreating && }
+ {!isCreating && (
+
+ )}
@@ -371,6 +382,7 @@ class TripBasicsPane extends Component {
@@ -391,6 +403,7 @@ class TripBasicsPane extends Component {
errorSelectingDays={selectOneDayError}
finalItineraryExistence={finalItineraryExistence}
isCreating={isCreating}
+ isReadOnly={isReadOnly}
monitoredTrip={monitoredTrip}
/>
@@ -402,7 +415,7 @@ class TripBasicsPane extends Component {
@@ -413,11 +426,16 @@ class TripBasicsPane extends Component {
errorCheckingTrip={errorCheckingTrip}
finalItineraryExistence={finalItineraryExistence}
isCreating={isCreating}
+ isReadOnly={isReadOnly}
monitoredTrip={monitoredTrip}
/>
>
)}
-
+
{
+ isReadOnly: boolean
notificationChannel: string
}
@@ -65,7 +66,7 @@ class TripNotificationsPane extends Component {
}
render(): JSX.Element {
- const { notificationChannel, values } = this.props
+ const { isReadOnly, notificationChannel, values } = this.props
const areNotificationsDisabled =
notificationChannel === 'none' || !notificationChannel?.length
// Define a common trip delay field for simplicity, set to the smallest between the
@@ -122,6 +123,7 @@ class TripNotificationsPane extends Component {
}
@@ -132,6 +134,7 @@ class TripNotificationsPane extends Component {
}
@@ -146,6 +149,7 @@ class TripNotificationsPane extends Component {
as above.
onChange={this._handleDelayThresholdChange}
@@ -168,6 +172,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 && (
+ }
+ />
+ )}
+
+ )}
)
}
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/stacked-panes-with-save.tsx b/lib/components/user/stacked-panes-with-save.tsx
index 596132ba4..5304a01ff 100644
--- a/lib/components/user/stacked-panes-with-save.tsx
+++ b/lib/components/user/stacked-panes-with-save.tsx
@@ -8,6 +8,7 @@ import StackedPanes, { Props as StackedPanesProps } from './stacked-panes'
interface Props extends StackedPanesProps {
extraButton?: ButtonType
+ isReadOnly?: boolean
onCancel: () => void
}
@@ -18,6 +19,7 @@ interface Props extends StackedPanesProps {
*/
const StackedPanesWithSave = ({
extraButton,
+ isReadOnly,
onCancel,
panes,
title
@@ -49,21 +51,25 @@ const StackedPanesWithSave = ({
)
}}
- extraButton={extraButton}
- okayButton={{
- 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'
- }}
+ extraButton={isReadOnly ? undefined : extraButton}
+ okayButton={
+ 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 be05872cd..6babd41f7 100644
--- a/lib/components/user/types.ts
+++ b/lib/components/user/types.ts
@@ -81,6 +81,7 @@ export interface JourneyState {
export type MonitoredTrip = Record & {
arrivalVarianceMinutesThreshold: number
+ companion?: CompanionInfo
departureVarianceMinutesThreshold: number
excludeFederalHolidays?: boolean
id: string
@@ -89,8 +90,11 @@ export type MonitoredTrip = Record & {
itineraryExistence?: ItineraryExistence
journeyState?: JourneyState
leadTimeInMinutes: number
+ observers?: CompanionInfo[]
otp2QueryParams: Record
+ primary?: DependentInfo
queryParams: Record
+ secondary?: CompanionInfo
tripName: string
userId: string
}
@@ -98,6 +102,7 @@ export type MonitoredTrip = Record & {
export interface MonitoredTripProps {
from?: Place
handleTogglePauseMonitoring?: () => void
+ isReadOnly?: boolean
monitoredTrip: MonitoredTrip
pendingRequest?: boolean | string
to?: Place
diff --git a/lib/util/user.js b/lib/util/user.js
index ec571227b..e909be808 100644
--- a/lib/util/user.js
+++ b/lib/util/user.js
@@ -264,3 +264,13 @@ export function getPlaceMainText(place, intl) {
? toSentenceCase(getFormattedPlaces(place.type, intl))
: place.name || place.address
}
+
+/** Helper for matching the email field. */
+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
+}