Replace notification bar by notification toasts (#1265)

* Replace notification bar by notification toasts

- Extract alert colors into variables
- Remove old CSS and Javascript files

* Update E2E tests

* Add updated package-lock.json

* Rework CSS

- Make the toast opaque
- Fix .env file
- Fix E2E tests

- Optimize time by closing notification manually
- Fix some issues created by the new toasts
diff --git a/.env b/.env
index bafbd5d..d18f8e8 100644
--- a/.env
+++ b/.env
@@ -2,4 +2,4 @@
 # This file needs to be placed where the docker-compose command is run from.
 
 # Used to provide a CouchDB 2 / 3 build matrix in .travis.yml
-COUCHDB_IMAGE=ibmcom/couchdb3:preview-1569600329
+COUCHDB_IMAGE=ibmcom/couchdb3
diff --git a/.gitignore b/.gitignore
index e8b3d39..047db07 100644
--- a/.gitignore
+++ b/.gitignore
@@ -42,3 +42,6 @@
 test/dashboard.assets
 # test coverage dir
 coverage
+
+# Jetbrains IDE
+.idea/
diff --git a/app/addons/components/components/stringeditmodal.js b/app/addons/components/components/stringeditmodal.js
index e0950de..df5877d 100644
--- a/app/addons/components/components/stringeditmodal.js
+++ b/app/addons/components/components/stringeditmodal.js
@@ -61,13 +61,14 @@
     this.props.onSave(this.editor.getValue());
   };
 
