diff --git a/i18n/en.pot b/i18n/en.pot
index 4b42c3961..d1adc2c0e 100644
--- a/i18n/en.pot
+++ b/i18n/en.pot
@@ -5,12 +5,15 @@ msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
-"POT-Creation-Date: 2020-09-08T17:54:39.674Z\n"
-"PO-Revision-Date: 2020-09-08T17:54:39.674Z\n"
+"POT-Creation-Date: 2020-10-12T06:11:46.298Z\n"
+"PO-Revision-Date: 2020-10-12T06:11:46.298Z\n"
msgid "Untitled dashboard"
msgstr ""
+msgid "Loading dashboard – {{name}}"
+msgstr ""
+
msgid "Cancel"
msgstr ""
diff --git a/src/actions/alert.js b/src/actions/alert.js
new file mode 100644
index 000000000..f92170988
--- /dev/null
+++ b/src/actions/alert.js
@@ -0,0 +1,10 @@
+import { SET_ALERT_MESSAGE, CLEAR_ALERT_MESSAGE } from '../reducers/alert'
+
+export const acSetAlertMessage = value => ({
+ type: SET_ALERT_MESSAGE,
+ value,
+})
+
+export const acClearAlertMessage = () => ({
+ type: CLEAR_ALERT_MESSAGE,
+})
diff --git a/src/actions/selected.js b/src/actions/selected.js
index 253eac62c..ffaa13b26 100644
--- a/src/actions/selected.js
+++ b/src/actions/selected.js
@@ -1,8 +1,5 @@
-import {
- getCustomDashboards,
- sGetDashboardById,
- EMPTY_DASHBOARD,
-} from '../reducers/dashboards'
+import i18n from '@dhis2/d2-i18n'
+import { getCustomDashboards, sGetDashboardById } from '../reducers/dashboards'
import {
SET_SELECTED_ID,
SET_SELECTED_ISLOADING,
@@ -17,9 +14,8 @@ import { sGetUserUsername } from '../reducers/user'
import { acSetDashboardItems, acAppendDashboards } from './dashboards'
import { acClearItemFilters } from './itemFilters'
import { tGetMessages } from '../components/Item/MessagesItem/actions'
-import { acReceivedSnackbarMessage, acCloseSnackbar } from './snackbar'
+import { acSetAlertMessage, acClearAlertMessage } from './alert'
import { acAddVisualization, acClearVisualizations } from './visualizations'
-
import { apiFetchDashboard } from '../api/dashboards'
import { storePreferredDashboardId } from '../api/localStorage'
import {
@@ -28,7 +24,6 @@ import {
} from '../api/description'
import { withShape } from '../components/ItemGrid/gridUtil'
-import { loadingDashboardMsg } from '../components/SnackbarMessage/SnackbarMessage'
import { extractFavorite } from '../components/Item/VisualizationItem/plugin'
import {
@@ -75,18 +70,14 @@ export const acClearSelectedItemActiveTypes = () => ({
export const tSetSelectedDashboardById = id => async (dispatch, getState) => {
dispatch(acSetSelectedIsLoading(true))
- const snackbarTimeout = setTimeout(() => {
- const dashboardName = (
- sGetDashboardById(getState(), id) || EMPTY_DASHBOARD
- ).displayName
- if (sGetSelectedIsLoading(getState()) && dashboardName) {
- loadingDashboardMsg.name = dashboardName
+ const alertTimeout = setTimeout(() => {
+ const name = sGetDashboardById(getState(), id)?.displayName
+ if (sGetSelectedIsLoading(getState()) && name) {
dispatch(
- acReceivedSnackbarMessage({
- message: loadingDashboardMsg,
- open: true,
- })
+ acSetAlertMessage(
+ i18n.t('Loading dashboard – {{name}}', { name })
+ )
)
}
}, 500)
@@ -127,9 +118,9 @@ export const tSetSelectedDashboardById = id => async (dispatch, getState) => {
dispatch(acSetSelectedIsLoading(false))
- clearTimeout(snackbarTimeout)
+ clearTimeout(alertTimeout)
- dispatch(acCloseSnackbar())
+ dispatch(acClearAlertMessage())
return selected
}
diff --git a/src/actions/snackbar.js b/src/actions/snackbar.js
deleted file mode 100644
index cc258af69..000000000
--- a/src/actions/snackbar.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import { RECEIVED_SNACKBAR_MESSAGE, CLOSE_SNACKBAR } from '../reducers/snackbar'
-
-export const acReceivedSnackbarMessage = value => ({
- type: RECEIVED_SNACKBAR_MESSAGE,
- value,
-})
-
-export const acCloseSnackbar = () => ({
- type: CLOSE_SNACKBAR,
-})
diff --git a/src/components/AlertBar/AlertBar.js b/src/components/AlertBar/AlertBar.js
new file mode 100644
index 000000000..30b28c763
--- /dev/null
+++ b/src/components/AlertBar/AlertBar.js
@@ -0,0 +1,29 @@
+import React from 'react'
+import { connect } from 'react-redux'
+import { AlertBar, AlertStack } from '@dhis2/ui'
+import PropTypes from 'prop-types'
+
+import { sGetAlertMessage } from '../../reducers/alert'
+import { acClearAlertMessage } from '../../actions/alert'
+
+export const Alert = ({ message, onClose }) =>
+ message ? (
+
+
+ {message}
+
+
+ ) : null
+
+Alert.propTypes = {
+ message: PropTypes.string,
+ onClose: PropTypes.func,
+}
+
+const mapStateToProps = state => ({
+ message: sGetAlertMessage(state),
+})
+
+export default connect(mapStateToProps, {
+ onClose: acClearAlertMessage,
+})(Alert)
diff --git a/src/components/AlertBar/__tests__/AlertBar.spec.js b/src/components/AlertBar/__tests__/AlertBar.spec.js
new file mode 100644
index 000000000..5e45463af
--- /dev/null
+++ b/src/components/AlertBar/__tests__/AlertBar.spec.js
@@ -0,0 +1,18 @@
+import React from 'react'
+import { shallow } from 'enzyme'
+import toJson from 'enzyme-to-json'
+import { Alert } from '../AlertBar'
+
+describe('AlertBar', () => {
+ it('renders alert message', () => {
+ const AlertBar = shallow(
+
+ )
+ expect(toJson(AlertBar)).toMatchSnapshot()
+ })
+
+ it('renders nothing when no message', () => {
+ const AlertBar = shallow()
+ expect(toJson(AlertBar)).toMatchSnapshot()
+ })
+})
diff --git a/src/components/AlertBar/__tests__/__snapshots__/AlertBar.spec.js.snap b/src/components/AlertBar/__tests__/__snapshots__/AlertBar.spec.js.snap
new file mode 100644
index 000000000..db496373f
--- /dev/null
+++ b/src/components/AlertBar/__tests__/__snapshots__/AlertBar.spec.js.snap
@@ -0,0 +1,19 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`AlertBar renders alert message 1`] = `
+
+
+ Luke I am your father
+
+
+`;
+
+exports[`AlertBar renders nothing when no message 1`] = `""`;
diff --git a/src/components/App.js b/src/components/App.js
index 2859b9299..104c0bce8 100644
--- a/src/components/App.js
+++ b/src/components/App.js
@@ -5,6 +5,15 @@ import PropTypes from 'prop-types'
import i18n from '@dhis2/d2-i18n'
import { CssVariables } from '@dhis2/ui'
+import Dashboard from './Dashboard/Dashboard'
+import AlertBar from './AlertBar/AlertBar'
+
+import { acReceivedUser } from '../actions/user'
+import { tFetchDashboards } from '../actions/dashboards'
+import { tSetControlBarRows } from '../actions/controlBar'
+import { tSetShowDescription } from '../actions/selected'
+import { tSetDimensions } from '../actions/dimensions'
+
import {
EDIT,
VIEW,
@@ -12,13 +21,6 @@ import {
PRINT,
PRINT_LAYOUT,
} from './Dashboard/dashboardModes'
-import { acReceivedUser } from '../actions/user'
-import { tFetchDashboards } from '../actions/dashboards'
-import { tSetControlBarRows } from '../actions/controlBar'
-import { tSetShowDescription } from '../actions/selected'
-import { tSetDimensions } from '../actions/dimensions'
-import Dashboard from './Dashboard/Dashboard'
-import SnackbarMessage from './SnackbarMessage/SnackbarMessage'
import './App.css'
@@ -85,7 +87,7 @@ export class App extends Component {
/>
-
+
>
)
}
diff --git a/src/components/ControlBar/EditBar.js b/src/components/ControlBar/EditBar.js
index 46cebbdf0..1e9f16f83 100644
--- a/src/components/ControlBar/EditBar.js
+++ b/src/components/ControlBar/EditBar.js
@@ -148,7 +148,6 @@ export class EditBar extends Component {
onRequestClose={this.toggleTranslationDialog}
objectToTranslate={this.state.dashboardModel}
fieldsToTranslate={['name', 'description']}
- // TODO handle messages in snackbar
onTranslationSaved={this.onTranslationsSaved}
onTranslationError={err =>
console.log('translation update error', err)
diff --git a/src/components/SnackbarMessage/SnackbarMessage.js b/src/components/SnackbarMessage/SnackbarMessage.js
deleted file mode 100644
index 38b280eea..000000000
--- a/src/components/SnackbarMessage/SnackbarMessage.js
+++ /dev/null
@@ -1,55 +0,0 @@
-import React from 'react'
-import { connect } from 'react-redux'
-import Snackbar from '@material-ui/core/Snackbar'
-import PropTypes from 'prop-types'
-
-import { sGetSnackbar } from '../../reducers/snackbar'
-import { acCloseSnackbar } from '../../actions/snackbar'
-
-const LOADING_DASHBOARD = 'LOADING_DASHBOARD'
-export const loadingDashboardMsg = { name: '', type: LOADING_DASHBOARD }
-
-const SnackbarMessageContent = ({ message }) => {
- if (typeof message === 'object') {
- //future message types: switch(message.type)
- return (
-
- Loading {message.name}{' '}
- dashboard
-
- )
- }
- return message
-}
-
-export const SnackbarMessage = props => {
- return (
- }
- autoHideDuration={props.snackbarDuration}
- onClose={props.onCloseSnackbar}
- />
- )
-}
-
-const mapStateToProps = state => {
- const { message, duration, open } = sGetSnackbar(state)
- return {
- snackbarOpen: open,
- snackbarMessage: message,
- snackbarDuration: duration,
- }
-}
-
-SnackbarMessage.propTypes = {
- snackbarDuration: PropTypes.string,
- snackbarMessage: PropTypes.object,
- snackbarOpen: PropTypes.bool,
- onCloseSnackbar: PropTypes.func,
-}
-
-export default connect(mapStateToProps, {
- onCloseSnackbar: acCloseSnackbar,
-})(SnackbarMessage)
diff --git a/src/components/SnackbarMessage/__tests__/SnackbarMessage.spec.js b/src/components/SnackbarMessage/__tests__/SnackbarMessage.spec.js
deleted file mode 100644
index 3d04e277c..000000000
--- a/src/components/SnackbarMessage/__tests__/SnackbarMessage.spec.js
+++ /dev/null
@@ -1,39 +0,0 @@
-import React from 'react'
-import { shallow } from 'enzyme'
-import Snackbar from '@material-ui/core/Snackbar'
-import { SnackbarMessage } from '../SnackbarMessage'
-
-describe('SnackbarMessage', () => {
- let props
- let shallowSnackbarMessage
- const testMessage = { name: 'Luke, I am your father', type: 'Test' }
- const snackbarMessage = () => {
- if (!shallowSnackbarMessage) {
- shallowSnackbarMessage = shallow()
- }
- return shallowSnackbarMessage
- }
-
- beforeEach(() => {
- props = {
- snackbarOpen: false,
- onCloseSnackbar: jest.fn(),
- snackbarMessage: testMessage,
- }
- shallowSnackbarMessage = undefined
- })
-
- it('renders a MUI Snackbar', () => {
- expect(snackbarMessage().find(Snackbar)).toHaveLength(1)
- })
-
- it('renders a closed MUI Snackbar', () => {
- expect(snackbarMessage().prop('open')).toBeFalsy()
- })
-
- it('renders a MUI Snackbar with given message', () => {
- expect(snackbarMessage().prop('message').props.message).toEqual(
- testMessage
- )
- })
-})
diff --git a/src/reducers/__tests__/alert.spec.js b/src/reducers/__tests__/alert.spec.js
new file mode 100644
index 000000000..8c9b8d836
--- /dev/null
+++ b/src/reducers/__tests__/alert.spec.js
@@ -0,0 +1,52 @@
+import reducer, {
+ DEFAULT_STATE_ALERT,
+ SET_ALERT_MESSAGE,
+ CLEAR_ALERT_MESSAGE,
+ sGetAlertMessage,
+} from '../alert'
+
+describe('alert reducer', () => {
+ it('should return the default state', () => {
+ const actualState = reducer(undefined, {})
+
+ expect(actualState).toEqual(DEFAULT_STATE_ALERT)
+ })
+
+ it('sets the alert message', () => {
+ const message = 'Loading dashboard: Rainbow dash'
+ const action = {
+ type: SET_ALERT_MESSAGE,
+ value: message,
+ }
+
+ const expectedState = message
+
+ const actualState = reducer(DEFAULT_STATE_ALERT, action)
+ expect(actualState).toEqual(expectedState)
+ })
+
+ it('clears the alert message', () => {
+ const action = {
+ type: CLEAR_ALERT_MESSAGE,
+ }
+
+ const currentState = 'Loading dashboard: Rainbow dash'
+
+ const actualState = reducer(currentState, action)
+
+ expect(actualState).toEqual(DEFAULT_STATE_ALERT)
+ })
+
+ it('gets the current message from state', () => {
+ const message = 'Loading dashboard: Rainbow dash'
+ const action = {
+ type: SET_ALERT_MESSAGE,
+ value: message,
+ }
+ const alert = reducer(null, action)
+
+ const messageInState = sGetAlertMessage({ alert })
+
+ expect(messageInState).toEqual(message)
+ })
+})
diff --git a/src/reducers/__tests__/snackbar.spec.js b/src/reducers/__tests__/snackbar.spec.js
deleted file mode 100644
index 1744a5f65..000000000
--- a/src/reducers/__tests__/snackbar.spec.js
+++ /dev/null
@@ -1,133 +0,0 @@
-import reducer, {
- DEFAULT_STATE_SNACKBAR,
- RECEIVED_SNACKBAR_MESSAGE,
- CLOSE_SNACKBAR,
-} from '../snackbar'
-
-describe('snackbar reducer', () => {
- it('should return the default state', () => {
- const actualState = reducer(undefined, {})
-
- expect(actualState).toEqual(DEFAULT_STATE_SNACKBAR)
- })
-
- it('should handle RECEIVED_SNACKBAR_MESSAGE action with message object containing only message text', () => {
- const message = {
- name: 'Loading "tinkywinky" dashboard',
- type: 'LOADING_TINKYWINKY',
- }
-
- const action = {
- type: RECEIVED_SNACKBAR_MESSAGE,
- value: {
- message,
- },
- }
-
- const expectedState = {
- message,
- duration: null,
- open: false,
- }
-
- const actualState = reducer(DEFAULT_STATE_SNACKBAR, action)
- expect(actualState).toEqual(expectedState)
- })
-
- it('should handle RECEIVED_SNACKBAR_MESSAGE action with message object with duration previously set', () => {
- const message = {
- name: 'Loading "tinkywinky" dashboard',
- type: 'LOADING_TINKYWINKY',
- }
- const duration = 3000
-
- const action = {
- type: RECEIVED_SNACKBAR_MESSAGE,
- value: {
- message,
- },
- }
-
- const expectedState = {
- message,
- duration,
- open: true,
- }
-
- const currentState = {
- message: 'You just won 1000 dollars',
- duration,
- open: true,
- }
-
- const actualState = reducer(currentState, action)
- expect(actualState).toEqual(expectedState)
- })
-
- it('should handle RECEIVED_SNACKBAR_MESSAGE action with message object containing text and duration', () => {
- const message = {
- name: 'Loading "tinkywinky" dashboard',
- type: 'LOADING_TINKYWINKY',
- }
- const duration = 3000
- const open = true
-
- const action = {
- type: RECEIVED_SNACKBAR_MESSAGE,
- value: {
- message,
- duration,
- open,
- },
- }
-
- const expectedState = {
- message,
- duration,
- open,
- }
-
- const actualState = reducer(DEFAULT_STATE_SNACKBAR, action)
- expect(actualState).toEqual(expectedState)
- })
-
- it('should handle RECEIVED_SNACKBAR_MESSAGE action with message string', () => {
- const message = 'Loading "tinkywinky" dashboard'
- const duration = 3000
- const open = true
-
- const action = {
- type: RECEIVED_SNACKBAR_MESSAGE,
- value: {
- message,
- duration,
- open,
- },
- }
-
- const expectedState = {
- message,
- duration,
- open,
- }
-
- const actualState = reducer(DEFAULT_STATE_SNACKBAR, action)
- expect(actualState).toEqual(expectedState)
- })
-
- it('should handle the CLOSE_SNACKBAR action', () => {
- const action = {
- type: CLOSE_SNACKBAR,
- }
-
- const currentState = {
- message: 'You just won 1000 dollars',
- duration: 3000,
- open: true,
- }
-
- const actualState = reducer(currentState, action)
-
- expect(actualState).toEqual(DEFAULT_STATE_SNACKBAR)
- })
-})
diff --git a/src/reducers/alert.js b/src/reducers/alert.js
new file mode 100644
index 000000000..c314bd887
--- /dev/null
+++ b/src/reducers/alert.js
@@ -0,0 +1,21 @@
+export const SET_ALERT_MESSAGE = 'SET_ALERT_MESSAGE'
+export const CLEAR_ALERT_MESSAGE = 'CLEAR_ALERT_MESSAGE'
+
+export const DEFAULT_STATE_ALERT = null
+
+export default (state = DEFAULT_STATE_ALERT, action) => {
+ switch (action.type) {
+ case SET_ALERT_MESSAGE: {
+ return action.value
+ }
+ case CLEAR_ALERT_MESSAGE: {
+ return DEFAULT_STATE_ALERT
+ }
+ default:
+ return state
+ }
+}
+
+// selectors
+
+export const sGetAlertMessage = state => state.alert
diff --git a/src/reducers/index.js b/src/reducers/index.js
index bc29ae33d..56b73270b 100644
--- a/src/reducers/index.js
+++ b/src/reducers/index.js
@@ -15,7 +15,7 @@ import editDashboard from './editDashboard'
import printDashboard from './printDashboard'
import messages from './messages'
import user from './user'
-import snackbar from './snackbar'
+import alert from './alert'
import itemFilters from './itemFilters'
import style from './style'
import dimensions from './dimensions'
@@ -39,7 +39,7 @@ export default combineReducers({
printDashboard,
itemFilters,
style,
- snackbar,
+ alert,
dimensions,
settings,
activeModalDimension,
diff --git a/src/reducers/snackbar.js b/src/reducers/snackbar.js
deleted file mode 100644
index 5d80c27d4..000000000
--- a/src/reducers/snackbar.js
+++ /dev/null
@@ -1,25 +0,0 @@
-export const RECEIVED_SNACKBAR_MESSAGE = 'RECEIVED_SNACKBAR_MESSAGE'
-export const CLOSE_SNACKBAR = 'CLOSE_SNACKBAR'
-
-export const DEFAULT_STATE_SNACKBAR = {
- message: {},
- duration: null,
- open: false,
-}
-
-export default (state = DEFAULT_STATE_SNACKBAR, action) => {
- switch (action.type) {
- case RECEIVED_SNACKBAR_MESSAGE: {
- return { ...state, ...action.value }
- }
- case CLOSE_SNACKBAR: {
- return DEFAULT_STATE_SNACKBAR
- }
- default:
- return state
- }
-}
-
-// selectors
-
-export const sGetSnackbar = state => state.snackbar || DEFAULT_STATE_SNACKBAR