Update api error codes

This commit is contained in:
Arnaud Vergnet 2021-05-13 10:58:47 +02:00
parent 50c62dd676
commit 9b4caade00
12 changed files with 217 additions and 204 deletions

View file

@ -25,6 +25,8 @@ import ConnectionManager from '../../../managers/ConnectionManager';
import LoadingConfirmDialog from '../../Dialogs/LoadingConfirmDialog'; import LoadingConfirmDialog from '../../Dialogs/LoadingConfirmDialog';
import ErrorDialog from '../../Dialogs/ErrorDialog'; import ErrorDialog from '../../Dialogs/ErrorDialog';
import type { VoteTeamType } from '../../../screens/Amicale/VoteScreen'; import type { VoteTeamType } from '../../../screens/Amicale/VoteScreen';
import { ApiRejectType } from '../../../utils/WebData';
import { REQUEST_STATUS } from '../../../utils/Requests';
type PropsType = { type PropsType = {
teams: Array<VoteTeamType>; teams: Array<VoteTeamType>;
@ -36,7 +38,7 @@ type StateType = {
selectedTeam: string; selectedTeam: string;
voteDialogVisible: boolean; voteDialogVisible: boolean;
errorDialogVisible: boolean; errorDialogVisible: boolean;
currentError: number; currentError: ApiRejectType;
}; };
const styles = StyleSheet.create({ const styles = StyleSheet.create({
@ -58,7 +60,7 @@ export default class VoteSelect extends React.PureComponent<
selectedTeam: 'none', selectedTeam: 'none',
voteDialogVisible: false, voteDialogVisible: false,
errorDialogVisible: false, errorDialogVisible: false,
currentError: 0, currentError: { status: REQUEST_STATUS.SUCCESS },
}; };
} }
@ -88,7 +90,7 @@ export default class VoteSelect extends React.PureComponent<
props.onVoteSuccess(); props.onVoteSuccess();
resolve(); resolve();
}) })
.catch((error: number) => { .catch((error: ApiRejectType) => {
this.onVoteDialogDismiss(); this.onVoteDialogDismiss();
this.showErrorDialog(error); this.showErrorDialog(error);
resolve(); resolve();
@ -96,7 +98,7 @@ export default class VoteSelect extends React.PureComponent<
}); });
}; };
showErrorDialog = (error: number): void => showErrorDialog = (error: ApiRejectType): void =>
this.setState({ this.setState({
errorDialogVisible: true, errorDialogVisible: true,
currentError: error, currentError: error,
@ -156,7 +158,8 @@ export default class VoteSelect extends React.PureComponent<
<ErrorDialog <ErrorDialog
visible={state.errorDialogVisible} visible={state.errorDialogVisible}
onDismiss={this.onErrorDialogDismiss} onDismiss={this.onErrorDialogDismiss}
errorCode={state.currentError} status={state.currentError.status}
code={state.currentError.code}
/> />
</View> </View>
); );

View file

@ -19,60 +19,27 @@
import * as React from 'react'; import * as React from 'react';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import { ERROR_TYPE } from '../../utils/WebData';
import AlertDialog from './AlertDialog'; import AlertDialog from './AlertDialog';
import {
API_REQUEST_CODES,
getErrorMessage,
REQUEST_STATUS,
} from '../../utils/Requests';
type PropsType = { type PropsType = {
visible: boolean; visible: boolean;
onDismiss: () => void; onDismiss: () => void;
errorCode: number; status?: REQUEST_STATUS;
code?: API_REQUEST_CODES;
}; };
function ErrorDialog(props: PropsType) { 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 ( return (
<AlertDialog <AlertDialog
visible={props.visible} visible={props.visible}
onDismiss={props.onDismiss} onDismiss={props.onDismiss}
title={title} title={i18n.t('errors.title')}
message={message} message={getErrorMessage(props)}
/> />
); );
} }

View file

@ -21,13 +21,16 @@ import * as React from 'react';
import { Button, Subheading, useTheme } from 'react-native-paper'; import { Button, Subheading, useTheme } from 'react-native-paper';
import { StyleSheet, View, ViewStyle } from 'react-native'; import { StyleSheet, View, ViewStyle } from 'react-native';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import i18n from 'i18n-js';
import * as Animatable from 'react-native-animatable'; 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 = { type Props = {
status?: REQUEST_STATUS; status?: REQUEST_STATUS;
code?: REQUEST_CODES; code?: API_REQUEST_CODES;
icon?: string; icon?: string;
message?: string; message?: string;
loading?: boolean; 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) { function ErrorView(props: Props) {
const theme = useTheme(); const theme = useTheme();
const fullMessage = getMessage(props); const fullMessage = getErrorMessage(props, props.message, props.icon);
const { button } = props; const { button } = props;
return ( return (

View file

@ -4,7 +4,7 @@ import { useRequestLogic } from '../../utils/customHooks';
import { useFocusEffect } from '@react-navigation/native'; import { useFocusEffect } from '@react-navigation/native';
import BasicLoadingScreen from './BasicLoadingScreen'; import BasicLoadingScreen from './BasicLoadingScreen';
import i18n from 'i18n-js'; 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<T> = { export type RequestScreenProps<T> = {
request: () => Promise<T>; request: () => Promise<T>;
@ -14,7 +14,7 @@ export type RequestScreenProps<T> = {
lastRefreshDate: Date | undefined, lastRefreshDate: Date | undefined,
refreshData: (newRequest?: () => Promise<T>) => void, refreshData: (newRequest?: () => Promise<T>) => void,
status: REQUEST_STATUS, status: REQUEST_STATUS,
code?: REQUEST_CODES code?: API_REQUEST_CODES
) => React.ReactElement; ) => React.ReactElement;
cache?: T; cache?: T;
onCacheUpdate?: (newCache: T) => void; onCacheUpdate?: (newCache: T) => void;

View file

@ -29,7 +29,7 @@ import ErrorView from './ErrorView';
import CollapsibleSectionList from '../Collapsible/CollapsibleSectionList'; import CollapsibleSectionList from '../Collapsible/CollapsibleSectionList';
import RequestScreen, { RequestScreenProps } from './RequestScreen'; import RequestScreen, { RequestScreenProps } from './RequestScreen';
import { CollapsibleComponentPropsType } from '../Collapsible/CollapsibleComponent'; 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<ItemT> = Array<{ export type SectionListDataType<ItemT> = Array<{
title: string; title: string;
@ -61,7 +61,7 @@ type Props<ItemT, RawData> = Omit<
lastRefreshDate: Date | undefined, lastRefreshDate: Date | undefined,
refreshData: (newRequest?: () => Promise<RawData>) => void, refreshData: (newRequest?: () => Promise<RawData>) => void,
status: REQUEST_STATUS, status: REQUEST_STATUS,
code?: REQUEST_CODES code?: API_REQUEST_CODES
) => SectionListDataType<ItemT>; ) => SectionListDataType<ItemT>;
renderListHeaderComponent?: ( renderListHeaderComponent?: (
data: RawData | undefined, data: RawData | undefined,
@ -69,7 +69,7 @@ type Props<ItemT, RawData> = Omit<
lastRefreshDate: Date | undefined, lastRefreshDate: Date | undefined,
refreshData: (newRequest?: () => Promise<RawData>) => void, refreshData: (newRequest?: () => Promise<RawData>) => void,
status: REQUEST_STATUS, status: REQUEST_STATUS,
code?: REQUEST_CODES code?: API_REQUEST_CODES
) => React.ComponentType<any> | React.ReactElement | null; ) => React.ComponentType<any> | React.ReactElement | null;
itemHeight?: number | null; itemHeight?: number | null;
}; };
@ -103,7 +103,7 @@ function WebSectionList<ItemT, RawData>(props: Props<ItemT, RawData>) {
lastRefreshDate: Date | undefined, lastRefreshDate: Date | undefined,
refreshData: (newRequest?: () => Promise<RawData>) => void, refreshData: (newRequest?: () => Promise<RawData>) => void,
status: REQUEST_STATUS, status: REQUEST_STATUS,
code?: REQUEST_CODES code?: API_REQUEST_CODES
) => { ) => {
const { itemHeight } = props; const { itemHeight } = props;
const dataset = props.createDataset( const dataset = props.createDataset(

View file

@ -43,11 +43,11 @@ import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityI
import { useTheme } from 'react-native-paper'; import { useTheme } from 'react-native-paper';
import { useCollapsibleHeader } from 'react-navigation-collapsible'; import { useCollapsibleHeader } from 'react-navigation-collapsible';
import MaterialHeaderButtons, { Item } from '../Overrides/CustomHeaderButton'; import MaterialHeaderButtons, { Item } from '../Overrides/CustomHeaderButton';
import { ERROR_TYPE } from '../../utils/WebData';
import ErrorView from './ErrorView'; import ErrorView from './ErrorView';
import BasicLoadingScreen from './BasicLoadingScreen'; import BasicLoadingScreen from './BasicLoadingScreen';
import { useFocusEffect, useNavigation } from '@react-navigation/core'; import { useFocusEffect, useNavigation } from '@react-navigation/core';
import { useCollapsible } from '../../utils/CollapsibleContext'; import { useCollapsible } from '../../utils/CollapsibleContext';
import { REQUEST_STATUS } from '../../utils/Requests';
type Props = { type Props = {
url: string; url: string;
@ -259,7 +259,7 @@ function WebViewScreen(props: Props) {
renderLoading={getRenderLoading} renderLoading={getRenderLoading}
renderError={() => ( renderError={() => (
<ErrorView <ErrorView
status={ERROR_TYPE.CONNECTION_ERROR} status={REQUEST_STATUS.CONNECTION_ERROR}
button={{ button={{
icon: 'refresh', icon: 'refresh',
text: i18n.t('general.retry'), text: i18n.t('general.retry'),

View file

@ -18,8 +18,9 @@
*/ */
import * as Keychain from 'react-native-keychain'; import * as Keychain from 'react-native-keychain';
import type { ApiDataLoginType } from '../utils/WebData'; import { REQUEST_STATUS } from '../utils/Requests';
import { apiRequest, ERROR_TYPE } from '../utils/WebData'; import type { ApiDataLoginType, ApiRejectType } from '../utils/WebData';
import { apiRequest } from '../utils/WebData';
/** /**
* champ: error * champ: error
@ -141,7 +142,7 @@ export default class ConnectionManager {
*/ */
async connect(email: string, password: string): Promise<void> { async connect(email: string, password: string): Promise<void> {
return new Promise( return new Promise(
(resolve: () => void, reject: (error: number) => void) => { (resolve: () => void, reject: (error: ApiRejectType) => void) => {
const data = { const data = {
email, email,
password, password,
@ -150,13 +151,19 @@ export default class ConnectionManager {
.then((response: ApiDataLoginType) => { .then((response: ApiDataLoginType) => {
if (response.token != null) { if (response.token != null) {
this.saveLogin(email, response.token) this.saveLogin(email, response.token)
.then((): void => resolve()) .then(() => resolve())
.catch((): void => reject(ERROR_TYPE.TOKEN_SAVE)); .catch(() =>
reject({
status: REQUEST_STATUS.TOKEN_SAVE,
})
);
} else { } 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 } params: { [key: string]: any }
): Promise<T> { ): Promise<T> {
return new Promise( return new Promise(
(resolve: (response: T) => void, reject: (error: number) => void) => { (
resolve: (response: T) => void,
reject: (error: ApiRejectType) => void
) => {
if (this.getToken() !== null) { if (this.getToken() !== null) {
const data = { const data = {
...params, ...params,
token: this.getToken(), token: this.getToken(),
}; };
apiRequest<T>(path, 'POST', data) apiRequest<T>(path, 'POST', data)
.then((response: T): void => resolve(response)) .then((response: T) => resolve(response))
.catch((error: number): void => reject(error)); .catch(reject);
} else { } else {
reject(ERROR_TYPE.TOKEN_RETRIEVE); reject({
status: REQUEST_STATUS.TOKEN_RETRIEVE,
});
} }
} }
); );

View file

@ -46,6 +46,8 @@ import ConnectionManager from '../../../managers/ConnectionManager';
import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView'; import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView';
import { MainStackParamsList } from '../../../navigation/MainNavigator'; import { MainStackParamsList } from '../../../navigation/MainNavigator';
import GENERAL_STYLES from '../../../constants/Styles'; import GENERAL_STYLES from '../../../constants/Styles';
import { ApiRejectType } from '../../../utils/WebData';
import { REQUEST_STATUS } from '../../../utils/Requests';
type EquipmentRentScreenNavigationProp = StackScreenProps< type EquipmentRentScreenNavigationProp = StackScreenProps<
MainStackParamsList, MainStackParamsList,
@ -65,7 +67,7 @@ type StateType = {
dialogVisible: boolean; dialogVisible: boolean;
errorDialogVisible: boolean; errorDialogVisible: boolean;
markedDates: MarkedDatesObjectType; markedDates: MarkedDatesObjectType;
currentError: number; currentError: ApiRejectType;
}; };
const styles = StyleSheet.create({ const styles = StyleSheet.create({
@ -133,7 +135,7 @@ class EquipmentRentScreen extends React.Component<Props, StateType> {
dialogVisible: false, dialogVisible: false,
errorDialogVisible: false, errorDialogVisible: false,
markedDates: {}, markedDates: {},
currentError: 0, currentError: { status: REQUEST_STATUS.SUCCESS },
}; };
this.resetSelection(); this.resetSelection();
this.bookRef = React.createRef(); this.bookRef = React.createRef();
@ -231,7 +233,7 @@ class EquipmentRentScreen extends React.Component<Props, StateType> {
}); });
resolve(); resolve();
}) })
.catch((error: number) => { .catch((error: ApiRejectType) => {
this.onDialogDismiss(); this.onDialogDismiss();
this.showErrorDialog(error); this.showErrorDialog(error);
resolve(); resolve();
@ -284,7 +286,7 @@ class EquipmentRentScreen extends React.Component<Props, StateType> {
} }
}; };
showErrorDialog = (error: number) => { showErrorDialog = (error: ApiRejectType) => {
this.setState({ this.setState({
errorDialogVisible: true, errorDialogVisible: true,
currentError: error, currentError: error,
@ -460,7 +462,8 @@ class EquipmentRentScreen extends React.Component<Props, StateType> {
<ErrorDialog <ErrorDialog
visible={state.errorDialogVisible} visible={state.errorDialogVisible}
onDismiss={this.onErrorDialogDismiss} onDismiss={this.onErrorDialogDismiss}
errorCode={state.currentError} status={state.currentError.status}
code={state.currentError.code}
/> />
<Animatable.View <Animatable.View
ref={this.bookRef} ref={this.bookRef}

View file

@ -38,6 +38,8 @@ import CollapsibleScrollView from '../../components/Collapsible/CollapsibleScrol
import { MainStackParamsList } from '../../navigation/MainNavigator'; import { MainStackParamsList } from '../../navigation/MainNavigator';
import GENERAL_STYLES from '../../constants/Styles'; import GENERAL_STYLES from '../../constants/Styles';
import Urls from '../../constants/Urls'; import Urls from '../../constants/Urls';
import { ApiRejectType } from '../../utils/WebData';
import { REQUEST_STATUS } from '../../utils/Requests';
type LoginScreenNavigationProp = StackScreenProps<MainStackParamsList, 'login'>; type LoginScreenNavigationProp = StackScreenProps<MainStackParamsList, 'login'>;
@ -53,7 +55,7 @@ type StateType = {
isPasswordValidated: boolean; isPasswordValidated: boolean;
loading: boolean; loading: boolean;
dialogVisible: boolean; dialogVisible: boolean;
dialogError: number; dialogError: REQUEST_STATUS;
mascotDialogVisible: boolean; mascotDialogVisible: boolean;
}; };
@ -115,7 +117,7 @@ class LoginScreen extends React.Component<Props, StateType> {
isPasswordValidated: false, isPasswordValidated: false,
loading: false, loading: false,
dialogVisible: false, dialogVisible: false,
dialogError: 0, dialogError: REQUEST_STATUS.SUCCESS,
mascotDialogVisible: AsyncStorageManager.getBool( mascotDialogVisible: AsyncStorageManager.getBool(
AsyncStorageManager.PREFERENCES.loginShowMascot.key AsyncStorageManager.PREFERENCES.loginShowMascot.key
), ),
@ -335,10 +337,10 @@ class LoginScreen extends React.Component<Props, StateType> {
* *
* @param error The error given by the login request * @param error The error given by the login request
*/ */
showErrorDialog = (error: number) => { showErrorDialog = (error: ApiRejectType) => {
this.setState({ this.setState({
dialogVisible: true, dialogVisible: true,
dialogError: error, dialogError: error.status,
}); });
}; };
@ -433,10 +435,10 @@ class LoginScreen extends React.Component<Props, StateType> {
end={{ x: 0.1, y: 1 }} end={{ x: 0.1, y: 1 }}
> >
<KeyboardAvoidingView <KeyboardAvoidingView
behavior="height" behavior={'height'}
contentContainerStyle={GENERAL_STYLES.flex} contentContainerStyle={GENERAL_STYLES.flex}
style={GENERAL_STYLES.flex} style={GENERAL_STYLES.flex}
enabled enabled={true}
keyboardVerticalOffset={100} keyboardVerticalOffset={100}
> >
<CollapsibleScrollView headerColors={'transparent'}> <CollapsibleScrollView headerColors={'transparent'}>
@ -445,7 +447,7 @@ class LoginScreen extends React.Component<Props, StateType> {
visible={mascotDialogVisible} visible={mascotDialogVisible}
title={i18n.t('screens.login.mascotDialog.title')} title={i18n.t('screens.login.mascotDialog.title')}
message={i18n.t('screens.login.mascotDialog.message')} message={i18n.t('screens.login.mascotDialog.message')}
icon="help" icon={'help'}
buttons={{ buttons={{
cancel: { cancel: {
message: i18n.t('screens.login.mascotDialog.button'), message: i18n.t('screens.login.mascotDialog.button'),
@ -458,7 +460,7 @@ class LoginScreen extends React.Component<Props, StateType> {
<ErrorDialog <ErrorDialog
visible={dialogVisible} visible={dialogVisible}
onDismiss={this.hideErrorDialog} onDismiss={this.hideErrorDialog}
errorCode={dialogError} status={dialogError}
/> />
</CollapsibleScrollView> </CollapsibleScrollView>
</KeyboardAvoidingView> </KeyboardAvoidingView>

View file

@ -1,17 +1,8 @@
import i18n from 'i18n-js';
import { ApiRejectType } from './WebData';
export enum REQUEST_STATUS { export enum REQUEST_STATUS {
SUCCESS = 200, 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_SAVE = 4,
TOKEN_RETRIEVE = 5, TOKEN_RETRIEVE = 5,
BAD_INPUT = 400, BAD_INPUT = 400,
@ -20,3 +11,92 @@ export enum REQUEST_CODES {
SERVER_ERROR = 500, SERVER_ERROR = 500,
UNKNOWN = 999, 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<ApiRejectType>,
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;
}

View file

@ -18,20 +18,21 @@
*/ */
import Urls from '../constants/Urls'; import Urls from '../constants/Urls';
import { API_REQUEST_CODES, REQUEST_STATUS } from './Requests';
export const ERROR_TYPE = { // export const ERROR_TYPE = {
SUCCESS: 0, // SUCCESS: 0,
BAD_CREDENTIALS: 1, // BAD_CREDENTIALS: 1,
BAD_TOKEN: 2, // BAD_TOKEN: 2,
NO_CONSENT: 3, // NO_CONSENT: 3,
TOKEN_SAVE: 4, // TOKEN_SAVE: 4,
TOKEN_RETRIEVE: 5, // TOKEN_RETRIEVE: 5,
BAD_INPUT: 400, // BAD_INPUT: 400,
FORBIDDEN: 403, // FORBIDDEN: 403,
CONNECTION_ERROR: 404, // CONNECTION_ERROR: 404,
SERVER_ERROR: 500, // SERVER_ERROR: 500,
UNKNOWN: 999, // UNKNOWN: 999,
}; // };
export type ApiDataLoginType = { export type ApiDataLoginType = {
token: string; token: string;
@ -42,6 +43,11 @@ type ApiResponseType<T> = {
data: T; data: T;
}; };
export type ApiRejectType = {
status: REQUEST_STATUS;
code?: API_REQUEST_CODES;
};
/** /**
* Checks if the given API response is valid. * Checks if the given API response is valid.
* *
@ -76,7 +82,7 @@ export async function apiRequest<T>(
params?: object params?: object
): Promise<T> { ): Promise<T> {
return new Promise( return new Promise(
(resolve: (data: T) => void, reject: (error: number) => void) => { (resolve: (data: T) => void, reject: (error: ApiRejectType) => void) => {
let requestParams = {}; let requestParams = {};
if (params != null) { if (params != null) {
requestParams = { ...params }; requestParams = { ...params };
@ -95,16 +101,25 @@ export async function apiRequest<T>(
) )
.then((response: ApiResponseType<T>) => { .then((response: ApiResponseType<T>) => {
if (isApiResponseValid(response)) { if (isApiResponseValid(response)) {
if (response.error === ERROR_TYPE.SUCCESS) { if (response.error === API_REQUEST_CODES.SUCCESS) {
resolve(response.data); resolve(response.data);
} else { } else {
reject(response.error); reject({
status: REQUEST_STATUS.SUCCESS,
code: response.error,
});
} }
} else { } 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,
})
);
} }
); );
} }

View file

@ -19,6 +19,7 @@
import { DependencyList, useEffect, useRef, useState } from 'react'; import { DependencyList, useEffect, useRef, useState } from 'react';
import { REQUEST_STATUS } from './Requests'; import { REQUEST_STATUS } from './Requests';
import { ApiRejectType } from './WebData';
export function useMountEffect(func: () => void) { export function useMountEffect(func: () => void) {
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@ -97,14 +98,24 @@ export function useRequestLogic<T>(
onCacheUpdate(requestResponse); onCacheUpdate(requestResponse);
} }
}) })
.catch(() => { .catch((error: ApiRejectType | undefined) => {
setResponse((prevState) => ({ if (!error) {
loading: false, setResponse((prevState) => ({
lastRefreshDate: prevState.lastRefreshDate, loading: false,
status: REQUEST_STATUS.CONNECTION_ERROR, lastRefreshDate: prevState.lastRefreshDate,
code: undefined, status: REQUEST_STATUS.CONNECTION_ERROR,
data: prevState.data, code: undefined,
})); data: prevState.data,
}));
} else {
setResponse((prevState) => ({
loading: false,
lastRefreshDate: prevState.lastRefreshDate,
status: error.status,
code: error.code,
data: prevState.data,
}));
}
}); });
} }
}; };