-  render() {
-    const { editorInitialized } = this.state;
-    const saveBt = editorInitialized ? (
+  getSaveBtn = () => {
+    return this.state.editorInitialized && (
       <button id="string-edit-save-btn" onClick={this.save} className="btn btn-primary save">
         <i className="fonticon-circle-check"></i> Modify Text
-      </button>
-    ) : null;
+      </button>);
+  };
+
+  render() {
     return (
       <Modal className="string-editor-modal" show={this.props.visible} onHide={this.closeModal}>
         <Modal.Header closeButton={true}>
@@ -81,7 +82,7 @@
         </Modal.Body>
         <Modal.Footer>
           <a className="cancel-link" onClick={this.closeModal}>Cancel</a>
-          { saveBt }
+          { this.getSaveBtn() }
         </Modal.Footer>
       </Modal>
     );
diff --git a/app/addons/databases/tests/nightwatch/checkDatabaseTooltip.js b/app/addons/databases/tests/nightwatch/checkDatabaseTooltip.js
index dbc896f..5da4cb2 100644
--- a/app/addons/databases/tests/nightwatch/checkDatabaseTooltip.js
+++ b/app/addons/databases/tests/nightwatch/checkDatabaseTooltip.js
@@ -34,7 +34,7 @@
       .clickWhenVisible('.tableview-checkbox-cell input[type="checkbox"]')
       .clickWhenVisible('.bulk-action-component-selector-group button.fonticon-trash', waitTime, false)
       .acceptAlert()
-      .waitForElementVisible('#global-notifications .alert.alert-info', waitTime, false)
+      .waitForElementVisible('.Toastify__toast-container .Toastify__toast--info', waitTime, false)
 
       .checkForStringPresent(newDatabaseName, '"doc_del_count":1')
 
diff --git a/app/addons/databases/tests/nightwatch/createsDatabase.js b/app/addons/databases/tests/nightwatch/createsDatabase.js
index 164ad84..2371b69 100644
--- a/app/addons/databases/tests/nightwatch/createsDatabase.js
+++ b/app/addons/databases/tests/nightwatch/createsDatabase.js
@@ -81,7 +81,7 @@
       .waitForElementVisible('#js-new-database-name', waitTime, false)
       .setValue('#js-new-database-name', [invalidDatabaseName])
       .clickWhenVisible('#js-create-database', waitTime, false)
-      .waitForElementVisible('.global-notification.alert.alert-error', waitTime, false)
+      .waitForElementVisible('.Toastify__toast-container .Toastify__toast--error', waitTime, false)
       .url(baseUrl + '/_all_dbs')
       .waitForElementVisible('html', waitTime, false)
       .getText('html', function (result) {
diff --git a/app/addons/databases/tests/nightwatch/createsDatabasePartitioned.js b/app/addons/databases/tests/nightwatch/createsDatabasePartitioned.js
index 4da4b31..93c3bf1 100644
--- a/app/addons/databases/tests/nightwatch/createsDatabasePartitioned.js
+++ b/app/addons/databases/tests/nightwatch/createsDatabasePartitioned.js
@@ -83,7 +83,7 @@
       .setValue('#js-new-database-name', [invalidDatabaseName])
       .clickWhenVisible('#non-partitioned-db', waitTime, false)
       .clickWhenVisible('#js-create-database', waitTime, false)
-      .waitForElementVisible('.global-notification.alert.alert-error', waitTime, false)
+      .waitForElementVisible('.Toastify__toast-container .Toastify__toast--error', waitTime, false)
       .url(baseUrl + '/_all_dbs')
       .waitForElementVisible('html', waitTime, false)
       .getText('html', function (result) {
diff --git a/app/addons/databases/tests/nightwatch/deletesDatabase.js b/app/addons/databases/tests/nightwatch/deletesDatabase.js
index d82c314..e5723d8 100644
--- a/app/addons/databases/tests/nightwatch/deletesDatabase.js
+++ b/app/addons/databases/tests/nightwatch/deletesDatabase.js
@@ -47,7 +47,7 @@
       .waitForElementVisible('.delete-db-modal', waitTime, false)
       .clickWhenVisible('.delete-db-modal input[type="text"]', waitTime, false)
       .setValue('.delete-db-modal input[type="text"]', [newDatabaseName, client.Keys.ENTER])
-      .waitForElementNotPresent('.global-notification .fonticon-cancel', waitTime, false)
+      .closeNotifications()
       .waitForElementPresent('.fauxton-table-list', waitTime, false)
       .checkForDatabaseDeleted(newDatabaseName, waitTime)
       .assert.elementNotPresent('a[href="database/' + newDatabaseName + '/_all_docs"]')
diff --git a/app/addons/databases/tests/nightwatch/deletesDatabaseSpecialChars.js b/app/addons/databases/tests/nightwatch/deletesDatabaseSpecialChars.js
index 37d8b28..3476b89 100644
--- a/app/addons/databases/tests/nightwatch/deletesDatabaseSpecialChars.js
+++ b/app/addons/databases/tests/nightwatch/deletesDatabaseSpecialChars.js
@@ -21,7 +21,6 @@
       .createDatabase(newDatabaseName)
       .loginToGUI()
       .url(baseUrl + '/#/database/' + encodeURIComponent(newDatabaseName) + '/_all_docs')
-      .waitForElementNotPresent('.global-notification .fonticon-cancel', waitTime, false)
       .clickWhenVisible('.faux-header__doc-header-dropdown-toggle')
       .clickWhenVisible('.faux-header__doc-header-dropdown-itemwrapper .fonticon-trash')
       .waitForElementVisible('.delete-db-modal', waitTime, false)
@@ -48,7 +47,7 @@
       .waitForElementPresent('.delete-db-modal', waitTime, false)
       .clickWhenVisible('.delete-db-modal input[type="text"]', waitTime, false)
       .setValue('.delete-db-modal input[type="text"]', [newDatabaseName, client.Keys.ENTER])
-      .waitForElementNotPresent('.global-notification .fonticon-cancel', waitTime, false)
+      .closeNotifications()
       .waitForElementPresent('.fauxton-table-list', waitTime, false)
       .checkForDatabaseDeleted(newDatabaseName, waitTime)
       .assert.elementNotPresent('a[href="database/' + newDatabaseName + '/_all_docs"]')
diff --git a/app/addons/databases/tests/nightwatch/specialCharListLinks.js b/app/addons/databases/tests/nightwatch/specialCharListLinks.js
index aef1b68..650410f 100644
--- a/app/addons/databases/tests/nightwatch/specialCharListLinks.js
+++ b/app/addons/databases/tests/nightwatch/specialCharListLinks.js
@@ -37,7 +37,6 @@
 
       .url(baseUrl + '#/_all_dbs')
       .clickWhenVisible(`.fauxton-table-list a[href="database/${encodeURIComponent(db)}/_all_docs"]`)
-      .waitForElementNotPresent('#global-notifications .fonticon-cancel', waitTime, false)
 
       .waitForElementVisible('.faux-header__doc-header-title', waitTime, false)
       .waitForElementVisible('.no-results-screen', waitTime, false)
diff --git a/app/addons/documents/doc-editor/components/DocEditorScreen.js b/app/addons/documents/doc-editor/components/DocEditorScreen.js
index f8f661f..242b3e6 100644
--- a/app/addons/documents/doc-editor/components/DocEditorScreen.js
+++ b/app/addons/documents/doc-editor/components/DocEditorScreen.js
@@ -96,7 +96,10 @@
     if (this.props.numFilesUploaded !== nextProps.numFilesUploaded ||
         this.props.doc && this.props.doc.hasChanged() ||
         (this.props.doc && nextProps.doc && this.props.doc.id !== nextProps.doc.id)) {
-      this.getEditor().setValue(JSON.stringify(nextProps.doc.attributes, null, '  '));
+      const editor = this.getEditor();
+      if (editor) {
+        this.getEditor().setValue(JSON.stringify(nextProps.doc.attributes, null, '  '));
+      }
       this.onSaveComplete();
     }
   }
diff --git a/app/addons/documents/tests/nightwatch/bulkDelete.js b/app/addons/documents/tests/nightwatch/bulkDelete.js
index c3330ea..9f88268 100644
--- a/app/addons/documents/tests/nightwatch/bulkDelete.js
+++ b/app/addons/documents/tests/nightwatch/bulkDelete.js
@@ -35,7 +35,7 @@
       .clickWhenVisible('.bulk-action-component-selector-group input[type="checkbox"]')
       .clickWhenVisible('.bulk-action-component-selector-group button.fonticon-trash', waitTime, false)
       .acceptAlert()
-      .waitForElementVisible('#global-notifications .alert.alert-info', waitTime, false)
+      .waitForElementVisible('.Toastify__toast-container .Toastify__toast--info', waitTime, false)
       .waitForElementNotPresent('[data-id="' + newDocumentName1 + '"]', waitTime, false)
       .getText('body', function (result) {
         var data = result.value,
@@ -69,7 +69,7 @@
       .clickWhenVisible('.bulk-action-component-selector-group input[type="checkbox"]')
       .clickWhenVisible('.bulk-action-component-selector-group button.fonticon-trash', waitTime, false)
       .acceptAlert()
-      .waitForElementVisible('#global-notifications .alert.alert-info', waitTime, false)
+      .waitForElementVisible('.Toastify__toast-container .Toastify__toast--info', waitTime, false)
       .waitForElementNotPresent('.table-view-docs ', waitTime, false)
       .getText('body', function (result) {
         var data = result.value,
diff --git a/app/addons/documents/tests/nightwatch/deleteDatabaseModal.js b/app/addons/documents/tests/nightwatch/deleteDatabaseModal.js
index 32fc47b..40a6029 100644
--- a/app/addons/documents/tests/nightwatch/deleteDatabaseModal.js
+++ b/app/addons/documents/tests/nightwatch/deleteDatabaseModal.js
@@ -36,7 +36,6 @@
     client
       .loginToGUI()
       .url(baseUrl + '/#/database/_replicator/_all_docs')
-      .waitForElementNotPresent('.global-notification .fonticon-cancel', waitTime, false)
 
       .clickWhenVisible('.faux-header__doc-header-dropdown-toggle')
       .clickWhenVisible('.faux-header__doc-header-dropdown-itemwrapper .fonticon-trash')
@@ -55,7 +54,6 @@
     client
       .loginToGUI()
       .url(baseUrl + '/#/database/' + newDatabaseName + '/_all_docs')
-      .waitForElementNotPresent('.global-notification .fonticon-cancel', waitTime, false)
 
       .clickWhenVisible('.faux-header__doc-header-dropdown-toggle')
       .clickWhenVisible('.faux-header__doc-header-dropdown-itemwrapper .fonticon-trash')
diff --git a/app/addons/documents/tests/nightwatch/deletesDocuments.js b/app/addons/documents/tests/nightwatch/deletesDocuments.js
index 6d0372b..536ca23 100644
--- a/app/addons/documents/tests/nightwatch/deletesDocuments.js
+++ b/app/addons/documents/tests/nightwatch/deletesDocuments.js
@@ -33,7 +33,7 @@
       .clickWhenVisible('label[for="checkbox-' + newDocumentName + '"]', waitTime, false)
       .clickWhenVisible('.bulk-action-component-selector-group button.fonticon-trash', waitTime, false)
       .acceptAlert()
-      .waitForElementVisible('.alert.alert-info', waitTime, false)
+      .waitForElementVisible('.Toastify__toast-container .Toastify__toast--info', waitTime, false)
 
       .waitForElementVisible('label[for="checkbox-' + newDocumentName + '2' + '"]', waitTime, false)
       .clickWhenVisible('label[for="checkbox-' + newDocumentName + '2' + '"]', waitTime, false)
@@ -74,7 +74,7 @@
       .clickWhenVisible('#checkbox-' + newDocumentName, waitTime, false)
       .clickWhenVisible('.bulk-action-component-selector-group button.fonticon-trash', waitTime, false)
       .acceptAlert()
-      .waitForElementVisible('.alert.alert-info', waitTime, false)
+      .waitForElementVisible('.Toastify__toast-container .Toastify__toast--info', waitTime, false)
 
       .clickWhenVisible('#checkbox-' + newDocumentName + '2', waitTime, false)
       .clickWhenVisible('.bulk-action-component-selector-group button.fonticon-trash', waitTime, false)
diff --git a/app/addons/documents/tests/nightwatch/navigateNotFoundDB.js b/app/addons/documents/tests/nightwatch/navigateNotFoundDB.js
index 610b493..02c4ab2 100644
--- a/app/addons/documents/tests/nightwatch/navigateNotFoundDB.js
+++ b/app/addons/documents/tests/nightwatch/navigateNotFoundDB.js
@@ -23,8 +23,11 @@
     client
       .loginToGUI()
       .url(baseUrl + '/#/database/does-not-exist/_all_docs')
-      .waitForElementPresent('.global-notification', waitTime, false)
-      .assert.containsText('.global-notification', 'does not exist')
+      .waitForElementVisible('.Toastify__toast-container .Toastify__toast--error', waitTime, false)
+      // We wait for the first toasts to be cleared
+      .pause(1000)
+      .waitForElementVisible('.Toastify__toast-container .Toastify__toast--error', waitTime, false)
+      .assert.containsText('.Toastify__toast-container .Toastify__toast--error .Toastify__toast-body', 'does not exist')
       .verify.urlEquals(baseUrl + '/#');
   }
 };
diff --git a/app/addons/documents/tests/nightwatch/replicateDatabaseButton.js b/app/addons/documents/tests/nightwatch/replicateDatabaseButton.js
index 711a348..f1b399b 100644
--- a/app/addons/documents/tests/nightwatch/replicateDatabaseButton.js
+++ b/app/addons/documents/tests/nightwatch/replicateDatabaseButton.js
@@ -35,7 +35,6 @@
     client
       .loginToGUI()
       .url(baseUrl + '/#/database/' + testDbName + '/_all_docs')
-      .waitForElementNotPresent('.global-notification .fonticon-cancel', waitTime, false)
 
       .clickWhenVisible('.faux-header__doc-header-dropdown-toggle')
       .clickWhenVisible('.faux-header__doc-header-dropdown-itemwrapper .fonticon-replicate')
diff --git a/app/addons/documents/tests/nightwatch/revBrowser.js b/app/addons/documents/tests/nightwatch/revBrowser.js
index 7d19a70..716120a 100644
--- a/app/addons/documents/tests/nightwatch/revBrowser.js
+++ b/app/addons/documents/tests/nightwatch/revBrowser.js
@@ -91,7 +91,7 @@
       .clickWhenVisible('.modal-footer input[type="checkbox"]')
       .clickWhenVisible('.modal-footer button.btn-danger')
       .clickWhenVisible('.fonticon-json')
-
+      .waitForElementNotVisible('.Toastify__toast-container .Toastify__toast', waitTime, false)
       .clickWhenVisible('[data-id="_design/animals"] a')
 
       .waitForElementVisible('.panel-section', waitTime, false)
diff --git a/app/addons/documents/tests/nightwatch/viewCreate.js b/app/addons/documents/tests/nightwatch/viewCreate.js
index dba31d0..d9d1295 100644
--- a/app/addons/documents/tests/nightwatch/viewCreate.js
+++ b/app/addons/documents/tests/nightwatch/viewCreate.js
@@ -144,7 +144,6 @@
     .loginToGUI()
     .populateDatabase(newDatabaseName)
     .url(baseUrl + '/#/database/' + newDatabaseName + '/_all_docs')
-    .waitForElementNotPresent('.global-notification .fonticon-cancel', waitTime, false)
     .clickWhenVisible('.faux-header__doc-header-dropdown-toggle')
     .clickWhenVisible('.faux-header__doc-header-dropdown-itemwrapper a[href*="new_view"]')
     .waitForElementPresent('.index-cancel-link', waitTime, false);
diff --git a/app/addons/documents/tests/nightwatch/viewCreateBadView.js b/app/addons/documents/tests/nightwatch/viewCreateBadView.js
index 7405ed0..384406a 100644
--- a/app/addons/documents/tests/nightwatch/viewCreateBadView.js
+++ b/app/addons/documents/tests/nightwatch/viewCreateBadView.js
@@ -38,7 +38,7 @@
       ')
       .execute('document.querySelector("#save-view").scrollIntoView();')
       .clickWhenVisible('#save-view')
-      .waitForElementNotPresent('.global-notification .fonticon-cancel', waitTime, false)
+      .closeNotifications()
       .clickWhenVisible('.control-toggle-queryoptions', waitTime, false)
       .clickWhenVisible('label[for="qoReduce"]', waitTime, false)
       .clickWhenVisible('.query-options .btn-secondary', waitTime, false)
diff --git a/app/addons/documents/tests/nightwatch/viewEdit.js b/app/addons/documents/tests/nightwatch/viewEdit.js
index 96cf838..8ff46d4 100644
--- a/app/addons/documents/tests/nightwatch/viewEdit.js
+++ b/app/addons/documents/tests/nightwatch/viewEdit.js
@@ -139,7 +139,7 @@
 
       // create the second view
       .url(baseUrl + '/#/database/' + newDatabaseName + '/_all_docs')
-      .waitForElementNotPresent('.global-notification .fonticon-cancel', waitTime, false)
+      .closeNotifications()
 
       .clickWhenVisible('.faux-header__doc-header-dropdown-toggle')
       .clickWhenVisible('.faux-header__doc-header-dropdown-itemwrapper a[href*="new_view"]')
diff --git a/app/addons/fauxton/appwrapper.js b/app/addons/fauxton/appwrapper.js
index 3df37c8..77cbca3 100644
--- a/app/addons/fauxton/appwrapper.js
+++ b/app/addons/fauxton/appwrapper.js
@@ -12,12 +12,13 @@
 
 import React from 'react';
 import { connect } from 'react-redux';
-import GlobalNotificationsContainer from './notifications/components/GlobalNotificationsContainer';
 import NotificationPanelContainer from './notifications/components/NotificationPanelContainer';
 import PermanentNotificationContainer from './notifications/components/PermanentNotificationContainer';
 import NavBar from './navigation/container/NavBar';
 import * as NavbarActions from './navigation/actions';
 import classNames from 'classnames';
+import {toast, ToastContainer} from "react-toastify";
+
 
 class ContentWrapper extends React.Component {
   constructor(props) {
@@ -31,6 +32,13 @@
     }
   }
 
+  onKeydown = e => {
+    const code = e.keyCode || e.which;
+    if (code === 27) {
+      toast.dismiss();
+    }
+  };
+
   componentDidMount () {
     this.props.router.on('new-component', (routerOptions) => {
       this.setState({routerOptions});
@@ -40,6 +48,12 @@
     this.props.router.on('trigger-update', () => {
       this.forceUpdate();
     });
+
+    document.addEventListener('keydown', this.onKeydown);
+  }
+
+  componentWillUnmount() {
+    document.removeEventListener('keydown', this.onKeydown);
   }
 
   render () {
@@ -64,9 +78,21 @@
     );
     return (
       <div>
-        <PermanentNotificationContainer />
+        <ToastContainer
+          className='toast-container'
+          position="top-right"
+          autoClose={5000}
+          closeButton={false}
+          hideProgressBar
+          newestOnTop={false}
+          closeOnClick
+          rtl={false}
+          pauseOnFocusLoss
+          draggable
+          pauseOnHover
+        />
+        <PermanentNotificationContainer/>
         <div id="notifications">
-          <GlobalNotificationsContainer />
           <NotificationPanelContainer />
         </div>
         <div id="main"  className={mainClass}>
diff --git a/app/addons/fauxton/notifications/__tests__/components.test.js b/app/addons/fauxton/notifications/__tests__/components.test.js
index 19eac52..34c3248 100644
--- a/app/addons/fauxton/notifications/__tests__/components.test.js
+++ b/app/addons/fauxton/notifications/__tests__/components.test.js
@@ -14,81 +14,27 @@
 import moment from "moment";
 import { mount } from 'enzyme';
 import sinon from 'sinon';
-import Notification from '../components/Notification';
 import NotificationCenterPanel from '../components/NotificationCenterPanel';
 import NotificationPanelRow from '../components/NotificationPanelRow';
 
-describe('Notification', () => {
-  it('startHide is only called after visible time is out', (done) => {
-    const spy = sinon.spy();
-    const component = mount(<Notification
-      notificationId={123}
-      isHiding={false}
-      key={11}
-      msg={'a msg'}
-      type={'error'}
-      escape={true}
-      style={{opacity:1}}
-      visibleTime={1000}
-      onStartHide={spy}
-      onHideComplete={() => {}}
-    />);
-
-    expect(spy.called).toBeFalsy();
-
-    setTimeout(() => {
-      component.update();
-      expect(spy.called).toBeTruthy();
-      done();
-    }, 3000);
-  });
-
-  it('notification text should be escaped by default', () => {
-    const wrapper = mount(<Notification
-      notificationId={123}
-      isHiding={false}
-      msg={'<script>window.whatever=1;</script>'}
-      type={'error'}
-      style={{opacity:1}}
-      onStartHide={() => {}}
-      onHideComplete={() => {}}
-    />);
-    expect(/&lt;script&gt;window.whatever=1;&lt;\/script&gt;/.test(wrapper.html())).toBeTruthy();
-  });
-
-  it('notification text can be rendered unescaped', () => {
-    const wrapper = mount(<Notification
-      notificationId={123}
-      isHiding={false}
-      msg={'<script>window.whatever=1;</script>'}
-      type={'error'}
-      escape={false}
-      style={{opacity:1}}
-      onStartHide={() => {}}
-      onHideComplete={() => {}}
-    />);
-    expect(/<script>window.whatever=1;<\/script>/.test(wrapper.html())).toBeTruthy();
-  });
-});
-
 describe('NotificationPanelRow', () => {
   const notifications = {
     success: {
-      notificationId: 1,
+      toastId: 1,
       type: 'success',
       msg: 'Success!',
       cleanMsg: 'Success!',
       time: moment()
     },
     info: {
-      notificationId: 2,
+      toastId: 2,
       type: 'info',
       msg: 'Error!',
       cleanMsg: 'Error!',
       time: moment()
     },
     error: {
-      notificationId: 3,
+      toastId: 3,
       type: 'error',
       msg: 'Error!',
       cleanMsg: 'Error!',
diff --git a/app/addons/fauxton/notifications/__tests__/reducers.test.js b/app/addons/fauxton/notifications/__tests__/reducers.test.js
index 6304974..66da88b 100644
--- a/app/addons/fauxton/notifications/__tests__/reducers.test.js
+++ b/app/addons/fauxton/notifications/__tests__/reducers.test.js
@@ -74,11 +74,11 @@
     newState = reducer(newState, action);
     expect(newState.notifications.length).toBe(4);
 
-    const idToRemove = newState.notifications[1].notificationId;
+    const idToRemove = newState.notifications[1].toastId;
     const msgToRemove = newState.notifications[1].msg;
     newState = reducer(newState, {
       type: ActionTypes.CLEAR_SINGLE_NOTIFICATION,
-      options: { notificationId: idToRemove }
+      options: { toastId: idToRemove }
     });
     expect(newState.notifications.length).toBe(3);
     const item = newState.notifications.find(el => {
diff --git a/app/addons/fauxton/notifications/actions.js b/app/addons/fauxton/notifications/actions.js
index 0e57faf..8ef9621 100644
--- a/app/addons/fauxton/notifications/actions.js
+++ b/app/addons/fauxton/notifications/actions.js
@@ -33,11 +33,11 @@
   dispatch({ type: ActionTypes.CLEAR_ALL_NOTIFICATIONS });
 };
 
-export const clearSingleNotification = (notificationId) => (dispatch) => {
+export const clearSingleNotification = (toastId) => (dispatch) => {
   dispatch({
     type: ActionTypes.CLEAR_SINGLE_NOTIFICATION,
     options: {
-      notificationId: notificationId
+      toastId: toastId
     }
   });
 };
@@ -50,25 +50,3 @@
     }
   });
 };
-
-export const startHidingNotification = (notificationId) => (dispatch) => {
-  dispatch({
-    type: ActionTypes.START_HIDING_NOTIFICATION,
-    options: {
-      notificationId: notificationId
-    }
-  });
-};
-
-export const hideNotification = (notificationId) => (dispatch) => {
-  dispatch({
-    type: ActionTypes.HIDE_NOTIFICATION,
-    options: {
-      notificationId: notificationId
-    }
-  });
-};
-
-export const hideAllVisibleNotifications = () => (dispatch) => {
-  dispatch({ type: ActionTypes.HIDE_ALL_NOTIFICATIONS });
-};
diff --git a/app/addons/fauxton/notifications/actiontypes.js b/app/addons/fauxton/notifications/actiontypes.js
index cd6d275..654925b 100644
--- a/app/addons/fauxton/notifications/actiontypes.js
+++ b/app/addons/fauxton/notifications/actiontypes.js
@@ -17,9 +17,6 @@
   CLEAR_SINGLE_NOTIFICATION: 'CLEAR_SINGLE_NOTIFICATION',
   CLEAR_ALL_NOTIFICATIONS: 'CLEAR_ALL_NOTIFICATIONS',
   SELECT_NOTIFICATION_FILTER: 'SELECT_NOTIFICATION_FILTER',
-  START_HIDING_NOTIFICATION: 'START_HIDING_NOTIFICATION',
-  HIDE_NOTIFICATION: 'HIDE_NOTIFICATION',
-  HIDE_ALL_NOTIFICATIONS: 'HIDE_ALL_NOTIFICATIONS',
   SHOW_PERMANENT_NOTIFICATION: 'SHOW_PERMANENT_NOTIFICATION',
   HIDE_PERMANENT_NOTIFICATION: 'HIDE_PERMANENT_NOTIFICATION'
 };
diff --git a/app/addons/fauxton/notifications/components/GlobalNotifications.js b/app/addons/fauxton/notifications/components/GlobalNotifications.js
deleted file mode 100644
index 2a3d7ce..0000000
--- a/app/addons/fauxton/notifications/components/GlobalNotifications.js
+++ /dev/null
@@ -1,135 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-
-import PropTypes from 'prop-types';
-import React from 'react';
-import {TransitionMotion, spring, presets} from 'react-motion';
-import Notification from './Notification';
-
-export default class GlobalNotifications extends React.Component {
-  static propTypes = {
-    notifications: PropTypes.array.isRequired,
-    hideAllVisibleNotifications: PropTypes.func.isRequired,
-    startHidingNotification: PropTypes.func.isRequired,
-    hideNotification: PropTypes.func.isRequired
-  };
-
-  componentDidMount() {
-    document.addEventListener('keydown', this.onKeyDown);
-  }
-
-  componentWillUnmount() {
-    document.removeEventListener('keydown', this.onKeyDown);
-  }
-
-  onKeyDown = (e) => {
-    const code = e.keyCode || e.which;
-    if (code === 27) {
-      this.props.hideAllVisibleNotifications();
-    }
-  };
-
-  getNotifications = () => {
-    if (!this.props.notifications.length) {
-      return null;
-    }
-
-    return this.props.notifications.map((notification, index) => {
-
-      // notifications are completely removed from the DOM once they're
-      if (!notification.visible) {
-        return;
-      }
-
-      return (
-        <Notification
-          notificationId={notification.notificationId}
-          isHiding={notification.isHiding}
-          key={index}
-          msg={notification.msg}
-          type={notification.type}
-          escape={notification.escape}
-          visibleTime={notification.visibleTime}
-          onStartHide={this.props.startHidingNotification}
-          onHideComplete={this.props.hideNotification} />
-      );
-    });
-  };
-
-  getchildren = (items) => {
-    const notifications = items.map(({key, data, style}) => {
-      const notification = data;
-      return (
-        <Notification
-          key={key}
-          style={style}
-          notificationId={notification.notificationId}
-          isHiding={notification.isHiding}
-          msg={notification.msg}
-          type={notification.type}
-          escape={notification.escape}
-          visibleTime={notification.visibleTime}
-          onStartHide={this.props.startHidingNotification}
-          onHideComplete={this.props.hideNotification} />
-      );
-    });
-
-    return (
-      <div>
-        {notifications}
-      </div>
-    );
-  };
-
-  getStyles = (prevItems) => {
-    if (!prevItems) {
-      prevItems = [];
-    }
-
-    return this.props.notifications
-      .filter(notification => notification.visible)
-      .map(notification => {
-        let item = prevItems.find(style => style.key === (notification.notificationId.toString()));
-        let style = !item ? {opacity: 0.5, minHeight: 50} : false;
-
-        if (!style && !notification.isHiding) {
-          style = {
-            opacity: spring(1, presets.stiff),
-            minHeight: spring(64)
-          };
-        } else if (!style && notification.isHiding) {
-          style = {
-            opacity: spring(0, presets.stiff),
-            minHeight: spring(0, presets.stiff)
-          };
-        }
-
-        return {
-          key: notification.notificationId.toString(),
-          style,
-          data: notification
-        };
-      });
-  };
-
-  render() {
-    return (
-      <div id="global-notifications" role="alert">
-        <TransitionMotion
-          styles={this.getStyles}
-        >
-          {this.getchildren}
-        </TransitionMotion>
-      </div>
-    );
-  }
-}
diff --git a/app/addons/fauxton/notifications/components/GlobalNotificationsContainer.js b/app/addons/fauxton/notifications/components/GlobalNotificationsContainer.js
deleted file mode 100644
index 8eb19b2..0000000
--- a/app/addons/fauxton/notifications/components/GlobalNotificationsContainer.js
+++ /dev/null
@@ -1,30 +0,0 @@
-import { connect } from 'react-redux';
-import * as Actions from '../actions';
-import GlobalNotifications from './GlobalNotifications';
-
-const mapStateToProps = ({ notifications }) => {
-  return {
-    notifications: notifications.notifications
-  };
-};
-
-const mapDispatchToProps = (dispatch) => {
-  return {
-    startHidingNotification: (notificationId) => {
-      dispatch(Actions.startHidingNotification(notificationId));
-    },
-    hideNotification: (notificationId) => {
-      dispatch(Actions.hideNotification(notificationId));
-    },
-    hideAllVisibleNotifications: () => {
-      dispatch(Actions.hideAllVisibleNotifications());
-    }
-  };
-};
-
-const GlobalNotificationsContainer = connect(
-  mapStateToProps,
-  mapDispatchToProps
-)(GlobalNotifications);
-
-export default GlobalNotificationsContainer;
diff --git a/app/addons/fauxton/notifications/components/Notification.js b/app/addons/fauxton/notifications/components/Notification.js
deleted file mode 100644
index ed7c6ca..0000000
--- a/app/addons/fauxton/notifications/components/Notification.js
+++ /dev/null
@@ -1,91 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-
-import PropTypes from 'prop-types';
-import React from 'react';
-
-export default class Notification extends React.Component {
-  static propTypes = {
-    notificationId: PropTypes.number.isRequired,
-    msg: PropTypes.string.isRequired,
-    onStartHide: PropTypes.func.isRequired,
-    onHideComplete: PropTypes.func.isRequired,
-    type: PropTypes.oneOf(['error', 'info', 'success']),
-    escape: PropTypes.bool,
-    isHiding: PropTypes.bool.isRequired,
-    visibleTime: PropTypes.number
-  };
-
-  static defaultProps = {
-    type: 'info',
-    visibleTime: 8000,
-    escape: true
-  };
-
-  componentWillUnmount() {
-    if (this.timeout) {
-      window.clearTimeout(this.timeout);
-    }
-  }
-
-  componentDidMount() {
-    this.timeout = setTimeout(this.hide, this.props.visibleTime);
-  }
-
-  hide = (e) => {
-    if (e) {
-      e.preventDefault();
-    }
-    this.props.onStartHide(this.props.notificationId);
-  };
-
-  // many messages contain HTML, hence the need for dangerouslySetInnerHTML
-  getMsg = () => {
-    var msg = (this.props.escape) ? _.escape(this.props.msg) : this.props.msg;
-    return {
-      __html: msg
-    };
-  };
-
-  onAnimationComplete = () => {
-    if (this.props.isHiding) {
-      window.setTimeout(() => this.props.onHideComplete(this.props.notificationId));
-    }
-  };
-
-  render() {
-    const {style, notificationId} = this.props;
-    const iconMap = {
-      error: 'fonticon-attention-circled',
-      info: 'fonticon-info-circled',
-      success: 'fonticon-ok-circled'
-    };
-
-    if (style.opacity === 0 && this.props.isHiding) {
-      this.onAnimationComplete();
-    }
-
-    return (
-      <div
-        key={notificationId.toString()} className="notification-wrapper" style={{opacity: style.opacity, minHeight: style.minHeight + 'px'}}>
-        <div
-          style={{opacity: style.opacity, minHeight: style.minHeight + 'px'}}
-          className={'global-notification alert alert-' + this.props.type}
-          ref={node => this.notification = node}>
-          <a data-bypass href="#" onClick={this.hide}><i className="pull-right fonticon-cancel" /></a>
-          <i className={'notification-icon ' + iconMap[this.props.type]} />
-          <span dangerouslySetInnerHTML={this.getMsg()}></span>
-        </div>
-      </div>
-    );
-  }
-}
diff --git a/app/addons/fauxton/notifications/components/NotificationCenterPanel.js b/app/addons/fauxton/notifications/components/NotificationCenterPanel.js
index 85ca45a..9657035 100644
--- a/app/addons/fauxton/notifications/components/NotificationCenterPanel.js
+++ b/app/addons/fauxton/notifications/components/NotificationCenterPanel.js
@@ -56,7 +56,7 @@
   getStyles = (prevItems = []) => {
     const styles = this.props.notifications
       .map(notification => {
-        let item = prevItems.find(style => style.key === (notification.notificationId.toString()));
+        let item = prevItems.find(style => style.key === (notification.toastId.toString()));
         let style = !item ? {opacity: 0, height: 0} : false;
 
         if (!style && (notification.type === this.props.filter || this.props.filter === 'all')) {
@@ -72,7 +72,7 @@
         }
 
         return {
-          key: notification.notificationId.toString(),
+          key: notification.toastId.toString(),
           style,
           data: notification
         };
diff --git a/app/addons/fauxton/notifications/components/NotificationPanelContainer.js b/app/addons/fauxton/notifications/components/NotificationPanelContainer.js
index ec03cdc..a4d37e2 100644
--- a/app/addons/fauxton/notifications/components/NotificationPanelContainer.js
+++ b/app/addons/fauxton/notifications/components/NotificationPanelContainer.js
@@ -21,8 +21,8 @@
     clearAllNotifications: () => {
       dispatch(Actions.clearAllNotifications());
     },
-    clearSingleNotification: (notificationId) => {
-      dispatch(Actions.clearSingleNotification(notificationId));
+    clearSingleNotification: (toastId) => {
+      dispatch(Actions.clearSingleNotification(toastId));
     }
   };
 };
diff --git a/app/addons/fauxton/notifications/components/NotificationPanelRow.js b/app/addons/fauxton/notifications/components/NotificationPanelRow.js
index f267c20..8ece5bb 100644
--- a/app/addons/fauxton/notifications/components/NotificationPanelRow.js
+++ b/app/addons/fauxton/notifications/components/NotificationPanelRow.js
@@ -24,8 +24,8 @@
   };
 
   clearNotification = () => {
-    const {notificationId} = this.props.item;
-    this.props.clearSingleNotification(notificationId);
+    const {toastId} = this.props.item;
+    this.props.clearSingleNotification(toastId);
   };
 
   render() {
diff --git a/app/addons/fauxton/notifications/reducers.js b/app/addons/fauxton/notifications/reducers.js
index 0f755d1..c2b4f77 100644
--- a/app/addons/fauxton/notifications/reducers.js
+++ b/app/addons/fauxton/notifications/reducers.js
@@ -10,9 +10,11 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
+import React from 'react';
 import moment from 'moment';
 import app from '../../../app';
 import ActionTypes from './actiontypes';
+import {toast} from "react-toastify";
 
 const initialState = {
   notifications: [],
@@ -29,32 +31,38 @@
 function addNotification ({ notifications }, info) {
   const newNotifications = notifications.slice();
   info = { ...info };
-  info.notificationId = ++counter;
-  info.cleanMsg = app.utils.stripHTML(info.msg);
+  info.toastId = ++counter;
   info.time = moment();
+  info.cleanMsg = app.utils.stripHTML(info.msg);
+
   if (info.escape !== true && info.escape !== false) {
     info.escape = true;
   }
 
-  // all new notifications are visible by default. They get hidden after their time expires, by the component
-  info.visible = true;
-  info.isHiding = false;
+  info.htmlMsg = {
+    __html: (info.escape ? _.escape(info.cleanMsg) : info.cleanMsg)
+  };
 
   // clear: true causes all visible messages to be hidden
   if (info.clear) {
-    newNotifications.forEach((notification) => {
-      if (notification.visible) {
-        notification.isHiding = true;
-      }
-    });
+    const idsToClear = _.map(notifications, 'toastId');
+
+    // Add some delay to prevent weird flickering
+    setTimeout(() => {
+      idsToClear.forEach(id => toast.dismiss(id));
+    }, 800);
   }
+
   newNotifications.unshift(info);
+
+  toast(<span dangerouslySetInnerHTML={info.htmlMsg}/>, info);
+
   return newNotifications;
 }
 
-function clearNotification({ notifications }, notificationId) {
+function clearNotification({ notifications }, toastId) {
   const idx = notifications.findIndex(el => {
-    return el.notificationId === notificationId;
+    return el.toastId === toastId;
   });
   if (idx === -1) {
     // no changes
@@ -63,45 +71,9 @@
 
   const newNotifications = [].concat(notifications);
   newNotifications.splice(idx, 1);
-  return newNotifications;
-}
 
-function startHidingNotification({ notifications }, notificationId) {
-  const idx = notifications.findIndex(el => {
-    return el.notificationId === notificationId;
-  });
-  if (idx === -1) {
-    // no changes
-    return notifications;
-  }
+  toast.dismiss(toastId);
 
-  const newNotifications = [].concat(notifications);
-  newNotifications[idx].isHiding = true;
-  return newNotifications;
-}
-
-function hideNotification({ notifications }, notificationId) {
-  const idx = notifications.findIndex(el => {
-    return el.notificationId === notificationId;
-  });
-  if (idx === -1) {
-    // no changes
-    return notifications;
-  }
-
-  const newNotifications = [].concat(notifications);
-  newNotifications[idx].visible = false;
-  newNotifications[idx].isHiding = false;
-  return newNotifications;
-}
-
-function hideAllNotifications({ notifications }) {
-  const newNotifications = [].concat(notifications);
-  newNotifications.forEach((notification) => {
-    if (notification.visible) {
-      notification.isHiding = true;
-    }
-  });
   return newNotifications;
 }
 
@@ -119,6 +91,7 @@
       };
 
     case ActionTypes.CLEAR_ALL_NOTIFICATIONS:
+      toast.dismiss();
       return {
         ...state,
         notifications: []
@@ -127,25 +100,7 @@
     case ActionTypes.CLEAR_SINGLE_NOTIFICATION:
       return {
         ...state,
-        notifications: clearNotification(state, options.notificationId)
-      };
-
-    case ActionTypes.START_HIDING_NOTIFICATION:
-      return {
-        ...state,
-        notifications: startHidingNotification(state, options.notificationId)
-      };
-
-    case ActionTypes.HIDE_NOTIFICATION:
-      return {
-        ...state,
-        notifications: hideNotification(state, options.notificationId)
-      };
-
-    case ActionTypes.HIDE_ALL_NOTIFICATIONS:
-      return {
-        ...state,
-        notifications: hideAllNotifications(state)
+        notifications: clearNotification(state, options.toastId)
       };
 
     case ActionTypes.SHOW_NOTIFICATION_CENTER:
diff --git a/app/addons/fauxton/notifications/stores.js b/app/addons/fauxton/notifications/stores.js
deleted file mode 100644
index a1304e1..0000000
--- a/app/addons/fauxton/notifications/stores.js
+++ /dev/null
@@ -1,153 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-
-import FauxtonAPI from "../../../core/api";
-import app from "../../../app";
-import ActionTypes from "./actiontypes";
-import moment from "moment";
-var Stores = {};
-
-// static var used to assign a unique ID to each notification
-var counter = 0;
-var validNotificationTypes = ['success', 'error', 'info'];
-
-
-/**
- * Notifications are of the form:
- * {
- *   notificationId: N,
- *   message: "string",
- *   type: "success"|etc. see above list
- *   clear: true|false,
- *   escape: true|false
- * }
- */
-
-Stores.NotificationStore = FauxtonAPI.Store.extend({
-
-
-  addNotification (info) {
-    if (_.isEmpty(info.type) || !_.includes(validNotificationTypes, info.type)) {
-      console.warn('Invalid message type: ', info);
-      return;
-    }
-
-    info.notificationId = ++counter;
-    info.cleanMsg = app.utils.stripHTML(info.msg);
-    info.time = moment();
-
-    // all new notifications are visible by default. They get hidden after their time expires, by the component
-    info.visible = true;
-    info.isHiding = false;
-
-    // clear: true causes all visible messages to be hidden
-    if (info.clear) {
-      this._notifications.forEach(function (notification) {
-        if (notification.visible) {
-          notification.isHiding = true;
-        }
-      });
-    }
-    this._notifications.unshift(info);
-  },
-
-  clearNotification (notificationId) {
-    this._notifications = _.without(this._notifications, _.find(this._notifications, { notificationId: notificationId }));
-  },
-
-  hideNotification (notificationId) {
-    var notification = _.find(this._notifications, { notificationId: notificationId });
-    notification.visible = false;
-    notification.isHiding = false;
-  },
-
-  hideAllNotifications () {
-    this._notifications.forEach(function (notification) {
-      if (notification.visible) {
-        notification.isHiding = true;
-      }
-    });
-  },
-
-  startHidingNotification (notificationId) {
-    var notification = _.find(this._notifications, { notificationId: notificationId });
-    notification.isHiding = true;
-  },
-
-  setNotificationFilter (filter) {
-    if ((_.isEmpty(filter) || !_.includes(validNotificationTypes, filter)) && filter !== 'all') {
-      console.warn('Invalid notification filter: ', filter);
-      return;
-    }
-    this._selectedNotificationFilter = filter;
-  },
-
-  dispatch (action) {
-    switch (action.type) {
-      case ActionTypes.ADD_NOTIFICATION:
-        this.addNotification(action.options.info);
-        break;
-
-      case ActionTypes.CLEAR_ALL_NOTIFICATIONS:
-        this.clearNotifications();
-        break;
-
-      case ActionTypes.CLEAR_SINGLE_NOTIFICATION:
-        this.clearNotification(action.options.notificationId);
-        break;
-
-      case ActionTypes.START_HIDING_NOTIFICATION:
-        this.startHidingNotification(action.options.notificationId);
-        break;
-
-      case ActionTypes.HIDE_NOTIFICATION:
-        this.hideNotification(action.options.notificationId);
-        break;
-
-      case ActionTypes.HIDE_ALL_NOTIFICATIONS:
-        this.hideAllNotifications();
-        break;
-
-      case ActionTypes.SHOW_NOTIFICATION_CENTER:
-        this._notificationCenterVisible = true;
-        break;
-
-      case ActionTypes.HIDE_NOTIFICATION_CENTER:
-        this._notificationCenterVisible = false;
-        break;
-
-      case ActionTypes.SELECT_NOTIFICATION_FILTER:
-        this.setNotificationFilter(action.options.filter);
-        break;
-
-      case ActionTypes.SHOW_PERMANENT_NOTIFICATION:
-        this._permanentNotificationVisible = true;
-        this.setPermanentNotificationMessage(action.options.msg);
-        break;
-
-      case ActionTypes.HIDE_PERMANENT_NOTIFICATION:
-        this._permanentNotificationVisible = false;
-        break;
-
-      default:
-        return;
-        // do nothing
-    }
-
-    this.triggerChange();
-  }
-});
-
-Stores.notificationStore = new Stores.NotificationStore();
-Stores.notificationStore.dispatchToken = FauxtonAPI.dispatcher.register(Stores.notificationStore.dispatch.bind(Stores.notificationStore));
-
-export default Stores;
diff --git a/app/addons/replication/tests/nightwatch/replication.js b/app/addons/replication/tests/nightwatch/replication.js
index 0d2551f..39f123a 100644
--- a/app/addons/replication/tests/nightwatch/replication.js
+++ b/app/addons/replication/tests/nightwatch/replication.js
@@ -80,7 +80,7 @@
 
       .clickWhenVisible('#replicate')
 
-      .waitForElementNotPresent('.global-notification .fonticon-cancel', waitTime, false)
+      .waitForElementNotPresent('.Toastify__toast-container .Toastify__toast-body', waitTime, true)
       .end();
   },
 
@@ -138,7 +138,7 @@
       })
       .clickWhenVisible('#replicate')
 
-      .waitForElementNotPresent('.global-notification .fonticon-cancel', waitTime, false)
+      .waitForElementNotPresent('.Toastify__toast-container .Toastify__toast-body', waitTime, true)
       .end();
   },
 
@@ -208,7 +208,7 @@
       .waitForElementVisible('.replication__error-doc-modal .replication__error-continue', waitTime, true)
       .clickWhenVisible('.replication__error-doc-modal .replication__error-continue')
 
-      .waitForElementNotPresent('.global-notification .fonticon-cancel', waitTime, false)
+      .waitForElementNotPresent('.Toastify__toast-container .Toastify__toast-body', waitTime, true)
       .end();
   },
 
@@ -240,7 +240,7 @@
 
       .clickWhenVisible('#replicate')
 
-      .waitForElementPresent('.global-notification.alert-error', waitTime, true)
+      .waitForElementPresent('.Toastify__toast-container .Toastify__toast--error', waitTime, true)
       .end();
   },
 
@@ -280,7 +280,7 @@
 
       .clickWhenVisible('#replicate')
 
-      .waitForElementPresent('.global-notification.alert-error', waitTime, true)
+      .waitForElementPresent('.Toastify__toast-container .Toastify__toast--error', waitTime, true)
       .end();
   }
 };
diff --git a/app/addons/replication/tests/nightwatch/replicationactivity.js b/app/addons/replication/tests/nightwatch/replicationactivity.js
index a29e363..3a6bfa5 100644
--- a/app/addons/replication/tests/nightwatch/replicationactivity.js
+++ b/app/addons/replication/tests/nightwatch/replicationactivity.js
@@ -27,7 +27,6 @@
       .createDatabase('_replicator')
       .createDocument(replicatorDoc._id, '_replicator', replicatorDoc)
       .loginToGUI()
-      .waitForElementNotPresent('.global-notification .fonticon-cancel', waitTime, false)
       .url(baseUrl + '/#replication')
       .waitForElementNotPresent('.load-lines', waitTime, true)
       .waitForElementPresent('.replication__filter', waitTime, true)
@@ -51,7 +50,6 @@
       .createDatabase('_replicator')
       .createDocument(replicatorDoc._id, '_replicator', replicatorDoc)
       .loginToGUI()
-      .waitForElementNotPresent('.global-notification .fonticon-cancel', waitTime, false)
       .url(baseUrl + '/#replication')
       .waitForElementNotPresent('.load-lines', waitTime, true)
       .waitForElementPresent('.replication__filter', waitTime, true)
@@ -84,7 +82,6 @@
       .createDocument(replicatorDoc1._id, '_replicator', replicatorDoc1)
       .createDocument(replicatorDoc2._id, '_replicator', replicatorDoc2)
       .loginToGUI()
-      .waitForElementNotPresent('.global-notification .fonticon-cancel', waitTime, false)
       .url(baseUrl + '/#replication')
       .waitForElementNotPresent('.load-lines', waitTime, true)
       .waitForElementVisible('.replication__filter-input', waitTime, true)
@@ -118,7 +115,6 @@
       .createDocument(replicatorDoc1._id, '_replicator', replicatorDoc1)
       .createDocument(replicatorDoc2._id, '_replicator', replicatorDoc2)
       .loginToGUI()
-      .waitForElementNotPresent('.global-notification .fonticon-cancel', waitTime, false)
       .url(baseUrl + '/#replication')
       .waitForElementNotPresent('.load-lines', waitTime, true)
       .waitForElementVisible(firstRowSelector, waitTime, true)
diff --git a/app/addons/search/tests/nightwatch/cloneSearchIndex.js b/app/addons/search/tests/nightwatch/cloneSearchIndex.js
index 8056f78..44e194d 100644
--- a/app/addons/search/tests/nightwatch/cloneSearchIndex.js
+++ b/app/addons/search/tests/nightwatch/cloneSearchIndex.js
@@ -31,7 +31,7 @@
       .clearValue('#search-name')
       .setValue('#search-name', 'test1-index')
       .clickWhenVisible('#save-index')
-      .waitForElementVisible('#global-notifications .alert.alert-success', waitTime, false)
+      .waitForElementVisible('.Toastify__toast-container .Toastify__toast--success', waitTime, false)
 
       .clickWhenVisible('.index-list li span', waitTime, true)
       .clickWhenVisible('.popover-content .fonticon-files-o', waitTime, true)
diff --git a/app/addons/search/tests/nightwatch/createNewSearch.js b/app/addons/search/tests/nightwatch/createNewSearch.js
index 0015d51..270432c 100644
--- a/app/addons/search/tests/nightwatch/createNewSearch.js
+++ b/app/addons/search/tests/nightwatch/createNewSearch.js
@@ -67,7 +67,7 @@
       .clearValue('#search-name')
       .setValue('#search-name', 'clean-slate-test')
       .clickWhenVisible('#save-index')
-      .waitForElementVisible('#global-notifications .alert.alert-success', client.globals.maxWaitTime, false)
+      .waitForElementVisible('.Toastify__toast-container .Toastify__toast--success', client.globals.maxWaitTime, false)
       .url(baseUrl + '/#/database/' + newDatabaseName + '/_all_docs')
       .waitForElementPresent('.tableview-checkbox-cell', client.globals.maxWaitTime, false)
       .waitForElementNotPresent('.loading-lines', client.globals.maxWaitTime, false)
@@ -99,7 +99,7 @@
       .clearValue('#search-name')
       .setValue('#search-name', 'test1-index')
       .clickWhenVisible('#save-index')
-      .waitForElementVisible('#global-notifications .alert.alert-success', client.globals.maxWaitTime, false)
+      .waitForElementVisible('.Toastify__toast-container .Toastify__toast--success', client.globals.maxWaitTime, false)
       .url(baseUrl + '/#/database/' + newDatabaseName + '/_all_docs')
       .waitForElementPresent('.tableview-checkbox-cell', client.globals.maxWaitTime, false)
       .waitForElementNotPresent('.loading-lines', client.globals.maxWaitTime, false)
@@ -114,7 +114,7 @@
       .setValue('#search-name', 'test2-index')
       .clickWhenVisible('#save-index')
 
-      .waitForElementVisible('#global-notifications .alert.alert-success', client.globals.maxWaitTime, false)
+      .waitForElementVisible('.Toastify__toast-container .Toastify__toast--success', client.globals.maxWaitTime, false)
 
       // 3. confirm that the nav bar shows ONLY one search index each:
       //  _design/test1 has the single _design/test1-index
diff --git a/app/addons/search/tests/nightwatch/deleteSearchIndex.js b/app/addons/search/tests/nightwatch/deleteSearchIndex.js
index b8e4a03..d847c43 100644
--- a/app/addons/search/tests/nightwatch/deleteSearchIndex.js
+++ b/app/addons/search/tests/nightwatch/deleteSearchIndex.js
@@ -30,7 +30,7 @@
       .clearValue('#search-name')
       .setValue('#search-name', 'test1-index')
       .clickWhenVisible('#save-index')
-      .waitForElementVisible('#global-notifications .alert.alert-success', waitTime, false)
+      .waitForElementVisible('.Toastify__toast-container .Toastify__toast--success', waitTime, false)
       .url(baseUrl + '/#/database/' + newDatabaseName + '/_all_docs')
       .waitForElementPresent('.tableview-checkbox-cell', client.globals.maxWaitTime, false)
       .waitForElementNotPresent('.loading-lines', waitTime, false)
@@ -64,7 +64,7 @@
       .clearValue('#search-name')
       .setValue('#search-name', 'search-index1')
       .clickWhenVisible('#save-index')
-      .waitForElementVisible('#global-notifications .alert.alert-success', waitTime, false)
+      .waitForElementVisible('.Toastify__toast-container .Toastify__toast--success', waitTime, false)
       .url(baseUrl + '/#/database/' + newDatabaseName + '/_all_docs')
       .waitForElementPresent('.tableview-checkbox-cell', client.globals.maxWaitTime, false)
       .waitForElementNotPresent('.loading-lines', waitTime, false)
diff --git a/app/addons/search/tests/nightwatch/searchPageApiBar.js b/app/addons/search/tests/nightwatch/searchPageApiBar.js
index 775de1b..d8e70b9 100644
--- a/app/addons/search/tests/nightwatch/searchPageApiBar.js
+++ b/app/addons/search/tests/nightwatch/searchPageApiBar.js
@@ -39,7 +39,7 @@
       .clearValue('#search-name')
       .setValue('#search-name', 'api-bar-test')
       .clickWhenVisible('#save-index')
-      .waitForElementVisible('#global-notifications .alert.alert-success', client.globals.maxWaitTime, false)
+      .waitForElementVisible('.Toastify__toast-container .Toastify__toast--success', client.globals.maxWaitTime, false)
 
       // confirm the API URL field now shows up (we're on the edit search index page now)
       .assert.elementPresent('.faux__jsonlink')
@@ -48,7 +48,7 @@
       .setValue('#search-index-preview-form input', searchStr)
       .clickWhenVisible('#search-index-preview-form button')
 
-      .waitForElementNotVisible('#global-notifications', client.globals.maxWaitTime, false)
+      .waitForElementNotVisible('.Toastify__toast-container .Toastify__toast--success', client.globals.maxWaitTime, false)
       .waitForElementNotPresent('.loading-lines', client.globals.maxWaitTime, false)
       .assert.attributeContains('.faux__jsonlink-link', 'href', fullURL)
       .end();
diff --git a/app/app.js b/app/app.js
index ed6d994..713b8de 100644
--- a/app/app.js
+++ b/app/app.js
@@ -15,6 +15,7 @@
 import Helpers from "./helpers";
 import Utils from "./core/utils";
 import FauxtonAPI from "./core/api";
+import 'react-toastify/dist/ReactToastify.min.css';
 import "../assets/less/fauxton.less";
 
 // Make sure we have a console.log
diff --git a/assets/less/fauxton.less b/assets/less/fauxton.less
index c2564ad..5bc847a 100644
--- a/assets/less/fauxton.less
+++ b/assets/less/fauxton.less
@@ -192,53 +192,6 @@
   font-size: 16px;
 }
 
-// customize the twitter bootstrap alert
-.global-notification {
-  &.alert {
-    padding-left: 20px;
-    padding-top: 20px;
-    min-height: @collapsedNavWidth;
-    margin-bottom: auto;
-    text-shadow: none;
-    .border-radius(0);
-    border-bottom: 1px solid @brandDark2;
-    a, a:hover {
-      color: #cecece;
-      text-decoration: none;
-    }
-    a.js-dismiss {
-      color: rgba(255, 255, 255, 1);
-    }
-    a.js-dismiss :hover {
-      color: rgba(255, 255, 255, 0.4);
-    }
-  }
-  &.alert-success {
-    background-color: #448c11;
-    color: #CBDFBD;
-  }
-  &.alert-success h4 {
-    color: #CBDFBD;
-  }
-  &.alert-danger,
-  &.alert-error {
-    background-color: #222;
-    color: #ff0c35;
-  }
-  &.alert-danger h4,
-  &.alert-error h4 {
-    color: #ff0c35;
-  }
-  &.alert-info {
-    background-color: #329898;
-    color: #fff;
-  }
-  &.alert-info h4 {
-    color: #fff;
-  }
-}
-
-
 .notification-icon {
   padding-right: 8px;
 }
diff --git a/assets/less/notification-center.less b/assets/less/notification-center.less
index caaa7ec..e5b6f02 100644
--- a/assets/less/notification-center.less
+++ b/assets/less/notification-center.less
@@ -181,13 +181,13 @@
   }
 
   .fonticon-ok-circled {
-    color: @successAlertColor;
+    color: @successAlertBackground;
   }
   .fonticon-attention-circled {
-    color: @errorAlertColor;
+    color: @globalErrorAlertBackground;
   }
   .fonticon-info-circled {
-    color: @infoAlertColor;
+    color: @infoAlertBackground;
   }
 
   .notification-page-mask {
diff --git a/assets/less/templates.less b/assets/less/templates.less
index b2819ac..b5bc864 100644
--- a/assets/less/templates.less
+++ b/assets/less/templates.less
@@ -24,14 +24,6 @@
   height: 100%;
 }
 
-#global-notifications {
-  position: fixed;
-  top: 0;
-  display: block;
-  z-index: 100000;
-  width: 100%;
-}
-
 #app-container {
   height: 100vh;
   position: relative;
@@ -289,3 +281,39 @@
   opacity: 0;
   height: 0;
 }
+
+.toast-container {
+  margin-top: @breadcrumbHeight;
+  padding:0;
+  right: 0;
+}
+
+.Toastify__toast {
+  border: 1px solid #999;
+  background-color: #fff;
+  border-right: 0;
+  color: black;
+}
+
+.Toastify__toast--info {
+  &, .Toastify__close-button--info {
+    border-left: 6px solid @infoAlertBackground;
+    }
+}
+
+.Toastify__toast--success {
+  &, .Toastify__close-button--success {
+    border-left: 6px solid @successAlertBackground;
+  }
+}
+.Toastify__toast--warning {
+  &, .Toastify__close-button--warning {
+    border-left: 6px solid @warningBackground;
+  }
+}
+.Toastify__toast--error {
+  &, .Toastify__close-button--error {
+    border-left: 6px solid @errorAlertColor;
+  }
+}
+
diff --git a/assets/less/variables.less b/assets/less/variables.less
index 57aec02..7a7a8a1 100644
--- a/assets/less/variables.less
+++ b/assets/less/variables.less
@@ -60,6 +60,7 @@
 @breadcrumbText: @brandHighlight;
 @breadcrumbArrow: #999999;
 @breadcrumbBorder: @brandHighlight;
+@breadcrumbHeight: 64px;
 
 /* document-header in doclist */
 @docHeaderBG: #3a3a3a;
@@ -111,10 +112,15 @@
 /* padding and margins */
 @panelPadding: 15px;
 
-/* alerts */
-@infoAlertColor: #329898;
-@successAlertColor: #448c11;
-@errorAlertColor: #c45b55;
+/* Alerts */
+@globalErrorAlertBackground: #c45b55;
+
+@infoAlertBackground: #329898;
+@infoAlertColor: #fff;
+@successAlertBackground: #448c11;
+@successAlertColor: #CBDFBD;
+@errorAlertBackground: #222;
+@errorAlertColor: #ff0c35;
 
 /* query history */
 @queryHistoryBGColor: white;
diff --git a/package-lock.json b/package-lock.json
index 6c26735..be5787f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4945,6 +4945,11 @@
         "cssom": "0.3.x"
       }
     },
+    "csstype": {
+      "version": "2.6.10",
+      "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.10.tgz",
+      "integrity": "sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w=="
+    },
     "currently-unhandled": {
       "version": "0.4.1",
       "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
@@ -5330,6 +5335,15 @@
         "utila": "~0.4"
       }
     },
+    "dom-helpers": {
+      "version": "5.1.4",
+      "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.1.4.tgz",
+      "integrity": "sha512-TjMyeVUvNEnOnhzs6uAn9Ya47GmMo3qq7m+Lr/3ON0Rs5kHvb8I+SQYjLUSYn7qhEm0QjW0yrBkvz9yOrwwz1A==",
+      "requires": {
+        "@babel/runtime": "^7.8.7",
+        "csstype": "^2.6.7"
+      }
+    },
     "dom-serializer": {
       "version": "0.1.0",
       "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz",
@@ -13574,6 +13588,13 @@
         "map-age-cleaner": "^0.1.1",
         "mimic-fn": "^2.0.0",
         "p-is-promise": "^2.0.0"
+      },
+      "dependencies": {
+        "mimic-fn": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+          "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="
+        }
       }
     },
     "memory-fs": {
@@ -13740,11 +13761,6 @@
         "mime-db": "~1.35.0"
       }
     },
