From d55c692bd383b24bb11475720bbf33078c5550e9 Mon Sep 17 00:00:00 2001
From: Arnaud Vergnet <arnaud.vergnet@mailo.com>
Date: Thu, 13 May 2021 19:10:28 +0200
Subject: [PATCH] Improve request error handling

---
 src/components/Screens/RequestScreen.tsx      | 43 ++++++++++--
 src/screens/Amicale/Clubs/ClubListScreen.tsx  |  2 +-
 .../Amicale/Equipment/EquipmentListScreen.tsx |  2 +-
 src/screens/Amicale/LoginScreen.tsx           |  7 +-
 src/screens/Planex/GroupSelectionScreen.tsx   | 69 +++++++++----------
 .../Services/Proximo/ProximoListScreen.tsx    |  8 +--
 .../Services/Proximo/ProximoMainScreen.tsx    |  8 +--
 src/screens/Services/SelfMenuScreen.tsx       | 39 ++++++-----
 8 files changed, 100 insertions(+), 78 deletions(-)

diff --git a/src/components/Screens/RequestScreen.tsx b/src/components/Screens/RequestScreen.tsx
index 146ae19..5c0055d 100644
--- a/src/components/Screens/RequestScreen.tsx
+++ b/src/components/Screens/RequestScreen.tsx
@@ -1,10 +1,17 @@
 import React, { useEffect, useRef } from 'react';
 import ErrorView from './ErrorView';
 import { useRequestLogic } from '../../utils/customHooks';
-import { useFocusEffect } from '@react-navigation/native';
+import {
+  useFocusEffect,
+  useNavigation,
+  useRoute,
+} from '@react-navigation/native';
 import BasicLoadingScreen from './BasicLoadingScreen';
 import i18n from 'i18n-js';
 import { API_REQUEST_CODES, REQUEST_STATUS } from '../../utils/Requests';
+import { StackNavigationProp } from '@react-navigation/stack';
+import { MainRoutes } from '../../navigation/MainNavigator';
+import ConnectionManager from '../../managers/ConnectionManager';
 
 export type RequestScreenProps<T> = {
   request: () => Promise<T>;
@@ -37,6 +44,8 @@ type Props<T> = RequestScreenProps<T>;
 const MIN_REFRESH_TIME = 5 * 1000;
 
 export default function RequestScreen<T>(props: Props<T>) {
+  const navigation = useNavigation<StackNavigationProp<any>>();
+  const route = useRoute();
   const refreshInterval = useRef<number>();
   const [
     loading,
@@ -89,22 +98,42 @@ export default function RequestScreen<T>(props: Props<T>) {
     }, [props.cache, props.refreshOnFocus])
   );
 
+  const isErrorCritical = (e: API_REQUEST_CODES | undefined) => {
+    return e === API_REQUEST_CODES.BAD_TOKEN;
+  };
+
+  useEffect(() => {
+    if (isErrorCritical(code)) {
+      ConnectionManager.getInstance()
+        .disconnect()
+        .then(() => {
+          navigation.replace(MainRoutes.Login, { nextScreen: route.name });
+        });
+    }
+  }, [code, navigation, route]);
+
   if (data === undefined && loading && props.showLoading !== false) {
     return <BasicLoadingScreen />;
   } else if (
     data === undefined &&
-    status !== REQUEST_STATUS.SUCCESS &&
+    (status !== REQUEST_STATUS.SUCCESS ||
+      (status === REQUEST_STATUS.SUCCESS && code !== undefined)) &&
     props.showError !== false
   ) {
     return (
       <ErrorView
         status={status}
+        code={code}
         loading={loading}
-        button={{
-          icon: 'refresh',
-          text: i18n.t('general.retry'),
-          onPress: () => refreshData(),
-        }}
+        button={
+          isErrorCritical(code)
+            ? undefined
+            : {
+                icon: 'refresh',
+                text: i18n.t('general.retry'),
+                onPress: () => refreshData(),
+              }
+        }
       />
     );
   } else {
diff --git a/src/screens/Amicale/Clubs/ClubListScreen.tsx b/src/screens/Amicale/Clubs/ClubListScreen.tsx
index 7bf8a76..c86ca82 100644
--- a/src/screens/Amicale/Clubs/ClubListScreen.tsx
+++ b/src/screens/Amicale/Clubs/ClubListScreen.tsx
@@ -156,7 +156,7 @@ class ClubListScreen extends React.Component<PropsType, StateType> {
       this.categories = data?.categories;
       return [{ title: '', data: data.clubs }];
     } else {
-      return [{ title: '', data: [] }];
+      return [];
     }
   };
 
diff --git a/src/screens/Amicale/Equipment/EquipmentListScreen.tsx b/src/screens/Amicale/Equipment/EquipmentListScreen.tsx
index 7c51e7a..99ba202 100644
--- a/src/screens/Amicale/Equipment/EquipmentListScreen.tsx
+++ b/src/screens/Amicale/Equipment/EquipmentListScreen.tsx
@@ -136,7 +136,7 @@ class EquipmentListScreen extends React.Component<PropsType, StateType> {
       }
       return [{ title: '', data: data.devices }];
     } else {
-      return [{ title: '', data: [] }];
+      return [];
     }
   };
 
