diff --git a/App.tsx b/App.tsx
index 9d5a93f..bcea3ee 100644
--- a/App.tsx
+++ b/App.tsx
@@ -21,7 +21,6 @@ import React from 'react';
 import { LogBox, Platform } from 'react-native';
 import { setSafeBounceHeight } from 'react-navigation-collapsible';
 import SplashScreen from 'react-native-splash-screen';
-import ConnectionManager from './src/managers/ConnectionManager';
 import type { ParsedUrlDataType } from './src/utils/URLHandler';
 import URLHandler from './src/utils/URLHandler';
 import initLocales from './src/utils/Locales';
@@ -48,6 +47,8 @@ import {
   ProxiwashPreferencesProvider,
 } from './src/components/providers/PreferencesProvider';
 import MainApp from './src/screens/MainApp';
+import LoginProvider from './src/components/providers/LoginProvider';
+import { retrieveLoginToken } from './src/utils/loginToken';
 
 // Native optimizations https://reactnavigation.org/docs/react-native-screens
 // Crashes app when navigating away from webview on android 9+
@@ -67,6 +68,7 @@ type StateType = {
     proxiwash: ProxiwashPreferencesType;
     mascot: MascotPreferencesType;
   };
+  loginToken?: string;
 };
 
 export default class App extends React.Component<{}, StateType> {
@@ -88,6 +90,7 @@ export default class App extends React.Component<{}, StateType> {
         proxiwash: defaultProxiwashPreferences,
         mascot: defaultMascotPreferences,
       },
+      loginToken: undefined,
     };
     initLocales();
     this.navigatorRef = React.createRef();
@@ -136,10 +139,11 @@ export default class App extends React.Component<{}, StateType> {
       | PlanexPreferencesType
       | ProxiwashPreferencesType
       | MascotPreferencesType
-      | void
+      | string
+      | undefined
     >
   ) => {
-    const [general, planex, proxiwash, mascot] = values;
+    const [general, planex, proxiwash, mascot, token] = values;
     this.setState({
       isLoading: false,
       initialPreferences: {
@@ -148,6 +152,7 @@ export default class App extends React.Component<{}, StateType> {
         proxiwash: proxiwash as ProxiwashPreferencesType,
         mascot: mascot as MascotPreferencesType,
       },
+      loginToken: token as string | undefined,
     });
     SplashScreen.hide();
   };
@@ -175,7 +180,7 @@ export default class App extends React.Component<{}, StateType> {
         Object.values(MascotPreferenceKeys),
         defaultMascotPreferences
       ),
-      ConnectionManager.getInstance().recoverLogin(),
+      retrieveLoginToken(),
     ])
       .then(this.onLoadFinished)
       .catch(this.onLoadFinished);
@@ -202,11 +207,13 @@ export default class App extends React.Component<{}, StateType> {
             
-              
+              
+                
+              
             
           
         
diff --git a/src/components/Amicale/Login/LoginForm.tsx b/src/components/Amicale/Login/LoginForm.tsx
new file mode 100644
index 0000000..4fbcf60
--- /dev/null
+++ b/src/components/Amicale/Login/LoginForm.tsx
@@ -0,0 +1,231 @@
+import React, { useRef, useState } from 'react';
+import {
+  Image,
+  StyleSheet,
+  View,
+  TextInput as RNTextInput,
+} from 'react-native';
+import {
+  Button,
+  Card,
+  HelperText,
+  TextInput,
+  useTheme,
+} from 'react-native-paper';
+import i18n from 'i18n-js';
+import GENERAL_STYLES from '../../../constants/Styles';
+
+type Props = {
+  loading: boolean;
+  onSubmit: (email: string, password: string) => void;
+  onHelpPress: () => void;
+  onResetPasswordPress: () => void;
+};
+
+const ICON_AMICALE = require('../../../assets/amicale.png');
+
+const styles = StyleSheet.create({
+  card: {
+    marginTop: 'auto',
+    marginBottom: 'auto',
+  },
+  header: {
+    fontSize: 36,
+    marginBottom: 48,
+  },
+  text: {
+    color: '#ffffff',
+  },
+  buttonContainer: {
+    flexWrap: 'wrap',
+  },
+  lockButton: {
+    marginRight: 'auto',
+    marginBottom: 20,
+  },
+  sendButton: {
+    marginLeft: 'auto',
+  },
+});
+
+const emailRegex = /^.+@.+\..+$/;
+
+/**
+ * Checks if the entered email is valid (matches the regex)
+ *
+ * @returns {boolean}
+ */
+function isEmailValid(email: string): boolean {
+  return emailRegex.test(email);
+}
+
+/**
+ * Checks if the user has entered a password
+ *
+ * @returns {boolean}
+ */
+function isPasswordValid(password: string): boolean {
+  return password !== '';
+}
+
+export default function LoginForm(props: Props) {
+  const theme = useTheme();
+  const [email, setEmail] = useState('');
+  const [password, setPassword] = useState('');
+  const [isEmailValidated, setIsEmailValidated] = useState(false);
+  const [isPasswordValidated, setIsPasswordValidated] = useState(false);
+  const passwordRef = useRef(null);
+  /**
+   * Checks if we should tell the user his email is invalid.
+   * We should only show this if his email is invalid and has been checked when un-focusing the input
+   *
+   * @returns {boolean|boolean}
+   */
+  const shouldShowEmailError = () => {
+    return isEmailValidated && !isEmailValid(email);
+  };
+
+  /**
+   * Checks if we should tell the user his password is invalid.
+   * We should only show this if his password is invalid and has been checked when un-focusing the input
+   *
+   * @returns {boolean|boolean}
+   */
+  const shouldShowPasswordError = () => {
+    return isPasswordValidated && !isPasswordValid(password);
+  };
+
+  const onEmailSubmit = () => {
+    if (passwordRef.current) {
+      passwordRef.current.focus();
+    }
+  };
+
+  /**
+   * The user has unfocused the input, his email is ready to be validated
+   */
+  const validateEmail = () => setIsEmailValidated(true);
+
+  /**
+   * The user has unfocused the input, his password is ready to be validated
+   */
+  const validatePassword = () => setIsPasswordValidated(true);
+
+  const onEmailChange = (value: string) => {
+    if (isEmailValidated) {
+      setIsEmailValidated(false);
+    }
+    setEmail(value);
+  };
+
+  const onPasswordChange = (value: string) => {
+    if (isPasswordValidated) {
+      setIsPasswordValidated(false);
+    }
+    setPassword(value);
+  };
+
+  const shouldEnableLogin = () => {
+    return isEmailValid(email) && isPasswordValid(password) && !props.loading;
+  };
+
+  const onSubmit = () => {
+    if (shouldEnableLogin()) {
+      props.onSubmit(email, password);
+    }
+  };
+
+  return (
+    
+       (
+          
+        )}
+      />
+      
+        
+          
+          
+            {i18n.t('screens.login.emailError')}
+          
+          
+          
+            {i18n.t('screens.login.passwordError')}
+          
+        
+        
+          
+          
+        
+        
+          
+        
+      
+    
+  );
+}
diff --git a/src/components/Amicale/LogoutDialog.tsx b/src/components/Amicale/LogoutDialog.tsx
index 6e060a3..8d2759c 100644
--- a/src/components/Amicale/LogoutDialog.tsx
+++ b/src/components/Amicale/LogoutDialog.tsx
@@ -20,8 +20,7 @@
 import * as React from 'react';
 import i18n from 'i18n-js';
 import LoadingConfirmDialog from '../Dialogs/LoadingConfirmDialog';
-import ConnectionManager from '../../managers/ConnectionManager';
-import { useNavigation } from '@react-navigation/native';
+import { useLogout } from '../../utils/logout';
 
 type PropsType = {
   visible: boolean;
@@ -29,19 +28,12 @@ type PropsType = {
 };
 
 function LogoutDialog(props: PropsType) {
-  const navigation = useNavigation();
+  const onLogout = useLogout();
+  // Use a loading dialog as it can take some time to update the context
   const onClickAccept = async (): Promise => {
     return new Promise((resolve: () => void) => {
-      ConnectionManager.getInstance()
-        .disconnect()
-        .then(() => {
-          navigation.reset({
-            index: 0,
-            routes: [{ name: 'main' }],
-          });
-          props.onDismiss();
-          resolve();
-        });
+      onLogout();
+      resolve();
     });
   };
 
diff --git a/src/components/Amicale/Profile/ProfileClubCard.tsx b/src/components/Amicale/Profile/ProfileClubCard.tsx
new file mode 100644
index 0000000..efb7876
--- /dev/null
+++ b/src/components/Amicale/Profile/ProfileClubCard.tsx
@@ -0,0 +1,100 @@
+import React from 'react';
+import { Card, Avatar, Divider, useTheme, List } from 'react-native-paper';
+import i18n from 'i18n-js';
+import { FlatList, StyleSheet } from 'react-native';
+import { ProfileClubType } from '../../../screens/Amicale/ProfileScreen';
+import { useNavigation } from '@react-navigation/core';
+
+type Props = {
+  clubs?: Array;
+};
+
+const styles = StyleSheet.create({
+  card: {
+    margin: 10,
+  },
+  icon: {
+    backgroundColor: 'transparent',
+  },
+});
+
+export default function ProfileClubCard(props: Props) {
+  const theme = useTheme();
+  const navigation = useNavigation();
+
+  const clubKeyExtractor = (item: ProfileClubType) => item.name;
+
+  const getClubListItem = ({ item }: { item: ProfileClubType }) => {
+    const onPress = () =>
+      navigation.navigate('club-information', { clubId: item.id });
+    let description = i18n.t('screens.profile.isMember');
+    let icon = (leftProps: {
+      color: string;
+      style: {
+        marginLeft: number;
+        marginRight: number;
+        marginVertical?: number;
+      };
+    }) => (
+      
+    );
+    if (item.is_manager) {
+      description = i18n.t('screens.profile.isManager');
+      icon = (leftProps) => (
+        
+      );
+    }
+    return (
+      
+    );
+  };
+
+  function getClubList(list: Array | undefined) {
+    if (!list) {
+      return null;
+    }
+
+    list.sort((a) => (a.is_manager ? -1 : 1));
+    return (
+      
+    );
+  }
+
+  return (
+    
+       (
+          
+        )}
+      />
+      
+        
+        {getClubList(props.clubs)}
+      
+    
+  );
+}
diff --git a/src/components/Amicale/Profile/ProfileMembershipCard.tsx b/src/components/Amicale/Profile/ProfileMembershipCard.tsx
new file mode 100644
index 0000000..1b11902
--- /dev/null
+++ b/src/components/Amicale/Profile/ProfileMembershipCard.tsx
@@ -0,0 +1,56 @@
+import React from 'react';
+import { Avatar, Card, List, useTheme } from 'react-native-paper';
+import i18n from 'i18n-js';
+import { StyleSheet } from 'react-native';
+
+type Props = {
+  valid?: boolean;
+};
+
+const styles = StyleSheet.create({
+  card: {
+    margin: 10,
+  },
+  icon: {
+    backgroundColor: 'transparent',
+  },
+});
+
+export default function ProfileMembershipCard(props: Props) {
+  const theme = useTheme();
+  const state = props.valid === true;
+  return (
+    
+       (
+          
+        )}
+      />
+      
+        
+           (
+              
+            )}
+          />
+        
+      
+    
+  );
+}
diff --git a/src/components/Amicale/Profile/ProfilePersonalCard.tsx b/src/components/Amicale/Profile/ProfilePersonalCard.tsx
new file mode 100644
index 0000000..2c8f3b6
--- /dev/null
+++ b/src/components/Amicale/Profile/ProfilePersonalCard.tsx
@@ -0,0 +1,110 @@
+import { useNavigation } from '@react-navigation/core';
+import React from 'react';
+import { StyleSheet } from 'react-native';
+import {
+  Avatar,
+  Button,
+  Card,
+  Divider,
+  List,
+  useTheme,
+} from 'react-native-paper';
+import Urls from '../../../constants/Urls';
+import { ProfileDataType } from '../../../screens/Amicale/ProfileScreen';
+import i18n from 'i18n-js';
+
+type Props = {
+  profile?: ProfileDataType;
+};
+
+const styles = StyleSheet.create({
+  card: {
+    margin: 10,
+  },
+  icon: {
+    backgroundColor: 'transparent',
+  },
+  editButton: {
+    marginLeft: 'auto',
+  },
+  mascot: {
+    width: 60,
+  },
+  title: {
+    marginLeft: 10,
+  },
+});
+
+function getFieldValue(field?: string): string {
+  return field ? field : i18n.t('screens.profile.noData');
+}
+
+export default function ProfilePersonalCard(props: Props) {
+  const { profile } = props;
+  const theme = useTheme();
+  const navigation = useNavigation();
+
+  function getPersonalListItem(field: string | undefined, icon: string) {
+    const title = field != null ? getFieldValue(field) : ':(';
+    const subtitle = field != null ? '' : getFieldValue(field);
+    return (
+       (
+          
+        )}
+      />
+    );
+  }
+
+  return (
+    
+       (
+          
+        )}
+      />
+      
+        
+        
+          
+            {i18n.t('screens.profile.personalInformation')}
+          
+          {getPersonalListItem(profile?.birthday, 'cake-variant')}
+          {getPersonalListItem(profile?.phone, 'phone')}
+          {getPersonalListItem(profile?.email, 'email')}
+          {getPersonalListItem(profile?.branch, 'school')}
+        
+        
+        
+          
+        
+      
+    
+  );
+}
diff --git a/src/components/Amicale/Profile/ProfileWelcomeCard.tsx b/src/components/Amicale/Profile/ProfileWelcomeCard.tsx
new file mode 100644
index 0000000..ca564ca
--- /dev/null
+++ b/src/components/Amicale/Profile/ProfileWelcomeCard.tsx
@@ -0,0 +1,81 @@
+import { useNavigation } from '@react-navigation/core';
+import React from 'react';
+import { Button, Card, Divider, Paragraph } from 'react-native-paper';
+import Mascot, { MASCOT_STYLE } from '../../Mascot/Mascot';
+import i18n from 'i18n-js';
+import { StyleSheet } from 'react-native';
+import CardList from '../../Lists/CardList/CardList';
+import { getAmicaleServices, SERVICES_KEY } from '../../../utils/Services';
+
+type Props = {
+  firstname?: string;
+};
+
+const styles = StyleSheet.create({
+  card: {
+    margin: 10,
+  },
+  editButton: {
+    marginLeft: 'auto',
+  },
+  mascot: {
+    width: 60,
+  },
+  title: {
+    marginLeft: 10,
+  },
+});
+
+function ProfileWelcomeCard(props: Props) {
+  const navigation = useNavigation();
+  return (
+    
+       (
+          
+        )}
+        titleStyle={styles.title}
+      />
+      
+        
+        {i18n.t('screens.profile.welcomeDescription')}
+        
+        {i18n.t('screens.profile.welcomeFeedback')}
+        
+        
+          
+        
+      
+    
+  );
+}
+
+export default React.memo(
+  ProfileWelcomeCard,
+  (pp, np) => pp.firstname === np.firstname
+);
diff --git a/src/components/Amicale/Vote/VoteSelect.tsx b/src/components/Amicale/Vote/VoteSelect.tsx
index f0d1f81..fc290c0 100644
--- a/src/components/Amicale/Vote/VoteSelect.tsx
+++ b/src/components/Amicale/Vote/VoteSelect.tsx
@@ -17,30 +17,23 @@
  * along with Campus INSAT.  If not, see .
  */
 