-    "mimic-fn": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
-      "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="
-    },
     "min-document": {
       "version": "2.19.0",
       "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz",
@@ -14060,16 +14076,107 @@
       "optional": true
     },
     "nano": {
-      "version": "7.0.0",
-      "resolved": "https://registry.npmjs.org/nano/-/nano-7.0.0.tgz",
-      "integrity": "sha512-zR1jRRfpG/lcFjYnGGxabABLFRtFX1E7YqWIJzvC0dLRJ9NTxodJC4MzVifhriMT9yhulsOf8k2UNOX8fMULAg==",
+      "version": "8.0.1",
+      "resolved": "https://registry.npmjs.org/nano/-/nano-8.0.1.tgz",
+      "integrity": "sha512-q9q8894oLNyWHhsUjF4Nk9gAvGorsCrmx0aeA7AG9pYRnM+xtAKVX5GtWfW8c2qx4mJftXHeN1/6RR8Oo9eQaQ==",
       "requires": {
         "@types/request": "^2.47.1",
-        "cloudant-follow": "~0.17.0",
+        "cloudant-follow": "^0.18.0",
         "debug": "^2.2.0",
         "errs": "^0.3.2",
-        "lodash.isempty": "^4.4.0",
         "request": "^2.85.0"
+      },
+      "dependencies": {
+        "aws4": {
+          "version": "1.9.1",
+          "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz",
+          "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug=="
+        },
+        "cloudant-follow": {
+          "version": "0.18.2",
+          "resolved": "https://registry.npmjs.org/cloudant-follow/-/cloudant-follow-0.18.2.tgz",
+          "integrity": "sha512-qu/AmKxDqJds+UmT77+0NbM7Yab2K3w0qSeJRzsq5dRWJTEJdWeb+XpG4OpKuTE9RKOa/Awn2gR3TTnvNr3TeA==",
+          "requires": {
+            "browser-request": "~0.3.0",
+            "debug": "^4.0.1",
+            "request": "^2.88.0"
+          },
+          "dependencies": {
+            "debug": {
+              "version": "4.1.1",
+              "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+              "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+              "requires": {
+                "ms": "^2.1.1"
+              }
+            },
+            "request": {
+              "version": "2.88.2",
+              "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
+              "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
+              "requires": {
+                "aws-sign2": "~0.7.0",
+                "aws4": "^1.8.0",
+                "caseless": "~0.12.0",
+                "combined-stream": "~1.0.6",
+                "extend": "~3.0.2",
+                "forever-agent": "~0.6.1",
+                "form-data": "~2.3.2",
+                "har-validator": "~5.1.3",
+                "http-signature": "~1.2.0",
+                "is-typedarray": "~1.0.0",
+                "isstream": "~0.1.2",
+                "json-stringify-safe": "~5.0.1",
+                "mime-types": "~2.1.19",
+                "oauth-sign": "~0.9.0",
+                "performance-now": "^2.1.0",
+                "qs": "~6.5.2",
+                "safe-buffer": "^5.1.2",
+                "tough-cookie": "~2.5.0",
+                "tunnel-agent": "^0.6.0",
+                "uuid": "^3.3.2"
+              }
+            }
+          }
+        },
+        "har-validator": {
+          "version": "5.1.3",
+          "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
+          "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
+          "requires": {
+            "ajv": "^6.5.5",
+            "har-schema": "^2.0.0"
+          }
+        },
+        "ms": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+        },
+        "oauth-sign": {
+          "version": "0.9.0",
+          "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
+          "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
+        },
+        "safe-buffer": {
+          "version": "5.2.0",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
+          "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg=="
+        },
+        "tough-cookie": {
+          "version": "2.5.0",
+          "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
+          "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
+          "requires": {
+            "psl": "^1.1.28",
+            "punycode": "^2.1.1"
+          }
+        },
+        "uuid": {
+          "version": "3.4.0",
+          "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
+          "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
+        }
       }
     },
     "nanomatch": {
@@ -14682,6 +14789,13 @@
       "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==",
       "requires": {
         "mimic-fn": "^2.1.0"
+      },
+      "dependencies": {
+        "mimic-fn": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+          "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="
+        }
       }
     },
     "opn": {
@@ -15634,6 +15748,11 @@
       "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
       "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY="
     },
