Application Android et IOS pour l'amicale des élèves https://play.google.com/store/apps/details?id=fr.amicaleinsat.application
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

AuthenticatedScreen.tsx 6.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. /*
  2. * Copyright (c) 2019 - 2020 Arnaud Vergnet.
  3. *
  4. * This file is part of Campus INSAT.
  5. *
  6. * Campus INSAT is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * Campus INSAT is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
  18. */
  19. import * as React from 'react';
  20. import { StackNavigationProp } from '@react-navigation/stack';
  21. import ConnectionManager from '../../managers/ConnectionManager';
  22. import { ERROR_TYPE } from '../../utils/WebData';
  23. import ErrorView from '../Screens/ErrorView';
  24. import BasicLoadingScreen from '../Screens/BasicLoadingScreen';
  25. import i18n from 'i18n-js';
  26. type PropsType<T> = {
  27. navigation: StackNavigationProp<any>;
  28. requests: Array<{
  29. link: string;
  30. params: object;
  31. mandatory: boolean;
  32. }>;
  33. renderFunction: (data: Array<T | null>) => React.ReactNode;
  34. errorViewOverride?: Array<{
  35. errorCode: number;
  36. message: string;
  37. icon: string;
  38. showRetryButton: boolean;
  39. }> | null;
  40. };
  41. type StateType = {
  42. loading: boolean;
  43. };
  44. class AuthenticatedScreen<T> extends React.Component<PropsType<T>, StateType> {
  45. static defaultProps = {
  46. errorViewOverride: null,
  47. };
  48. currentUserToken: string | null;
  49. connectionManager: ConnectionManager;
  50. errors: Array<number>;
  51. fetchedData: Array<T | null>;
  52. constructor(props: PropsType<T>) {
  53. super(props);
  54. this.state = {
  55. loading: true,
  56. };
  57. this.currentUserToken = null;
  58. this.connectionManager = ConnectionManager.getInstance();
  59. props.navigation.addListener('focus', this.onScreenFocus);
  60. this.fetchedData = new Array(props.requests.length);
  61. this.errors = new Array(props.requests.length);
  62. }
  63. /**
  64. * Refreshes screen if user changed
  65. */
  66. onScreenFocus = () => {
  67. if (this.currentUserToken !== this.connectionManager.getToken()) {
  68. this.currentUserToken = this.connectionManager.getToken();
  69. this.fetchData();
  70. }
  71. };
  72. /**
  73. * Callback used when a request finishes, successfully or not.
  74. * Saves data and error code.
  75. * If the token is invalid, logout the user and open the login screen.
  76. * If the last request was received, stop the loading screen.
  77. *
  78. * @param data The data fetched from the server
  79. * @param index The index for the data
  80. * @param error The error code received
  81. */
  82. onRequestFinished(data: T | null, index: number, error?: number) {
  83. const { props } = this;
  84. if (index >= 0 && index < props.requests.length) {
  85. this.fetchedData[index] = data;
  86. this.errors[index] = error != null ? error : ERROR_TYPE.SUCCESS;
  87. }
  88. // Token expired, logout user
  89. if (error === ERROR_TYPE.BAD_TOKEN) {
  90. this.connectionManager.disconnect();
  91. }
  92. if (this.allRequestsFinished()) {
  93. this.setState({ loading: false });
  94. }
  95. }
  96. /**
  97. * Gets the error to render.
  98. * Non-mandatory requests are ignored.
  99. *
  100. *
  101. * @return {number} The error code or ERROR_TYPE.SUCCESS if no error was found
  102. */
  103. getError(): number {
  104. const { props } = this;
  105. for (let i = 0; i < this.errors.length; i += 1) {
  106. if (
  107. this.errors[i] !== ERROR_TYPE.SUCCESS &&
  108. props.requests[i].mandatory
  109. ) {
  110. return this.errors[i];
  111. }
  112. }
  113. return ERROR_TYPE.SUCCESS;
  114. }
  115. /**
  116. * Gets the error view to display in case of error
  117. *
  118. * @return {*}
  119. */
  120. getErrorRender() {
  121. const { props } = this;
  122. const errorCode = this.getError();
  123. let shouldOverride = false;
  124. let override = null;
  125. const overrideList = props.errorViewOverride;
  126. if (overrideList != null) {
  127. for (let i = 0; i < overrideList.length; i += 1) {
  128. if (overrideList[i].errorCode === errorCode) {
  129. shouldOverride = true;
  130. override = overrideList[i];
  131. break;
  132. }
  133. }
  134. }
  135. if (shouldOverride && override != null) {
  136. return (
  137. <ErrorView
  138. icon={override.icon}
  139. message={override.message}
  140. button={
  141. override.showRetryButton
  142. ? {
  143. icon: 'refresh',
  144. text: i18n.t('general.retry'),
  145. onPress: this.fetchData,
  146. }
  147. : undefined
  148. }
  149. />
  150. );
  151. }
  152. return (
  153. <ErrorView
  154. status={errorCode}
  155. button={{
  156. icon: 'refresh',
  157. text: i18n.t('general.retry'),
  158. onPress: this.fetchData,
  159. }}
  160. />
  161. );
  162. }
  163. /**
  164. * Fetches the data from the server.
  165. *
  166. * If the user is not logged in errorCode is set to BAD_TOKEN and all requests fail.
  167. *
  168. * If the user is logged in, send all requests.
  169. */
  170. fetchData = () => {
  171. const { state, props } = this;
  172. if (!state.loading) {
  173. this.setState({ loading: true });
  174. }
  175. if (this.connectionManager.isLoggedIn()) {
  176. for (let i = 0; i < props.requests.length; i += 1) {
  177. this.connectionManager
  178. .authenticatedRequest<T>(
  179. props.requests[i].link,
  180. props.requests[i].params
  181. )
  182. .then((response: T): void => this.onRequestFinished(response, i))
  183. .catch((error: number): void =>
  184. this.onRequestFinished(null, i, error)
  185. );
  186. }
  187. } else {
  188. for (let i = 0; i < props.requests.length; i += 1) {
  189. this.onRequestFinished(null, i, ERROR_TYPE.BAD_TOKEN);
  190. }
  191. }
  192. };
  193. /**
  194. * Checks if all requests finished processing
  195. *
  196. * @return {boolean} True if all finished
  197. */
  198. allRequestsFinished(): boolean {
  199. let finished = true;
  200. this.errors.forEach((error: number | null) => {
  201. if (error == null) {
  202. finished = false;
  203. }
  204. });
  205. return finished;
  206. }
  207. /**
  208. * Reloads the data, to be called using ref by parent components
  209. */
  210. reload() {
  211. this.fetchData();
  212. }
  213. render() {
  214. const { state, props } = this;
  215. if (state.loading) {
  216. return <BasicLoadingScreen />;
  217. }
  218. if (this.getError() === ERROR_TYPE.SUCCESS) {
  219. return props.renderFunction(this.fetchedData);
  220. }
  221. return this.getErrorRender();
  222. }
  223. }
  224. export default AuthenticatedScreen;