Browse Source

Implemented basic login state

Arnaud Vergnet 4 years ago
parent
commit
c5e79f45c3

+ 22
- 7
__tests__/managers/ConnectionManager.test.js View File

@@ -10,6 +10,21 @@ afterEach(() => {
10 10
     jest.restoreAllMocks();
11 11
 });
12 12
 
13
+test('isLoggedIn yes', () => {
14
+    jest.spyOn(ConnectionManager.prototype, 'recoverLogin').mockImplementationOnce(() => {
15
+        return Promise.resolve(true);
16
+    });
17
+    return expect(c.isLoggedIn()).resolves.toBe(true);
18
+});
19
+
20
+test('isLoggedIn no', () => {
21
+    jest.spyOn(ConnectionManager.prototype, 'recoverLogin').mockImplementationOnce(() => {
22
+        return Promise.reject(false);
23
+    });
24
+    return expect(c.isLoggedIn()).rejects.toBe(false);
25
+});
26
+
27
+
13 28
 test('recoverLogin error crypto', () => {
14 29
     jest.spyOn(SecureStore, 'getItemAsync').mockImplementationOnce(() => {
15 30
         return Promise.reject();
@@ -157,7 +172,7 @@ test("connect good credentials", () => {
157 172
             },
158 173
         })
159 174
     });
160
-    c.saveLogin = jest.fn(() => {
175
+    jest.spyOn(ConnectionManager.prototype, 'saveLogin').mockImplementationOnce(() => {
161 176
         return Promise.resolve(true);
162 177
     });
163 178
     return expect(c.connect('email', 'password')).resolves.toBeTruthy();
@@ -175,7 +190,7 @@ test("connect good credentials, fail save token", () => {
175 190
             },
176 191
         })
177 192
     });