+    "psl": {
+      "version": "1.8.0",
+      "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
+      "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ=="
+    },
     "public-encrypt": {
       "version": "4.0.3",
       "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz",
@@ -16207,6 +16326,83 @@
         }
       }
     },
+    "react-toastify": {
+      "version": "5.5.0",
+      "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-5.5.0.tgz",
+      "integrity": "sha512-jsVme7jALIFGRyQsri/g4YTsRuaaGI70T6/ikjwZMB4mwTZaCWqj5NqxhGrRStKlJc5npXKKvKeqTiRGQl78LQ==",
+      "requires": {
+        "@babel/runtime": "^7.4.2",
+        "classnames": "^2.2.6",
+        "prop-types": "^15.7.2",
+        "react-transition-group": "^4"
+      },
+      "dependencies": {
+        "classnames": {
+          "version": "2.2.6",
+          "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz",
+          "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q=="
+        },
+        "loose-envify": {
+          "version": "1.4.0",
+          "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+          "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+          "requires": {
+            "js-tokens": "^3.0.0 || ^4.0.0"
+          }
+        },
+        "prop-types": {
+          "version": "15.7.2",
+          "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
+          "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
+          "requires": {
+            "loose-envify": "^1.4.0",
+            "object-assign": "^4.1.1",
+            "react-is": "^16.8.1"
+          }
+        },
+        "react-is": {
+          "version": "16.13.1",
+          "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+          "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+        }
+      }
+    },
+    "react-transition-group": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.3.0.tgz",
+      "integrity": "sha512-1qRV1ZuVSdxPlPf4O8t7inxUGpdyO5zG9IoNfJxSO0ImU2A1YWkEQvFPuIPZmMLkg5hYs7vv5mMOyfgSkvAwvw==",
+      "requires": {
+        "@babel/runtime": "^7.5.5",
+        "dom-helpers": "^5.0.1",
+        "loose-envify": "^1.4.0",
+        "prop-types": "^15.6.2"
+      },
+      "dependencies": {
+        "loose-envify": {
+          "version": "1.4.0",
+          "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+          "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+          "requires": {
+            "js-tokens": "^3.0.0 || ^4.0.0"
+          }
+        },
+        "prop-types": {
+          "version": "15.7.2",
+          "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
+          "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
+          "requires": {
+            "loose-envify": "^1.4.0",
+            "object-assign": "^4.1.1",
+            "react-is": "^16.8.1"
+          }
+        },
+        "react-is": {
+          "version": "16.13.1",
+          "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+          "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+        }
+      }
+    },
     "read-pkg": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
