From c5e79f45c3f529ec54c404a5e40cd7347ac180a5 Mon Sep 17 00:00:00 2001 From: Arnaud Vergnet Date: Tue, 31 Mar 2020 14:21:01 +0200 Subject: [PATCH] Implemented basic login state --- __tests__/managers/ConnectionManager.test.js | 29 ++++++++--- components/AuthenticatedScreen.js | 52 ++++++++++++++++++ components/Sidebar.js | 55 ++++++++++++++++++-- managers/ConnectionManager.js | 33 ++++++++++-- navigation/DrawerNavigator.js | 29 +++++++++++ screens/Amicale/LoginScreen.js | 7 ++- screens/Amicale/ProfileScreen.js | 46 ++++++++++++++++ 7 files changed, 233 insertions(+), 18 deletions(-) create mode 100644 components/AuthenticatedScreen.js create mode 100644 screens/Amicale/ProfileScreen.js diff --git a/__tests__/managers/ConnectionManager.test.js b/__tests__/managers/ConnectionManager.test.js index 848676c..d854559 100644 --- a/__tests__/managers/ConnectionManager.test.js +++ b/__tests__/managers/ConnectionManager.test.js @@ -10,6 +10,21 @@ afterEach(() => { jest.restoreAllMocks(); }); +test('isLoggedIn yes', () => { + jest.spyOn(ConnectionManager.prototype, 'recoverLogin').mockImplementationOnce(() => { + return Promise.resolve(true); + }); + return expect(c.isLoggedIn()).resolves.toBe(true); +}); + +test('isLoggedIn no', () => { + jest.spyOn(ConnectionManager.prototype, 'recoverLogin').mockImplementationOnce(() => { + return Promise.reject(false); + }); + return expect(c.isLoggedIn()).rejects.toBe(false); +}); + + test('recoverLogin error crypto', () => { jest.spyOn(SecureStore, 'getItemAsync').mockImplementationOnce(() => { return Promise.reject(); @@ -157,7 +172,7 @@ test("connect good credentials", () => { }, }) }); - c.saveLogin = jest.fn(() => { + jest.spyOn(ConnectionManager.prototype, 'saveLogin').mockImplementationOnce(() => { return Promise.resolve(true); }); return expect(c.connect('email', 'password')).resolves.toBeTruthy(); @@ -175,7 +190,7 @@ test("connect good credentials, fail save token", () => { }, }) }); - c.saveLogin = jest.fn(() => { + jest.spyOn(ConnectionManager.prototype, 'saveLogin').mockImplementationOnce(() => { return Promise.reject(false); }); return expect(c.connect('email', 'password')).rejects.toBe(ERROR_TYPE.SAVE_TOKEN); @@ -206,7 +221,7 @@ test("connect bogus response 1", () => { test("authenticatedRequest success", () => { - c.recoverLogin = jest.fn(() => { + jest.spyOn(ConnectionManager.prototype, 'recoverLogin').mockImplementationOnce(() => { return Promise.resolve('token'); }); jest.spyOn(global, 'fetch').mockImplementationOnce(() => { @@ -221,7 +236,7 @@ test("authenticatedRequest success", () => { }); test("authenticatedRequest error wrong token", () => { - c.recoverLogin = jest.fn(() => { + jest.spyOn(ConnectionManager.prototype, 'recoverLogin').mockImplementationOnce(() => { return Promise.resolve('token'); }); jest.spyOn(global, 'fetch').mockImplementationOnce(() => { @@ -236,7 +251,7 @@ test("authenticatedRequest error wrong token", () => { }); test("authenticatedRequest error bogus response", () => { - c.recoverLogin = jest.fn(() => { + jest.spyOn(ConnectionManager.prototype, 'recoverLogin').mockImplementationOnce(() => { return Promise.resolve('token'); }); jest.spyOn(global, 'fetch').mockImplementationOnce(() => { @@ -251,7 +266,7 @@ test("authenticatedRequest error bogus response", () => { }); test("authenticatedRequest connection error", () => { - c.recoverLogin = jest.fn(() => { + jest.spyOn(ConnectionManager.prototype, 'recoverLogin').mockImplementationOnce(() => { return Promise.resolve('token'); }); jest.spyOn(global, 'fetch').mockImplementationOnce(() => { @@ -262,7 +277,7 @@ test("authenticatedRequest connection error", () => { }); test("authenticatedRequest error no token", () => { - c.recoverLogin = jest.fn(() => { + jest.spyOn(ConnectionManager.prototype, 'recoverLogin').mockImplementationOnce(() => { return Promise.reject(false); }); jest.spyOn(global, 'fetch').mockImplementationOnce(() => { diff --git a/components/AuthenticatedScreen.js b/components/AuthenticatedScreen.js new file mode 100644 index 0000000..9bdfcf3 --- /dev/null +++ b/components/AuthenticatedScreen.js @@ -0,0 +1,52 @@ +import * as React from 'react'; +import {View} from "react-native"; +import {Text} from 'react-native-paper'; +import ConnectionManager from "../managers/ConnectionManager"; + +type Props = { + navigation: Object, + theme: Object, + link: string, + renderFunction: Function, +} + +type State = { + loading: boolean, +} + +export default class AuthenticatedScreen extends React.Component { + + state = { + loading: true, + }; + + connectionManager: ConnectionManager; + + constructor(props) { + super(props); + this.connectionManager = ConnectionManager.getInstance(); + this.connectionManager.isLoggedIn() + .then(() => { + this.setState({loading: false}); + this.connectionManager.authenticatedRequest(this.props.link) + .then((data) => { + console.log(data); + }) + .catch((error) => { + console.log(error); + }); + }) + .catch(() => { + this.props.navigation.navigate('LoginScreen'); + }); + } + + render() { + return ( + this.state.loading + ? LOADING + : this.props.renderFunction() + ); + } + +} diff --git a/components/Sidebar.js b/components/Sidebar.js index dcded25..012ab48 100644 --- a/components/Sidebar.js +++ b/components/Sidebar.js @@ -1,12 +1,13 @@ // @flow import * as React from 'react'; -import {Dimensions, FlatList, Image, Platform, StyleSheet, View} from 'react-native'; +import {Alert, Dimensions, FlatList, Image, Platform, StyleSheet, View} from 'react-native'; import i18n from "i18n-js"; import {openBrowser} from "../utils/WebBrowser"; import SidebarDivider from "./SidebarDivider"; import SidebarItem from "./SidebarItem"; import {TouchableRipple, withTheme} from "react-native-paper"; +import ConnectionManager from "../managers/ConnectionManager"; const deviceWidth = Dimensions.get("window").width; @@ -18,6 +19,7 @@ type Props = { type State = { active: string, + isLoggedIn: boolean, }; /** @@ -29,6 +31,7 @@ class SideBar extends React.PureComponent { state = { active: 'Home', + isLoggedIn: false, }; getRenderItem: Function; @@ -48,10 +51,27 @@ class SideBar extends React.PureComponent { route: "Main", icon: "home", }, + { + name: "AMICALE", + route: "Divider4" + }, { name: 'LOGIN', route: "LoginScreen", icon: "login", + onlyWhenLoggedOut: true, + }, + { + name: 'DISCONNECT', + action: () => this.onClickDisconnect(), + icon: "logout", + onlyWhenLoggedIn: true, + }, + { + name: 'PROFILE', + route: "ProfileScreen", + icon: "circle", + onlyWhenLoggedIn: true, }, { name: i18n.t('sidenav.divider2'), @@ -129,6 +149,24 @@ class SideBar extends React.PureComponent { ]; this.getRenderItem = this.getRenderItem.bind(this); this.colors = props.theme.colors; + ConnectionManager.getInstance().setLoginCallback((value) => this.onLoginStateChange(value)); + } + + onClickDisconnect() { + console.log('coucou'); + Alert.alert( + 'DISCONNECT', + 'DISCONNECT?', + [ + {text: 'YES', onPress: () => ConnectionManager.getInstance().disconnect()}, + {text: 'NO', undefined}, + ], + {cancelable: false}, + ); + } + + onLoginStateChange(isLoggedIn: boolean) { + this.setState({isLoggedIn: isLoggedIn}); } /** @@ -138,10 +176,13 @@ class SideBar extends React.PureComponent { * @param item The item pressed */ onListItemPress(item: Object) { - if (item.link === undefined) - this.props.navigation.navigate(item.route); - else + console.log(item.action); + if (item.link !== undefined) openBrowser(item.link, this.colors.primary); + else if (item.action !== undefined) + item.action(); + else + this.props.navigation.navigate(item.route); } /** @@ -162,7 +203,11 @@ class SideBar extends React.PureComponent { */ getRenderItem({item}: Object) { const onListItemPress = this.onListItemPress.bind(this, item); - if (item.icon !== undefined) { + const onlyWhenLoggedOut = item.onlyWhenLoggedOut !== undefined && item.onlyWhenLoggedOut === true; + const onlyWhenLoggedIn = item.onlyWhenLoggedIn !== undefined && item.onlyWhenLoggedIn === true; + if (onlyWhenLoggedIn && !this.state.isLoggedIn || onlyWhenLoggedOut && this.state.isLoggedIn) + return null; + else if (item.icon !== undefined) { return ( { - console.log(this.#token); if (this.#token !== undefined) resolve(this.#token); else { SecureStore.getItemAsync('token') .then((token) => { - console.log(token); + this.onLoginStateChange(true); resolve(token); }) .catch(error => { @@ -45,13 +54,25 @@ export default class ConnectionManager { }); } + async isLoggedIn() { + return new Promise((resolve, reject) => { + this.recoverLogin() + .then(() => { + resolve(true); + }) + .catch(() => { + reject(false); + }) + }); + } + async saveLogin(email: string, token: string) { - console.log(token); return new Promise((resolve, reject) => { SecureStore.setItemAsync('token', token) .then(() => { this.#token = token; this.#email = email; + this.onLoginStateChange(true); resolve(true); }) .catch(error => { @@ -60,6 +81,11 @@ export default class ConnectionManager { }); } + async disconnect() { + SecureStore.deleteItemAsync('token'); // TODO use promise + this.onLoginStateChange(false); + } + async connect(email: string, password: string) { let data = { email: email, @@ -133,7 +159,6 @@ export default class ConnectionManager { body: JSON.stringify({token: token}) }).then(async (response) => response.json()) .then((data) => { - console.log(data); if (this.isRequestResponseValid(data)) { if (data.state) resolve(data.data); diff --git a/navigation/DrawerNavigator.js b/navigation/DrawerNavigator.js index 5c3570a..59672c9 100644 --- a/navigation/DrawerNavigator.js +++ b/navigation/DrawerNavigator.js @@ -16,6 +16,7 @@ import {createStackNavigator, TransitionPresets} from "@react-navigation/stack"; import HeaderButton from "../components/HeaderButton"; import i18n from "i18n-js"; import LoginScreen from "../screens/Amicale/LoginScreen"; +import ProfileScreen from "../screens/Amicale/ProfileScreen"; const defaultScreenOptions = { gestureEnabled: true, @@ -211,6 +212,30 @@ function LoginStackComponent() { ); } +const ProfileStack = createStackNavigator(); + +function ProfileStackComponent() { + return ( + + { + const openDrawer = getDrawerButton.bind(this, navigation); + return { + title: 'PROFILE', + headerLeft: openDrawer + }; + }} + /> + + ); +} + const Drawer = createDrawerNavigator(); function getDrawerContent(props) { @@ -260,6 +285,10 @@ export default function DrawerNavigator() { name="LoginScreen" component={LoginStackComponent} /> + ); } diff --git a/screens/Amicale/LoginScreen.js b/screens/Amicale/LoginScreen.js index dc16550..f36b289 100644 --- a/screens/Amicale/LoginScreen.js +++ b/screens/Amicale/LoginScreen.js @@ -121,8 +121,7 @@ class LoginScreen extends React.Component { this.setState({loading: true}); ConnectionManager.getInstance().connect(this.state.email, this.state.password) .then((data) => { - console.log(data); - Alert.alert('COOL', 'ÇA MARCHE'); + this.handleSuccess(); }) .catch((error) => { this.handleErrors(error); @@ -133,6 +132,10 @@ class LoginScreen extends React.Component { } } + handleSuccess() { + this.props.navigation.navigate('ProfileScreen'); + } + handleErrors(error: number) { switch (error) { case ERROR_TYPE.CONNECTION_ERROR: diff --git a/screens/Amicale/ProfileScreen.js b/screens/Amicale/ProfileScreen.js new file mode 100644 index 0000000..0fde168 --- /dev/null +++ b/screens/Amicale/ProfileScreen.js @@ -0,0 +1,46 @@ +import * as React from 'react'; +import {View} from "react-native"; +import {Text, withTheme} from 'react-native-paper'; +import AuthenticatedScreen from "../../components/AuthenticatedScreen"; + +type Props = { + navigation: Object, + theme: Object, +} + +type State = { +} + +class ProfileScreen extends React.Component { + + state = { + }; + + colors: Object; + + constructor(props) { + super(props); + this.colors = props.theme.colors; + } + + getScreen(data: Object) { + return ( + + PAGE + + ) + } + + render() { + return ( + this.getScreen()} + /> + ); + } + +} + +export default withTheme(ProfileScreen);