-import * as React from 'react';
+import React, { useState } from 'react';
 import { Avatar, Button, Card, RadioButton } from 'react-native-paper';
 import { FlatList, StyleSheet, View } from 'react-native';
 import i18n from 'i18n-js';
-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';
+import { useAuthenticatedRequest } from '../../../context/loginContext';
 
-type PropsType = {
+type Props = {
   teams: Array;
   onVoteSuccess: () => void;
   onVoteError: () => void;
 };
 
-type StateType = {
-  selectedTeam: string;
-  voteDialogVisible: boolean;
-  errorDialogVisible: boolean;
-  currentError: ApiRejectType;
-};
-
 const styles = StyleSheet.create({
   card: {
     margin: 10,
@@ -50,118 +43,98 @@ const styles = StyleSheet.create({
   },
 });
 
-export default class VoteSelect extends React.PureComponent<
-  PropsType,
-  StateType
-> {
-  constructor(props: PropsType) {
-    super(props);
-    this.state = {
-      selectedTeam: 'none',
-      voteDialogVisible: false,
-      errorDialogVisible: false,
-      currentError: { status: REQUEST_STATUS.SUCCESS },
-    };
-  }
+function VoteSelect(props: Props) {
+  const [selectedTeam, setSelectedTeam] = useState('none');
+  const [voteDialogVisible, setVoteDialogVisible] = useState(false);
+  const [currentError, setCurrentError] = useState({
+    status: REQUEST_STATUS.SUCCESS,
+  });
+  const request = useAuthenticatedRequest('elections/vote', {
+    team: parseInt(selectedTeam, 10),
+  });
 
-  onVoteSelectionChange = (teamName: string): void =>
-    this.setState({ selectedTeam: teamName });
+  const voteKeyExtractor = (item: VoteTeamType) => item.id.toString();
 
-  voteKeyExtractor = (item: VoteTeamType): string => item.id.toString();
-
-  voteRenderItem = ({ item }: { item: VoteTeamType }) => (
+  const voteRenderItem = ({ item }: { item: VoteTeamType }) => (
     
   );
 
-  showVoteDialog = (): void => this.setState({ voteDialogVisible: true });
+  const showVoteDialog = () => setVoteDialogVisible(true);
 
-  onVoteDialogDismiss = (): void => this.setState({ voteDialogVisible: false });
+  const onVoteDialogDismiss = () => setVoteDialogVisible(false);
 
-  onVoteDialogAccept = async (): Promise => {
+  const onVoteDialogAccept = async (): Promise => {
     return new Promise((resolve: () => void) => {
-      const { state } = this;
-      ConnectionManager.getInstance()
-        .authenticatedRequest('elections/vote', {
-          team: parseInt(state.selectedTeam, 10),
-        })
+      request()
         .then(() => {
-          this.onVoteDialogDismiss();
-          const { props } = this;
+          onVoteDialogDismiss();
           props.onVoteSuccess();
           resolve();
         })
         .catch((error: ApiRejectType) => {
-          this.onVoteDialogDismiss();
-          this.showErrorDialog(error);
+          onVoteDialogDismiss();
+          setCurrentError(error);
           resolve();
         });
     });
   };
 
-  showErrorDialog = (error: ApiRejectType): void =>
-    this.setState({
-      errorDialogVisible: true,
-      currentError: error,
-    });
-
-  onErrorDialogDismiss = () => {
-    this.setState({ errorDialogVisible: false });
-    const { props } = this;
+  const onErrorDialogDismiss = () => {
+    setCurrentError({ status: REQUEST_STATUS.SUCCESS });
     props.onVoteError();
   };
 
-  render() {
-    const { state, props } = this;
-    return (
-      
-        
-           (
-              
-            )}
-          />
-          
-            
-              
-            
-          
-          
-            
-          
-        
-        
+      
+         (
+            
+          )}
         />
-        
-      
-    );
-  }
+        
+          
+            
+          
+        
+        
+          
+        
+      
+      
+      
+    
+  );
 }
+
+export default VoteSelect;
diff --git a/src/components/Lists/Equipment/EquipmentListItem.tsx b/src/components/Lists/Equipment/EquipmentListItem.tsx
index f76414a..9797643 100644
--- a/src/components/Lists/Equipment/EquipmentListItem.tsx
+++ b/src/components/Lists/Equipment/EquipmentListItem.tsx
@@ -20,7 +20,6 @@
 import * as React from 'react';
 import { Avatar, List, useTheme } from 'react-native-paper';
 import i18n from 'i18n-js';
-import { StackNavigationProp } from '@react-navigation/stack';
 import type { DeviceType } from '../../../screens/Amicale/Equipment/EquipmentListScreen';
 import {
   getFirstEquipmentAvailability,
@@ -29,9 +28,9 @@ import {
 } from '../../../utils/EquipmentBooking';
 import { StyleSheet } from 'react-native';
 import GENERAL_STYLES from '../../../constants/Styles';
+import { useNavigation } from '@react-navigation/native';
 
 type PropsType = {
-  navigation: StackNavigationProp;
   userDeviceRentDates: [string, string] | null;
   item: DeviceType;
   height: number;
@@ -48,7 +47,8 @@ const styles = StyleSheet.create({
 
 function EquipmentListItem(props: PropsType) {
   const theme = useTheme();
-  const { item, userDeviceRentDates, navigation, height } = props;
+  const navigation = useNavigation();
+  const { item, userDeviceRentDates, height } = props;
   const isRented = userDeviceRentDates != null;
   const isAvailable = isEquipmentAvailable(item);
   const firstAvailability = getFirstEquipmentAvailability(item);
diff --git a/src/components/Screens/RequestScreen.tsx b/src/components/Screens/RequestScreen.tsx
index 6d6550d..f886c75 100644
--- a/src/components/Screens/RequestScreen.tsx
+++ b/src/components/Screens/RequestScreen.tsx
@@ -11,7 +11,7 @@ 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';
+import { useLogout } from '../../utils/logout';
 
 export type RequestScreenProps = {
   request: () => Promise;
@@ -44,6 +44,7 @@ type Props = RequestScreenProps;
 const MIN_REFRESH_TIME = 3 * 1000;
 
 export default function RequestScreen(props: Props) {
+  const onLogout = useLogout();
   const navigation = useNavigation>();
   const route = useRoute();
   const refreshInterval = useRef();
@@ -103,13 +104,10 @@ export default function RequestScreen(props: Props) {
 
   useEffect(() => {
     if (isErrorCritical(code)) {
-      ConnectionManager.getInstance()
-        .disconnect()
-        .then(() => {
-          navigation.replace(MainRoutes.Login, { nextScreen: route.name });
-        });
+      onLogout();
+      navigation.replace(MainRoutes.Login, { nextScreen: route.name });
     }
-  }, [code, navigation, route]);
+  }, [code, navigation, route, onLogout]);
 
   if (data === undefined && loading && props.showLoading !== false) {
     return ;
diff --git a/src/components/providers/LoginProvider.tsx b/src/components/providers/LoginProvider.tsx
new file mode 100644
index 0000000..f355cd6
--- /dev/null
+++ b/src/components/providers/LoginProvider.tsx
@@ -0,0 +1,27 @@
+import React, { useState } from 'react';
+import { LoginContext, LoginContextType } from '../../context/loginContext';
+
+type Props = {
+  children: React.ReactChild;
+  initialToken: string | undefined;
+};
+
+export default function LoginProvider(props: Props) {
+  const setLogin = (token: string | undefined) => {
+    setLoginState((prevState) => ({
+      ...prevState,
+      token,
+    }));
+  };
+
+  const [loginState, setLoginState] = useState({
+    token: props.initialToken,
+    setLogin: setLogin,
+  });
+
+  return (
+    
+      {props.children}
+    
+  );
+}
diff --git a/src/context/loginContext.ts b/src/context/loginContext.ts
new file mode 100644
index 0000000..4792281
--- /dev/null
+++ b/src/context/loginContext.ts
@@ -0,0 +1,46 @@
+import React, { useContext } from 'react';
+import { apiRequest } from '../utils/WebData';
+
+export type LoginContextType = {
+  token: string | undefined;
+  setLogin: (token: string | undefined) => void;
+};
+
+export const LoginContext = React.createContext({
+  token: undefined,
+  setLogin: () => undefined,
+});
+
+/**
+ * Hook used to retrieve the user token and puid.
+ * @returns Login context with token and puid to undefined if user is not logged in
+ */
+export function useLogin() {
+  return useContext(LoginContext);
+}
+
+/**
+ * Checks if the user is connected
+ * @returns True if the user is connected
+ */
+export function useLoginState() {
+  const { token } = useLogin();
+  return token !== undefined;
+}
+
+/**
+ * Gets the current user token.
+ * @returns The token, or empty string if the user is not logged in
+ */
+export function useLoginToken() {
+  const { token } = useLogin();
+  return token ? token : '';
+}
+
+export function useAuthenticatedRequest(
+  path: string,
+  params?: { [key: string]: any }
+) {
+  const token = useLoginToken();
+  return () => apiRequest(path, 'POST', params, token);
+}
diff --git a/src/managers/ConnectionManager.ts b/src/managers/ConnectionManager.ts
deleted file mode 100644
index ee11ff2..0000000
--- a/src/managers/ConnectionManager.ts
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- * Copyright (c) 2019 - 2020 Arnaud Vergnet.
- *
- * This file is part of Campus INSAT.
- *
- * Campus INSAT is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Campus INSAT is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Campus INSAT.  If not, see .
- */
-
-import * as Keychain from 'react-native-keychain';
-import { REQUEST_STATUS } from '../utils/Requests';
-import type { ApiDataLoginType, ApiRejectType } from '../utils/WebData';
-import { apiRequest } from '../utils/WebData';
-
-/**
- * champ: error
- *
- * 0 : SUCCESS -> pas d'erreurs
- * 1 : BAD_CREDENTIALS -> email ou mdp invalide
- * 2 : BAD_TOKEN -> session expirée
- * 3 : NO_CONSENT
- * 403 : FORBIDDEN -> accès a la ressource interdit
- * 500 : SERVER_ERROR -> pb coté serveur
- */
-
-const AUTH_PATH = 'password';
-
-export default class ConnectionManager {
-  static instance: ConnectionManager | null = null;
-
-  private token: string | null;
-
-  constructor() {
-    this.token = null;
-  }
-
-  /**
-   * Gets this class instance or create one if none is found
-   *
-   * @returns {ConnectionManager}
-   */
-  static getInstance(): ConnectionManager {
-    if (ConnectionManager.instance == null) {
-      ConnectionManager.instance = new ConnectionManager();
-    }
-    return ConnectionManager.instance;
-  }
-
-  /**
-   * Gets the current token
-   *
-   * @returns {string | null}
-   */
-  getToken(): string | null {
-    return this.token;
-  }
-
-  /**
-   * Tries to recover login token from the secure keychain
-   *
-   * @returns Promise
-   */
-  async recoverLogin(): Promise {
-    return new Promise((resolve: () => void) => {
-      const token = this.getToken();
-      if (token != null) {
-        resolve();
-      } else {
-        Keychain.getGenericPassword()
-          .then((data: Keychain.UserCredentials | false) => {
-            if (data && data.password != null) {
-              this.token = data.password;
-            }
-            resolve();
-          })
-          .catch(() => resolve());
-      }
-    });
-  }
-
-  /**
-   * Check if the user has a valid token
-   *
-   * @returns {boolean}
-   */
-  isLoggedIn(): boolean {
-    return this.getToken() !== null;
-  }
-
-  /**
-   * Saves the login token in the secure keychain
-   *
-   * @param email
-   * @param token
-   * @returns Promise
-   */
-  async saveLogin(_email: string, token: string): Promise {
-    return new Promise((resolve: () => void, reject: () => void) => {
-      Keychain.setGenericPassword('token', token)
-        .then(() => {
-          this.token = token;
-          resolve();
-        })
-        .catch((): void => reject());
-    });
-  }
-
-  /**
-   * Deletes the login token from the keychain
-   *
-   * @returns Promise
-   */
-  async disconnect(): Promise {
-    return new Promise((resolve: () => void, reject: () => void) => {
-      Keychain.resetGenericPassword()
-        .then(() => {
-          this.token = null;
-          resolve();
-        })
-        .catch((): void => reject());
-    });
-  }
-
-  /**
-   * Sends the given login and password to the api.
-   * If the combination is valid, the login token is received and saved in the secure keychain.
-   * If not, the promise is rejected with the corresponding error code.
-   *
-   * @param email
-   * @param password
-   * @returns Promise
-   */
-  async connect(email: string, password: string): Promise {
-    return new Promise(
-      (resolve: () => void, reject: (error: ApiRejectType) => void) => {
-        const data = {
-          email,
-          password,
-        };
-        apiRequest(AUTH_PATH, 'POST', data)
-          .then((response: ApiDataLoginType) => {
-            if (response.token != null) {
-              this.saveLogin(email, response.token)
-                .then(() => resolve())
-                .catch(() =>
-                  reject({
-                    status: REQUEST_STATUS.TOKEN_SAVE,
-                  })
-                );
-            } else {
-              reject({
-                status: REQUEST_STATUS.SERVER_ERROR,
-              });
-            }
-          })
-          .catch((err) => {
-            reject(err);
-          });
-      }
-    );
-  }
-
-  /**
-   * Sends an authenticated request with the login token to the API
-   *
-   * @param path
-   * @param params
-   * @returns Promise
-   */
-  async authenticatedRequest(
-    path: string,
-    params?: { [key: string]: any }
-  ): Promise {
-    return new Promise(
-      (
-        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) => resolve(response))
-            .catch(reject);
-        } else {
-          reject({
-            status: REQUEST_STATUS.TOKEN_RETRIEVE,
-          });
-        }
-      }
-    );
-  }
-}
diff --git a/src/screens/Amicale/Clubs/ClubDisplayScreen.tsx b/src/screens/Amicale/Clubs/ClubDisplayScreen.tsx
index 65cd4b0..e0b14b5 100644
--- a/src/screens/Amicale/Clubs/ClubDisplayScreen.tsx
+++ b/src/screens/Amicale/Clubs/ClubDisplayScreen.tsx
@@ -17,7 +17,7 @@
  * along with Campus INSAT.  If not, see .
  */
 
-import * as React from 'react';
+import React, { useState } from 'react';
 import { Linking, StyleSheet, View } from 'react-native';
 import {
   Avatar,
@@ -25,20 +25,21 @@ import {
   Card,
   Chip,
   Paragraph,
-  withTheme,
+  useTheme,
 } from 'react-native-paper';
 import i18n from 'i18n-js';
-import { StackNavigationProp } from '@react-navigation/stack';
 import CustomHTML from '../../../components/Overrides/CustomHTML';
 import { TAB_BAR_HEIGHT } from '../../../components/Tabbar/CustomTabBar';
 import type { ClubCategoryType, ClubType } from './ClubListScreen';
 import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView';
 import ImageGalleryButton from '../../../components/Media/ImageGalleryButton';
 import RequestScreen from '../../../components/Screens/RequestScreen';
-import ConnectionManager from '../../../managers/ConnectionManager';
+import { useFocusEffect } from '@react-navigation/core';
+import { useCallback } from 'react';
+import { useNavigation } from '@react-navigation/native';
+import { useAuthenticatedRequest } from '../../../context/loginContext';
 
-type PropsType = {
-  navigation: StackNavigationProp;
+type Props = {
   route: {
     params?: {
       data?: ClubType;
@@ -46,7 +47,6 @@ type PropsType = {
       clubId?: number;
     };
   };
-  theme: ReactNativePaper.Theme;
 };
 
 type ResponseType = ClubType;
@@ -89,33 +89,28 @@ const styles = StyleSheet.create({
  * If called with data and categories navigation parameters, will use those to display the data.
  * If called with clubId parameter, will fetch the information on the server
  */
-class ClubDisplayScreen extends React.Component {
-  displayData: ClubType | undefined;
+function ClubDisplayScreen(props: Props) {
+  const navigation = useNavigation();
+  const theme = useTheme();
 
-  categories: Array | null;
+  const [displayData, setDisplayData] = useState();
+  const [categories, setCategories] = useState<
+    Array | undefined
+  >();
+  const [clubId, setClubId] = useState();
 
-  clubId: number;
-
-  shouldFetchData: boolean;
-
-  constructor(props: PropsType) {
-    super(props);
-    this.displayData = undefined;
-    this.categories = null;
-    this.clubId = props.route.params?.clubId ? props.route.params.clubId : 0;
-    this.shouldFetchData = true;
-
-    if (
-      props.route.params &&
-      props.route.params.data &&
-      props.route.params.categories
-    ) {
-      this.displayData = props.route.params.data;
-      this.categories = props.route.params.categories;
-      this.clubId = props.route.params.data.id;
-      this.shouldFetchData = false;
-    }
-  }
+  useFocusEffect(
+    useCallback(() => {
+      if (props.route.params?.data && props.route.params?.categories) {
+        setDisplayData(props.route.params.data);
+        setCategories(props.route.params.categories);
+        setClubId(props.route.params.data.id);
+      } else {
+        const id = props.route.params?.clubId;
+        setClubId(id ? id : 0);
+      }
+    }, [props.route.params])
+  );
 
   /**
    * Gets the name of the category with the given ID
@@ -123,17 +118,17 @@ class ClubDisplayScreen extends React.Component {
    * @param id The category's ID
    * @returns {string|*}
    */
-  getCategoryName(id: number): string {
+  const getCategoryName = (id: number): string => {
     let categoryName = '';
-    if (this.categories !== null) {
-      this.categories.forEach((item: ClubCategoryType) => {
+    if (categories) {
+      categories.forEach((item: ClubCategoryType) => {
         if (id === item.id) {
           categoryName = item.name;
         }
       });
     }
     return categoryName;
-  }
+  };
 
   /**
    * Gets the view for rendering categories
@@ -141,23 +136,23 @@ class ClubDisplayScreen extends React.Component {
    * @param categories The categories to display (max 2)
    * @returns {null|*}
    */
-  getCategoriesRender(categories: Array) {
-    if (this.categories == null) {
+  const getCategoriesRender = (c: Array) => {
+    if (!categories) {
       return null;
     }
 
     const final: Array = [];
-    categories.forEach((cat: number | null) => {
+    c.forEach((cat: number | null) => {
       if (cat != null) {
         final.push(
           
-            {this.getCategoryName(cat)}
+            {getCategoryName(cat)}
           
         );
       }
     });
     return {final};
-  }
+  };
 
   /**
    * Gets the view for rendering club managers if any
@@ -166,8 +161,7 @@ class ClubDisplayScreen extends React.Component {
    * @param email The club contact email
    * @returns {*}
    */
-  getManagersRender(managers: Array, email: string | null) {
-    const { props } = this;
+  const getManagersRender = (managers: Array, email: string | null) => {
     const managersListView: Array = [];
     managers.forEach((item: string) => {
       managersListView.push({item});
@@ -191,22 +185,18 @@ class ClubDisplayScreen extends React.Component {
             
           )}
         />
         
           {managersListView}
-          {ClubDisplayScreen.getEmailButton(email, hasManagers)}
+          {getEmailButton(email, hasManagers)}
         
       
     );
-  }
+  };
 
   /**
    * Gets the email button to contact the club, or the amicale if the club does not have any managers
@@ -215,7 +205,7 @@ class ClubDisplayScreen extends React.Component {
    * @param hasManagers True if the club has managers
    * @returns {*}
    */
-  static getEmailButton(email: string | null, hasManagers: boolean) {
+  const getEmailButton = (email: string | null, hasManagers: boolean) => {
     const destinationEmail =
       email != null && hasManagers ? email : AMICALE_MAIL;
     const text =
@@ -236,14 +226,14 @@ class ClubDisplayScreen extends React.Component {
         
       
     );
-  }
+  };
 
-  getScreen = (data: ResponseType | undefined) => {
+  const getScreen = (data: ResponseType | undefined) => {
     if (data) {
-      this.updateHeaderTitle(data);
+      updateHeaderTitle(data);
       return (
         
-          {this.getCategoriesRender(data.category)}
+          {getCategoriesRender(data.category)}
           {data.logo !== null ? (
              {
           ) : (
             
           )}
-          {this.getManagersRender(data.responsibles, data.email)}
+          {getManagersRender(data.responsibles, data.email)}
         
       );
     }
@@ -273,27 +263,22 @@ class ClubDisplayScreen extends React.Component {
    *
    * @param data The club data
    */
-  updateHeaderTitle(data: ClubType) {
-    const { props } = this;
-    props.navigation.setOptions({ title: data.name });
-  }
+  const updateHeaderTitle = (data: ClubType) => {
+    navigation.setOptions({ title: data.name });
+  };
 
-  render() {
-    if (this.shouldFetchData) {
-      return (
-        
-            ConnectionManager.getInstance().authenticatedRequest(
-              'clubs/info',
-              { id: this.clubId }
-            )
-          }
-          render={this.getScreen}
-        />
-      );
-    }
-    return this.getScreen(this.displayData);
-  }
+  const request = useAuthenticatedRequest('clubs/info', {
+    id: clubId,
+  });
+
+  return (
+    
+  );
 }
 
-export default withTheme(ClubDisplayScreen);
+export default ClubDisplayScreen;
diff --git a/src/screens/Amicale/Clubs/ClubListScreen.tsx b/src/screens/Amicale/Clubs/ClubListScreen.tsx
index c86ca82..048c343 100644
--- a/src/screens/Amicale/Clubs/ClubListScreen.tsx
+++ b/src/screens/Amicale/Clubs/ClubListScreen.tsx
@@ -17,11 +17,10 @@
  * along with Campus INSAT.  If not, see .
  */
 
-import * as React from 'react';
+import React, { useLayoutEffect, useRef, useState } from 'react';
 import { Platform } from 'react-native';
 import { Searchbar } from 'react-native-paper';
 import i18n from 'i18n-js';
-import { StackNavigationProp } from '@react-navigation/stack';
 import ClubListItem from '../../../components/Lists/Clubs/ClubListItem';
 import {
   isItemInCategoryFilter,
@@ -31,8 +30,9 @@ import ClubListHeader from '../../../components/Lists/Clubs/ClubListHeader';
 import MaterialHeaderButtons, {
   Item,
 } from '../../../components/Overrides/CustomHeaderButton';
-import ConnectionManager from '../../../managers/ConnectionManager';
 import WebSectionList from '../../../components/Screens/WebSectionList';
+import { useNavigation } from '@react-navigation/native';
+import { useAuthenticatedRequest } from '../../../context/loginContext';
 
 export type ClubCategoryType = {
   id: number;
@@ -49,15 +49,6 @@ export type ClubType = {
   responsibles: Array;
 };
 
-type PropsType = {
-  navigation: StackNavigationProp;
-};
-
-type StateType = {
-  currentlySelectedCategories: Array;
-  currentSearchString: string;
-};
-
 type ResponseType = {
   categories: Array;
   clubs: Array;
@@ -65,33 +56,52 @@ type ResponseType = {
 
 const LIST_ITEM_HEIGHT = 96;
 
-class ClubListScreen extends React.Component {
-  categories: Array;
+function ClubListScreen() {
+  const navigation = useNavigation();
+  const request = useAuthenticatedRequest('clubs/list');
+  const [
+    currentlySelectedCategories,
+    setCurrentlySelectedCategories,
+  ] = useState>([]);
+  const [currentSearchString, setCurrentSearchString] = useState('');
+  const categories = useRef>([]);
 
-  constructor(props: PropsType) {
-    super(props);
-    this.categories = [];
-    this.state = {
-      currentlySelectedCategories: [],
-      currentSearchString: '',
+  useLayoutEffect(() => {
+    const getSearchBar = () => {
+      return (
+        // @ts-ignore
+        
+      );
     };
-  }
-
-  /**
-   * Creates the header content
-   */
-  componentDidMount() {
-    const { props } = this;
-    props.navigation.setOptions({
-      headerTitle: this.getSearchBar,
-      headerRight: this.getHeaderButtons,
+    const getHeaderButtons = () => {
+      return (
+        
+          -  navigation.navigate('club-about')}
+          />
+        +      );
+    };
+    navigation.setOptions({
+      headerTitle: getSearchBar,
+      headerRight: getHeaderButtons,
       headerBackTitleVisible: false,
       headerTitleContainerStyle:
         Platform.OS === 'ios'
           ? { marginHorizontal: 0, width: '70%' }
           : { marginHorizontal: 0, right: 50, left: 50 },
     });
-  }
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [navigation]);
+
+  const onSearchStringChange = (str: string) => {
+    updateFilteredData(str, null);
+  };
 
   /**
    * Callback used when clicking an article in the list.
@@ -99,61 +109,20 @@ class ClubListScreen extends React.Component {
    *
    * @param item The article pressed
    */
-  onListItemPress(item: ClubType) {
-    const { props } = this;
-    props.navigation.navigate('club-information', {
+  const onListItemPress = (item: ClubType) => {
+    navigation.navigate('club-information', {
       data: item,
-      categories: this.categories,
+      categories: categories.current,
     });
-  }
-
-  /**
-   * Callback used when the search changes
-   *
-   * @param str The new search string
-   */
-  onSearchStringChange = (str: string) => {
-    this.updateFilteredData(str, null);
   };
 
-  /**
-   * Gets the header search bar
-   *
-   * @return {*}
-   */
-  getSearchBar = () => {
-    return (
-      // @ts-ignore
-      
-    );
+  const onChipSelect = (id: number) => {
+    updateFilteredData(null, id);
   };
 
-  onChipSelect = (id: number) => {
-    this.updateFilteredData(null, id);
-  };
-
-  /**
-   * Gets the header button
-   * @return {*}
-   */
-  getHeaderButtons = () => {
-    const onPress = () => {
-      const { props } = this;
-      props.navigation.navigate('club-about');
-    };
-    return (
-      
-
-      
-    );
-  };
-
-  createDataset = (data: ResponseType | undefined) => {
+  const createDataset = (data: ResponseType | undefined) => {
     if (data) {
-      this.categories = data?.categories;
+      categories.current = data.categories;
       return [{ title: '', data: data.clubs }];
     } else {
       return [];
@@ -165,30 +134,23 @@ class ClubListScreen extends React.Component {
    *
    * @returns {*}
    */
-  getListHeader(data: ResponseType | undefined) {
-    const { state } = this;
+  const getListHeader = (data: ResponseType | undefined) => {
     if (data) {
       return (
         
       );
     } else {
       return null;
     }
-  }
+  };
 
-  /**
-   * Gets the category object of the given ID
-   *
-   * @param id The ID of the category to find
-   * @returns {*}
-   */
-  getCategoryOfId = (id: number): ClubCategoryType | null => {
+  const getCategoryOfId = (id: number): ClubCategoryType | null => {
     let cat = null;
-    this.categories.forEach((item: ClubCategoryType) => {
+    categories.current.forEach((item: ClubCategoryType) => {
       if (id === item.id) {
         cat = item;
       }
@@ -196,14 +158,14 @@ class ClubListScreen extends React.Component {
     return cat;
   };
 
-  getRenderItem = ({ item }: { item: ClubType }) => {
+  const getRenderItem = ({ item }: { item: ClubType }) => {
     const onPress = () => {
-      this.onListItemPress(item);
+      onListItemPress(item);
     };
-    if (this.shouldRenderItem(item)) {
+    if (shouldRenderItem(item)) {
       return (
          {
     return null;
   };
 
-  keyExtractor = (item: ClubType): string => item.id.toString();
+  const keyExtractor = (item: ClubType): string => item.id.toString();
 
   /**
    * Updates the search string and category filter, saving them to the State.
@@ -224,10 +186,12 @@ class ClubListScreen extends React.Component {
    * @param filterStr The new filter string to use
    * @param categoryId The category to add/remove from the filter
    */
-  updateFilteredData(filterStr: string | null, categoryId: number | null) {
-    const { state } = this;
-    const newCategoriesState = [...state.currentlySelectedCategories];
-    let newStrState = state.currentSearchString;
+  const updateFilteredData = (
+    filterStr: string | null,
+    categoryId: number | null
+  ) => {
+    const newCategoriesState = [...currentlySelectedCategories];
+    let newStrState = currentSearchString;
     if (filterStr !== null) {
       newStrState = filterStr;
     }
@@ -240,12 +204,10 @@ class ClubListScreen extends React.Component {
       }
     }
     if (filterStr !== null || categoryId !== null) {
-      this.setState({
-        currentSearchString: newStrState,
-        currentlySelectedCategories: newCategoriesState,
-      });
+      setCurrentSearchString(newStrState);
+      setCurrentlySelectedCategories(newCategoriesState);
     }
-  }
+  };
 
   /**
    * Checks if the given item should be rendered according to current name and category filters
@@ -253,35 +215,28 @@ class ClubListScreen extends React.Component {
    * @param item The club to check
    * @returns {boolean}
    */
-  shouldRenderItem(item: ClubType): boolean {
-    const { state } = this;
+  const shouldRenderItem = (item: ClubType): boolean => {
     let shouldRender =
-      state.currentlySelectedCategories.length === 0 ||
-      isItemInCategoryFilter(state.currentlySelectedCategories, item.category);
+      currentlySelectedCategories.length === 0 ||
+      isItemInCategoryFilter(currentlySelectedCategories, item.category);
     if (shouldRender) {
-      shouldRender = stringMatchQuery(item.name, state.currentSearchString);
+      shouldRender = stringMatchQuery(item.name, currentSearchString);
     }
     return shouldRender;
-  }
+  };
 
-  render() {
-    return (
-      
-          ConnectionManager.getInstance().authenticatedRequest(
-            'clubs/list'
-          )
-        }
-        createDataset={this.createDataset}
-        keyExtractor={this.keyExtractor}
-        renderItem={this.getRenderItem}
-        renderListHeaderComponent={(data) => this.getListHeader(data)}
-        // Performance props, see https://reactnative.dev/docs/optimizing-flatlist-configuration
-        removeClippedSubviews={true}
-        itemHeight={LIST_ITEM_HEIGHT}
-      />
-    );
-  }
+  return (
+    
+  );
 }
 
 export default ClubListScreen;
diff --git a/src/screens/Amicale/Equipment/EquipmentListScreen.tsx b/src/screens/Amicale/Equipment/EquipmentListScreen.tsx
index 8dfe8af..f437168 100644
--- a/src/screens/Amicale/Equipment/EquipmentListScreen.tsx
+++ b/src/screens/Amicale/Equipment/EquipmentListScreen.tsx
@@ -17,26 +17,17 @@
  * along with Campus INSAT.  If not, see .
  */
 
-import * as React from 'react';
+import React, { useRef, useState } from 'react';
 import { StyleSheet, View } from 'react-native';
 import { Button } from 'react-native-paper';
-import { StackNavigationProp } from '@react-navigation/stack';
 import i18n from 'i18n-js';
 import EquipmentListItem from '../../../components/Lists/Equipment/EquipmentListItem';
 import MascotPopup from '../../../components/Mascot/MascotPopup';
 import { MASCOT_STYLE } from '../../../components/Mascot/Mascot';
 import GENERAL_STYLES from '../../../constants/Styles';
-import ConnectionManager from '../../../managers/ConnectionManager';
 import { ApiRejectType } from '../../../utils/WebData';
 import WebSectionList from '../../../components/Screens/WebSectionList';
-
-type PropsType = {
-  navigation: StackNavigationProp;
-};
-
-type StateType = {
-  mascotDialogVisible: boolean | undefined;
-};
+import { useAuthenticatedRequest } from '../../../context/loginContext';
 
 export type DeviceType = {
   id: number;
@@ -67,69 +58,62 @@ const styles = StyleSheet.create({
   },
 });
 
-class EquipmentListScreen extends React.Component {
-  userRents: null | Array;
+function EquipmentListScreen() {
+  const userRents = useRef>();
+  const [mascotDialogVisible, setMascotDialogVisible] = useState(false);
 
-  constructor(props: PropsType) {
-    super(props);
-    this.userRents = null;
-    this.state = {
-      mascotDialogVisible: undefined,
-    };
-  }
+  const requestAll = useAuthenticatedRequest<{ devices: Array }>(
+    'location/all'
+  );
+  const requestOwn = useAuthenticatedRequest<{
+    locations: Array;
+  }>('location/my');
 
-  getRenderItem = ({ item }: { item: DeviceType }) => {
-    const { navigation } = this.props;
+  const getRenderItem = ({ item }: { item: DeviceType }) => {
     return (
       
     );
   };
 
-  getUserDeviceRentDates(item: DeviceType): [string, string] | null {
+  const getUserDeviceRentDates = (
+    item: DeviceType
+  ): [string, string] | null => {
     let dates = null;
-    if (this.userRents != null) {
-      this.userRents.forEach((device: RentedDeviceType) => {
+    if (userRents.current) {
+      userRents.current.forEach((device: RentedDeviceType) => {
         if (item.id === device.device_id) {
           dates = [device.begin, device.end];
         }
       });
     }
     return dates;
-  }
+  };
 
-  /**
-   * Gets the list header, with explains this screen's purpose
-   *
-   * @returns {*}
-   */
-  getListHeader() {
+  const getListHeader = () => {
     return (
       
         
       
     );
-  }
+  };
 
-  keyExtractor = (item: DeviceType): string => item.id.toString();
+  const keyExtractor = (item: DeviceType): string => item.id.toString();
 
-  createDataset = (data: ResponseType | undefined) => {
+  const createDataset = (data: ResponseType | undefined) => {
     if (data) {
-      const userRents = data.locations;
-
-      if (userRents) {
-        this.userRents = userRents;
+      if (data.locations) {
+        userRents.current = data.locations;
       }
       return [{ title: '', data: data.devices }];
     } else {
@@ -137,27 +121,19 @@ class EquipmentListScreen extends React.Component {
     }
   };
 
-  showMascotDialog = () => {
-    this.setState({ mascotDialogVisible: true });
-  };
+  const showMascotDialog = () => setMascotDialogVisible(true);
 
-  hideMascotDialog = () => {
-    this.setState({ mascotDialogVisible: false });
-  };
+  const hideMascotDialog = () => setMascotDialogVisible(false);
 
-  request = () => {
+  const request = () => {
     return new Promise(
       (
         resolve: (data: ResponseType) => void,
         reject: (error: ApiRejectType) => void
       ) => {
-        ConnectionManager.getInstance()
-          .authenticatedRequest<{ devices: Array }>('location/all')
+        requestAll()
           .then((devicesData) => {
-            ConnectionManager.getInstance()
-              .authenticatedRequest<{
-                locations: Array;
-              }>('location/my')
+            requestOwn()
               .then((rentsData) => {
                 resolve({
                   devices: devicesData.devices,
@@ -175,34 +151,31 @@ class EquipmentListScreen extends React.Component {
     );
   };
 
-  render() {
-    const { state } = this;
-    return (
-      
-         this.getListHeader()}
-        />
-        
-      
-    );
-  }
+  return (
+    
+      
+      
+    
+  );
 }
 
 export default EquipmentListScreen;
diff --git a/src/screens/Amicale/Equipment/EquipmentRentScreen.tsx b/src/screens/Amicale/Equipment/EquipmentRentScreen.tsx
index e81fbb8..a145c98 100644
--- a/src/screens/Amicale/Equipment/EquipmentRentScreen.tsx
+++ b/src/screens/Amicale/Equipment/EquipmentRentScreen.tsx
@@ -17,21 +17,20 @@
  * along with Campus INSAT.  If not, see .
  */
 
-import * as React from 'react';
+import React, { useCallback, useRef, useState } from 'react';
 import {
   Button,
   Caption,
   Card,
   Headline,
   Subheading,
-  withTheme,
+  useTheme,
 } from 'react-native-paper';
 import { StackNavigationProp, StackScreenProps } from '@react-navigation/stack';
 import { BackHandler, StyleSheet, View } from 'react-native';
 import * as Animatable from 'react-native-animatable';
 import i18n from 'i18n-js';
 import { CalendarList, PeriodMarking } from 'react-native-calendars';
-import type { DeviceType } from './EquipmentListScreen';
 import LoadingConfirmDialog from '../../../components/Dialogs/LoadingConfirmDialog';
 import ErrorDialog from '../../../components/Dialogs/ErrorDialog';
 import {
@@ -42,34 +41,21 @@ import {
   getValidRange,
   isEquipmentAvailable,
 } from '../../../utils/EquipmentBooking';
-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';
+import { useFocusEffect } from '@react-navigation/core';
+import { useNavigation } from '@react-navigation/native';
+import { useAuthenticatedRequest } from '../../../context/loginContext';
 
-type EquipmentRentScreenNavigationProp = StackScreenProps<
-  MainStackParamsList,
-  'equipment-rent'
->;
-
-type Props = EquipmentRentScreenNavigationProp & {
-  navigation: StackNavigationProp;
-  theme: ReactNativePaper.Theme;
-};
+type Props = StackScreenProps;
 
 export type MarkedDatesObjectType = {
   [key: string]: PeriodMarking;
 };
 
-type StateType = {
-  dialogVisible: boolean;
-  errorDialogVisible: boolean;
-  markedDates: MarkedDatesObjectType;
-  currentError: ApiRejectType;
-};
-
 const styles = StyleSheet.create({
   titleContainer: {
     marginLeft: 'auto',
@@ -114,98 +100,101 @@ const styles = StyleSheet.create({
   },
 });
 
-class EquipmentRentScreen extends React.Component {
-  item: DeviceType | null;
+function EquipmentRentScreen(props: Props) {
+  const theme = useTheme();
+  const navigation = useNavigation>();
+  const [currentError, setCurrentError] = useState({
+    status: REQUEST_STATUS.SUCCESS,
+  });
+  const [markedDates, setMarkedDates] = useState({});
+  const [dialogVisible, setDialogVisible] = useState(false);
 
-  bookedDates: Array;
+  const item = props.route.params.item;
 
-  bookRef: { current: null | (Animatable.View & View) };
+  const bookedDates = useRef>([]);
+  const canBookEquipment = useRef(false);
 
-  canBookEquipment: boolean;
+  const bookRef = useRef(null);
 
-  lockedDates: {
+  let lockedDates: {
     [key: string]: PeriodMarking;
-  };
+  } = {};
 
-  constructor(props: Props) {
-    super(props);
-    this.item = null;
-    this.lockedDates = {};
-    this.state = {
-      dialogVisible: false,
-      errorDialogVisible: false,
-      markedDates: {},
-      currentError: { status: REQUEST_STATUS.SUCCESS },
-    };
-    this.resetSelection();
-    this.bookRef = React.createRef();
-    this.canBookEquipment = false;
-    this.bookedDates = [];
-    if (props.route.params != null) {
-      if (props.route.params.item != null) {
-        this.item = props.route.params.item;
-      } else {
-        this.item = null;
-      }
-    }
-    const { item } = this;
-    if (item != null) {
-      this.lockedDates = {};
-      item.booked_at.forEach((date: { begin: string; end: string }) => {
-        const range = getValidRange(
-          new Date(date.begin),
-          new Date(date.end),
-          null
-        );
-        this.lockedDates = {
-          ...this.lockedDates,
-          ...generateMarkedDates(false, props.theme, range),
-        };
-      });
-    }
+  if (item) {
+    item.booked_at.forEach((date: { begin: string; end: string }) => {
+      const range = getValidRange(
+        new Date(date.begin),
+        new Date(date.end),
+        null
+      );
+      lockedDates = {
+        ...lockedDates,
+        ...generateMarkedDates(false, theme, range),
+      };
+    });
   }
 
-  /**
-   * Captures focus and blur events to hook on android back button
-   */
-  componentDidMount() {
-    const { navigation } = this.props;
-    navigation.addListener('focus', () => {
+  useFocusEffect(
+    useCallback(() => {
       BackHandler.addEventListener(
         'hardwareBackPress',
-        this.onBackButtonPressAndroid
+        onBackButtonPressAndroid
       );
-    });
-    navigation.addListener('blur', () => {
-      BackHandler.removeEventListener(
-        'hardwareBackPress',
-        this.onBackButtonPressAndroid
-      );
-    });
-  }
+      return () => {
+        BackHandler.removeEventListener(
+          'hardwareBackPress',
+          onBackButtonPressAndroid
+        );
+      };
+      // eslint-disable-next-line react-hooks/exhaustive-deps
+    }, [])
+  );
 
   /**
    * Overrides default android back button behaviour to deselect date if any is selected.
    *
    * @return {boolean}
    */
-  onBackButtonPressAndroid = (): boolean => {
-    if (this.bookedDates.length > 0) {
-      this.resetSelection();
-      this.updateMarkedSelection();
+  const onBackButtonPressAndroid = (): boolean => {
+    if (bookedDates.current.length > 0) {
+      resetSelection();
+      updateMarkedSelection();
       return true;
     }
     return false;
   };
 
-  onDialogDismiss = () => {
-    this.setState({ dialogVisible: false });
+  const showDialog = () => setDialogVisible(true);
+
+  const onDialogDismiss = () => setDialogVisible(false);
+
+  const onErrorDialogDismiss = () =>
+    setCurrentError({ status: REQUEST_STATUS.SUCCESS });
+
+  const getBookStartDate = (): Date | null => {
+    return bookedDates.current.length > 0
+      ? new Date(bookedDates.current[0])
+      : null;
   };
 
-  onErrorDialogDismiss = () => {
-    this.setState({ errorDialogVisible: false });
+  const getBookEndDate = (): Date | null => {
+    const { length } = bookedDates.current;
+    return length > 0 ? new Date(bookedDates.current[length - 1]) : null;
   };
 
+  const start = getBookStartDate();
+  const end = getBookEndDate();
+  const request = useAuthenticatedRequest(
+    'location/booking',
+    item && start && end
+      ? {
+          device: item.id,
+          begin: getISODate(start),
+          end: getISODate(end),
+        }
+      : undefined
+  );
+
   /**
    * Sends the selected data to the server and waits for a response.
    * If the request is a success, navigate to the recap screen.
@@ -213,54 +202,37 @@ class EquipmentRentScreen extends React.Component {
    *
    * @returns {Promise}
    */
-  onDialogAccept = (): Promise => {
+  const onDialogAccept = (): Promise => {
     return new Promise((resolve: () => void) => {
-      const { item, props } = this;
-      const start = this.getBookStartDate();
-      const end = this.getBookEndDate();
       if (item != null && start != null && end != null) {
-        ConnectionManager.getInstance()
-          .authenticatedRequest('location/booking', {
-            device: item.id,
-            begin: getISODate(start),
-            end: getISODate(end),
-          })
+        request()
           .then(() => {
-            this.onDialogDismiss();
-            props.navigation.replace('equipment-confirm', {
-              item: this.item,
+            onDialogDismiss();
+            navigation.replace('equipment-confirm', {
+              item: item,
               dates: [getISODate(start), getISODate(end)],
             });
             resolve();
           })
           .catch((error: ApiRejectType) => {
-            this.onDialogDismiss();
-            this.showErrorDialog(error);
+            onDialogDismiss();
+            setCurrentError(error);
             resolve();
           });
       } else {
-        this.onDialogDismiss();
+        onDialogDismiss();
         resolve();
       }
     });
   };
 
-  getBookStartDate(): Date | null {
-    return this.bookedDates.length > 0 ? new Date(this.bookedDates[0]) : null;
-  }
-
-  getBookEndDate(): Date | null {
-    const { length } = this.bookedDates;
-    return length > 0 ? new Date(this.bookedDates[length - 1]) : null;
-  }
-
   /**
    * Selects a new date on the calendar.
    * If both start and end dates are already selected, unselect all.
    *
    * @param day The day selected
    */
-  selectNewDate = (day: {
+  const selectNewDate = (day: {
     dateString: string;
     day: number;
     month: number;
@@ -268,222 +240,196 @@ class EquipmentRentScreen extends React.Component {
     year: number;
   }) => {
     const selected = new Date(day.dateString);
-    const start = this.getBookStartDate();
 
-    if (!this.lockedDates[day.dateString] != null) {
+    if (!lockedDates[day.dateString] != null) {
       if (start === null) {
-        this.updateSelectionRange(selected, selected);
-        this.enableBooking();
+        updateSelectionRange(selected, selected);
+        enableBooking();
       } else if (start.getTime() === selected.getTime()) {
-        this.resetSelection();
-      } else if (this.bookedDates.length === 1) {
-        this.updateSelectionRange(start, selected);
-        this.enableBooking();
+        resetSelection();
+      } else if (bookedDates.current.length === 1) {
+        updateSelectionRange(start, selected);
+        enableBooking();
       } else {
-        this.resetSelection();
+        resetSelection();
       }
-      this.updateMarkedSelection();
+      updateMarkedSelection();
     }
   };
 
-  showErrorDialog = (error: ApiRejectType) => {
-    this.setState({
-      errorDialogVisible: true,
-      currentError: error,
-    });
+  const showBookButton = () => {
+    if (bookRef.current && bookRef.current.fadeInUp) {
+      bookRef.current.fadeInUp(500);
+    }
   };
 
-  showDialog = () => {
-    this.setState({ dialogVisible: true });
+  const hideBookButton = () => {
+    if (bookRef.current && bookRef.current.fadeOutDown) {
+      bookRef.current.fadeOutDown(500);
+    }
   };
 
-  /**
-   * Shows the book button by plying a fade animation
-   */
-  showBookButton() {
-    if (this.bookRef.current && this.bookRef.current.fadeInUp) {
-      this.bookRef.current.fadeInUp(500);
+  const enableBooking = () => {
+    if (!canBookEquipment.current) {
+      showBookButton();
+      canBookEquipment.current = true;
     }
-  }
+  };
 
-  /**
-   * Hides the book button by plying a fade animation
-   */
-  hideBookButton() {
-    if (this.bookRef.current && this.bookRef.current.fadeOutDown) {
-      this.bookRef.current.fadeOutDown(500);
+  const resetSelection = () => {
+    if (canBookEquipment.current) {
+      hideBookButton();
     }
-  }
+    canBookEquipment.current = false;
+    bookedDates.current = [];
+  };
 
-  enableBooking() {
-    if (!this.canBookEquipment) {
-      this.showBookButton();
-      this.canBookEquipment = true;
-    }
-  }
-
-  resetSelection() {
-    if (this.canBookEquipment) {
-      this.hideBookButton();
-    }
-    this.canBookEquipment = false;
-    this.bookedDates = [];
-  }
-
-  updateSelectionRange(start: Date, end: Date) {
-    this.bookedDates = getValidRange(start, end, this.item);
-  }
-
-  updateMarkedSelection() {
-    const { theme } = this.props;
-    this.setState({
-      markedDates: generateMarkedDates(true, theme, this.bookedDates),
-    });
-  }
-
-  render() {
-    const { item, props, state } = this;
-    const start = this.getBookStartDate();
-    const end = this.getBookEndDate();
-    let subHeadingText;
-    if (start == null) {
-      subHeadingText = i18n.t('screens.equipment.booking');
-    } else if (end != null && start.getTime() !== end.getTime()) {
-      subHeadingText = i18n.t('screens.equipment.bookingPeriod', {
-        begin: getRelativeDateString(start),
-        end: getRelativeDateString(end),
-      });
+  const updateSelectionRange = (s: Date, e: Date) => {
+    if (item) {
+      bookedDates.current = getValidRange(s, e, item);
     } else {
-      subHeadingText = i18n.t('screens.equipment.bookingDay', {
-        date: getRelativeDateString(start),
-      });
+      bookedDates.current = [];
     }
-    if (item != null) {
-      const isAvailable = isEquipmentAvailable(item);
-      const firstAvailability = getFirstEquipmentAvailability(item);
-      return (
-        
-          
-            
-              
-                
-                  
-                    {item.name}
-                    
-                      (
-                      {i18n.t('screens.equipment.bail', { cost: item.caution })}
-                      )
-                    
-                  
-                
+  };
 
-                
-                
-                  {subHeadingText}
-                
-              
-            
-            
-          
-          
+  const updateMarkedSelection = () => {
+    setMarkedDates(generateMarkedDates(true, theme, bookedDates.current));
+  };
 
-          
-          
-            
-          
-        
-      );
-    }
-    return null;
+  let subHeadingText;
+
+  if (start == null) {
+    subHeadingText = i18n.t('screens.equipment.booking');
+  } else if (end != null && start.getTime() !== end.getTime()) {
+    subHeadingText = i18n.t('screens.equipment.bookingPeriod', {
+      begin: getRelativeDateString(start),
+      end: getRelativeDateString(end),
+    });
+  } else {
+    subHeadingText = i18n.t('screens.equipment.bookingDay', {
+      date: getRelativeDateString(start),
+    });
   }
+
+  if (item) {
+    const isAvailable = isEquipmentAvailable(item);
+    const firstAvailability = getFirstEquipmentAvailability(item);
+    return (
+      
+        
+          
+            
+              
+                
+                  {item.name}
+                  
+                    ({i18n.t('screens.equipment.bail', { cost: item.caution })})
+                  
+                
+              
+
+              
+              {subHeadingText}
+            
+          
+          
+        
+        
+
+        
+        
+          
+        
+      
+    );
+  }
+  return null;
 }
 
-export default withTheme(EquipmentRentScreen);
+export default EquipmentRentScreen;
diff --git a/src/screens/Amicale/LoginScreen.tsx b/src/screens/Amicale/LoginScreen.tsx
index 8bf3185..cfcc7c8 100644
--- a/src/screens/Amicale/LoginScreen.tsx
+++ b/src/screens/Amicale/LoginScreen.tsx
@@ -17,19 +17,11 @@
  * along with Campus INSAT.  If not, see .
  */
 
-import * as React from 'react';
-import { Image, KeyboardAvoidingView, StyleSheet, View } from 'react-native';
-import {
-  Button,
-  Card,
-  HelperText,
-  TextInput,
-  withTheme,
-} from 'react-native-paper';
+import React, { useCallback, useState } from 'react';
+import { KeyboardAvoidingView, View } from 'react-native';
 import i18n from 'i18n-js';
 import { StackNavigationProp, StackScreenProps } from '@react-navigation/stack';
 import LinearGradient from 'react-native-linear-gradient';
-import ConnectionManager from '../../managers/ConnectionManager';
 import ErrorDialog from '../../components/Dialogs/ErrorDialog';
 import { MASCOT_STYLE } from '../../components/Mascot/Mascot';
 import MascotPopup from '../../components/Mascot/MascotPopup';
@@ -37,99 +29,32 @@ import CollapsibleScrollView from '../../components/Collapsible/CollapsibleScrol
 import { MainStackParamsList } from '../../navigation/MainNavigator';
 import GENERAL_STYLES from '../../constants/Styles';
 import Urls from '../../constants/Urls';
-import { ApiRejectType } from '../../utils/WebData';
+import { ApiRejectType, connectToAmicale } from '../../utils/WebData';
 import { REQUEST_STATUS } from '../../utils/Requests';
+import LoginForm from '../../components/Amicale/Login/LoginForm';
+import { useFocusEffect, useNavigation } from '@react-navigation/native';
+import { TabRoutes } from '../../navigation/TabNavigator';
+import { useShouldShowMascot } from '../../context/preferencesContext';
 
-type LoginScreenNavigationProp = StackScreenProps;
+type Props = StackScreenProps;
 
-type Props = LoginScreenNavigationProp & {
-  navigation: StackNavigationProp;
-  theme: ReactNativePaper.Theme;
-};
+function LoginScreen(props: Props) {
+  const navigation = useNavigation>();
+  const [loading, setLoading] = useState(false);
+  const [nextScreen, setNextScreen] = useState(undefined);
+  const [mascotDialogVisible, setMascotDialogVisible] = useState(false);
+  const [currentError, setCurrentError] = useState({
+    status: REQUEST_STATUS.SUCCESS,
+  });
+  const homeMascot = useShouldShowMascot(TabRoutes.Home);
 
-type StateType = {
-  email: string;
-  password: string;
-  isEmailValidated: boolean;
-  isPasswordValidated: boolean;
-  loading: boolean;
-  dialogVisible: boolean;
-  dialogError: ApiRejectType;
-  mascotDialogVisible: boolean | undefined;
-};
+  useFocusEffect(
+    useCallback(() => {
+      setNextScreen(props.route.params?.nextScreen);
+    }, [props.route.params])
+  );
 
-const ICON_AMICALE = require('../../../assets/amicale.png');
-
-const emailRegex = /^.+@.+\..+$/;
-
-const styles = StyleSheet.create({
-  card: {
-    marginTop: 'auto',
-    marginBottom: 'auto',
-  },
-  header: {
-    fontSize: 36,
-    marginBottom: 48,
-  },
-  text: {
-    color: '#ffffff',
-  },
-  buttonContainer: {
-    flexWrap: 'wrap',
-  },
-  lockButton: {
-    marginRight: 'auto',
-    marginBottom: 20,
-  },
-  sendButton: {
-    marginLeft: 'auto',
-  },
-});
-
-class LoginScreen extends React.Component {
-  onEmailChange: (value: string) => void;
-
-  onPasswordChange: (value: string) => void;
-
-  passwordInputRef: {
-    // @ts-ignore
-    current: null | TextInput;
-  };
-
-  nextScreen: string | null;
-
-  constructor(props: Props) {
-    super(props);
-    this.nextScreen = null;
-    this.passwordInputRef = React.createRef();
-    this.onEmailChange = (value: string) => {
-      this.onInputChange(true, value);
-    };
-    this.onPasswordChange = (value: string) => {
-      this.onInputChange(false, value);
-    };
-    props.navigation.addListener('focus', this.onScreenFocus);
-    this.state = {
-      email: '',
-      password: '',
-      isEmailValidated: false,
-      isPasswordValidated: false,
-      loading: false,
-      dialogVisible: false,
-      dialogError: { status: REQUEST_STATUS.SUCCESS },
-      mascotDialogVisible: undefined,
-    };
-  }
-
-  onScreenFocus = () => {
-    this.handleNavigationParams();
-  };
-
-  /**
-   * Navigates to the Amicale website screen with the reset password link as navigation parameters
-   */
-  onResetPasswordClick = () => {
-    const { navigation } = this.props;
+  const onResetPasswordClick = () => {
     navigation.navigate('website', {
       host: Urls.websites.amicale,
       path: Urls.amicale.resetPassword,
@@ -137,38 +62,6 @@ class LoginScreen extends React.Component {
     });
   };
 
-  /**
-   * Called when the user input changes in the email or password field.
-   * This saves the new value in the State and disabled input validation (to prevent errors to show while typing)
-   *
-   * @param isEmail True if the field is the email field
-   * @param value The new field value
-   */
-  onInputChange(isEmail: boolean, value: string) {
-    if (isEmail) {
-      this.setState({
-        email: value,
-        isEmailValidated: false,
-      });
-    } else {
-      this.setState({
-        password: value,
-        isPasswordValidated: false,
-      });
-    }
-  }
-
-  /**
-   * Focuses the password field when the email field is done
-   *
-   * @returns {*}
-   */
-  onEmailSubmit = () => {
-    if (this.passwordInputRef.current != null) {
-      this.passwordInputRef.current.focus();
-    }
-  };
-
   /**
    * Called when the user clicks on login or finishes to type his password.
    *
@@ -176,294 +69,84 @@ class LoginScreen extends React.Component {
    * then makes the login request and enters a loading state until the request finishes
    *
    */
-  onSubmit = () => {
-    const { email, password } = this.state;
-    if (this.shouldEnableLogin()) {
-      this.setState({ loading: true });
-      ConnectionManager.getInstance()
-        .connect(email, password)
-        .then(this.handleSuccess)
-        .catch(this.showErrorDialog)
-        .finally(() => {
-          this.setState({ loading: false });
-        });
-    }
+  const onSubmit = (email: string, password: string) => {
+    setLoading(true);
+    connectToAmicale(email, password)
+      .then(handleSuccess)
+      .catch(setCurrentError)
+      .finally(() => setLoading(false));
   };
 
-  /**
-   * Gets the form input
-   *
-   * @returns {*}
-   */
-  getFormInput() {
-    const { email, password } = this.state;
-    return (
-      
-        
-        
-          {i18n.t('screens.login.emailError')}
-        
-        
-        
-          {i18n.t('screens.login.passwordError')}
-        
-      
-    );
-  }
+  const hideMascotDialog = () => setMascotDialogVisible(true);
 
-  /**
-   * Gets the card containing the input form
-   * @returns {*}
-   */
-  getMainCard() {
-    const { props, state } = this;
-    return (
-      
-         (
-            
-          )}
-        />
-        
-          {this.getFormInput()}
-          
-            
-            
-          
-          
-            
-          
-        
-      
-    );
-  }
+  const showMascotDialog = () => setMascotDialogVisible(false);
 
-  /**
-   * The user has unfocused the input, his email is ready to be validated
-   */
-  validateEmail = () => {
-    this.setState({ isEmailValidated: true });
-  };
-
-  /**
-   * The user has unfocused the input, his password is ready to be validated
-   */
-  validatePassword = () => {
-    this.setState({ isPasswordValidated: true });
-  };
-
-  hideMascotDialog = () => {
-    this.setState({ mascotDialogVisible: false });
-  };
-
-  showMascotDialog = () => {
-    this.setState({ mascotDialogVisible: true });
-  };
-
-  /**
-   * Shows an error dialog with the corresponding login error
-   *
-   * @param error The error given by the login request
-   */
-  showErrorDialog = (error: ApiRejectType) => {
-    console.log(error);
-
-    this.setState({
-      dialogVisible: true,
-      dialogError: error,
-    });
-  };
-
-  hideErrorDialog = () => {
-    this.setState({ dialogVisible: false });
-  };
+  const hideErrorDialog = () =>
+    setCurrentError({ status: REQUEST_STATUS.SUCCESS });
 
   /**
    * Navigates to the screen specified in navigation parameters or simply go back tha stack.
    * Saves in user preferences to not show the login banner again.
    */
-  handleSuccess = () => {
-    const { navigation } = this.props;
+  const handleSuccess = () => {
     // Do not show the home login banner again
-    // TODO
-    // AsyncStorageManager.set(
-    //   AsyncStorageManager.PREFERENCES.homeShowMascot.key,
-    //   false
-    // );
-    if (this.nextScreen == null) {
+    if (homeMascot.shouldShow) {
+      homeMascot.setShouldShow(false);
+    }
+    if (!nextScreen) {
       navigation.goBack();
     } else {
-      navigation.replace(this.nextScreen);
+      navigation.replace(nextScreen);
     }
   };
 
-  /**
-   * 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;
-  }
-
-  /**
-   * Checks if the entered email is valid (matches the regex)
-   *
-   * @returns {boolean}
-   */
-  isEmailValid(): boolean {
-    const { email } = this.state;
-    return emailRegex.test(email);
-  }
-
-  /**
-   * Checks if we should tell the user his email is invalid.
-   * We should only show this if his email is invalid and has been checked when un-focusing the input
-   *
-   * @returns {boolean|boolean}
-   */
-  shouldShowEmailError(): boolean {
-    const { isEmailValidated } = this.state;
-    return isEmailValidated && !this.isEmailValid();
-  }
-
-  /**
-   * Checks if the user has entered a password
-   *
-   * @returns {boolean}
-   */
-  isPasswordValid(): boolean {
-    const { password } = this.state;
-    return password !== '';
-  }
-
-  /**
-   * Checks if we should tell the user his password is invalid.
-   * We should only show this if his password is invalid and has been checked when un-focusing the input
-   *
-   * @returns {boolean|boolean}
-   */
-  shouldShowPasswordError(): boolean {
-    const { isPasswordValidated } = this.state;
-    return isPasswordValidated && !this.isPasswordValid();
-  }
-
-  /**
-   * If the email and password are valid, and we are not loading a request, then the login button can be enabled
-   *
-   * @returns {boolean}
-   */
-  shouldEnableLogin(): boolean {
-    const { loading } = this.state;
-    return this.isEmailValid() && this.isPasswordValid() && !loading;
-  }
-
-  render() {
-    const { mascotDialogVisible, dialogVisible, dialogError } = this.state;
-    return (
-      
+      
-        
-          
-            {this.getMainCard()}
-            
+          
+            
-            
-          
-        
-      
-    );
-  }
+          
+          
+          
+        
+      
+    
+  );
 }
 
-export default withTheme(LoginScreen);
+export default LoginScreen;
diff --git a/src/screens/Amicale/ProfileScreen.tsx b/src/screens/Amicale/ProfileScreen.tsx
index ed7004d..fa3a57c 100644
--- a/src/screens/Amicale/ProfileScreen.tsx
+++ b/src/screens/Amicale/ProfileScreen.tsx
@@ -17,52 +17,29 @@
  * along with Campus INSAT.  If not, see .
  */
 
-import * as React from 'react';
-import { FlatList, StyleSheet, View } from 'react-native';
-import {
-  Avatar,
-  Button,
-  Card,
-  Divider,
-  List,
-  Paragraph,
-  withTheme,
-} from 'react-native-paper';
-import i18n from 'i18n-js';
-import { StackNavigationProp } from '@react-navigation/stack';
+import React, { useLayoutEffect, useState } from 'react';
+import { View } from 'react-native';
 import LogoutDialog from '../../components/Amicale/LogoutDialog';
 import MaterialHeaderButtons, {
   Item,
 } from '../../components/Overrides/CustomHeaderButton';
-import CardList from '../../components/Lists/CardList/CardList';
-import Mascot, { MASCOT_STYLE } from '../../components/Mascot/Mascot';
 import CollapsibleFlatList from '../../components/Collapsible/CollapsibleFlatList';
 import GENERAL_STYLES from '../../constants/Styles';
-import Urls from '../../constants/Urls';
 import RequestScreen from '../../components/Screens/RequestScreen';
-import ConnectionManager from '../../managers/ConnectionManager';
-import {
-  getAmicaleServices,
-  ServiceItemType,
-  SERVICES_KEY,
-} from '../../utils/Services';
+import ProfileWelcomeCard from '../../components/Amicale/Profile/ProfileWelcomeCard';
+import ProfilePersonalCard from '../../components/Amicale/Profile/ProfilePersonalCard';
+import ProfileClubCard from '../../components/Amicale/Profile/ProfileClubCard';
+import ProfileMembershipCard from '../../components/Amicale/Profile/ProfileMembershipCard';
+import { useNavigation } from '@react-navigation/core';
+import { useAuthenticatedRequest } from '../../context/loginContext';
 
-type PropsType = {
-  navigation: StackNavigationProp;
-  theme: ReactNativePaper.Theme;
-};
-
-type StateType = {
-  dialogVisible: boolean;
-};
-
-type ClubType = {
+export type ProfileClubType = {
   id: number;
   name: string;
   is_manager: boolean;
 };
 
-type ProfileDataType = {
+export type ProfileDataType = {
   first_name: string;
   last_name: string;
   email: string;
@@ -71,87 +48,68 @@ type ProfileDataType = {
   branch: string;
   link: string;
   validity: boolean;
-  clubs: Array;
+  clubs: Array;
 };
 
-const styles = StyleSheet.create({
-  card: {
-    margin: 10,
-  },
-  icon: {
-    backgroundColor: 'transparent',
-  },
-  editButton: {
-    marginLeft: 'auto',
-  },
-  mascot: {
-    width: 60,
-  },
-  title: {
-    marginLeft: 10,
-  },
-});
+function ProfileScreen() {
+  const navigation = useNavigation();
+  const [dialogVisible, setDialogVisible] = useState(false);
+  const request = useAuthenticatedRequest('user/profile');
 
-class ProfileScreen extends React.Component {
-  data: ProfileDataType | undefined;
-
-  flatListData: Array<{ id: string }>;
-
-  amicaleDataset: Array;
-
-  constructor(props: PropsType) {
-    super(props);
-    this.data = undefined;
-    this.flatListData = [{ id: '0' }, { id: '1' }, { id: '2' }, { id: '3' }];
-    this.amicaleDataset = getAmicaleServices(props.navigation.navigate, [
-      SERVICES_KEY.PROFILE,
-    ]);
-    this.state = {
-      dialogVisible: false,
-    };
-  }
-
-  componentDidMount() {
-    const { navigation } = this.props;
+  useLayoutEffect(() => {
+    const getHeaderButton = () => (
+      
+
+      
+    );
     navigation.setOptions({
-      headerRight: this.getHeaderButton,
+      headerRight: getHeaderButton,
     });
-  }
+  }, [navigation]);
 
-  /**
-   * Gets the logout header button
-   *
-   * @returns {*}
-   */
-  getHeaderButton = () => (
-    
-
-    
-  );
-
-  /**
-   * Gets the main screen component with the fetched data
-   *
-   * @param data The data fetched from the server
-   * @returns {*}
-   */
-  getScreen = (data: ProfileDataType | undefined) => {
-    const { dialogVisible } = this.state;
+  const getScreen = (data: ProfileDataType | undefined) => {
     if (data) {
-      this.data = data;
+      const flatListData: Array<{
+        id: string;
+        render: () => React.ReactElement;
+      }> = [];
+      for (let i = 0; i < 4; i++) {
+        switch (i) {
+          case 0:
+            flatListData.push({
+              id: i.toString(),
+              render: () => ,
+            });
+            break;
+          case 1:
+            flatListData.push({
+              id: i.toString(),
+              render: () => ,
+            });
+            break;
+          case 2:
+            flatListData.push({
+              id: i.toString(),
+              render: () => ,
+            });
+            break;
+          default:
+            flatListData.push({
+              id: i.toString(),
+              render: () => ,
+            });
+        }
+      }
       return (
         
-          
+          
           
         
       );
@@ -160,346 +118,17 @@ class ProfileScreen extends React.Component {
     }
   };
 
-  getRenderItem = ({ item }: { item: { id: string } }) => {
-    switch (item.id) {
-      case '0':
-        return this.getWelcomeCard();
-      case '1':
-        return this.getPersonalCard();
-      case '2':
-        return this.getClubCard();
-      default:
-        return this.getMembershipCar();
-    }
-  };
+  const getRenderItem = ({
+    item,
+  }: {
+    item: { id: string; render: () => React.ReactElement };
+  }) => item.render();
 
-  /**
-   * Gets the list of services available with the Amicale account
-   *
-   * @returns {*}
-   */
-  getServicesList() {
-    return ;
-  }
+  const showDisconnectDialog = () => setDialogVisible(true);
 
-  /**
-   * Gets a card welcoming the user to his account
-   *
-   * @returns {*}
-   */
-  getWelcomeCard() {
-    const { navigation } = this.props;
-    return (
-      
-         (
-            
-          )}
-          titleStyle={styles.title}
-        />
-        
-          
-          {i18n.t('screens.profile.welcomeDescription')}
-          {this.getServicesList()}
-          {i18n.t('screens.profile.welcomeFeedback')}
-          
-          
-            
-          
-        
-      
-    );
-  }
+  const hideDisconnectDialog = () => setDialogVisible(false);
 
-  /**
-   * Gets the given field value.
-   * If the field does not have a value, returns a placeholder text
-   *
-   * @param field The field to get the value from
-   * @return {*}
-   */
-  static getFieldValue(field?: string): string {
-    return field ? field : i18n.t('screens.profile.noData');
-  }
-
-  /**
-   * Gets a list item showing personal information
-   *
-   * @param field The field to display
-   * @param icon The icon to use
-   * @return {*}
-   */
-  getPersonalListItem(field: string | undefined, icon: string) {
-    const { theme } = this.props;
-    const title = field != null ? ProfileScreen.getFieldValue(field) : ':(';
-    const subtitle = field != null ? '' : ProfileScreen.getFieldValue(field);
-    return (
-       (
-          
-        )}
-      />
-    );
-  }
-
-  /**
-   * Gets a card containing user personal information
-   *
-   * @return {*}
-   */
-  getPersonalCard() {
-    const { theme, navigation } = this.props;
-    return (
-      
-         (
-            
-          )}
-        />
-        
-          
-          
-            
-              {i18n.t('screens.profile.personalInformation')}
-            
-            {this.getPersonalListItem(this.data?.birthday, 'cake-variant')}
-            {this.getPersonalListItem(this.data?.phone, 'phone')}
-            {this.getPersonalListItem(this.data?.email, 'email')}
-            {this.getPersonalListItem(this.data?.branch, 'school')}
-          
-          
-          
-            
-          
-        
-      
-    );
-  }
-
-  /**
-   * Gets a cars containing clubs the user is part of
-   *
-   * @return {*}
-   */
-  getClubCard() {
-    const { theme } = this.props;
-    return (
-      
-         (
-            
-          )}
-        />
-        
-          
-          {this.getClubList(this.data?.clubs)}
-        
-      
-    );
-  }
-
-  /**
-   * Gets a card showing if the user has payed his membership
-   *
-   * @return {*}
-   */
-  getMembershipCar() {
-    const { theme } = this.props;
-    return (
-      
-         (
-            
-          )}
-        />
-        
-          
-            {this.getMembershipItem(this.data?.validity === true)}
-          
-        
-      
-    );
-  }
-
-  /**
-   * Gets the item showing if the user has payed his membership
-   *
-   * @return {*}
-   */
-  getMembershipItem(state: boolean) {
-    const { theme } = this.props;
-    return (
-       (
-          
-        )}
-      />
-    );
-  }
-
-  /**
-   * Gets a list item for the club list
-   *
-   * @param item The club to render
-   * @return {*}
-   */
-  getClubListItem = ({ item }: { item: ClubType }) => {
-    const { theme } = this.props;
-    const onPress = () => {
-      this.openClubDetailsScreen(item.id);
-    };
-    let description = i18n.t('screens.profile.isMember');
-    let icon = (props: {
-      color: string;
-      style: {
-        marginLeft: number;
-        marginRight: number;
-        marginVertical?: number;
-      };
-    }) => (
-      
-    );
-    if (item.is_manager) {
-      description = i18n.t('screens.profile.isManager');
-      icon = (props) => (
-        
-      );
-    }
-    return (
-      
-    );
-  };
-
-  /**
-   * Renders the list of clubs the user is part of
-   *
-   * @param list The club list
-   * @return {*}
-   */
-  getClubList(list: Array | undefined) {
-    if (!list) {
-      return null;
-    }
-
-    list.sort(this.sortClubList);
-    return (
-      
-    );
-  }
-
-  clubKeyExtractor = (item: ClubType): string => item.name;
-
-  sortClubList = (a: ClubType): number => (a.is_manager ? -1 : 1);
-
-  showDisconnectDialog = () => {
-    this.setState({ dialogVisible: true });
-  };
-
-  hideDisconnectDialog = () => {
-    this.setState({ dialogVisible: false });
-  };
-
-  /**
-   * Opens the club details screen for the club of given ID
-   * @param id The club's id to open
-   */
-  openClubDetailsScreen(id: number) {
-    const { navigation } = this.props;
-    navigation.navigate('club-information', { clubId: id });
-  }
-
-  render() {
-    return (
-      
-        request={() =>
-          ConnectionManager.getInstance().authenticatedRequest('user/profile')
-        }
-        render={this.getScreen}
-      />
-    );
-  }
+  return ;
 }
 
-export default withTheme(ProfileScreen);
+export default ProfileScreen;
diff --git a/src/screens/Amicale/VoteScreen.tsx b/src/screens/Amicale/VoteScreen.tsx
index 8309911..49a0686 100644
--- a/src/screens/Amicale/VoteScreen.tsx
+++ b/src/screens/Amicale/VoteScreen.tsx
@@ -17,7 +17,7 @@
  * along with Campus INSAT.  If not, see .
  */
 
-import * as React from 'react';
+import React, { useRef, useState } from 'react';
 import { StyleSheet, View } from 'react-native';
 import i18n from 'i18n-js';
 import { Button } from 'react-native-paper';
@@ -30,10 +30,10 @@ import { MASCOT_STYLE } from '../../components/Mascot/Mascot';
 import MascotPopup from '../../components/Mascot/MascotPopup';
 import VoteNotAvailable from '../../components/Amicale/Vote/VoteNotAvailable';
 import GENERAL_STYLES from '../../constants/Styles';
-import ConnectionManager from '../../managers/ConnectionManager';
 import WebSectionList, {
   SectionListDataType,
 } from '../../components/Screens/WebSectionList';
+import { useAuthenticatedRequest } from '../../context/loginContext';
 
 export type VoteTeamType = {
   id: number;
@@ -65,6 +65,13 @@ type ResponseType = {
   dates?: VoteDatesStringType;
 };
 
+type FlatlistType = {
+  teams: Array;
+  hasVoted: boolean;
+  datesString?: VoteDatesStringType;
+  dates?: VoteDatesObjectType;
+};
+
 // const FAKE_DATE = {
 //     "date_begin": "2020-08-19 15:50",
 //     "date_end": "2020-08-19 15:50",
@@ -113,13 +120,6 @@ type ResponseType = {
 //     ],
 // };
 
-type PropsType = {};
-
-type StateType = {
-  hasVoted: boolean;
-  mascotDialogVisible: boolean | undefined;
-};
-
 const styles = StyleSheet.create({
   button: {
     marginLeft: 'auto',
@@ -131,38 +131,19 @@ const styles = StyleSheet.create({
 /**
  * Screen displaying vote information and controls
  */
-export default class VoteScreen extends React.Component {
-  teams: Array;
+export default function VoteScreen() {
+  const [hasVoted, setHasVoted] = useState(false);
+  const [mascotDialogVisible, setMascotDialogVisible] = useState(false);
 
-  hasVoted: boolean;
-
-  datesString: undefined | VoteDatesStringType;
-
-  dates: undefined | VoteDatesObjectType;
-
-  today: Date;
-
-  mainFlatListData: SectionListDataType<{ key: string }>;
-
-  refreshData: () => void;
-
-  constructor(props: PropsType) {
-    super(props);
-    this.teams = [];
-    this.datesString = undefined;
-    this.dates = undefined;
-    this.state = {
-      hasVoted: false,
-      mascotDialogVisible: undefined,
-    };
-    this.hasVoted = false;
-    this.today = new Date();
-    this.refreshData = () => undefined;
-    this.mainFlatListData = [
-      { title: '', data: [{ key: 'main' }, { key: 'info' }] },
-    ];
-  }
+  const datesRequest = useAuthenticatedRequest(
+    'elections/dates'
+  );
+  const teamsRequest = useAuthenticatedRequest(
+    'elections/teams'
+  );
 
+  const today = new Date();
+  const refresh = useRef<() => void | undefined>();
   /**
    * Gets the string representation of the given date.
    *
@@ -173,22 +154,26 @@ export default class VoteScreen extends React.Component {
    * @param dateString The string representation of the wanted date
    * @returns {string}
    */
-  getDateString(date: Date, dateString: string): string {
-    if (this.today.getDate() === date.getDate()) {
+  const getDateString = (date: Date, dateString: string) => {
+    if (today.getDate() === date.getDate()) {
       const str = getTimeOnlyString(dateString);
       return str != null ? str : '';
     }
     return dateString;
-  }
+  };
 
-  getMainRenderItem = ({ item }: { item: { key: string } }) => {
+  const getMainRenderItem = ({
+    item,
+  }: {
+    item: { key: string; data?: FlatlistType };
+  }) => {
     if (item.key === 'info') {
       return (
         
           
       );
     }
-    return this.getContent();
+    if (item.data) {
+      return getContent(item.data);
+    } else {
+      return ;
+    }
   };
 
-  createDataset = (
+  const createDataset = (
     data: ResponseType | undefined,
     _loading: boolean,
     _lastRefreshDate: Date | undefined,
@@ -207,157 +196,158 @@ export default class VoteScreen extends React.Component {
   ) => {
     // data[0] = FAKE_TEAMS2;
     // data[1] = FAKE_DATE;
-    this.refreshData = refreshData;
+
+    const mainFlatListData: SectionListDataType<{
+      key: string;
+      data?: FlatlistType;
+    }> = [
+      {
+        title: '',
+        data: [{ key: 'main' }, { key: 'info' }],
+      },
+    ];
+    refresh.current = refreshData;
     if (data) {
       const { teams, dates } = data;
-
-      if (dates && dates.date_begin == null) {
-        this.datesString = undefined;
-      } else {
-        this.datesString = dates;
+      const flatlistData: FlatlistType = {
+        teams: [],
+        hasVoted: false,
+      };
+      if (dates && dates.date_begin != null) {
+        flatlistData.datesString = dates;
       }
-
       if (teams) {
-        this.teams = teams.teams;
-        this.hasVoted = teams.has_voted;
+        flatlistData.teams = teams.teams;
+        flatlistData.hasVoted = teams.has_voted;
       }
-
-      this.generateDateObject();
+      flatlistData.dates = generateDateObject(flatlistData.datesString);
     }
-    return this.mainFlatListData;
+    return mainFlatListData;
   };
 
-  getContent() {
-    const { state } = this;
-    if (!this.isVoteStarted()) {
-      return this.getTeaseVoteCard();
+  const getContent = (data: FlatlistType) => {
+    const { dates } = data;
+    if (!isVoteStarted(dates)) {
+      return getTeaseVoteCard(data);
     }
-    if (this.isVoteRunning() && !this.hasVoted && !state.hasVoted) {
-      return this.getVoteCard();
+    if (isVoteRunning(dates) && !data.hasVoted && !hasVoted) {
+      return getVoteCard(data);
     }
-    if (!this.isResultStarted()) {
-      return this.getWaitVoteCard();
+    if (!isResultStarted(dates)) {
+      return getWaitVoteCard(data);
     }
-    if (this.isResultRunning()) {
-      return this.getVoteResultCard();
+    if (isResultRunning(dates)) {
+      return getVoteResultCard(data);
     }
     return ;
-  }
-
-  onVoteSuccess = (): void => this.setState({ hasVoted: true });
+  };
 
+  const onVoteSuccess = () => setHasVoted(true);
   /**
    * The user has not voted yet, and the votes are open
    */
-  getVoteCard() {
+  const getVoteCard = (data: FlatlistType) => {
     return (
        {
+          if (refresh.current) {
+            refresh.current();
+          }
+        }}
       />
     );
-  }
-
+  };
   /**
    * Votes have ended, results can be displayed
    */
-  getVoteResultCard() {
-    if (this.dates != null && this.datesString != null) {
+  const getVoteResultCard = (data: FlatlistType) => {
+    if (data.dates != null && data.datesString != null) {
       return (
         
       );
     }
     return ;
-  }
-
+  };
   /**
    * Vote will open shortly
    */
-  getTeaseVoteCard() {
-    if (this.dates != null && this.datesString != null) {
+  const getTeaseVoteCard = (data: FlatlistType) => {
+    if (data.dates != null && data.datesString != null) {
       return (
         
       );
     }
     return ;
-  }
-
+  };
   /**
    * Votes have ended, or user has voted waiting for results
    */
-  getWaitVoteCard() {
-    const { state } = this;
+  const getWaitVoteCard = (data: FlatlistType) => {
     let startDate = null;
     if (
-      this.dates != null &&
-      this.datesString != null &&
-      this.dates.date_result_begin != null
+      data.dates != null &&
+      data.datesString != null &&
+      data.dates.date_result_begin != null
     ) {
-      startDate = this.getDateString(
-        this.dates.date_result_begin,
-        this.datesString.date_result_begin
+      startDate = getDateString(
+        data.dates.date_result_begin,
+        data.datesString.date_result_begin
       );
     }
     return (
       
     );
-  }
-
-  showMascotDialog = () => {
-    this.setState({ mascotDialogVisible: true });
   };
 
-  hideMascotDialog = () => {
-    this.setState({ mascotDialogVisible: false });
+  const showMascotDialog = () => setMascotDialogVisible(true);
+
+  const hideMascotDialog = () => setMascotDialogVisible(false);
+
+  const isVoteStarted = (dates?: VoteDatesObjectType) => {
+    return dates != null && today > dates.date_begin;
   };
 
-  isVoteStarted(): boolean {
-    return this.dates != null && this.today > this.dates.date_begin;
-  }
-
-  isResultRunning(): boolean {
+  const isResultRunning = (dates?: VoteDatesObjectType) => {
     return (
-      this.dates != null &&
-      this.today > this.dates.date_result_begin &&
-      this.today < this.dates.date_result_end
+      dates != null &&
+      today > dates.date_result_begin &&
+      today < dates.date_result_end
     );
-  }
+  };
 
-  isResultStarted(): boolean {
-    return this.dates != null && this.today > this.dates.date_result_begin;
-  }
+  const isResultStarted = (dates?: VoteDatesObjectType) => {
+    return dates != null && today > dates.date_result_begin;
+  };
 
-  isVoteRunning(): boolean {
-    return (
-      this.dates != null &&
-      this.today > this.dates.date_begin &&
-      this.today < this.dates.date_end
-    );
-  }
+  const isVoteRunning = (dates?: VoteDatesObjectType) => {
+    return dates != null && today > dates.date_begin && today < dates.date_end;
+  };
 
   /**
    * Generates the objects containing string and Date representations of key vote dates
    */
-  generateDateObject() {
-    const strings = this.datesString;
-    if (strings != null) {
+  const generateDateObject = (
+    strings?: VoteDatesStringType
+  ): VoteDatesObjectType | undefined => {
+    if (strings) {
       const dateBegin = stringToDate(strings.date_begin);
       const dateEnd = stringToDate(strings.date_end);
       const dateResultBegin = stringToDate(strings.date_result_begin);
@@ -368,27 +358,25 @@ export default class VoteScreen extends React.Component {
         dateResultBegin != null &&
         dateResultEnd != null
       ) {
-        this.dates = {
+        return {
           date_begin: dateBegin,
           date_end: dateEnd,
           date_result_begin: dateResultBegin,
           date_result_end: dateResultEnd,
         };
       } else {
-        this.dates = undefined;
+        return undefined;
       }
     } else {
-      this.dates = undefined;
+      return undefined;
     }
-  }
+  };
 
-  request = () => {
+  const request = () => {
     return new Promise((resolve: (data: ResponseType) => void) => {
-      ConnectionManager.getInstance()
-        .authenticatedRequest('elections/dates')
+      datesRequest()
         .then((datesData) => {
-          ConnectionManager.getInstance()
-            .authenticatedRequest('elections/teams')
+          teamsRequest()
             .then((teamsData) => {
               resolve({
                 dates: datesData,
@@ -405,38 +393,28 @@ export default class VoteScreen extends React.Component {
     });
   };
 
-  /**
-   * Renders the authenticated screen.
-   *
-   * Teams and dates are not mandatory to allow showing the information box even if api requests fail
-   *
-   * @returns {*}
-   */
-  render() {
-    const { state } = this;
-    return (
-      
-        
-        
-      
-    );
-  }
+  return (
+    
+      
+      
+    
+  );
 }
diff --git a/src/screens/Home/HomeScreen.tsx b/src/screens/Home/HomeScreen.tsx
index e14cb9d..c129034 100644
--- a/src/screens/Home/HomeScreen.tsx
+++ b/src/screens/Home/HomeScreen.tsx
@@ -46,7 +46,6 @@ import MaterialHeaderButtons, {
   Item,
 } from '../../components/Overrides/CustomHeaderButton';
 import AnimatedFAB from '../../components/Animations/AnimatedFAB';
-import ConnectionManager from '../../managers/ConnectionManager';
 import LogoutDialog from '../../components/Amicale/LogoutDialog';
 import { MASCOT_STYLE } from '../../components/Mascot/Mascot';
 import MascotPopup from '../../components/Mascot/MascotPopup';
@@ -59,6 +58,7 @@ import { TabRoutes, TabStackParamsList } from '../../navigation/TabNavigator';
 import { ServiceItemType } from '../../utils/Services';
 import { useCurrentDashboard } from '../../context/preferencesContext';
 import { MainRoutes } from '../../navigation/MainNavigator';
+import { useLoginState } from '../../context/loginContext';
 
 const FEED_ITEM_HEIGHT = 500;
 
@@ -146,9 +146,7 @@ function HomeScreen(props: Props) {
   const [dialogVisible, setDialogVisible] = useState(false);
   const fabRef = useRef(null);
 
-  const [isLoggedIn, setIsLoggedIn] = useState(
-    ConnectionManager.getInstance().isLoggedIn()
-  );
+  const isLoggedIn = useLoginState();
   const { currentDashboard } = useCurrentDashboard();
 
   let homeDashboard: FullDashboardType | null = null;
@@ -199,13 +197,8 @@ function HomeScreen(props: Props) {
           }
         }
       };
-
-      if (ConnectionManager.getInstance().isLoggedIn() !== isLoggedIn) {
-        setIsLoggedIn(ConnectionManager.getInstance().isLoggedIn());
-      }
       // handle link open when home is not focused or created
       handleNavigationParams();
-      return () => {};
       // eslint-disable-next-line react-hooks/exhaustive-deps
     }, [isLoggedIn])
   );
diff --git a/src/utils/WebData.ts b/src/utils/WebData.ts
index 854c69a..ba4117e 100644
--- a/src/utils/WebData.ts
+++ b/src/utils/WebData.ts
@@ -80,7 +80,8 @@ export function isApiResponseValid(response: ApiResponseType): boolean {
 export async function apiRequest(
   path: string,
   method: string,
-  params?: object
+  params?: object,
+  token?: string
 ): Promise {
   return new Promise(
     (resolve: (data: T) => void, reject: (error: ApiRejectType) => void) => {
@@ -88,7 +89,9 @@ export async function apiRequest(
       if (params != null) {
         requestParams = { ...params };
       }
-      console.log(Urls.amicale.api + path);
+      if (token) {
+        requestParams = { ...requestParams, token: token };
+      }
 
       fetch(Urls.amicale.api + path, {
         method,
@@ -135,6 +138,33 @@ export async function apiRequest(
   );
 }
 
+export async function connectToAmicale(email: string, password: string) {
+  return new Promise(
+    (
+      resolve: (token: string) => void,
+      reject: (error: ApiRejectType) => void
+    ) => {
+      const data = {
+        email,
+        password,
+      };
+      apiRequest('password', 'POST', data)
+        .then((response: ApiDataLoginType) => {
+          if (response.token != null) {
+            resolve(response.token);
+          } else {
+            reject({
+              status: REQUEST_STATUS.SERVER_ERROR,
+            });
+          }
+        })
+        .catch((err) => {
+          reject(err);
+        });
+    }
+  );
+}
+
 /**
  * Reads data from the given url and returns it.
  *
diff --git a/src/utils/loginToken.ts b/src/utils/loginToken.ts
new file mode 100644
index 0000000..f1c6b1b
--- /dev/null
+++ b/src/utils/loginToken.ts
@@ -0,0 +1,46 @@
+import * as Keychain from 'react-native-keychain';
+
+/**
+ * Tries to recover login token from the secure keychain
+ *
+ * @returns Promise
+ */
+export async function retrieveLoginToken(): Promise {
+  return new Promise((resolve: (token: string | undefined) => void) => {
+    Keychain.getGenericPassword()
+      .then((data: Keychain.UserCredentials | false) => {
+        if (data && data.password) {
+          resolve(data.password);
+        } else {
+          resolve(undefined);
+        }
+      })
+      .catch(() => resolve(undefined));
+  });
+}
+/**
+ * Saves the login token in the secure keychain
+ *
+ * @param email
+ * @param token
+ * @returns Promise
+ */
+export async function saveLoginToken(
+  email: string,
+  token: string
+): Promise {
+  return new Promise((resolve: () => void, reject: () => void) => {
+    Keychain.setGenericPassword(email, token).then(resolve).catch(reject);
+  });
+}
+
+/**
+ * Deletes the login token from the keychain
+ *
+ * @returns Promise
+ */
+export async function deleteLoginToken(): Promise {
+  return new Promise((resolve: () => void, reject: () => void) => {
+    Keychain.resetGenericPassword().then(resolve).catch(reject);
+  });
+}
diff --git a/src/utils/logout.ts b/src/utils/logout.ts
new file mode 100644
index 0000000..6c13b03
--- /dev/null
+++ b/src/utils/logout.ts
@@ -0,0 +1,11 @@
+import { useCallback } from 'react';
+import { useLogin } from '../context/loginContext';
+
+export const useLogout = () => {
+  const { setLogin } = useLogin();
+
+  const onLogout = useCallback(() => {
+    setLogin(undefined);
+  }, [setLogin]);
+  return onLogout;
+};