diff --git a/src/screens/Amicale/LoginScreen.tsx b/src/screens/Amicale/LoginScreen.tsx
index a6f513d..aec0509 100644
--- a/src/screens/Amicale/LoginScreen.tsx
+++ b/src/screens/Amicale/LoginScreen.tsx
@@ -110,10 +110,9 @@ class LoginScreen extends React.Component<Props, StateType> {
       this.onInputChange(false, value);
     };
     props.navigation.addListener('focus', this.onScreenFocus);
-    // TODO remove
     this.state = {
-      email: 'vergnet@etud.insa-toulouse.fr',
-      password: 'IGtt25ùj',
+      email: '',
+      password: '',
       isEmailValidated: false,
       isPasswordValidated: false,
       loading: false,
@@ -373,7 +372,7 @@ class LoginScreen extends React.Component<Props, StateType> {
    * Saves the screen to navigate to after a successful login if one was provided in navigation parameters
    */
   handleNavigationParams() {
-    this.nextScreen = this.props.route.params.nextScreen;
+    this.nextScreen = this.props.route.params?.nextScreen;
   }
 
   /**
diff --git a/src/screens/Planex/GroupSelectionScreen.tsx b/src/screens/Planex/GroupSelectionScreen.tsx
index c4b70d1..0176bc6 100644
--- a/src/screens/Planex/GroupSelectionScreen.tsx
+++ b/src/screens/Planex/GroupSelectionScreen.tsx
@@ -121,12 +121,16 @@ function GroupSelectionScreen() {
         }
       | undefined
   ): Array<{ title: string; data: Array<PlanexGroupCategoryType> }> => {
-    return [
-      {
-        title: '',
-        data: generateData(fetchedData),
-      },
-    ];
+    if (fetchedData) {
+      return [
+        {
+          title: '',
+          data: generateData(fetchedData),
+        },
+      ];
+    } else {
+      return [];
+    }
   };
 
   /**
@@ -181,39 +185,34 @@ function GroupSelectionScreen() {
    * @returns {[]}
    */
   const generateData = (
-    fetchedData: PlanexGroupsType | undefined
+    fetchedData: PlanexGroupsType
   ): Array<PlanexGroupCategoryType> => {
     const data: Array<PlanexGroupCategoryType> = [];
-
-    if (fetchedData) {
-      // Convert the object into an array
-      Object.values(fetchedData).forEach(
-        (category: PlanexGroupCategoryType) => {
-          const content: Array<PlanexGroupType> = [];
-          // Filter groups matching the search query
-          category.content.forEach((g: PlanexGroupType) => {
-            if (stringMatchQuery(g.name, currentSearchString)) {
-              content.push(g);
-            }
-          });
-          // Only add categories with groups matching the query
-          if (content.length > 0) {
-            data.push({
-              id: category.id,
-              name: category.name,
-              content: content,
-            });
-          }
+    // Convert the object into an array
+    Object.values(fetchedData).forEach((category: PlanexGroupCategoryType) => {
+      const content: Array<PlanexGroupType> = [];
+      // Filter groups matching the search query
+      category.content.forEach((g: PlanexGroupType) => {
+        if (stringMatchQuery(g.name, currentSearchString)) {
+          content.push(g);
         }
-      );
-      data.sort(sortName);
-      // Add the favorites at the top
-      data.unshift({
-        name: i18n.t('screens.planex.favorites.title'),
-        id: 0,
-        content: favoriteGroups,
       });
-    }
+      // Only add categories with groups matching the query
+      if (content.length > 0) {
+        data.push({
+          id: category.id,
+          name: category.name,
+          content: content,
+        });
+      }
+    });
+    data.sort(sortName);
+    // Add the favorites at the top
+    data.unshift({
+      name: i18n.t('screens.planex.favorites.title'),
+      id: 0,
+      content: favoriteGroups,
+    });
     return data;
   };
 
diff --git a/src/screens/Services/Proximo/ProximoListScreen.tsx b/src/screens/Services/Proximo/ProximoListScreen.tsx
index cdd4f90..367473c 100644
--- a/src/screens/Services/Proximo/ProximoListScreen.tsx
+++ b/src/screens/Services/Proximo/ProximoListScreen.tsx
@@ -349,13 +349,7 @@ function ProximoListScreen(props: Props) {
         },
       ];
     } else {
-      return [
-        {
-          title: '',
-          data: [],
-          keyExtractor: keyExtractor,
-        },
-      ];
+      return [];
     }
   };
 
diff --git a/src/screens/Services/Proximo/ProximoMainScreen.tsx b/src/screens/Services/Proximo/ProximoMainScreen.tsx
index b5aaca9..a770e75 100644
--- a/src/screens/Services/Proximo/ProximoMainScreen.tsx
+++ b/src/screens/Services/Proximo/ProximoMainScreen.tsx
@@ -239,13 +239,7 @@ function ProximoMainScreen() {
         },
       ];
     } else {
-      return [
-        {
-          title: '',
-          data: [],
-          keyExtractor: getKeyExtractor,
-        },
-      ];
+      return [];
     }
   };
 
