From 20047af21b3835b311c83123222f0104821e4195 Mon Sep 17 00:00:00 2001 From: amy-corson-ibigroup <115499534+amy-corson-ibigroup@users.noreply.github.com> Date: Tue, 26 Nov 2024 18:06:31 -0600 Subject: [PATCH 1/6] feat: add network connection status to ui and redux --- i18n/en-US.yml | 2 + lib/actions/ui.js | 3 + lib/components/app/desktop-nav.tsx | 8 ++ .../app/network-connection-banner.tsx | 78 +++++++++++++++++++ lib/components/app/responsive-webapp.js | 6 +- lib/components/mobile/navigation-bar.js | 9 ++- lib/reducers/create-otp-reducer.js | 12 +++ 7 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 lib/components/app/network-connection-banner.tsx diff --git a/i18n/en-US.yml b/i18n/en-US.yml index e3f9c8025..9bb360c76 100644 --- a/i18n/en-US.yml +++ b/i18n/en-US.yml @@ -183,6 +183,8 @@ components: closeMenu: Close Menu fieldTrip: Field Trip mailables: Mailables + networkConnectionLost: Network connection lost + networkConnectionRestored: Network connection restored openMenu: Open Menu skipNavigation: Skip navigation BackToTripPlanner: diff --git a/lib/actions/ui.js b/lib/actions/ui.js index cdd0691da..a548a43bc 100644 --- a/lib/actions/ui.js +++ b/lib/actions/ui.js @@ -47,6 +47,9 @@ const viewRoute = createAction('SET_VIEWED_ROUTE') export const toggleAutoRefresh = createAction('TOGGLE_AUTO_REFRESH') const settingItineraryView = createAction('SET_ITINERARY_VIEW') export const setPopupContent = createAction('SET_POPUP_CONTENT') +export const setNetworkConnectionLost = createAction( + 'SET_NETWORK_CONNECTION_LOST' +) // This code-less action calls the reducer code // and thus resets the session timeout. diff --git a/lib/components/app/desktop-nav.tsx b/lib/components/app/desktop-nav.tsx index f9a265b86..ef692d88c 100644 --- a/lib/components/app/desktop-nav.tsx +++ b/lib/components/app/desktop-nav.tsx @@ -13,6 +13,7 @@ import { DEFAULT_APP_TITLE } from '../../util/constants' import InvisibleA11yLabel from '../util/invisible-a11y-label' import NavLoginButtonAuth0 from '../user/nav-login-button-auth0' +import { NetworkConnectionBanner } from './network-connection-banner' import AppMenu, { Icon } from './app-menu' import LocaleSelector from './locale-selector' import NavbarItem from './nav-item' @@ -63,6 +64,7 @@ const NavItemOnLargeScreens = styled(NavbarItem)` // Typscript TODO: otpConfig type export type Props = { locale: string + networkConnectionLost: boolean otpConfig: AppConfig popupTarget?: string setPopupContent: (url: string) => void @@ -83,6 +85,7 @@ export type Props = { */ const DesktopNav = ({ locale, + networkConnectionLost, otpConfig, popupTarget, setPopupContent @@ -117,6 +120,8 @@ const DesktopNav = ({ id: `config.popups.${popupTarget}` }) + console.log(networkConnectionLost) + return (
@@ -166,6 +171,7 @@ const DesktopNav = ({ +
) } @@ -173,8 +179,10 @@ const DesktopNav = ({ // connect to the redux store const mapStateToProps = (state: AppReduxState) => { const { config: otpConfig } = state.otp + const { networkConnectionLost } = state.otp.ui.errors return { locale: state.otp.ui.locale, + networkConnectionLost, otpConfig, popupTarget: otpConfig.popups?.launchers?.toolbar } diff --git a/lib/components/app/network-connection-banner.tsx b/lib/components/app/network-connection-banner.tsx new file mode 100644 index 000000000..9ea7de51b --- /dev/null +++ b/lib/components/app/network-connection-banner.tsx @@ -0,0 +1,78 @@ +import { CSSTransition, TransitionGroup } from 'react-transition-group' +import { FormattedMessage } from 'react-intl' +import React, { useRef } from 'react' +import styled from 'styled-components' + +import { RED_ON_WHITE } from '../util/colors' + +const containerClassname = 'network-connection-banner' +const timeout = 200 + +const TransitionStyles = styled.div` + .${containerClassname} { + background: ${RED_ON_WHITE}; + border-left: 1px solid #e7e7e7; + border-right: 1px solid #e7e7e7; + color: #fff; + font-weight: 600; + padding: 5px; + position: absolute; + text-align: center; + top: 50px; + width: 100%; + z-index: 1; + } + .${containerClassname}-enter { + opacity: 0; + transform: translateY(-100%); + } + .${containerClassname}-enter-active { + opacity: 1; + transform: translateY(0); + transition: opacity ${timeout}ms ease-in; + } + .${containerClassname}-exit { + opacity: 1; + transform: translateY(0); + } + .${containerClassname}-exit-active { + opacity: 0; + transform: translateY(-100%); + transition: opacity ${timeout}ms ease-in, transform ${timeout}ms ease-in; + } +` +export const NetworkConnectionLostBanner = styled.div`` + +export const NetworkConnectionBanner = ({ + networkConnectionLost +}: { + networkConnectionLost: boolean +}): JSX.Element => { + const connectionLostBannerRef = useRef(null) + return ( + + + {networkConnectionLost && ( + + + {networkConnectionLost ? ( + + ) : ( + + )} + + + )} + + + ) +} diff --git a/lib/components/app/responsive-webapp.js b/lib/components/app/responsive-webapp.js index 2b2a44dfc..15e5d4a22 100644 --- a/lib/components/app/responsive-webapp.js +++ b/lib/components/app/responsive-webapp.js @@ -154,10 +154,13 @@ class ResponsiveWebapp extends Component { map, matchContentToUrl, parseUrlQueryString, - receivedPositionResponse + receivedPositionResponse, + setNetworkConnectionLost } = this.props // Add on back button press behavior. window.addEventListener('popstate', handleBackButtonPress) + window.addEventListener('online', () => setNetworkConnectionLost(false)) + window.addEventListener('offline', () => setNetworkConnectionLost(true)) // If a URL is detected without hash routing (e.g., http://localhost:9966?sessionId=test), // window.location.search will have a value. In this case, we need to redirect to the URL root with the @@ -441,6 +444,7 @@ const mapStateToWrapperProps = (state) => { const mapWrapperDispatchToProps = { processSignIn: authActions.processSignIn, setLocale: uiActions.setLocale, + setNetworkConnectionLost: uiActions.setNetworkConnectionLost, showAccessTokenError: authActions.showAccessTokenError, showLoginError: authActions.showLoginError } diff --git a/lib/components/mobile/navigation-bar.js b/lib/components/mobile/navigation-bar.js index 4426a244c..eaa071f3d 100644 --- a/lib/components/mobile/navigation-bar.js +++ b/lib/components/mobile/navigation-bar.js @@ -9,6 +9,7 @@ import * as uiActions from '../../actions/ui' import { accountLinks, getAuth0Config } from '../../util/auth' import { ComponentContext } from '../../util/contexts' import { injectIntl } from 'react-intl' +import { NetworkConnectionBanner } from '../app/network-connection-banner' import { StyledIconWrapper } from '../util/styledIcon' import AppMenu from '../app/app-menu' import LocaleSelector from '../app/locale-selector' @@ -32,6 +33,7 @@ class MobileNavigationBar extends Component { headerText: PropTypes.string, intl: PropTypes.object, locale: PropTypes.string, + networkConnectionLost: PropTypes.bool, onBackClicked: PropTypes.func, setMobileScreen: PropTypes.func, showBackButton: PropTypes.bool @@ -55,6 +57,7 @@ class MobileNavigationBar extends Component { headerText, intl, locale, + networkConnectionLost, showBackButton } = this.props @@ -111,6 +114,9 @@ class MobileNavigationBar extends Component { )} + ) } @@ -123,7 +129,8 @@ const mapStateToProps = (state) => { auth0Config: getAuth0Config(state.otp.config.persistence), configLanguages: state.otp.config.language, extraMenuItems: state.otp?.config?.extraMenuItems, - locale: state.otp.ui.locale + locale: state.otp.ui.locale, + networkConnectionLost: state.otp.ui.errors.networkConnectionLost } } diff --git a/lib/reducers/create-otp-reducer.js b/lib/reducers/create-otp-reducer.js index e753c466c..0028f5095 100644 --- a/lib/reducers/create-otp-reducer.js +++ b/lib/reducers/create-otp-reducer.js @@ -225,6 +225,9 @@ export function getInitialState(userDefinedConfig) { }, ui: { diagramLeg: null, + errors: { + networkConnectionLost: !navigator.onLine + }, locale: null, localizedMessages: null, mainPanelContent: null, @@ -1108,6 +1111,15 @@ function createOtpReducer(config) { } } }) + case 'SET_NETWORK_CONNECTION_LOST': + console.log(action.payload) + return update(state, { + ui: { + errors: { + networkConnectionLost: { $set: action.payload } + } + } + }) case 'SERVICE_TIME_RANGE_REQUEST': return update(state, { serviceTimeRange: { $set: { pending: true } } From 3fcf0592f4358959139a0c497707064f3f309c61 Mon Sep 17 00:00:00 2001 From: amy-corson-ibigroup <115499534+amy-corson-ibigroup@users.noreply.github.com> Date: Mon, 2 Dec 2024 13:17:10 -0600 Subject: [PATCH 2/6] tweak styling and create status region --- lib/components/app/app.css | 3 +++ lib/components/app/desktop-nav.tsx | 11 ++++++++--- .../app/network-connection-banner.tsx | 19 +++++++++++-------- lib/components/mobile/batch-search-screen.tsx | 5 +++-- lib/reducers/create-otp-reducer.js | 1 - 5 files changed, 25 insertions(+), 14 deletions(-) diff --git a/lib/components/app/app.css b/lib/components/app/app.css index 87fe44222..e5d39188a 100644 --- a/lib/components/app/app.css +++ b/lib/components/app/app.css @@ -108,6 +108,9 @@ height: 100%; width: 100%; } +.otp .navbar { + z-index: 25; +} /** These changes kick in when the map is hidden. These rules ensure that the entire "sidebar" (which at this point fills the entire screen) scrolls as a single unit. diff --git a/lib/components/app/desktop-nav.tsx b/lib/components/app/desktop-nav.tsx index ef692d88c..a92cd9df5 100644 --- a/lib/components/app/desktop-nav.tsx +++ b/lib/components/app/desktop-nav.tsx @@ -1,7 +1,7 @@ import { connect } from 'react-redux' +import { FormattedMessage, useIntl } from 'react-intl' import { isMobile } from '@opentripplanner/core-utils/lib/ui' import { Nav, Navbar } from 'react-bootstrap' -import { useIntl } from 'react-intl' import React from 'react' import styled from 'styled-components' @@ -120,8 +120,6 @@ const DesktopNav = ({ id: `config.popups.${popupTarget}` }) - console.log(networkConnectionLost) - return (
@@ -171,6 +169,13 @@ const DesktopNav = ({ + + {networkConnectionLost ? ( + + ) : ( + + )} +
) diff --git a/lib/components/app/network-connection-banner.tsx b/lib/components/app/network-connection-banner.tsx index 9ea7de51b..50c4924a0 100644 --- a/lib/components/app/network-connection-banner.tsx +++ b/lib/components/app/network-connection-banner.tsx @@ -6,7 +6,7 @@ import styled from 'styled-components' import { RED_ON_WHITE } from '../util/colors' const containerClassname = 'network-connection-banner' -const timeout = 200 +const timeout = 250 const TransitionStyles = styled.div` .${containerClassname} { @@ -20,7 +20,12 @@ const TransitionStyles = styled.div` text-align: center; top: 50px; width: 100%; - z-index: 1; + // When banner is fully loaded, set z-index higher than nav so we're not seeing the nav border. + z-index: 26; + + @media (max-width: 768px) { + border: 0; + } } .${containerClassname}-enter { opacity: 0; @@ -34,11 +39,13 @@ const TransitionStyles = styled.div` .${containerClassname}-exit { opacity: 1; transform: translateY(0); + z-index: 20; } .${containerClassname}-exit-active { opacity: 0; transform: translateY(-100%); transition: opacity ${timeout}ms ease-in, transform ${timeout}ms ease-in; + z-index: 20; } ` export const NetworkConnectionLostBanner = styled.div`` @@ -62,13 +69,9 @@ export const NetworkConnectionBanner = ({ aria-live="assertive" className={containerClassname} ref={connectionLostBannerRef} - role="status" + role="alert" > - {networkConnectionLost ? ( - - ) : ( - - )} + )} diff --git a/lib/components/mobile/batch-search-screen.tsx b/lib/components/mobile/batch-search-screen.tsx index 5be2cc8c7..04e2a850c 100644 --- a/lib/components/mobile/batch-search-screen.tsx +++ b/lib/components/mobile/batch-search-screen.tsx @@ -40,8 +40,9 @@ const MobileSearchSettings = styled.div<{ top: 50px; transition: ${(props) => `all ${props.transitionDuration}ms ease`}; transition-delay: ${(props) => props.transitionDelay}ms; - /* Must appear under the 'hamburger' dropdown which has z-index of 1000. */ - z-index: 999; + /* Must appear under the 'hamburger' dropdown which has z-index of 1000, and the "network lost" + banner which has a z-index of 10 */ + z-index: 9; ` interface Props { diff --git a/lib/reducers/create-otp-reducer.js b/lib/reducers/create-otp-reducer.js index 0028f5095..9d66ab43b 100644 --- a/lib/reducers/create-otp-reducer.js +++ b/lib/reducers/create-otp-reducer.js @@ -1112,7 +1112,6 @@ function createOtpReducer(config) { } }) case 'SET_NETWORK_CONNECTION_LOST': - console.log(action.payload) return update(state, { ui: { errors: { From f8141fd46567593e7a9848524a67584ae00e03ac Mon Sep 17 00:00:00 2001 From: amy-corson-ibigroup <115499534+amy-corson-ibigroup@users.noreply.github.com> Date: Mon, 2 Dec 2024 13:23:40 -0600 Subject: [PATCH 3/6] Add status region to mobile --- lib/components/mobile/navigation-bar.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/components/mobile/navigation-bar.js b/lib/components/mobile/navigation-bar.js index eaa071f3d..555690226 100644 --- a/lib/components/mobile/navigation-bar.js +++ b/lib/components/mobile/navigation-bar.js @@ -1,5 +1,6 @@ import { ArrowLeft } from '@styled-icons/fa-solid/ArrowLeft' import { connect } from 'react-redux' +import { FormattedMessage, injectIntl } from 'react-intl' import { Navbar } from 'react-bootstrap' import PropTypes from 'prop-types' import React, { Component } from 'react' @@ -8,10 +9,10 @@ import styled from 'styled-components' import * as uiActions from '../../actions/ui' import { accountLinks, getAuth0Config } from '../../util/auth' import { ComponentContext } from '../../util/contexts' -import { injectIntl } from 'react-intl' import { NetworkConnectionBanner } from '../app/network-connection-banner' import { StyledIconWrapper } from '../util/styledIcon' import AppMenu from '../app/app-menu' +import InvisibleA11yLabel from '../util/invisible-a11y-label' import LocaleSelector from '../app/locale-selector' import NavLoginButtonAuth0 from '../../components/user/nav-login-button-auth0' import PageTitle from '../util/page-title' @@ -114,6 +115,13 @@ class MobileNavigationBar extends Component { )} + + {networkConnectionLost ? ( + + ) : ( + + )} + From 27bd54808ed4c227f05889329017eaff7d24fa39 Mon Sep 17 00:00:00 2001 From: amy-corson-ibigroup <115499534+amy-corson-ibigroup@users.noreply.github.com> Date: Mon, 2 Dec 2024 13:35:53 -0600 Subject: [PATCH 4/6] consolidate network connection status regions into one --- lib/components/app/desktop-nav.tsx | 9 +-------- .../app/network-connection-banner.tsx | 18 ++++++++++-------- lib/components/mobile/navigation-bar.js | 10 +--------- 3 files changed, 12 insertions(+), 25 deletions(-) diff --git a/lib/components/app/desktop-nav.tsx b/lib/components/app/desktop-nav.tsx index a92cd9df5..43b4ca919 100644 --- a/lib/components/app/desktop-nav.tsx +++ b/lib/components/app/desktop-nav.tsx @@ -1,7 +1,7 @@ import { connect } from 'react-redux' -import { FormattedMessage, useIntl } from 'react-intl' import { isMobile } from '@opentripplanner/core-utils/lib/ui' import { Nav, Navbar } from 'react-bootstrap' +import { useIntl } from 'react-intl' import React from 'react' import styled from 'styled-components' @@ -169,13 +169,6 @@ const DesktopNav = ({ - - {networkConnectionLost ? ( - - ) : ( - - )} - ) diff --git a/lib/components/app/network-connection-banner.tsx b/lib/components/app/network-connection-banner.tsx index 50c4924a0..a323520ac 100644 --- a/lib/components/app/network-connection-banner.tsx +++ b/lib/components/app/network-connection-banner.tsx @@ -4,6 +4,7 @@ import React, { useRef } from 'react' import styled from 'styled-components' import { RED_ON_WHITE } from '../util/colors' +import InvisibleA11yLabel from '../util/invisible-a11y-label' const containerClassname = 'network-connection-banner' const timeout = 250 @@ -48,7 +49,6 @@ const TransitionStyles = styled.div` z-index: 20; } ` -export const NetworkConnectionLostBanner = styled.div`` export const NetworkConnectionBanner = ({ networkConnectionLost @@ -58,6 +58,13 @@ export const NetworkConnectionBanner = ({ const connectionLostBannerRef = useRef(null) return ( + + {networkConnectionLost ? ( + + ) : ( + + )} + {networkConnectionLost && ( - +
- +
)}
diff --git a/lib/components/mobile/navigation-bar.js b/lib/components/mobile/navigation-bar.js index 555690226..1e7b0079c 100644 --- a/lib/components/mobile/navigation-bar.js +++ b/lib/components/mobile/navigation-bar.js @@ -1,6 +1,6 @@ import { ArrowLeft } from '@styled-icons/fa-solid/ArrowLeft' import { connect } from 'react-redux' -import { FormattedMessage, injectIntl } from 'react-intl' +import { injectIntl } from 'react-intl' import { Navbar } from 'react-bootstrap' import PropTypes from 'prop-types' import React, { Component } from 'react' @@ -12,7 +12,6 @@ import { ComponentContext } from '../../util/contexts' import { NetworkConnectionBanner } from '../app/network-connection-banner' import { StyledIconWrapper } from '../util/styledIcon' import AppMenu from '../app/app-menu' -import InvisibleA11yLabel from '../util/invisible-a11y-label' import LocaleSelector from '../app/locale-selector' import NavLoginButtonAuth0 from '../../components/user/nav-login-button-auth0' import PageTitle from '../util/page-title' @@ -115,13 +114,6 @@ class MobileNavigationBar extends Component { )} - - {networkConnectionLost ? ( - - ) : ( - - )} - From 9ab8492883568a17ff85a727b3718395daa2d008 Mon Sep 17 00:00:00 2001 From: amy-corson-ibigroup <115499534+amy-corson-ibigroup@users.noreply.github.com> Date: Mon, 2 Dec 2024 13:56:45 -0600 Subject: [PATCH 5/6] French translations for network connection strings --- i18n/fr.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/i18n/fr.yml b/i18n/fr.yml index a8283e9f1..b7bddef7a 100644 --- a/i18n/fr.yml +++ b/i18n/fr.yml @@ -194,6 +194,8 @@ components: closeMenu: Fermer le menu fieldTrip: Groupes scolaires mailables: Prêt-à-poster + networkConnectionLost: Erreur de connexion réseau + networkConnectionRestored: Connexion réseau restaurée openMenu: Ouvrir le menu skipNavigation: Sauter la navigation BackToTripPlanner: From 0bbb6c07db301bc52a3ceec647c35f841aae1b1f Mon Sep 17 00:00:00 2001 From: amy-corson-ibigroup <115499534+amy-corson-ibigroup@users.noreply.github.com> Date: Wed, 4 Dec 2024 13:15:24 -0600 Subject: [PATCH 6/6] Update snapshots --- .../viewers/__snapshots__/nearby-view.js.snap | 496 +++++++++--------- .../__snapshots__/create-otp-reducer.js.snap | 3 + 2 files changed, 251 insertions(+), 248 deletions(-) diff --git a/__tests__/components/viewers/__snapshots__/nearby-view.js.snap b/__tests__/components/viewers/__snapshots__/nearby-view.js.snap index a3a1b7bc5..65c9368a3 100644 --- a/__tests__/components/viewers/__snapshots__/nearby-view.js.snap +++ b/__tests__/components/viewers/__snapshots__/nearby-view.js.snap @@ -46,7 +46,7 @@ exports[`components > viewers > nearby view renders nothing on a blank page 1`] className="nearby-view base-color-bg" >
viewers > nearby view renders nothing on a blank page 1`] } >
    viewers > nearby view renders proper scooter dates 1`] = ` className="nearby-view base-color-bg" >
    viewers > nearby view renders proper scooter dates 1`] = ` } >
      viewers > nearby view renders proper scooter dates 1`] = ` >

      viewers > nearby view renders proper scooter dates 1`] = ` >

      viewers > nearby view renders proper scooter dates 1`] = `

      viewers > nearby view renders proper scooter dates 1`] = ` >

      viewers > nearby view renders proper scooter dates 1`] = ` >

      viewers > nearby view renders proper scooter dates 1`] = `

      viewers > nearby view renders proper scooter dates 1`] = ` >

      viewers > nearby view renders proper scooter dates 1`] = ` >

      viewers > nearby view renders proper scooter dates 1`] = `

      viewers > nearby view renders proper scooter dates 1`] = ` >
      viewers > nearby view renders proper scooter dates 1`] = ` >

      viewers > nearby view renders proper scooter dates 1`] = ` >

      viewers > nearby view renders proper scooter dates 1`] = `

      viewers > nearby view renders proper scooter dates 1`] = `
        viewers > nearby view renders proper scooter dates 1`] = ` roundedTop={false} >
      • viewers > nearby view renders proper scooter dates 1`] = ` title="45" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

          viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` iconViewBox="0 0 448 512" > viewers > nearby view renders proper scooter dates 1`] = ` >

        1. viewers > nearby view renders proper scooter dates 1`] = ` title="62" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

            viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` iconViewBox="0 0 448 512" > viewers > nearby view renders proper scooter dates 1`] = ` >

          1. viewers > nearby view renders proper scooter dates 1`] = ` title="79" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

              viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` iconViewBox="0 0 448 512" > viewers > nearby view renders proper scooter dates 1`] = ` >

              viewers > nearby view renders proper scooter dates 1`] = ` >

              viewers > nearby view renders proper scooter dates 1`] = ` >

              viewers > nearby view renders proper scooter dates 1`] = `

              viewers > nearby view renders proper scooter dates 1`] = `
                viewers > nearby view renders proper scooter dates 1`] = ` roundedTop={false} >
              • viewers > nearby view renders proper scooter dates 1`] = ` title="1 Line" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

                  viewers > nearby view renders proper scooter dates 1`] = ` roundedTop={false} >

                1. viewers > nearby view renders proper scooter dates 1`] = ` title="1 Line" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

                    viewers > nearby view renders proper scooter dates 1`] = ` roundedTop={false} >

                  1. viewers > nearby view renders proper scooter dates 1`] = ` title="1 Line" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

                      viewers > nearby view renders proper scooter dates 1`] = ` >

                      viewers > nearby view renders proper scooter dates 1`] = ` >

                      viewers > nearby view renders proper scooter dates 1`] = `

                      viewers > nearby view renders proper scooter dates 1`] = ` >

                      viewers > nearby view renders proper scooter dates 1`] = ` >

                      viewers > nearby view renders proper scooter dates 1`] = `

                      viewers > nearby view renders proper scooter dates 1`] = ` >

                      viewers > nearby view renders proper scooter dates 1`] = ` >

                      viewers > nearby view renders proper scooter dates 1`] = `

                      viewers > nearby view renders proper scooter dates 1`] = ` >
                      viewers > nearby view renders proper scooter dates 1`] = ` >

                      viewers > nearby view renders proper scooter dates 1`] = ` >

                      viewers > nearby view renders proper scooter dates 1`] = `

                      viewers > nearby view renders proper scooter dates 1`] = `
                        viewers > nearby view renders proper scooter dates 1`] = ` roundedTop={false} >
                      • viewers > nearby view renders proper scooter dates 1`] = ` title="45" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

                          viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` iconViewBox="0 0 448 512" > viewers > nearby view renders proper scooter dates 1`] = ` >

                        1. viewers > nearby view renders proper scooter dates 1`] = ` title="62" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

                            viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` iconViewBox="0 0 448 512" > viewers > nearby view renders proper scooter dates 1`] = ` >

                          1. viewers > nearby view renders proper scooter dates 1`] = ` title="79" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

                              viewers > nearby view renders proper scooter dates 1`] = ` roundedTop={false} >

                            1. viewers > nearby view renders proper scooter dates 1`] = ` title="988" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

                                viewers > nearby view renders proper scooter dates 1`] = ` >

                                viewers > nearby view renders proper scooter dates 1`] = ` >

                                viewers > nearby view renders proper scooter dates 1`] = `

                                viewers > nearby view renders proper scooter dates 1`] = ` >

                                viewers > nearby view renders proper scooter dates 1`] = ` >

                                viewers > nearby view renders proper scooter dates 1`] = `

                                viewers > nearby view renders proper scooter dates 1`] = ` >

                                viewers > nearby view renders proper scooter dates 1`] = ` >

                                viewers > nearby view renders proper scooter dates 1`] = `

                                viewers > nearby view renders proper scooter dates 1`] = ` >

                                viewers > nearby view renders proper scooter dates 1`] = ` >

                                viewers > nearby view renders proper scooter dates 1`] = `

                                viewers > nearby view renders proper scooter dates 1`] = ` >

                                viewers > nearby view renders proper scooter dates 1`] = ` >

                                viewers > nearby view renders proper scooter dates 1`] = `

                                viewers > nearby view renders proper scooter dates 1`] = ` >
                                viewers > nearby view renders proper scooter dates 1`] = ` >

                                viewers > nearby view renders proper scooter dates 1`] = ` >

                                viewers > nearby view renders proper scooter dates 1`] = `

                                viewers > nearby view renders proper scooter dates 1`] = `
                                  viewers > nearby view renders proper scooter dates 1`] = ` roundedTop={false} >
                                • viewers > nearby view renders proper scooter dates 1`] = ` title="67" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

                                    viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` iconViewBox="0 0 448 512" > viewers > nearby view renders proper scooter dates 1`] = ` >

                                  1. viewers > nearby view renders proper scooter dates 1`] = ` title="73" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

                                      viewers > nearby view renders proper scooter dates 1`] = ` roundedTop={false} >

                                    1. viewers > nearby view renders proper scooter dates 1`] = ` title="984" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

                                        viewers > nearby view renders proper scooter dates 1`] = ` >

                                        viewers > nearby view renders proper scooter dates 1`] = ` >

                                        viewers > nearby view renders proper scooter dates 1`] = `

                                        viewers > nearby view renders proper scooter dates 1`] = ` >
                                        viewers > nearby view renders proper scooter dates 1`] = ` >

                                        viewers > nearby view renders proper scooter dates 1`] = ` >

                                        viewers > nearby view renders proper scooter dates 1`] = `

                                        viewers > nearby view renders proper scooter dates 1`] = `
                                          viewers > nearby view renders proper scooter dates 1`] = ` roundedTop={false} >
                                        • viewers > nearby view renders proper scooter dates 1`] = ` title="1 Line" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

                                            viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` iconViewBox="0 0 448 512" > viewers > nearby view renders proper scooter dates 1`] = ` >

                                            viewers > nearby view renders proper scooter dates 1`] = ` >

                                            viewers > nearby view renders proper scooter dates 1`] = ` >

                                            viewers > nearby view renders proper scooter dates 1`] = `

                                            viewers > nearby view renders proper scooter dates 1`] = `
                                              viewers > nearby view renders proper scooter dates 1`] = ` roundedTop={false} >
                                            • viewers > nearby view renders proper scooter dates 1`] = ` title="522" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

                                                viewers > nearby view renders proper scooter dates 1`] = ` roundedTop={false} >

                                              1. viewers > nearby view renders proper scooter dates 1`] = ` title="67" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

                                                  viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` iconViewBox="0 0 448 512" > viewers > nearby view renders proper scooter dates 1`] = ` >

                                                1. viewers > nearby view renders proper scooter dates 1`] = ` title="522" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

                                                    viewers > nearby view renders proper scooter dates 1`] = ` roundedTop={false} >

                                                  1. viewers > nearby view renders proper scooter dates 1`] = ` title="73" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

                                                      viewers > nearby view renders proper scooter dates 1`] = ` roundedTop={false} >

                                                    1. viewers > nearby view renders proper scooter dates 1`] = ` title="322" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

                                                        viewers > nearby view renders proper scooter dates 1`] = ` roundedTop={false} >

                                                      1. viewers > nearby view renders proper scooter dates 1`] = ` title="322" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

                                                          viewers > nearby view renders proper scooter dates 1`] = ` >

                                                          viewers > nearby view renders proper scooter dates 1`] = ` >

                                                          viewers > nearby view renders proper scooter dates 1`] = `

                                                          viewers > nearby view renders proper scooter dates 1`] = ` >
                                                          viewers > nearby view renders proper scooter dates 1`] = ` >

                                                          viewers > nearby view renders proper scooter dates 1`] = ` >

                                                          viewers > nearby view renders proper scooter dates 1`] = `

                                                          viewers > nearby view renders proper scooter dates 1`] = `
                                                            viewers > nearby view renders proper scooter dates 1`] = ` roundedTop={false} >
                                                          • viewers > nearby view renders proper scooter dates 1`] = ` title="45" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

                                                              viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` iconViewBox="0 0 448 512" > viewers > nearby view renders proper scooter dates 1`] = ` >

                                                            1. viewers > nearby view renders proper scooter dates 1`] = ` title="62" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

                                                                viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` iconViewBox="0 0 448 512" > viewers > nearby view renders proper scooter dates 1`] = ` >

                                                              1. viewers > nearby view renders proper scooter dates 1`] = ` title="79" > viewers > nearby view renders proper scooter dates 1`] = ` className="departure-times" >

                                                                  viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` iconViewBox="0 0 448 512" > viewers > nearby view renders proper scooter dates 1`] = ` >