Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add network connection status to ui and redux #1312

Merged
merged 11 commits into from
Dec 4, 2024
Merged
2 changes: 2 additions & 0 deletions i18n/en-US.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions i18n/fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
3 changes: 3 additions & 0 deletions lib/actions/ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 3 additions & 0 deletions lib/components/app/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,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.
Expand Down
6 changes: 6 additions & 0 deletions lib/components/app/desktop-nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -59,6 +60,7 @@ const NavItemOnLargeScreens = styled(NavbarItem)`
// Typscript TODO: otpConfig type
export type Props = {
locale: string
networkConnectionLost: boolean
otpConfig: AppConfig
popupTarget?: string
setPopupContent: (url: string) => void
Expand All @@ -79,6 +81,7 @@ export type Props = {
*/
const DesktopNav = ({
locale,
networkConnectionLost,
otpConfig,
popupTarget,
setPopupContent
Expand Down Expand Up @@ -158,15 +161,18 @@ const DesktopNav = ({
</StyledNav>
</Navbar.Header>
</Navbar>
<NetworkConnectionBanner networkConnectionLost={networkConnectionLost} />
</header>
)
}

// 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
}
Expand Down
83 changes: 83 additions & 0 deletions lib/components/app/network-connection-banner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
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'
import InvisibleA11yLabel from '../util/invisible-a11y-label'

const containerClassname = 'network-connection-banner'
const timeout = 250

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%;
// 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;
transform: translateY(-100%);
}
.${containerClassname}-enter-active {
opacity: 1;
transform: translateY(0);
transition: opacity ${timeout}ms ease-in;
}
.${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 NetworkConnectionBanner = ({
networkConnectionLost
}: {
networkConnectionLost: boolean
}): JSX.Element => {
const connectionLostBannerRef = useRef<HTMLDivElement>(null)
return (
<TransitionStyles>
<InvisibleA11yLabel aria-live="assertive" role="status">
{networkConnectionLost ? (
<FormattedMessage id="components.AppMenu.networkConnectionLost" />
) : (
<FormattedMessage id="components.AppMenu.networkConnectionRestored" />
)}
</InvisibleA11yLabel>
<TransitionGroup style={{ display: 'content' }}>
{networkConnectionLost && (
<CSSTransition
classNames={containerClassname}
nodeRef={connectionLostBannerRef}
timeout={timeout}
>
<div className={containerClassname} ref={connectionLostBannerRef}>
<FormattedMessage id="components.AppMenu.networkConnectionLost" />
</div>
</CSSTransition>
)}
</TransitionGroup>
</TransitionStyles>
)
}
6 changes: 5 additions & 1 deletion lib/components/app/responsive-webapp.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -441,6 +444,7 @@ const mapStateToWrapperProps = (state) => {
const mapWrapperDispatchToProps = {
processSignIn: authActions.processSignIn,
setLocale: uiActions.setLocale,
setNetworkConnectionLost: uiActions.setNetworkConnectionLost,
showAccessTokenError: authActions.showAccessTokenError,
showLoginError: authActions.showLoginError
}
Expand Down
5 changes: 3 additions & 2 deletions lib/components/mobile/batch-search-screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
11 changes: 9 additions & 2 deletions lib/components/mobile/navigation-bar.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ArrowLeft } from '@styled-icons/fa-solid/ArrowLeft'
import { connect } from 'react-redux'
import { injectIntl } from 'react-intl'
import { Navbar } from 'react-bootstrap'
import PropTypes from 'prop-types'
import React, { Component } from 'react'
Expand All @@ -8,7 +9,7 @@ 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 LocaleSelector from '../app/locale-selector'
Expand All @@ -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
Expand All @@ -55,6 +57,7 @@ class MobileNavigationBar extends Component {
headerText,
intl,
locale,
networkConnectionLost,
showBackButton
} = this.props

Expand Down Expand Up @@ -111,6 +114,9 @@ class MobileNavigationBar extends Component {
)}
</MobileBar>
</Navbar>
<NetworkConnectionBanner
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is causing the percy tests to fail on mobile nearby view? not sure what else would cause the issues percy has.

networkConnectionLost={networkConnectionLost}
/>
</header>
)
}
Expand All @@ -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
}
}

Expand Down
11 changes: 11 additions & 0 deletions lib/reducers/create-otp-reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,9 @@ export function getInitialState(userDefinedConfig) {
},
ui: {
diagramLeg: null,
errors: {
networkConnectionLost: !navigator.onLine
},
locale: null,
localizedMessages: null,
mainPanelContent: null,
Expand Down Expand Up @@ -1108,6 +1111,14 @@ function createOtpReducer(config) {
}
}
})
case 'SET_NETWORK_CONNECTION_LOST':
return update(state, {
ui: {
errors: {
networkConnectionLost: { $set: action.payload }
}
}
})
case 'SERVICE_TIME_RANGE_REQUEST':
return update(state, {
serviceTimeRange: { $set: { pending: true } }
Expand Down
Loading