@@ -17702,31 +17898,6 @@
         "strip-ansi": "^4.0.0"
       }
     },
-    "string-width": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
-      "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
-      "requires": {
-        "emoji-regex": "^7.0.1",
-        "is-fullwidth-code-point": "^2.0.0",
-        "strip-ansi": "^5.1.0"
-      },
-      "dependencies": {
-        "ansi-regex": {
-          "version": "4.1.0",
-          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
-          "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="
-        },
-        "strip-ansi": {
-          "version": "5.2.0",
-          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
-          "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
-          "requires": {
-            "ansi-regex": "^4.1.0"
-          }
-        }
-      }
-    },
     "string.prototype.matchall": {
       "version": "4.0.2",
       "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.2.tgz",
@@ -19874,6 +20045,31 @@
         "which-module": "^2.0.0",
         "y18n": "^4.0.0",
         "yargs-parser": "^13.1.0"
+      },
+      "dependencies": {
+        "ansi-regex": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+          "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="
+        },
+        "string-width": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+          "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+          "requires": {
+            "emoji-regex": "^7.0.1",
+            "is-fullwidth-code-point": "^2.0.0",
+            "strip-ansi": "^5.1.0"
+          }
+        },
+        "strip-ansi": {
+          "version": "5.2.0",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+          "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+          "requires": {
+            "ansi-regex": "^4.1.0"
+          }
+        }
       }
     },
     "yargs-parser": {
diff --git a/package.json b/package.json
index e585956..6f78690 100644
--- a/package.json
+++ b/package.json
@@ -80,7 +80,7 @@
     "mini-css-extract-plugin": "^0.7.0",
     "mkdirp": "^0.5.1",
     "moment": "^2.17.1",
-    "nano": "~7.0.0",
+    "nano": "~8.0.0",
     "optimist": "^0.6.1",
     "pouchdb-adapter-http": "^7.1.1",
     "pouchdb-core": "^7.1.1",
@@ -93,6 +93,7 @@
     "react-range": "0.0.7",
     "react-redux": "^5.0.7",
     "react-select": "^1.2.0",
+    "react-toastify": "^5.5.0",
     "redux": "^4.0.0",
     "redux-thunk": "^2.1.0",
     "regenerator-runtime": "^0.11.1",
diff --git a/test/nightwatch_tests/custom-commands/clickWhenVisible.js b/test/nightwatch_tests/custom-commands/clickWhenVisible.js
index 052169a..1c604ed 100644
--- a/test/nightwatch_tests/custom-commands/clickWhenVisible.js
+++ b/test/nightwatch_tests/custom-commands/clickWhenVisible.js
@@ -20,6 +20,7 @@
 
   this
     .waitForElementPresent(element, waitTime, false)
+    .moveToElement(element, 10, 10)
     .waitForElementVisible(element, waitTime, false)
     .click(element);
 
diff --git a/test/nightwatch_tests/custom-commands/closeNotification.js b/test/nightwatch_tests/custom-commands/closeNotification.js
index 6350d54..7b3fc66 100644
--- a/test/nightwatch_tests/custom-commands/closeNotification.js
+++ b/test/nightwatch_tests/custom-commands/closeNotification.js
@@ -14,10 +14,10 @@
 
 exports.command = function () {
   var client = this,
-      dismissSelector = '#global-notifications .fonticon-cancel';
+      dismissSelector = '.Toastify__toast-container .Toastify__toast-body';
 
   client
-    .waitForElementPresent(dismissSelector, helpers.maxWaitTime, false)
+    .waitForElementVisible(dismissSelector, helpers.maxWaitTime, false)
     .click(dismissSelector)
     .waitForElementNotPresent(dismissSelector, helpers.maxWaitTime, false);
 
diff --git a/test/nightwatch_tests/custom-commands/closeNotifications.js b/test/nightwatch_tests/custom-commands/closeNotifications.js
new file mode 100644
index 0000000..ba9fd7c
--- /dev/null
+++ b/test/nightwatch_tests/custom-commands/closeNotifications.js
@@ -0,0 +1,25 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+var helpers = require('../helpers/helpers.js');
+
+exports.command = function () {
+  var client = this,
+      dismissSelector = '.Toastify__toast-container .Toastify__toast-body';
+
+  client
+    .waitForElementVisible(dismissSelector, helpers.maxWaitTime, false)
+    .keys(client.keys.ESCAPE)
+    .waitForElementNotPresent(dismissSelector, helpers.maxWaitTime, false);
+
+  return this;
+};