178
-    c.saveLogin = jest.fn(() => {
193
+    jest.spyOn(ConnectionManager.prototype, 'saveLogin').mockImplementationOnce(() => {
179 194
         return Promise.reject(false);
180 195
     });
181 196
     return expect(c.connect('email', 'password')).rejects.toBe(ERROR_TYPE.SAVE_TOKEN);
@@ -206,7 +221,7 @@ test("connect bogus response 1", () => {
206 221
 
207 222
 
208 223
 test("authenticatedRequest success", () => {
209
-    c.recoverLogin = jest.fn(() => {
224
+    jest.spyOn(ConnectionManager.prototype, 'recoverLogin').mockImplementationOnce(() => {
210 225
         return Promise.resolve('token');
211 226
     });
212 227
     jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
@@ -221,7 +236,7 @@ test("authenticatedRequest success", () => {
221 236
 });
222 237
 
223 238
 test("authenticatedRequest error wrong token", () => {
224
-    c.recoverLogin = jest.fn(() => {
239
+    jest.spyOn(ConnectionManager.prototype, 'recoverLogin').mockImplementationOnce(() => {
225 240
         return Promise.resolve('token');
226 241
     });
227 242
     jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
@@ -236,7 +251,7 @@ test("authenticatedRequest error wrong token", () => {
236 251
 });
237 252
 
238 253
 test("authenticatedRequest error bogus response", () => {
239
-    c.recoverLogin = jest.fn(() => {
254
+    jest.spyOn(ConnectionManager.prototype, 'recoverLogin').mockImplementationOnce(() => {
240 255
         return Promise.resolve('token');
241 256
     });
242 257
     jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
@@ -251,7 +266,7 @@ test("authenticatedRequest error bogus response", () => {
251 266
 });
252 267
 
253 268
 test("authenticatedRequest connection error", () => {
254
-    c.recoverLogin = jest.fn(() => {
269
+    jest.spyOn(ConnectionManager.prototype, 'recoverLogin').mockImplementationOnce(() => {
255 270
         return Promise.resolve('token');
256 271
     });
257 272
     jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
@@ -262,7 +277,7 @@ test("authenticatedRequest connection error", () => {
262 277
 });
263 278
 
264 279
 test("authenticatedRequest error no token", () => {
265
-    c.recoverLogin = jest.fn(() => {
280
+    jest.spyOn(ConnectionManager.prototype, 'recoverLogin').mockImplementationOnce(() => {
266 281
         return Promise.reject(false);
267 282
     });
268 283
     jest.spyOn(global, 'fetch').mockImplementationOnce(() => {

+ 52
- 0
components/AuthenticatedScreen.js View File

@@ -0,0 +1,52 @@
1
+import * as React from 'react';
2
+import {View} from "react-native";
3
+import {Text} from 'react-native-paper';
4
+import ConnectionManager from "../managers/ConnectionManager";
5
+
6
+type Props = {
7
+    navigation: Object,
8
+    theme: Object,
9
+    link: string,
10
+    renderFunction: Function,
11
+}
12
+
13
+type State = {
14
+    loading: boolean,
15
+}
16
+
17
+export default class AuthenticatedScreen extends React.Component<Props, State> {
18
+
19
+    state = {
20
+        loading: true,
21
+    };
22
+
23
+    connectionManager: ConnectionManager;
24
+
25
+    constructor(props) {
26
+        super(props);
27
+        this.connectionManager = ConnectionManager.getInstance();
28
+        this.connectionManager.isLoggedIn()
29
+            .then(() => {
30
+                this.setState({loading: false});
31
+                this.connectionManager.authenticatedRequest(this.props.link)
32
+                    .then((data) => {
33
+                        console.log(data);
34
+                    })
35
+                    .catch((error) => {
36
+                        console.log(error);
37
+                    });
38
+            })
39
+            .catch(() => {
40
+                this.props.navigation.navigate('LoginScreen');
41
+            });
42
+    }
43
+
44
+    render() {
45
+        return (
46
+            this.state.loading
47
+                ? <View><Text>LOADING</Text></View>
48
+                : this.props.renderFunction()
49
+        );
50
+    }
51
+
52
+}

+ 50
- 5
components/Sidebar.js View File

@@ -1,12 +1,13 @@
1 1
 // @flow
2 2
 
3 3
 import * as React from 'react';
4
-import {Dimensions, FlatList, Image, Platform, StyleSheet, View} from 'react-native';
4
+import {Alert, Dimensions, FlatList, Image, Platform, StyleSheet, View} from 'react-native';
5 5
 import i18n from "i18n-js";
6 6
 import {openBrowser} from "../utils/WebBrowser";
7 7
 import SidebarDivider from "./SidebarDivider";
8 8
 import SidebarItem from "./SidebarItem";
9 9
 import {TouchableRipple, withTheme} from "react-native-paper";
10
+import ConnectionManager from "../managers/ConnectionManager";
10 11
 
11 12
 const deviceWidth = Dimensions.get("window").width;
12 13
 
@@ -18,6 +19,7 @@ type Props = {
18 19
 
19 20
 type State = {
20 21
     active: string,
22
+    isLoggedIn: boolean,
21 23
 };
22 24
 
23 25
 /**
@@ -29,6 +31,7 @@ class SideBar extends React.PureComponent<Props, State> {
29 31
 
30 32
     state = {
31 33
         active: 'Home',
34
+        isLoggedIn: false,
32 35
     };
33 36
 
34 37
     getRenderItem: Function;
@@ -49,9 +52,26 @@ class SideBar extends React.PureComponent<Props, State> {
49 52
                 icon: "home",
50 53
             },
51 54
             {
55
+                name: "AMICALE",
56
+                route: "Divider4"
57
+            },
58
+            {
52 59
                 name: 'LOGIN',
53 60
                 route: "LoginScreen",
54 61
                 icon: "login",
62
+                onlyWhenLoggedOut: true,
63
+            },
64
+            {
65
+                name: 'DISCONNECT',
66
+                action: () => this.onClickDisconnect(),
67
+                icon: "logout",
68
+                onlyWhenLoggedIn: true,
69
+            },
70
+            {
71
+                name: 'PROFILE',
72
+                route: "ProfileScreen",
73
+                icon: "circle",
74
+                onlyWhenLoggedIn: true,
55 75
             },
56 76
             {
57 77
                 name: i18n.t('sidenav.divider2'),
@@ -129,6 +149,24 @@ class SideBar extends React.PureComponent<Props, State> {
129 149
         ];
130 150
         this.getRenderItem = this.getRenderItem.bind(this);
131 151
         this.colors = props.theme.colors;
152
+        ConnectionManager.getInstance().setLoginCallback((value) => this.onLoginStateChange(value));
153
+    }
154
+
155
+    onClickDisconnect() {
156
+        console.log('coucou');
157
+        Alert.alert(
158
+            'DISCONNECT',
159
+            'DISCONNECT?',
160
+            [
161
+                {text: 'YES', onPress: () => ConnectionManager.getInstance().disconnect()},
162
+                {text: 'NO', undefined},
163
+            ],
164
+            {cancelable: false},
165
+        );
166
+    }
167
+
168
+    onLoginStateChange(isLoggedIn: boolean) {
169
+        this.setState({isLoggedIn: isLoggedIn});
132 170
     }
133 171
 
134 172
     /**
@@ -138,10 +176,13 @@ class SideBar extends React.PureComponent<Props, State> {
138 176
      * @param item The item pressed
139 177
      */
140 178
     onListItemPress(item: Object) {
141
-        if (item.link === undefined)
142
-            this.props.navigation.navigate(item.route);
143
-        else
179
+        console.log(item.action);
180
+        if (item.link !== undefined)
144 181
             openBrowser(item.link, this.colors.primary);
182
+        else if (item.action !== undefined)
183
+            item.action();
184
+        else
185
+            this.props.navigation.navigate(item.route);
145 186
     }
146 187
 
147 188
     /**
@@ -162,7 +203,11 @@ class SideBar extends React.PureComponent<Props, State> {
162 203
      */
163 204
     getRenderItem({item}: Object) {
164 205
         const onListItemPress = this.onListItemPress.bind(this, item);
165
-        if (item.icon !== undefined) {
206
+        const onlyWhenLoggedOut = item.onlyWhenLoggedOut !== undefined && item.onlyWhenLoggedOut === true;
207
+        const onlyWhenLoggedIn = item.onlyWhenLoggedIn !== undefined && item.onlyWhenLoggedIn === true;
208
+        if (onlyWhenLoggedIn && !this.state.isLoggedIn || onlyWhenLoggedOut && this.state.isLoggedIn)
209
+            return null;
210
+        else if (item.icon !== undefined) {
166 211
             return (
167 212
                 <SidebarItem
168 213
                     title={item.name}

+ 29
- 4
managers/ConnectionManager.js View File

@@ -17,6 +17,8 @@ export default class ConnectionManager {
17 17
     #email: string;
18 18
     #token: string;
19 19
 
20
+    loginCallback: Function;
21
+
20 22
     /**
21 23
      * Get this class instance or create one if none is found
22 24
      * @returns {ConnectionManager}
@@ -27,15 +29,22 @@ export default class ConnectionManager {
27 29
             ConnectionManager.instance;
28 30
     }
29 31
 
32
+    onLoginStateChange(newState: boolean) {
33
+        this.loginCallback(newState);
34
+    }
35
+
36
+    setLoginCallback(callback: Function) {
37
+        this.loginCallback = callback;
38
+    }
39
+
30 40
     async recoverLogin() {
31 41
         return new Promise((resolve, reject) => {
32
-            console.log(this.#token);
33 42
             if (this.#token !== undefined)
34 43
                 resolve(this.#token);
35 44
             else {
36 45
                 SecureStore.getItemAsync('token')
37 46
                     .then((token) => {
38
-                        console.log(token);
47
+                        this.onLoginStateChange(true);
39 48
                         resolve(token);
40 49
                     })
41 50
                     .catch(error => {
@@ -45,13 +54,25 @@ export default class ConnectionManager {
45 54
         });
46 55
     }
47 56
 
57
+    async isLoggedIn() {
58
+        return new Promise((resolve, reject) => {
59
+            this.recoverLogin()
60
+                .then(() => {
61
+                    resolve(true);
62
+                })
63
+                .catch(() => {
64
+                    reject(false);
65
+                })
66
+        });
67
+    }
68
+
48 69
     async saveLogin(email: string, token: string) {
49
-        console.log(token);
50 70
         return new Promise((resolve, reject) => {
51 71
             SecureStore.setItemAsync('token', token)
52 72
                 .then(() => {
53 73
                     this.#token = token;
54 74
                     this.#email = email;
75
+                    this.onLoginStateChange(true);
55 76
                     resolve(true);
56 77
                 })
57 78
                 .catch(error => {
@@ -60,6 +81,11 @@ export default class ConnectionManager {
60 81
         });
61 82
     }
62 83
 
84
+    async disconnect() {
85
+        SecureStore.deleteItemAsync('token'); // TODO use promise
86
+        this.onLoginStateChange(false);
87
+    }
88
+
63 89
     async connect(email: string, password: string) {
64 90
         let data = {
65 91
             email: email,
@@ -133,7 +159,6 @@ export default class ConnectionManager {
133 159
                         body: JSON.stringify({token: token})
134 160
                     }).then(async (response) => response.json())
135 161
                         .then((data) => {
136
-                            console.log(data);
137 162
                             if (this.isRequestResponseValid(data)) {
138 163
                                 if (data.state)
139 164
                                     resolve(data.data);

+ 29
- 0
navigation/DrawerNavigator.js View File

@@ -16,6 +16,7 @@ import {createStackNavigator, TransitionPresets} from "@react-navigation/stack";
16 16
 import HeaderButton from "../components/HeaderButton";
17 17
 import i18n from "i18n-js";
18 18
 import LoginScreen from "../screens/Amicale/LoginScreen";
19
+import ProfileScreen from "../screens/Amicale/ProfileScreen";
19 20
 
20 21
 const defaultScreenOptions = {
21 22
     gestureEnabled: true,
@@ -211,6 +212,30 @@ function LoginStackComponent() {
211 212
     );
212 213
 }
213 214
 
215
+const ProfileStack = createStackNavigator();
216
+
217
+function ProfileStackComponent() {
218
+    return (
219
+        <ProfileStack.Navigator
220
+            initialRouteName="ProfileScreen"
221
+            headerMode="float"
222
+            screenOptions={defaultScreenOptions}
223
+        >
224
+            <ProfileStack.Screen
225
+                name="ProfileScreen"
226
+                component={ProfileScreen}
227
+                options={({navigation}) => {
228
+                    const openDrawer = getDrawerButton.bind(this, navigation);
229
+                    return {
230
+                        title: 'PROFILE',
231
+                        headerLeft: openDrawer
232
+                    };
233
+                }}
234
+            />
235
+        </ProfileStack.Navigator>
236
+    );
237
+}
238
+
214 239
 const Drawer = createDrawerNavigator();
215 240
 
216 241
 function getDrawerContent(props) {
@@ -260,6 +285,10 @@ export default function DrawerNavigator() {
260 285
                 name="LoginScreen"
261 286
                 component={LoginStackComponent}
262 287
             />
288
+            <Drawer.Screen
289
+                name="ProfileScreen"
290
+                component={ProfileStackComponent}
291
+            />
263 292
         </Drawer.Navigator>
264 293
     );
265 294
 }

+ 5
- 2
screens/Amicale/LoginScreen.js View File

@@ -121,8 +121,7 @@ class LoginScreen extends React.Component<Props, State> {
121 121
             this.setState({loading: true});
122 122
             ConnectionManager.getInstance().connect(this.state.email, this.state.password)
123 123
                 .then((data) => {
124
-                    console.log(data);
125
-                    Alert.alert('COOL', 'ÇA MARCHE');
124
+                    this.handleSuccess();
126 125
                 })
127 126
                 .catch((error) => {
128 127
                     this.handleErrors(error);
@@ -133,6 +132,10 @@ class LoginScreen extends React.Component<Props, State> {
133 132
         }
134 133
     }
135 134
 
135
+    handleSuccess() {
136
+        this.props.navigation.navigate('ProfileScreen');
137
+    }
138
+
136 139
     handleErrors(error: number) {
137 140
         switch (error) {
138 141
             case ERROR_TYPE.CONNECTION_ERROR:

+ 46
- 0
screens/Amicale/ProfileScreen.js View File

@@ -0,0 +1,46 @@
1
+import * as React from 'react';
2
+import {View} from "react-native";
3
+import {Text, withTheme} from 'react-native-paper';
4
+import AuthenticatedScreen from "../../components/AuthenticatedScreen";
5
+
6
+type Props = {
7
+    navigation: Object,
8
+    theme: Object,
9
+}
10
+
11
+type State = {
12
+}
13
+
14
+class ProfileScreen extends React.Component<Props, State> {
15
+
16
+    state = {
17
+    };
18
+
19
+    colors: Object;
20
+
21
+    constructor(props) {
22
+        super(props);
23
+        this.colors = props.theme.colors;
24
+    }
25
+
26
+    getScreen(data: Object) {
27
+        return (
28
+            <View>
29
+                <Text>PAGE</Text>
30
+            </View>
31
+        )
32
+    }
33
+
34
+    render() {
35
+        return (
36
+            <AuthenticatedScreen
37
+                {...this.props}
38
+                link={'https://www.amicale-insat.fr/api/user/profile'}
39
+                renderFunction={() => this.getScreen()}
40
+            />
41
+        );
42
+    }
43
+
44
+}
45
+
46
+export default withTheme(ProfileScreen);

Loading…
Cancel
Save