diff --git a/src/HospitalRun.tsx b/src/HospitalRun.tsx
index 96ce24a194..b3f9cadcdf 100644
--- a/src/HospitalRun.tsx
+++ b/src/HospitalRun.tsx
@@ -5,6 +5,7 @@ import { Switch, Route } from 'react-router-dom'
import Breadcrumbs from './breadcrumbs/Breadcrumbs'
import Navbar from './components/Navbar'
+import { NetworkStatusMessage } from './components/network-status'
import PrivateRoute from './components/PrivateRoute'
import Sidebar from './components/Sidebar'
import Dashboard from './dashboard/Dashboard'
@@ -23,6 +24,7 @@ const HospitalRun = () => {
return (
+
diff --git a/src/__tests__/components/network-status/NetworkStatusMessage.test.tsx b/src/__tests__/components/network-status/NetworkStatusMessage.test.tsx
new file mode 100644
index 0000000000..cbd144510c
--- /dev/null
+++ b/src/__tests__/components/network-status/NetworkStatusMessage.test.tsx
@@ -0,0 +1,51 @@
+import { render, shallow } from 'enzyme'
+import React from 'react'
+
+import { useTranslation } from '../../../__mocks__/react-i18next'
+import { NetworkStatusMessage } from '../../../components/network-status'
+import { useNetworkStatus } from '../../../components/network-status/useNetworkStatus'
+
+jest.mock('../../../components/network-status/useNetworkStatus')
+const useNetworkStatusMock = (useNetworkStatus as unknown) as jest.MockInstance<
+ ReturnType
,
+ any
+>
+
+const englishTranslationsMock = {
+ 'networkStatus.offline': 'you are working in offline mode',
+ 'networkStatus.online': 'you are back online',
+}
+
+const useTranslationReturnValue = useTranslation() as any
+useTranslationReturnValue.t = (key: keyof typeof englishTranslationsMock) =>
+ englishTranslationsMock[key]
+const { t } = useTranslationReturnValue
+
+describe('NetworkStatusMessage', () => {
+ it('returns null if the app has always been online', () => {
+ useNetworkStatusMock.mockReturnValue({
+ isOnline: true,
+ wasOffline: false,
+ })
+ const wrapper = shallow()
+ expect(wrapper.equals(null as any)).toBe(true)
+ })
+ it(`shows the message "${t('networkStatus.offline')}" if the app goes offline`, () => {
+ useNetworkStatusMock.mockReturnValue({
+ isOnline: false,
+ wasOffline: false,
+ })
+ const wrapper = render()
+ expect(wrapper.text()).toContain(t('networkStatus.offline'))
+ })
+ it(`shows the message "${t(
+ 'networkStatus.online',
+ )}" if the app goes back online after it was offline`, () => {
+ useNetworkStatusMock.mockReturnValue({
+ isOnline: true,
+ wasOffline: true,
+ })
+ const wrapper = render()
+ expect(wrapper.text()).toContain(t('networkStatus.online'))
+ })
+})
diff --git a/src/components/network-status/NetworkStatusMessage.tsx b/src/components/network-status/NetworkStatusMessage.tsx
new file mode 100644
index 0000000000..c8269d22d6
--- /dev/null
+++ b/src/components/network-status/NetworkStatusMessage.tsx
@@ -0,0 +1,57 @@
+import React, { useState } from 'react'
+import { useTranslation } from 'react-i18next'
+
+import { useNetworkStatus } from './useNetworkStatus'
+
+const ONLINE_COLOR = 'rgba(0, 255, 0, 0.55)'
+const OFFLINE_COLOR = 'rgba(255, 0, 0, 0.65)'
+const OPACITY_TRANSITION_TIME = 4000
+const BASE_STYLE = {
+ height: '50px',
+ pointerEvents: 'none' as 'none',
+ transition: `opacity ${OPACITY_TRANSITION_TIME}ms ease-in`,
+}
+
+export const NetworkStatusMessage = () => {
+ const { t } = useTranslation()
+ const { isOnline, wasOffline } = useNetworkStatus()
+ const [shouldRender, setShouldRender] = useState(true)
+ const [opacity, setOpacity] = useState(1)
+
+ if (isOnline && !wasOffline) {
+ return null
+ }
+
+ if (!isOnline && opacity !== 1) {
+ setShouldRender(true)
+ setOpacity(1)
+ }
+
+ if (isOnline && wasOffline && opacity !== 0) {
+ setOpacity(0)
+ setTimeout(() => {
+ setShouldRender(false)
+ }, OPACITY_TRANSITION_TIME)
+ }
+
+ if (!shouldRender) {
+ return null
+ }
+
+ const style = {
+ ...BASE_STYLE,
+ backgroundColor: isOnline ? ONLINE_COLOR : OFFLINE_COLOR,
+ opacity,
+ }
+
+ return (
+
+ {isOnline ? t('networkStatus.online') : t('networkStatus.offline')}
+
+ )
+}
diff --git a/src/components/network-status/index.ts b/src/components/network-status/index.ts
new file mode 100644
index 0000000000..e69b8f6c18
--- /dev/null
+++ b/src/components/network-status/index.ts
@@ -0,0 +1,2 @@
+export { NetworkStatusMessage } from './NetworkStatusMessage'
+export { useNetworkStatus } from './useNetworkStatus'
diff --git a/src/components/network-status/types.ts b/src/components/network-status/types.ts
new file mode 100644
index 0000000000..1ab39c4229
--- /dev/null
+++ b/src/components/network-status/types.ts
@@ -0,0 +1,4 @@
+export interface NetworkStatus {
+ isOnline: boolean
+ wasOffline: boolean
+}
diff --git a/src/components/network-status/useNetworkStatus.ts b/src/components/network-status/useNetworkStatus.ts
new file mode 100644
index 0000000000..698a7471e5
--- /dev/null
+++ b/src/components/network-status/useNetworkStatus.ts
@@ -0,0 +1,28 @@
+import { useState, useEffect } from 'react'
+
+import { NetworkStatus } from './types'
+
+export const useNetworkStatus = (): NetworkStatus => {
+ const isOnline = navigator.onLine
+ const [networkStatus, setNetworkStatus] = useState({
+ isOnline,
+ wasOffline: !isOnline,
+ })
+ const handleOnline = () => {
+ setNetworkStatus((prevState) => ({ ...prevState, isOnline: true }))
+ }
+ const handleOffline = () => {
+ setNetworkStatus((prevState) => ({ ...prevState, isOnline: false, wasOffline: true }))
+ }
+ useEffect(() => {
+ window.addEventListener('online', handleOnline)
+ window.addEventListener('offline', handleOffline)
+
+ return () => {
+ window.removeEventListener('online', handleOnline)
+ window.removeEventListener('offline', handleOffline)
+ }
+ }, [])
+
+ return networkStatus
+}
diff --git a/src/locales/enUs/translations/index.ts b/src/locales/enUs/translations/index.ts
index ae9f29343f..55e5285113 100644
--- a/src/locales/enUs/translations/index.ts
+++ b/src/locales/enUs/translations/index.ts
@@ -2,6 +2,7 @@ import actions from './actions'
import dashboard from './dashboard'
import incidents from './incidents'
import labs from './labs'
+import networkStatus from './network-status'
import patient from './patient'
import patients from './patients'
import scheduling from './scheduling'
@@ -12,6 +13,7 @@ import states from './states'
export default {
...actions,
...dashboard,
+ ...networkStatus,
...patient,
...patients,
...scheduling,
diff --git a/src/locales/enUs/translations/network-status/index.ts b/src/locales/enUs/translations/network-status/index.ts
new file mode 100644
index 0000000000..925bfa9e69
--- /dev/null
+++ b/src/locales/enUs/translations/network-status/index.ts
@@ -0,0 +1,6 @@
+export default {
+ networkStatus: {
+ offline: 'you are working in offline mode',
+ online: 'you are back online',
+ },
+}