diff --git a/src/screens/Services/SelfMenuScreen.tsx b/src/screens/Services/SelfMenuScreen.tsx
index 763aded..8a883aa 100644
--- a/src/screens/Services/SelfMenuScreen.tsx
+++ b/src/screens/Services/SelfMenuScreen.tsx
@@ -27,6 +27,7 @@ import WebSectionList from '../../components/Screens/WebSectionList';
 import type { SectionListDataType } from '../../components/Screens/WebSectionList';
 import Urls from '../../constants/Urls';
 import { readData } from '../../utils/WebData';
+import { REQUEST_STATUS } from '../../utils/Requests';
 
 type PropsType = {
   navigation: StackNavigationProp<any>;
@@ -109,25 +110,31 @@ class SelfMenuScreen extends React.Component<PropsType> {
    * @return {[]}
    */
   createDataset = (
-    fetchedData: Array<RawRuMenuType> | undefined
+    fetchedData: Array<RawRuMenuType> | undefined,
+    _loading: boolean,
+    _lastRefreshDate: Date | undefined,
+    _refreshData: (newRequest?: () => Promise<Array<RawRuMenuType>>) => void,
+    status: REQUEST_STATUS
   ): SectionListDataType<RuFoodCategoryType> => {
     let result: SectionListDataType<RuFoodCategoryType> = [];
-    if (fetchedData == null || fetchedData.length === 0) {
-      result = [
-        {
-          title: i18n.t('general.notAvailable'),
-          data: [],
-          keyExtractor: this.getKeyExtractor,
-        },
-      ];
-    } else {
-      fetchedData.forEach((item: RawRuMenuType) => {
-        result.push({
-          title: DateManager.getInstance().getTranslatedDate(item.date),
-          data: item.meal[0].foodcategory,
-          keyExtractor: this.getKeyExtractor,
+    if (status === REQUEST_STATUS.SUCCESS) {
+      if (fetchedData == null || fetchedData.length === 0) {
+        result = [
+          {
+            title: i18n.t('general.notAvailable'),
+            data: [],
+            keyExtractor: this.getKeyExtractor,
+          },
+        ];
+      } else {
+        fetchedData.forEach((item: RawRuMenuType) => {
+          result.push({
+            title: DateManager.getInstance().getTranslatedDate(item.date),
+            data: item.meal[0].foodcategory,
+            keyExtractor: this.getKeyExtractor,
+          });
         });
-      });
+      }
     }
     return result;
   };