diff --git a/src/components/Notifier/actions.js b/src/components/Notifier/actions.js new file mode 100644 index 00000000..7cfffca1 --- /dev/null +++ b/src/components/Notifier/actions.js @@ -0,0 +1,16 @@ +// @flow +import enqueueSnackbar from '~/logic/notifications/store/actions/enqueueSnackbar' +import closeSnackbar from '~/logic/notifications/store/actions/closeSnackbar' +import removeSnackbar from '~/logic/notifications/store/actions/removeSnackbar' + +export type Actions = { + enqueueSnackbar: typeof enqueueSnackbar, + closeSnackbar: typeof closeSnackbar, + removeSnackbar: typeof removeSnackbar, +} + +export default { + enqueueSnackbar, + closeSnackbar, + removeSnackbar, +} diff --git a/src/components/Notifier/index.js b/src/components/Notifier/index.js new file mode 100644 index 00000000..3f4ad6e9 --- /dev/null +++ b/src/components/Notifier/index.js @@ -0,0 +1,73 @@ +// @flow +import React, { Component } from 'react' +import { connect } from 'react-redux' +import { withSnackbar } from 'notistack' +import actions from './actions' +import selector from './selector' + +class Notifier extends Component { + displayed = [] + + shouldComponentUpdate({ notifications: newSnacks = [] }) { + const { notifications: currentSnacks, closeSnackbar, removeSnackbar } = this.props + + if (!newSnacks.size) { + this.displayed = [] + return false + } + let notExists = false + for (let i = 0; i < newSnacks.size; i += 1) { + const newSnack = newSnacks.get(i) + + if (newSnack.dismissed) { + closeSnackbar(newSnack.key) + removeSnackbar(newSnack.key) + } + + if (notExists) { + continue + } + notExists = notExists || !currentSnacks.filter(({ key }) => newSnack.key === key).length + } + return notExists + } + + componentDidUpdate() { + const { notifications = [], enqueueSnackbar, removeSnackbar } = this.props + + notifications.map(({ key, message, options = {} }) => { + // Do nothing if snackbar is already displayed + if (this.displayed.includes(key)) { + return + } + // Display snackbar using notistack + enqueueSnackbar(message, { + ...options, + onClose: (event, reason, key) => { + if (options.onClose) { + options.onClose(event, reason, key) + } + // Dispatch action to remove snackbar from redux store + removeSnackbar(key) + }, + }) + // Keep track of snackbars that we've displayed + this.storeDisplayed(key) + }) + } + + storeDisplayed = (id) => { + this.displayed = [...this.displayed, id] + } + + render() { + return null + } +} + +export default withSnackbar( + connect( + selector, + actions, + )(Notifier), +) diff --git a/src/components/Notifier/selector.js b/src/components/Notifier/selector.js new file mode 100644 index 00000000..148b3d3b --- /dev/null +++ b/src/components/Notifier/selector.js @@ -0,0 +1,7 @@ +// @flow +import { createStructuredSelector } from 'reselect' +import { notificationsListSelector } from '~/logic/notifications/store/selectors' + +export default createStructuredSelector({ + notifications: notificationsListSelector, +}) diff --git a/src/components/layout/PageFrame/index.jsx b/src/components/layout/PageFrame/index.jsx index 3dccd1a0..6d3c71bf 100644 --- a/src/components/layout/PageFrame/index.jsx +++ b/src/components/layout/PageFrame/index.jsx @@ -5,6 +5,7 @@ import { withStyles } from '@material-ui/core/styles' import SidebarProvider from '~/components/Sidebar' import Header from '~/components/Header' import Img from '~/components/layout/Img' +import Notifier from '~/components/Notifier' import AlertLogo from './assets/alert.svg' import CheckLogo from './assets/check.svg' import ErrorLogo from './assets/error.svg' @@ -72,6 +73,7 @@ const PageFrame = ({ children, classes }: Props) => ( info: '', }} > +
{children}