From 00c786fb005c0e7bb97a3ce72d940d8b95cdddcb Mon Sep 17 00:00:00 2001 From: amy-corson-ibigroup <115499534+amy-corson-ibigroup@users.noreply.github.com> Date: Thu, 26 Sep 2024 16:09:35 -0500 Subject: [PATCH 1/5] Adds a configuration item that removes the ability to monitor a trip for only one day --- i18n/en-US.yml | 1 + .../user/monitored-trip/saved-trip-screen.js | 39 ++- .../user/monitored-trip/trip-basics-pane.tsx | 241 +++++++++++------- .../util/formatted-validation-error.js | 4 + lib/util/config-types.ts | 1 + 5 files changed, 188 insertions(+), 98 deletions(-) diff --git a/i18n/en-US.yml b/i18n/en-US.yml index 1b13a37da..5df0782de 100644 --- a/i18n/en-US.yml +++ b/i18n/en-US.yml @@ -534,6 +534,7 @@ components: SavedTripScreen: itineraryLoaded: Itinerary loaded itineraryLoading: Loading itinerary + selectAtLeastOneDay: Please select at least one day to monitor. tooManyTrips: > You already have reached the maximum of five saved trips. Please remove unused trips from your saved trips, and try again. diff --git a/lib/components/user/monitored-trip/saved-trip-screen.js b/lib/components/user/monitored-trip/saved-trip-screen.js index 14ab18f8b..d0b7f1c1f 100644 --- a/lib/components/user/monitored-trip/saved-trip-screen.js +++ b/lib/components/user/monitored-trip/saved-trip-screen.js @@ -149,8 +149,14 @@ class SavedTripScreen extends Component { } render() { - const { isCreating, itinerary, loggedInUser, monitoredTrips, pending } = - this.props + const { + disableSingleItineraryDays, + isCreating, + itinerary, + loggedInUser, + monitoredTrips, + pending + } = this.props const isAwaiting = !monitoredTrips || (isCreating && pending) let screenContents @@ -176,7 +182,32 @@ class SavedTripScreen extends Component { // Text constant is used to allow format.js command line tool to keep track of // which IDs are in the code. .notOneOf(otherTripNames, 'trip-name-already-used') - const validationSchema = yup.object(clonedSchemaShape) + const validationSchema = yup + .object(clonedSchemaShape) + // If disableSingleItineraryDays is true, test to see if at least one day checkbox is checked + .test('dayofweek', 'Please select one day', function (obj) { + if ( + obj.monday || + obj.tuesday || + obj.wednesday || + obj.thursday || + obj.friday || + obj.saturday || + obj.sunday || + !disableSingleItineraryDays + ) { + return true + } + + /* Hack: because the selected days values are not grouped, we need to assign this error to one of the + checkboxes so that form validates correctly. Monday makes sure the focus is on the first checkbox. */ + + return new yup.ValidationError( + 'Please select at least one day to monitor', + obj.monday, + 'monday' + ) + }) screenContents = ( { const pending = activeSearch ? Boolean(activeSearch.pending) : false const itineraries = getActiveItineraries(state) || [] const tripId = ownProps.match.params.id + const { disableSingleItineraryDays } = state.otp.config return { activeSearchId: state.otp.activeSearchId, + disableSingleItineraryDays, homeTimezone: state.otp.config.homeTimezone, isCreating: tripId === 'new', itinerary: itineraries[activeItinerary], diff --git a/lib/components/user/monitored-trip/trip-basics-pane.tsx b/lib/components/user/monitored-trip/trip-basics-pane.tsx index 9d98eedd1..a6a810b23 100644 --- a/lib/components/user/monitored-trip/trip-basics-pane.tsx +++ b/lib/components/user/monitored-trip/trip-basics-pane.tsx @@ -46,6 +46,7 @@ type TripBasicsProps = WrappedComponentProps & intl: IntlShape ) => void clearItineraryExistence: () => void + disableSingleItineraryDays: boolean | undefined isCreating: boolean itineraryExistence?: ItineraryExistence } @@ -200,6 +201,99 @@ class TripBasicsPane extends Component { } } + RenderAvailableDays = ({ + errorCheckingTrip, + errorSelectingDays, + finalItineraryExistence, + intl, + isCreating, + monitoredTrip + }: { + errorCheckingTrip: boolean + errorSelectingDays?: 'error' | null + finalItineraryExistence: ItineraryExistence | undefined + intl: IntlShape + isCreating: boolean + monitoredTrip: MonitoredTrip + }) => ( + <> + {errorCheckingTrip && ( + <> + {/* FIXME: Temporary solution until itinerary existence check is fixed. */} +
+ + + )} + + <> + {ALL_DAYS.map((day) => { + const isDayDisabled = isDisabled(day, finalItineraryExistence) + const labelClass = isDayDisabled ? 'disabled-day' : '' + const notAvailableText = isDayDisabled + ? intl.formatMessage( + { + id: 'components.TripBasicsPane.tripNotAvailableOnDay' + }, + { + repeatedDay: getFormattedDayOfWeekPlural(day, intl) + } + ) + : '' + + const baseColor = getBaseColor() + return ( + + + + + {notAvailableText} + + ) + })} + + + + + {finalItineraryExistence ? ( + + ) : ( + + } + now={100} + /> + )} + + + {errorSelectingDays && ( + + )} + + + ) + componentDidMount() { // Check itinerary availability (existence) for all days if not already done. const { checkItineraryExistence, intl, values: monitoredTrip } = this.props @@ -220,6 +314,7 @@ class TripBasicsPane extends Component { const { canceled, dirty, + disableSingleItineraryDays, errors, intl, isCreating, @@ -257,6 +352,9 @@ class TripBasicsPane extends Component { const errorCheckingTrip = ALL_DAYS.every((day) => isDisabled(day, finalItineraryExistence) ) + /* Hack: because the selected days checkboxes are not grouped, we need to assign this error to one of the + checkboxes so that the FormikErrorFocus works. */ + const selectOneDayError = errorStates.monday return (
{/* TODO: This component does not block navigation on reload or using the back button. @@ -286,104 +384,55 @@ class TripBasicsPane extends Component { )} - - - - - - - - {errorCheckingTrip && ( + {disableSingleItineraryDays ? ( + + + + + + + ) : ( + + + + + + + + {!isOneTime && ( <> - {/* FIXME: Temporary solution until itinerary existence check is fixed. */} -
- + )} -
- {!isOneTime && ( - <> - - {ALL_DAYS.map((day) => { - const isDayDisabled = isDisabled( - day, - finalItineraryExistence - ) - const labelClass = isDayDisabled ? 'disabled-day' : '' - const notAvailableText = isDayDisabled - ? intl.formatMessage( - { - id: 'components.TripBasicsPane.tripNotAvailableOnDay' - }, - { - repeatedDay: getFormattedDayOfWeekPlural(day, intl) - } - ) - : '' - - const baseColor = getBaseColor() - return ( - - - - - - {notAvailableText} - - - ) - })} - - - {finalItineraryExistence ? ( - - ) : ( - - } - now={100} - /> - )} - - - )} - - - - - {/* Scroll to the trip name/days fields if submitting and there is an error on these fields. */} - -
+ + + + + )} + + {/* Scroll to the trip name/days fields if submitting and there is an error on these fields. */} +
) } @@ -394,7 +443,9 @@ class TripBasicsPane extends Component { const mapStateToProps = (state: AppReduxState) => { const { itineraryExistence } = state.user + const { disableSingleItineraryDays } = state.otp.config return { + disableSingleItineraryDays, itineraryExistence } } diff --git a/lib/components/util/formatted-validation-error.js b/lib/components/util/formatted-validation-error.js index 2bf08ecb2..9b836207f 100644 --- a/lib/components/util/formatted-validation-error.js +++ b/lib/components/util/formatted-validation-error.js @@ -28,6 +28,10 @@ export default function FormattedValidationError({ type }) { return ( ) + case 'select-at-least-one-day': + return ( + + ) default: return null } diff --git a/lib/util/config-types.ts b/lib/util/config-types.ts index 120bf1bad..8f8d43552 100644 --- a/lib/util/config-types.ts +++ b/lib/util/config-types.ts @@ -375,6 +375,7 @@ export interface AppConfig { bugsnag?: BugsnagConfig co2?: CO2Config companies?: Company[] + disableSingleItineraryDays?: boolean elevationProfile?: boolean extraMenuItems?: AppMenuItemConfig[] geocoder: GeocoderConfig From 63233a8f4b586c3cb02ffc6016b053aad5b4488e Mon Sep 17 00:00:00 2001 From: Amy Corson <115499534+amy-corson-ibigroup@users.noreply.github.com> Date: Fri, 27 Sep 2024 15:17:10 -0500 Subject: [PATCH 2/5] Update lib/components/user/monitored-trip/trip-basics-pane.tsx Co-authored-by: Binh Dam <56846598+binh-dam-ibigroup@users.noreply.github.com> --- lib/components/user/monitored-trip/trip-basics-pane.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/components/user/monitored-trip/trip-basics-pane.tsx b/lib/components/user/monitored-trip/trip-basics-pane.tsx index a6a810b23..28561000c 100644 --- a/lib/components/user/monitored-trip/trip-basics-pane.tsx +++ b/lib/components/user/monitored-trip/trip-basics-pane.tsx @@ -46,7 +46,7 @@ type TripBasicsProps = WrappedComponentProps & intl: IntlShape ) => void clearItineraryExistence: () => void - disableSingleItineraryDays: boolean | undefined + disableSingleItineraryDays?: boolean isCreating: boolean itineraryExistence?: ItineraryExistence } From 5b0b98662afc215373192ea41086301bef40e224 Mon Sep 17 00:00:00 2001 From: amy-corson-ibigroup <115499534+amy-corson-ibigroup@users.noreply.github.com> Date: Fri, 27 Sep 2024 16:52:11 -0500 Subject: [PATCH 3/5] refactor: code cleanup --- example-config.yml | 2 + .../user/monitored-trip/trip-basics-pane.tsx | 192 +++++++++--------- 2 files changed, 96 insertions(+), 98 deletions(-) diff --git a/example-config.yml b/example-config.yml index 0312aa3ac..79a242954 100644 --- a/example-config.yml +++ b/example-config.yml @@ -436,6 +436,8 @@ itinerary: advancedSettingsPanel: # Show button in advanced panel that allows users to save and return saveAndReturnButton: true +# Prevent users from selecting a single day for saving trips. +disableSingleItineraryDays: false # The transitOperators key is a list of transit operators that can be used to # order transit agencies when sorting by route. Also, this can optionally diff --git a/lib/components/user/monitored-trip/trip-basics-pane.tsx b/lib/components/user/monitored-trip/trip-basics-pane.tsx index 28561000c..1fe2e9ac7 100644 --- a/lib/components/user/monitored-trip/trip-basics-pane.tsx +++ b/lib/components/user/monitored-trip/trip-basics-pane.tsx @@ -9,7 +9,7 @@ import { Radio } from 'react-bootstrap' import { Field, FormikProps } from 'formik' -import { FormattedMessage, injectIntl } from 'react-intl' +import { FormattedMessage, injectIntl, useIntl } from 'react-intl' import { Prompt } from 'react-router' // @ts-expect-error FormikErrorFocus does not support TypeScript yet. import FormikErrorFocus from 'formik-error-focus' @@ -133,6 +133,97 @@ function isDisabled(day: string, itineraryExistence?: ItineraryExistence) { return itineraryExistence && !itineraryExistence[day]?.valid } +const RenderAvailableDays = ({ + errorCheckingTrip, + errorSelectingDays, + finalItineraryExistence, + isCreating, + monitoredTrip +}: { + errorCheckingTrip: boolean + errorSelectingDays?: 'error' | null + finalItineraryExistence: ItineraryExistence | undefined + isCreating: boolean + monitoredTrip: MonitoredTrip +}) => { + const intl = useIntl() + const baseColor = getBaseColor() + return ( + <> + {errorCheckingTrip && ( + <> + {/* FIXME: Temporary solution until itinerary existence check is fixed. */} +
+ + + )} + + {ALL_DAYS.map((day) => { + const isDayDisabled = isDisabled(day, finalItineraryExistence) + const labelClass = isDayDisabled ? 'disabled-day' : '' + const notAvailableText = isDayDisabled + ? intl.formatMessage( + { + id: 'components.TripBasicsPane.tripNotAvailableOnDay' + }, + { + repeatedDay: getFormattedDayOfWeekPlural(day, intl) + } + ) + : '' + + return ( + + + + + {notAvailableText} + + ) + })} + + + {finalItineraryExistence ? ( + + ) : ( + + } + now={100} + /> + )} + + + {errorSelectingDays && ( + + )} + + + ) +} + /** * This component shows summary information for a trip * and lets the user edit the trip name and day. @@ -201,99 +292,6 @@ class TripBasicsPane extends Component { } } - RenderAvailableDays = ({ - errorCheckingTrip, - errorSelectingDays, - finalItineraryExistence, - intl, - isCreating, - monitoredTrip - }: { - errorCheckingTrip: boolean - errorSelectingDays?: 'error' | null - finalItineraryExistence: ItineraryExistence | undefined - intl: IntlShape - isCreating: boolean - monitoredTrip: MonitoredTrip - }) => ( - <> - {errorCheckingTrip && ( - <> - {/* FIXME: Temporary solution until itinerary existence check is fixed. */} -
- - - )} - - <> - {ALL_DAYS.map((day) => { - const isDayDisabled = isDisabled(day, finalItineraryExistence) - const labelClass = isDayDisabled ? 'disabled-day' : '' - const notAvailableText = isDayDisabled - ? intl.formatMessage( - { - id: 'components.TripBasicsPane.tripNotAvailableOnDay' - }, - { - repeatedDay: getFormattedDayOfWeekPlural(day, intl) - } - ) - : '' - - const baseColor = getBaseColor() - return ( - - - - - {notAvailableText} - - ) - })} - - - - - {finalItineraryExistence ? ( - - ) : ( - - } - now={100} - /> - )} - - - {errorSelectingDays && ( - - )} - - - ) - componentDidMount() { // Check itinerary availability (existence) for all days if not already done. const { checkItineraryExistence, intl, values: monitoredTrip } = this.props @@ -389,11 +387,10 @@ class TripBasicsPane extends Component { - @@ -413,10 +410,9 @@ class TripBasicsPane extends Component { {!isOneTime && ( <> - From ebdf73a0e2fc43430767cb53857e2c9d0f309442 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Mon, 30 Sep 2024 10:23:09 -0400 Subject: [PATCH 4/5] chore(i18n): Add matching FR text --- i18n/fr.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/i18n/fr.yml b/i18n/fr.yml index 741ad9f2c..5f0f2187f 100644 --- a/i18n/fr.yml +++ b/i18n/fr.yml @@ -559,6 +559,7 @@ components: SavedTripScreen: itineraryLoaded: Trajet chargé itineraryLoading: Chargement du trajet + selectAtLeastOneDay: Veuillez choisir au moins un jour pour le suivi. tooManyTrips: > Vous avez déjà atteint le nombre maximum de 5 trajets enregistrés. Veuillez supprimer les trajets enregistrés qui sont inutilisés, puis From 4c3c5d57ed79a898ea2e77b17119c1c22338cca6 Mon Sep 17 00:00:00 2001 From: amy-corson-ibigroup <115499534+amy-corson-ibigroup@users.noreply.github.com> Date: Mon, 30 Sep 2024 15:09:44 -0500 Subject: [PATCH 5/5] clean up prop types --- lib/components/user/monitored-trip/trip-basics-pane.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/components/user/monitored-trip/trip-basics-pane.tsx b/lib/components/user/monitored-trip/trip-basics-pane.tsx index 1fe2e9ac7..dafafcd00 100644 --- a/lib/components/user/monitored-trip/trip-basics-pane.tsx +++ b/lib/components/user/monitored-trip/trip-basics-pane.tsx @@ -142,7 +142,7 @@ const RenderAvailableDays = ({ }: { errorCheckingTrip: boolean errorSelectingDays?: 'error' | null - finalItineraryExistence: ItineraryExistence | undefined + finalItineraryExistence?: ItineraryExistence isCreating: boolean monitoredTrip: MonitoredTrip }) => {