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] 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 } }