diff --git a/src/components/Amicale/Vote/VoteSelect.tsx b/src/components/Amicale/Vote/VoteSelect.tsx index 77a4b90..f0d1f81 100644 --- a/src/components/Amicale/Vote/VoteSelect.tsx +++ b/src/components/Amicale/Vote/VoteSelect.tsx @@ -25,6 +25,8 @@ import ConnectionManager from '../../../managers/ConnectionManager'; import LoadingConfirmDialog from '../../Dialogs/LoadingConfirmDialog'; import ErrorDialog from '../../Dialogs/ErrorDialog'; import type { VoteTeamType } from '../../../screens/Amicale/VoteScreen'; +import { ApiRejectType } from '../../../utils/WebData'; +import { REQUEST_STATUS } from '../../../utils/Requests'; type PropsType = { teams: Array; @@ -36,7 +38,7 @@ type StateType = { selectedTeam: string; voteDialogVisible: boolean; errorDialogVisible: boolean; - currentError: number; + currentError: ApiRejectType; }; const styles = StyleSheet.create({ @@ -58,7 +60,7 @@ export default class VoteSelect extends React.PureComponent< selectedTeam: 'none', voteDialogVisible: false, errorDialogVisible: false, - currentError: 0, + currentError: { status: REQUEST_STATUS.SUCCESS }, }; } @@ -88,7 +90,7 @@ export default class VoteSelect extends React.PureComponent< props.onVoteSuccess(); resolve(); }) - .catch((error: number) => { + .catch((error: ApiRejectType) => { this.onVoteDialogDismiss(); this.showErrorDialog(error); resolve(); @@ -96,7 +98,7 @@ export default class VoteSelect extends React.PureComponent< }); }; - showErrorDialog = (error: number): void => + showErrorDialog = (error: ApiRejectType): void => this.setState({ errorDialogVisible: true, currentError: error, @@ -156,7 +158,8 @@ export default class VoteSelect extends React.PureComponent< ); diff --git a/src/components/Dialogs/ErrorDialog.tsx b/src/components/Dialogs/ErrorDialog.tsx index 78b9ac5..45ff687 100644 --- a/src/components/Dialogs/ErrorDialog.tsx +++ b/src/components/Dialogs/ErrorDialog.tsx @@ -19,60 +19,27 @@ import * as React from 'react'; import i18n from 'i18n-js'; -import { ERROR_TYPE } from '../../utils/WebData'; import AlertDialog from './AlertDialog'; +import { + API_REQUEST_CODES, + getErrorMessage, + REQUEST_STATUS, +} from '../../utils/Requests'; type PropsType = { visible: boolean; onDismiss: () => void; - errorCode: number; + status?: REQUEST_STATUS; + code?: API_REQUEST_CODES; }; function ErrorDialog(props: PropsType) { - let title: string; - let message: string; - - title = i18n.t('errors.title'); - switch (props.errorCode) { - case ERROR_TYPE.BAD_CREDENTIALS: - message = i18n.t('errors.badCredentials'); - break; - case ERROR_TYPE.BAD_TOKEN: - message = i18n.t('errors.badToken'); - break; - case ERROR_TYPE.NO_CONSENT: - message = i18n.t('errors.noConsent'); - break; - case ERROR_TYPE.TOKEN_SAVE: - message = i18n.t('errors.tokenSave'); - break; - case ERROR_TYPE.TOKEN_RETRIEVE: - message = i18n.t('errors.unknown'); - break; - case ERROR_TYPE.BAD_INPUT: - message = i18n.t('errors.badInput'); - break; - case ERROR_TYPE.FORBIDDEN: - message = i18n.t('errors.forbidden'); - break; - case ERROR_TYPE.CONNECTION_ERROR: - message = i18n.t('errors.connectionError'); - break; - case ERROR_TYPE.SERVER_ERROR: - message = i18n.t('errors.serverError'); - break; - default: - message = i18n.t('errors.unknown'); - break; - } - message += `\n\nCode ${props.errorCode}`; - return ( ); } diff --git a/src/components/Screens/ErrorView.tsx b/src/components/Screens/ErrorView.tsx index 67b763e..7d83c7e 100644 --- a/src/components/Screens/ErrorView.tsx +++ b/src/components/Screens/ErrorView.tsx @@ -21,13 +21,16 @@ import * as React from 'react'; import { Button, Subheading, useTheme } from 'react-native-paper'; import { StyleSheet, View, ViewStyle } from 'react-native'; import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; -import i18n from 'i18n-js'; import * as Animatable from 'react-native-animatable'; -import { REQUEST_CODES, REQUEST_STATUS } from '../../utils/Requests'; +import { + API_REQUEST_CODES, + getErrorMessage, + REQUEST_STATUS, +} from '../../utils/Requests'; type Props = { status?: REQUEST_STATUS; - code?: REQUEST_CODES; + code?: API_REQUEST_CODES; icon?: string; message?: string; loading?: boolean; @@ -63,92 +66,9 @@ const styles = StyleSheet.create({ }, }); -function getMessage(props: Props) { - let fullMessage = { - message: '', - icon: '', - }; - if (props.code === undefined) { - switch (props.status) { - case REQUEST_STATUS.BAD_INPUT: - fullMessage.message = i18n.t('errors.badInput'); - fullMessage.icon = 'alert-circle-outline'; - break; - case REQUEST_STATUS.FORBIDDEN: - fullMessage.message = i18n.t('errors.forbidden'); - fullMessage.icon = 'lock'; - break; - case REQUEST_STATUS.CONNECTION_ERROR: - fullMessage.message = i18n.t('errors.connectionError'); - fullMessage.icon = 'access-point-network-off'; - break; - case REQUEST_STATUS.SERVER_ERROR: - fullMessage.message = i18n.t('errors.serverError'); - fullMessage.icon = 'server-network-off'; - break; - default: - fullMessage.message = i18n.t('errors.unknown'); - fullMessage.icon = 'alert-circle-outline'; - break; - } - } else { - switch (props.code) { - case REQUEST_CODES.BAD_CREDENTIALS: - fullMessage.message = i18n.t('errors.badCredentials'); - fullMessage.icon = 'account-alert-outline'; - break; - case REQUEST_CODES.BAD_TOKEN: - fullMessage.message = i18n.t('errors.badToken'); - fullMessage.icon = 'account-alert-outline'; - break; - case REQUEST_CODES.NO_CONSENT: - fullMessage.message = i18n.t('errors.noConsent'); - fullMessage.icon = 'account-remove-outline'; - break; - case REQUEST_CODES.TOKEN_SAVE: - fullMessage.message = i18n.t('errors.tokenSave'); - fullMessage.icon = 'alert-circle-outline'; - break; - case REQUEST_CODES.BAD_INPUT: - fullMessage.message = i18n.t('errors.badInput'); - fullMessage.icon = 'alert-circle-outline'; - break; - case REQUEST_CODES.FORBIDDEN: - fullMessage.message = i18n.t('errors.forbidden'); - fullMessage.icon = 'lock'; - break; - case REQUEST_CODES.CONNECTION_ERROR: - fullMessage.message = i18n.t('errors.connectionError'); - fullMessage.icon = 'access-point-network-off'; - break; - case REQUEST_CODES.SERVER_ERROR: - fullMessage.message = i18n.t('errors.serverError'); - fullMessage.icon = 'server-network-off'; - break; - default: - fullMessage.message = i18n.t('errors.unknown'); - fullMessage.icon = 'alert-circle-outline'; - break; - } - } - - if (props.code !== undefined) { - fullMessage.message += `\n\nCode {${props.status}:${props.code}}`; - } else { - fullMessage.message += `\n\nCode {${props.status}}`; - } - if (props.message != null) { - fullMessage.message = props.message; - } - if (props.icon != null) { - fullMessage.icon = props.icon; - } - return fullMessage; -} - function ErrorView(props: Props) { const theme = useTheme(); - const fullMessage = getMessage(props); + const fullMessage = getErrorMessage(props, props.message, props.icon); const { button } = props; return ( diff --git a/src/components/Screens/RequestScreen.tsx b/src/components/Screens/RequestScreen.tsx index 6b008fa..146ae19 100644 --- a/src/components/Screens/RequestScreen.tsx +++ b/src/components/Screens/RequestScreen.tsx @@ -4,7 +4,7 @@ import { useRequestLogic } from '../../utils/customHooks'; import { useFocusEffect } from '@react-navigation/native'; import BasicLoadingScreen from './BasicLoadingScreen'; import i18n from 'i18n-js'; -import { REQUEST_CODES, REQUEST_STATUS } from '../../utils/Requests'; +import { API_REQUEST_CODES, REQUEST_STATUS } from '../../utils/Requests'; export type RequestScreenProps = { request: () => Promise; @@ -14,7 +14,7 @@ export type RequestScreenProps = { lastRefreshDate: Date | undefined, refreshData: (newRequest?: () => Promise) => void, status: REQUEST_STATUS, - code?: REQUEST_CODES + code?: API_REQUEST_CODES ) => React.ReactElement; cache?: T; onCacheUpdate?: (newCache: T) => void; diff --git a/src/components/Screens/WebSectionList.tsx b/src/components/Screens/WebSectionList.tsx index 8879333..d790fa7 100644 --- a/src/components/Screens/WebSectionList.tsx +++ b/src/components/Screens/WebSectionList.tsx @@ -29,7 +29,7 @@ import ErrorView from './ErrorView'; import CollapsibleSectionList from '../Collapsible/CollapsibleSectionList'; import RequestScreen, { RequestScreenProps } from './RequestScreen'; import { CollapsibleComponentPropsType } from '../Collapsible/CollapsibleComponent'; -import { REQUEST_CODES, REQUEST_STATUS } from '../../utils/Requests'; +import { API_REQUEST_CODES, REQUEST_STATUS } from '../../utils/Requests'; export type SectionListDataType = Array<{ title: string; @@ -61,7 +61,7 @@ type Props = Omit< lastRefreshDate: Date | undefined, refreshData: (newRequest?: () => Promise) => void, status: REQUEST_STATUS, - code?: REQUEST_CODES + code?: API_REQUEST_CODES ) => SectionListDataType; renderListHeaderComponent?: ( data: RawData | undefined, @@ -69,7 +69,7 @@ type Props = Omit< lastRefreshDate: Date | undefined, refreshData: (newRequest?: () => Promise) => void, status: REQUEST_STATUS, - code?: REQUEST_CODES + code?: API_REQUEST_CODES ) => React.ComponentType | React.ReactElement | null; itemHeight?: number | null; }; @@ -103,7 +103,7 @@ function WebSectionList(props: Props) { lastRefreshDate: Date | undefined, refreshData: (newRequest?: () => Promise) => void, status: REQUEST_STATUS, - code?: REQUEST_CODES + code?: API_REQUEST_CODES ) => { const { itemHeight } = props; const dataset = props.createDataset( diff --git a/src/components/Screens/WebViewScreen.tsx b/src/components/Screens/WebViewScreen.tsx index 84dd7fb..2207466 100644 --- a/src/components/Screens/WebViewScreen.tsx +++ b/src/components/Screens/WebViewScreen.tsx @@ -43,11 +43,11 @@ import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityI import { useTheme } from 'react-native-paper'; import { useCollapsibleHeader } from 'react-navigation-collapsible'; import MaterialHeaderButtons, { Item } from '../Overrides/CustomHeaderButton'; -import { ERROR_TYPE } from '../../utils/WebData'; import ErrorView from './ErrorView'; import BasicLoadingScreen from './BasicLoadingScreen'; import { useFocusEffect, useNavigation } from '@react-navigation/core'; import { useCollapsible } from '../../utils/CollapsibleContext'; +import { REQUEST_STATUS } from '../../utils/Requests'; type Props = { url: string; @@ -259,7 +259,7 @@ function WebViewScreen(props: Props) { renderLoading={getRenderLoading} renderError={() => ( { return new Promise( - (resolve: () => void, reject: (error: number) => void) => { + (resolve: () => void, reject: (error: ApiRejectType) => void) => { const data = { email, password, @@ -150,13 +151,19 @@ export default class ConnectionManager { .then((response: ApiDataLoginType) => { if (response.token != null) { this.saveLogin(email, response.token) - .then((): void => resolve()) - .catch((): void => reject(ERROR_TYPE.TOKEN_SAVE)); + .then(() => resolve()) + .catch(() => + reject({ + status: REQUEST_STATUS.TOKEN_SAVE, + }) + ); } else { - reject(ERROR_TYPE.SERVER_ERROR); + reject({ + status: REQUEST_STATUS.SERVER_ERROR, + }); } }) - .catch((error: number): void => reject(error)); + .catch(reject); } ); } @@ -173,17 +180,22 @@ export default class ConnectionManager { params: { [key: string]: any } ): Promise { return new Promise( - (resolve: (response: T) => void, reject: (error: number) => void) => { + ( + resolve: (response: T) => void, + reject: (error: ApiRejectType) => void + ) => { if (this.getToken() !== null) { const data = { ...params, token: this.getToken(), }; apiRequest(path, 'POST', data) - .then((response: T): void => resolve(response)) - .catch((error: number): void => reject(error)); + .then((response: T) => resolve(response)) + .catch(reject); } else { - reject(ERROR_TYPE.TOKEN_RETRIEVE); + reject({ + status: REQUEST_STATUS.TOKEN_RETRIEVE, + }); } } ); diff --git a/src/screens/Amicale/Equipment/EquipmentRentScreen.tsx b/src/screens/Amicale/Equipment/EquipmentRentScreen.tsx index c2c5b6e..e81fbb8 100644 --- a/src/screens/Amicale/Equipment/EquipmentRentScreen.tsx +++ b/src/screens/Amicale/Equipment/EquipmentRentScreen.tsx @@ -46,6 +46,8 @@ import ConnectionManager from '../../../managers/ConnectionManager'; import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView'; import { MainStackParamsList } from '../../../navigation/MainNavigator'; import GENERAL_STYLES from '../../../constants/Styles'; +import { ApiRejectType } from '../../../utils/WebData'; +import { REQUEST_STATUS } from '../../../utils/Requests'; type EquipmentRentScreenNavigationProp = StackScreenProps< MainStackParamsList, @@ -65,7 +67,7 @@ type StateType = { dialogVisible: boolean; errorDialogVisible: boolean; markedDates: MarkedDatesObjectType; - currentError: number; + currentError: ApiRejectType; }; const styles = StyleSheet.create({ @@ -133,7 +135,7 @@ class EquipmentRentScreen extends React.Component { dialogVisible: false, errorDialogVisible: false, markedDates: {}, - currentError: 0, + currentError: { status: REQUEST_STATUS.SUCCESS }, }; this.resetSelection(); this.bookRef = React.createRef(); @@ -231,7 +233,7 @@ class EquipmentRentScreen extends React.Component { }); resolve(); }) - .catch((error: number) => { + .catch((error: ApiRejectType) => { this.onDialogDismiss(); this.showErrorDialog(error); resolve(); @@ -284,7 +286,7 @@ class EquipmentRentScreen extends React.Component { } }; - showErrorDialog = (error: number) => { + showErrorDialog = (error: ApiRejectType) => { this.setState({ errorDialogVisible: true, currentError: error, @@ -460,7 +462,8 @@ class EquipmentRentScreen extends React.Component { ; @@ -53,7 +55,7 @@ type StateType = { isPasswordValidated: boolean; loading: boolean; dialogVisible: boolean; - dialogError: number; + dialogError: REQUEST_STATUS; mascotDialogVisible: boolean; }; @@ -115,7 +117,7 @@ class LoginScreen extends React.Component { isPasswordValidated: false, loading: false, dialogVisible: false, - dialogError: 0, + dialogError: REQUEST_STATUS.SUCCESS, mascotDialogVisible: AsyncStorageManager.getBool( AsyncStorageManager.PREFERENCES.loginShowMascot.key ), @@ -335,10 +337,10 @@ class LoginScreen extends React.Component { * * @param error The error given by the login request */ - showErrorDialog = (error: number) => { + showErrorDialog = (error: ApiRejectType) => { this.setState({ dialogVisible: true, - dialogError: error, + dialogError: error.status, }); }; @@ -433,10 +435,10 @@ class LoginScreen extends React.Component { end={{ x: 0.1, y: 1 }} > @@ -445,7 +447,7 @@ class LoginScreen extends React.Component { visible={mascotDialogVisible} title={i18n.t('screens.login.mascotDialog.title')} message={i18n.t('screens.login.mascotDialog.message')} - icon="help" + icon={'help'} buttons={{ cancel: { message: i18n.t('screens.login.mascotDialog.button'), @@ -458,7 +460,7 @@ class LoginScreen extends React.Component { diff --git a/src/utils/Requests.tsx b/src/utils/Requests.tsx index 9821cf0..cfcf2fe 100644 --- a/src/utils/Requests.tsx +++ b/src/utils/Requests.tsx @@ -1,17 +1,8 @@ +import i18n from 'i18n-js'; +import { ApiRejectType } from './WebData'; + export enum REQUEST_STATUS { SUCCESS = 200, - BAD_INPUT = 400, - FORBIDDEN = 403, - CONNECTION_ERROR = 404, - SERVER_ERROR = 500, - UNKNOWN = 999, -} - -export enum REQUEST_CODES { - SUCCESS = 0, - BAD_CREDENTIALS = 1, - BAD_TOKEN = 2, - NO_CONSENT = 3, TOKEN_SAVE = 4, TOKEN_RETRIEVE = 5, BAD_INPUT = 400, @@ -20,3 +11,92 @@ export enum REQUEST_CODES { SERVER_ERROR = 500, UNKNOWN = 999, } + +export enum API_REQUEST_CODES { + SUCCESS = 0, + BAD_CREDENTIALS = 1, + BAD_TOKEN = 2, + NO_CONSENT = 3, + BAD_INPUT = 400, + FORBIDDEN = 403, + UNKNOWN = 999, +} + +export function getErrorMessage( + props: Partial, + message?: string, + icon?: string +) { + let fullMessage = { + message: '', + icon: '', + }; + if (props.code === undefined) { + switch (props.status) { + case REQUEST_STATUS.BAD_INPUT: + fullMessage.message = i18n.t('errors.badInput'); + fullMessage.icon = 'alert-circle-outline'; + break; + case REQUEST_STATUS.FORBIDDEN: + fullMessage.message = i18n.t('errors.forbidden'); + fullMessage.icon = 'lock'; + break; + case REQUEST_STATUS.CONNECTION_ERROR: + fullMessage.message = i18n.t('errors.connectionError'); + fullMessage.icon = 'access-point-network-off'; + break; + case REQUEST_STATUS.SERVER_ERROR: + fullMessage.message = i18n.t('errors.serverError'); + fullMessage.icon = 'server-network-off'; + break; + case REQUEST_STATUS.TOKEN_SAVE: + fullMessage.message = i18n.t('errors.tokenSave'); + fullMessage.icon = 'alert-circle-outline'; + break; + default: + fullMessage.message = i18n.t('errors.unknown'); + fullMessage.icon = 'alert-circle-outline'; + break; + } + } else { + switch (props.code) { + case API_REQUEST_CODES.BAD_CREDENTIALS: + fullMessage.message = i18n.t('errors.badCredentials'); + fullMessage.icon = 'account-alert-outline'; + break; + case API_REQUEST_CODES.BAD_TOKEN: + fullMessage.message = i18n.t('errors.badToken'); + fullMessage.icon = 'account-alert-outline'; + break; + case API_REQUEST_CODES.NO_CONSENT: + fullMessage.message = i18n.t('errors.noConsent'); + fullMessage.icon = 'account-remove-outline'; + break; + case API_REQUEST_CODES.BAD_INPUT: + fullMessage.message = i18n.t('errors.badInput'); + fullMessage.icon = 'alert-circle-outline'; + break; + case API_REQUEST_CODES.FORBIDDEN: + fullMessage.message = i18n.t('errors.forbidden'); + fullMessage.icon = 'lock'; + break; + default: + fullMessage.message = i18n.t('errors.unknown'); + fullMessage.icon = 'alert-circle-outline'; + break; + } + } + + if (props.code !== undefined) { + fullMessage.message += `\n\nCode {${props.status}:${props.code}}`; + } else { + fullMessage.message += `\n\nCode {${props.status}}`; + } + if (message) { + fullMessage.message = message; + } + if (icon) { + fullMessage.icon = icon; + } + return fullMessage; +} diff --git a/src/utils/WebData.ts b/src/utils/WebData.ts index 06085c7..9a78cf2 100644 --- a/src/utils/WebData.ts +++ b/src/utils/WebData.ts @@ -18,20 +18,21 @@ */ import Urls from '../constants/Urls'; +import { API_REQUEST_CODES, REQUEST_STATUS } from './Requests'; -export const ERROR_TYPE = { - SUCCESS: 0, - BAD_CREDENTIALS: 1, - BAD_TOKEN: 2, - NO_CONSENT: 3, - TOKEN_SAVE: 4, - TOKEN_RETRIEVE: 5, - BAD_INPUT: 400, - FORBIDDEN: 403, - CONNECTION_ERROR: 404, - SERVER_ERROR: 500, - UNKNOWN: 999, -}; +// export const ERROR_TYPE = { +// SUCCESS: 0, +// BAD_CREDENTIALS: 1, +// BAD_TOKEN: 2, +// NO_CONSENT: 3, +// TOKEN_SAVE: 4, +// TOKEN_RETRIEVE: 5, +// BAD_INPUT: 400, +// FORBIDDEN: 403, +// CONNECTION_ERROR: 404, +// SERVER_ERROR: 500, +// UNKNOWN: 999, +// }; export type ApiDataLoginType = { token: string; @@ -42,6 +43,11 @@ type ApiResponseType = { data: T; }; +export type ApiRejectType = { + status: REQUEST_STATUS; + code?: API_REQUEST_CODES; +}; + /** * Checks if the given API response is valid. * @@ -76,7 +82,7 @@ export async function apiRequest( params?: object ): Promise { return new Promise( - (resolve: (data: T) => void, reject: (error: number) => void) => { + (resolve: (data: T) => void, reject: (error: ApiRejectType) => void) => { let requestParams = {}; if (params != null) { requestParams = { ...params }; @@ -95,16 +101,25 @@ export async function apiRequest( ) .then((response: ApiResponseType) => { if (isApiResponseValid(response)) { - if (response.error === ERROR_TYPE.SUCCESS) { + if (response.error === API_REQUEST_CODES.SUCCESS) { resolve(response.data); } else { - reject(response.error); + reject({ + status: REQUEST_STATUS.SUCCESS, + code: response.error, + }); } } else { - reject(ERROR_TYPE.SERVER_ERROR); + reject({ + status: REQUEST_STATUS.SERVER_ERROR, + }); } }) - .catch((): void => reject(ERROR_TYPE.CONNECTION_ERROR)); + .catch(() => + reject({ + status: REQUEST_STATUS.SERVER_ERROR, + }) + ); } ); } diff --git a/src/utils/customHooks.tsx b/src/utils/customHooks.tsx index 6279c08..b222f8b 100644 --- a/src/utils/customHooks.tsx +++ b/src/utils/customHooks.tsx @@ -19,6 +19,7 @@ import { DependencyList, useEffect, useRef, useState } from 'react'; import { REQUEST_STATUS } from './Requests'; +import { ApiRejectType } from './WebData'; export function useMountEffect(func: () => void) { // eslint-disable-next-line react-hooks/exhaustive-deps @@ -97,14 +98,24 @@ export function useRequestLogic( onCacheUpdate(requestResponse); } }) - .catch(() => { - setResponse((prevState) => ({ - loading: false, - lastRefreshDate: prevState.lastRefreshDate, - status: REQUEST_STATUS.CONNECTION_ERROR, - code: undefined, - data: prevState.data, - })); + .catch((error: ApiRejectType | undefined) => { + if (!error) { + setResponse((prevState) => ({ + loading: false, + lastRefreshDate: prevState.lastRefreshDate, + status: REQUEST_STATUS.CONNECTION_ERROR, + code: undefined, + data: prevState.data, + })); + } else { + setResponse((prevState) => ({ + loading: false, + lastRefreshDate: prevState.lastRefreshDate, + status: error.status, + code: error.code, + data: prevState.data, + })); + } }); } };