99 Radio
1010} from 'react-bootstrap'
1111import { Field , FormikProps } from 'formik'
12- import { FormattedMessage , injectIntl } from 'react-intl'
12+ import { FormattedMessage , injectIntl , useIntl } from 'react-intl'
1313import { Prompt } from 'react-router'
1414// @ts -expect-error FormikErrorFocus does not support TypeScript yet.
1515import FormikErrorFocus from 'formik-error-focus'
@@ -46,6 +46,7 @@ type TripBasicsProps = WrappedComponentProps &
4646 intl : IntlShape
4747 ) => void
4848 clearItineraryExistence : ( ) => void
49+ disableSingleItineraryDays ?: boolean
4950 isCreating : boolean
5051 itineraryExistence ?: ItineraryExistence
5152 }
@@ -132,6 +133,97 @@ function isDisabled(day: string, itineraryExistence?: ItineraryExistence) {
132133 return itineraryExistence && ! itineraryExistence [ day ] ?. valid
133134}
134135
136+ const RenderAvailableDays = ( {
137+ errorCheckingTrip,
138+ errorSelectingDays,
139+ finalItineraryExistence,
140+ isCreating,
141+ monitoredTrip
142+ } : {
143+ errorCheckingTrip : boolean
144+ errorSelectingDays ?: 'error' | null
145+ finalItineraryExistence ?: ItineraryExistence
146+ isCreating : boolean
147+ monitoredTrip : MonitoredTrip
148+ } ) => {
149+ const intl = useIntl ( )
150+ const baseColor = getBaseColor ( )
151+ return (
152+ < >
153+ { errorCheckingTrip && (
154+ < >
155+ { /* FIXME: Temporary solution until itinerary existence check is fixed. */ }
156+ < br />
157+ < FormattedMessage id = "actions.user.itineraryExistenceCheckFailed" />
158+ </ >
159+ ) }
160+ < AvailableDays >
161+ { ALL_DAYS . map ( ( day ) => {
162+ const isDayDisabled = isDisabled ( day , finalItineraryExistence )
163+ const labelClass = isDayDisabled ? 'disabled-day' : ''
164+ const notAvailableText = isDayDisabled
165+ ? intl . formatMessage (
166+ {
167+ id : 'components.TripBasicsPane.tripNotAvailableOnDay'
168+ } ,
169+ {
170+ repeatedDay : getFormattedDayOfWeekPlural ( day , intl )
171+ }
172+ )
173+ : ''
174+
175+ return (
176+ < MonitoredDayCircle
177+ baseColor = { baseColor }
178+ key = { day }
179+ monitored = { ! isDayDisabled && monitoredTrip [ day ] }
180+ title = { notAvailableText }
181+ >
182+ < Field
183+ // Let users save an existing trip, even though it may not be available on some days.
184+ // TODO: improve checking trip availability.
185+ disabled = { isDayDisabled && isCreating }
186+ id = { day }
187+ name = { day }
188+ type = "checkbox"
189+ />
190+ < Ban aria-hidden />
191+ < label htmlFor = { day } >
192+ < InvisibleA11yLabel >
193+ < FormattedDayOfWeek day = { day } />
194+ </ InvisibleA11yLabel >
195+ < span aria-hidden className = { labelClass } >
196+ { /* The abbreviated text is visual only. Screen readers should read out the full day. */ }
197+ < FormattedDayOfWeekCompact day = { day } />
198+ </ span >
199+ </ label >
200+ < InvisibleA11yLabel > { notAvailableText } </ InvisibleA11yLabel >
201+ </ MonitoredDayCircle >
202+ )
203+ } ) }
204+ </ AvailableDays >
205+ < HelpBlock role = "status" >
206+ { finalItineraryExistence ? (
207+ < FormattedMessage id = "components.TripBasicsPane.tripIsAvailableOnDaysIndicated" />
208+ ) : (
209+ < ProgressBar
210+ active
211+ label = {
212+ < FormattedMessage id = "components.TripBasicsPane.checkingItineraryExistence" />
213+ }
214+ now = { 100 }
215+ />
216+ ) }
217+ </ HelpBlock >
218+ < HelpBlock role = "alert" >
219+ { errorSelectingDays && (
220+ < FormattedValidationError type = "select-at-least-one-day" />
221+ ) }
222+ </ HelpBlock >
223+ </ >
224+ )
225+ }
226+
135227/**
136228 * This component shows summary information for a trip
137229 * and lets the user edit the trip name and day.
@@ -220,6 +312,7 @@ class TripBasicsPane extends Component<TripBasicsProps, State> {
220312 const {
221313 canceled,
222314 dirty,
315+ disableSingleItineraryDays,
223316 errors,
224317 intl,
225318 isCreating,
@@ -257,6 +350,9 @@ class TripBasicsPane extends Component<TripBasicsProps, State> {
257350 const errorCheckingTrip = ALL_DAYS . every ( ( day ) =>
258351 isDisabled ( day , finalItineraryExistence )
259352 )
353+ /* Hack: because the selected days checkboxes are not grouped, we need to assign this error to one of the
354+ checkboxes so that the FormikErrorFocus works. */
355+ const selectOneDayError = errorStates . monday
260356 return (
261357 < div >
262358 { /* TODO: This component does not block navigation on reload or using the back button.
@@ -286,104 +382,53 @@ class TripBasicsPane extends Component<TripBasicsProps, State> {
286382 ) }
287383 </ HelpBlock >
288384 </ FormGroup >
289-
290- < FormGroup >
291- < ControlLabel >
292- < FormattedMessage id = "components.TripBasicsPane.tripDaysPrompt" />
293- </ ControlLabel >
294- < Radio
295- checked = { ! isOneTime }
296- // FIXME: Temporary solution until itinerary existence check is fixed.
297- disabled = { errorCheckingTrip }
298- onChange = { this . _handleRecurringTrip }
299- >
300- < FormattedMessage id = "components.TripBasicsPane.recurringEachWeek" />
301- { errorCheckingTrip && (
385+ { disableSingleItineraryDays ? (
386+ < FormGroup validationState = { selectOneDayError } >
387+ < ControlLabel >
388+ < FormattedMessage id = "components.TripBasicsPane.tripDaysPrompt" />
389+ </ ControlLabel >
390+ < RenderAvailableDays
391+ errorCheckingTrip = { errorCheckingTrip }
392+ errorSelectingDays = { selectOneDayError }
393+ finalItineraryExistence = { finalItineraryExistence }
394+ isCreating = { isCreating }
395+ monitoredTrip = { monitoredTrip }
396+ />
397+ </ FormGroup >
398+ ) : (
399+ < FormGroup >
400+ < ControlLabel >
401+ < FormattedMessage id = "components.TripBasicsPane.tripDaysPrompt" />
402+ </ ControlLabel >
403+ < Radio
404+ checked = { ! isOneTime }
405+ // FIXME: Temporary solution until itinerary existence check is fixed.
406+ disabled = { errorCheckingTrip }
407+ onChange = { this . _handleRecurringTrip }
408+ >
409+ < FormattedMessage id = "components.TripBasicsPane.recurringEachWeek" />
410+ </ Radio >
411+ { ! isOneTime && (
302412 < >
303- { /* FIXME: Temporary solution until itinerary existence check is fixed. */ }
304- < br />
305- < FormattedMessage id = "actions.user.itineraryExistenceCheckFailed" />
413+ < RenderAvailableDays
414+ errorCheckingTrip = { errorCheckingTrip }
415+ finalItineraryExistence = { finalItineraryExistence }
416+ isCreating = { isCreating }
417+ monitoredTrip = { monitoredTrip }
418+ />
306419 </ >
307420 ) }
308- </ Radio >
309- { ! isOneTime && (
310- < >
311- < AvailableDays >
312- { ALL_DAYS . map ( ( day ) => {
313- const isDayDisabled = isDisabled (
314- day ,
315- finalItineraryExistence
316- )
317- const labelClass = isDayDisabled ? 'disabled-day' : ''
318- const notAvailableText = isDayDisabled
319- ? intl . formatMessage (
320- {
321- id : 'components.TripBasicsPane.tripNotAvailableOnDay'
322- } ,
323- {
324- repeatedDay : getFormattedDayOfWeekPlural ( day , intl )
325- }
326- )
327- : ''
328-
329- const baseColor = getBaseColor ( )
330- return (
331- < MonitoredDayCircle
332- baseColor = { baseColor }
333- key = { day }
334- monitored = { ! isDayDisabled && monitoredTrip [ day ] }
335- title = { notAvailableText }
336- >
337- < Field
338- // Let users save an existing trip, even though it may not be available on some days.
339- // TODO: improve checking trip availability.
340- disabled = { isDayDisabled && isCreating }
341- id = { day }
342- name = { day }
343- type = "checkbox"
344- />
345- < Ban aria-hidden />
346- < label htmlFor = { day } >
347- < InvisibleA11yLabel >
348- < FormattedDayOfWeek day = { day } />
349- </ InvisibleA11yLabel >
350- < span aria-hidden className = { labelClass } >
351- { /* The abbreviated text is visual only. Screen readers should read out the full day. */ }
352- < FormattedDayOfWeekCompact day = { day } />
353- </ span >
354- </ label >
355- < InvisibleA11yLabel >
356- { notAvailableText }
357- </ InvisibleA11yLabel >
358- </ MonitoredDayCircle >
359- )
360- } ) }
361- </ AvailableDays >
362- < HelpBlock role = "status" >
363- { finalItineraryExistence ? (
364- < FormattedMessage id = "components.TripBasicsPane.tripIsAvailableOnDaysIndicated" />
365- ) : (
366- < ProgressBar
367- active
368- label = {
369- < FormattedMessage id = "components.TripBasicsPane.checkingItineraryExistence" />
370- }
371- now = { 100 }
372- />
373- ) }
374- </ HelpBlock >
375- </ >
376- ) }
377- < Radio checked = { isOneTime } onChange = { this . _handleOneTimeTrip } >
378- < FormattedMessage
379- id = "components.TripBasicsPane.onlyOnDate"
380- values = { { date : itinerary . startTime } }
381- />
382- </ Radio >
383-
384- { /* Scroll to the trip name/days fields if submitting and there is an error on these fields. */ }
385- < FormikErrorFocus align = "middle" duration = { 200 } />
386- </ FormGroup >
421+ < Radio checked = { isOneTime } onChange = { this . _handleOneTimeTrip } >
422+ < FormattedMessage
423+ id = "components.TripBasicsPane.onlyOnDate"
424+ values = { { date : itinerary . startTime } }
425+ />
426+ </ Radio >
427+ </ FormGroup >
428+ ) }
429+
430+ { /* Scroll to the trip name/days fields if submitting and there is an error on these fields. */ }
431+ < FormikErrorFocus align = "middle" duration = { 200 } />
387432 </ div >
388433 )
389434 }
@@ -394,7 +439,9 @@ class TripBasicsPane extends Component<TripBasicsProps, State> {
394439
395440const mapStateToProps = ( state : AppReduxState ) => {
396441 const { itineraryExistence } = state . user
442+ const { disableSingleItineraryDays } = state . otp . config
397443 return {
444+ disableSingleItineraryDays,
398445 itineraryExistence
399446 }
400447}
0 commit comments