From ab6afe95a39330b2ff621889175578c81c3a8a42 Mon Sep 17 00:00:00 2001
From: Todd Dembrey <todd.dembrey@torchbox.com>
Date: Sun, 10 Feb 2019 15:58:14 +0000
Subject: [PATCH] GH-877: handle error message when fetching with existing data

---
 .../src/app/src/components/Listing/index.js   | 44 +++++++++++++------
 .../src/app/src/components/Listing/style.scss |  9 ++++
 .../src/app/src/containers/NoteListing.js     | 22 ++++++----
 .../src/app/src/redux/reducers/notes.js       | 15 +++++--
 .../src/app/src/redux/selectors/notes.js      |  4 +-
 5 files changed, 67 insertions(+), 27 deletions(-)

diff --git a/opentech/static_src/src/app/src/components/Listing/index.js b/opentech/static_src/src/app/src/components/Listing/index.js
index b92e13362..76a7ce103 100644
--- a/opentech/static_src/src/app/src/components/Listing/index.js
+++ b/opentech/static_src/src/app/src/components/Listing/index.js
@@ -35,22 +35,25 @@ export default class Listing extends React.Component {
             listRef,
         } = this.props;
 
-        if (isLoading && !items) {
-            return (
-                <div className="listing__list">
-                    <LoadingPanel />
-                </div>
-            );
-        } else if (isError) {
-            return this.renderError();
-        } else if (items.length === 0) {
-            return <EmptyPanel column={this.props.column} />;
+        if ( items.length === 0 ) {
+            if (isLoading) {
+                return (
+                    <div className="listing__list">
+                        <LoadingPanel />
+                    </div>
+                );
+            } else if (isError) {
+                return this.renderError();
+            } else {
+                return <EmptyPanel column={this.props.column} />;
+            }
         }
 
         return (
             <>
                 { isLoading && <InlineLoading /> }
                 <ul className={`listing__list listing__list--${column}`} ref={listRef}>
+                    { isError && this.renderErrorItem() }
                     <TransitionGroup component={null} >
                         {items.map(v => renderItem(v))}
                     </TransitionGroup>
@@ -59,9 +62,24 @@ export default class Listing extends React.Component {
         );
     }
 
+    retryButton = (handleRetry) => {
+        return <a className="listing__help-link" onClick={handleRetry}>Refresh</a>;
+    }
+
+    renderErrorItem = () => {
+        const { handleRetry, error } = this.props;
+        return (
+            <li className={`listing__item listing__item--error`}>
+                <h5>Something went wrong!</h5>
+                <p>{ error }</p>
+                { !navigator.onLine && <p>You appear to be offline.</p>}
+                { handleRetry && this.retryButton(handleRetry) }
+            </li>
+        )
+    }
+
     renderError = () => {
         const { handleRetry, error, column } = this.props;
-        const retryButton = <a className="listing__help-link" onClick={handleRetry}>Refresh</a>;
 
         return (
             <div className={`listing__list listing__list--${column} is-blank`}>
@@ -71,14 +89,14 @@ export default class Listing extends React.Component {
                     <p>Something went wrong!</p>
                 }
 
-                {handleRetry && retryButton &&
+                {handleRetry &&
                     <>
                         <div className="listing__blank-icon">
                             <SadNoteIcon  />
                         </div>
                         <p className="listing__help-text listing__help-text--standout">Something went wrong!</p>
                         <p className="listing__help-text">Sorry we couldn&apos;t load the notes</p>
-                        {retryButton}
+                        { this.handleRetry(handleRetry) }
                     </>
                 }
             </div>
diff --git a/opentech/static_src/src/app/src/components/Listing/style.scss b/opentech/static_src/src/app/src/components/Listing/style.scss
index 618ac1019..0f3b6ca6b 100644
--- a/opentech/static_src/src/app/src/components/Listing/style.scss
+++ b/opentech/static_src/src/app/src/components/Listing/style.scss
@@ -53,6 +53,14 @@
     &__item {
         @include submission-list-item;
 
+        &--error {
+            padding: 5px 20px 5px;
+
+            p {
+                margin: 5px 0px;
+            }
+        }
+
         &.is-active {
             @include target-edge {
                 margin-left: 8px;
@@ -71,6 +79,7 @@
         }
     }
 
+
     // <a> tags
     &__link {
         display: block;
diff --git a/opentech/static_src/src/app/src/containers/NoteListing.js b/opentech/static_src/src/app/src/containers/NoteListing.js
index b278537d9..587bc3d0e 100644
--- a/opentech/static_src/src/app/src/containers/NoteListing.js
+++ b/opentech/static_src/src/app/src/containers/NoteListing.js
@@ -8,6 +8,7 @@ import Listing from '@components/Listing';
 import Note from '@containers/Note';
 import {
     getNotesErrorState,
+    getNotesErrorMessage,
     getNoteIDsForSubmissionOfID,
     getNotesFetchState,
 } from '@selectors/notes';
@@ -19,6 +20,7 @@ class NoteListing extends React.Component {
         submissionID: PropTypes.number,
         noteIDs: PropTypes.array,
         isErrored: PropTypes.bool,
+        errorMessage: PropTypes.string,
         isLoading: PropTypes.bool,
     };
 
@@ -63,16 +65,17 @@ class NoteListing extends React.Component {
     }
 
     render() {
-        const { noteIDs } = this.props;
-        const passProps = {
-            isLoading: this.props.isLoading,
-            isError: this.props.isErrored,
-            handleRetry: this.handleRetry,
-            renderItem: this.renderItem,
-            items: noteIDs,
-        };
+        const { noteIDs, isLoading, isErrored, errorMessage } = this.props;
         return (
-            <Listing {...passProps} column="notes" />
+            <Listing
+                isLoading={ isLoading }
+                isError={ isErrored }
+                error={ errorMessage }
+                handleRetry={ this.handleRetry }
+                renderItem={ this.renderItem }
+                items={ noteIDs }
+                column="notes"
+            />
         );
     }
 }
@@ -85,6 +88,7 @@ const mapStateToProps = (state, ownProps) => ({
     noteIDs: getNoteIDsForSubmissionOfID(ownProps.submissionID)(state),
     isLoading: getNotesFetchState(state),
     isErrored: getNotesErrorState(state),
+    errorMessage: getNotesErrorMessage(state),
 });
 
 export default connect(mapStateToProps, mapDispatchToProps)(NoteListing);
diff --git a/opentech/static_src/src/app/src/redux/reducers/notes.js b/opentech/static_src/src/app/src/redux/reducers/notes.js
index 4c4a196fd..f27e5d351 100644
--- a/opentech/static_src/src/app/src/redux/reducers/notes.js
+++ b/opentech/static_src/src/app/src/redux/reducers/notes.js
@@ -21,13 +21,20 @@ function notesFetching(state = false, action) {
     }
 }
 
-function notesErrored(state = false, action) {
+function notesErrored(state = {errored: false, message: null}, action) {
     switch (action.type) {
         case UPDATE_NOTES:
         case START_FETCHING_NOTES:
-            return false;
+        return {
+            ...state,
+            errored: false,
+        };
         case FAIL_FETCHING_NOTES:
-            return true;
+            return {
+                ...state,
+                message: action.error,
+                errored: true,
+            };
         default:
             return state;
     }
@@ -110,7 +117,7 @@ function notesByID(state = {}, action) {
 export default combineReducers({
     byID: notesByID,
     isFetching: notesFetching,
-    isErrored: notesErrored,
+    error: notesErrored,
     createError: notesFailedCreating,
     isCreating: notesCreating,
 });
diff --git a/opentech/static_src/src/app/src/redux/selectors/notes.js b/opentech/static_src/src/app/src/redux/selectors/notes.js
index 98af76b0a..dd91f2cc0 100644
--- a/opentech/static_src/src/app/src/redux/selectors/notes.js
+++ b/opentech/static_src/src/app/src/redux/selectors/notes.js
@@ -10,7 +10,9 @@ export const getNoteOfID = (noteID) => createSelector(
 
 export const getNotesFetchState = state => state.notes.isFetching === true;
 
-export const getNotesErrorState = state => state.notes.isErrored === true;
+export const getNotesErrorState = state => state.notes.error.errored === true;
+
+export const getNotesErrorMessage = state => state.notes.error.message;
 
 export const getNoteIDsForSubmissionOfID = submissionID => createSelector(
     [getSubmissionOfID(submissionID)],
-- 
GitLab