Implemented basic login state

This commit is contained in:
Arnaud Vergnet 2020-03-31 14:21:01 +02:00
parent 2b0945fe5b
commit c5e79f45c3
7 changed files with 233 additions and 18 deletions

View file

@ -10,6 +10,21 @@ afterEach(() => {
jest.restoreAllMocks(); 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', () => { test('recoverLogin error crypto', () => {
jest.spyOn(SecureStore, 'getItemAsync').mockImplementationOnce(() => { jest.spyOn(SecureStore, 'getItemAsync').mockImplementationOnce(() => {
return Promise.reject(); 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 Promise.resolve(true);
}); });
return expect(c.connect('email', 'password')).resolves.toBeTruthy(); 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 Promise.reject(false);
}); });
return expect(c.connect('email', 'password')).rejects.toBe(ERROR_TYPE.SAVE_TOKEN); return expect(c.connect('email', 'password')).rejects.toBe(ERROR_TYPE.SAVE_TOKEN);
@ -206,7 +221,7 @@ test("connect bogus response 1", () => {
test("authenticatedRequest success", () => { test("authenticatedRequest success", () => {
c.recoverLogin = jest.fn(() => { jest.spyOn(ConnectionManager.prototype, 'recoverLogin').mockImplementationOnce(() => {
return Promise.resolve('token'); return Promise.resolve('token');
}); });
jest.spyOn(global, 'fetch').mockImplementationOnce(() => { jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
@ -221,7 +236,7 @@ test("authenticatedRequest success", () => {
}); });
test("authenticatedRequest error wrong token", () => { test("authenticatedRequest error wrong token", () => {
c.recoverLogin = jest.fn(() => { jest.spyOn(ConnectionManager.prototype, 'recoverLogin').mockImplementationOnce(() => {
return Promise.resolve('token'); return Promise.resolve('token');
}); });
jest.spyOn(global, 'fetch').mockImplementationOnce(() => { jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
@ -236,7 +251,7 @@ test("authenticatedRequest error wrong token", () => {
}); });
test("authenticatedRequest error bogus response", () => { test("authenticatedRequest error bogus response", () => {
c.recoverLogin = jest.fn(() => { jest.spyOn(ConnectionManager.prototype, 'recoverLogin').mockImplementationOnce(() => {
return Promise.resolve('token'); return Promise.resolve('token');
}); });
jest.spyOn(global, 'fetch').mockImplementationOnce(() => { jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
@ -251,7 +266,7 @@ test("authenticatedRequest error bogus response", () => {
}); });
test("authenticatedRequest connection error", () => { test("authenticatedRequest connection error", () => {
c.recoverLogin = jest.fn(() => { jest.spyOn(ConnectionManager.prototype, 'recoverLogin').mockImplementationOnce(() => {
return Promise.resolve('token'); return Promise.resolve('token');
}); });
jest.spyOn(global, 'fetch').mockImplementationOnce(() => { jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
@ -262,7 +277,7 @@ test("authenticatedRequest connection error", () => {
}); });
test("authenticatedRequest error no token", () => { test("authenticatedRequest error no token", () => {
c.recoverLogin = jest.fn(() => { jest.spyOn(ConnectionManager.prototype, 'recoverLogin').mockImplementationOnce(() => {
return Promise.reject(false); return Promise.reject(false);
}); });
jest.spyOn(global, 'fetch').mockImplementationOnce(() => { jest.spyOn(global, 'fetch').mockImplementationOnce(() => {

View file

@ -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<Props, State> {
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
? <View><Text>LOADING</Text></View>
: this.props.renderFunction()
);
}
}

View file

@ -1,12 +1,13 @@
// @flow // @flow
import * as React from 'react'; 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 i18n from "i18n-js";
import {openBrowser} from "../utils/WebBrowser"; import {openBrowser} from "../utils/WebBrowser";
import SidebarDivider from "./SidebarDivider"; import SidebarDivider from "./SidebarDivider";
import SidebarItem from "./SidebarItem"; import SidebarItem from "./SidebarItem";
import {TouchableRipple, withTheme} from "react-native-paper"; import {TouchableRipple, withTheme} from "react-native-paper";
import ConnectionManager from "../managers/ConnectionManager";
const deviceWidth = Dimensions.get("window").width; const deviceWidth = Dimensions.get("window").width;
@ -18,6 +19,7 @@ type Props = {
type State = { type State = {
active: string, active: string,
isLoggedIn: boolean,
}; };
/** /**
@ -29,6 +31,7 @@ class SideBar extends React.PureComponent<Props, State> {
state = { state = {
active: 'Home', active: 'Home',
isLoggedIn: false,
}; };
getRenderItem: Function; getRenderItem: Function;
@ -48,10 +51,27 @@ class SideBar extends React.PureComponent<Props, State> {
route: "Main", route: "Main",
icon: "home", icon: "home",
}, },
{
name: "AMICALE",
route: "Divider4"
},
{ {
name: 'LOGIN', name: 'LOGIN',
route: "LoginScreen", route: "LoginScreen",
icon: "login", 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'), name: i18n.t('sidenav.divider2'),
@ -129,6 +149,24 @@ class SideBar extends React.PureComponent<Props, State> {
]; ];
this.getRenderItem = this.getRenderItem.bind(this); this.getRenderItem = this.getRenderItem.bind(this);
this.colors = props.theme.colors; 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<Props, State> {
* @param item The item pressed * @param item The item pressed
*/ */
onListItemPress(item: Object) { onListItemPress(item: Object) {
if (item.link === undefined) console.log(item.action);
this.props.navigation.navigate(item.route); if (item.link !== undefined)
else
openBrowser(item.link, this.colors.primary); 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<Props, State> {
*/ */
getRenderItem({item}: Object) { getRenderItem({item}: Object) {
const onListItemPress = this.onListItemPress.bind(this, item); 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 ( return (
<SidebarItem <SidebarItem
title={item.name} title={item.name}

View file

@ -17,6 +17,8 @@ export default class ConnectionManager {
#email: string; #email: string;
#token: string; #token: string;
loginCallback: Function;
/** /**
* Get this class instance or create one if none is found * Get this class instance or create one if none is found
* @returns {ConnectionManager} * @returns {ConnectionManager}
@ -27,15 +29,22 @@ export default class ConnectionManager {
ConnectionManager.instance; ConnectionManager.instance;
} }
onLoginStateChange(newState: boolean) {
this.loginCallback(newState);
}
setLoginCallback(callback: Function) {
this.loginCallback = callback;
}
async recoverLogin() { async recoverLogin() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
console.log(this.#token);
if (this.#token !== undefined) if (this.#token !== undefined)
resolve(this.#token); resolve(this.#token);
else { else {
SecureStore.getItemAsync('token') SecureStore.getItemAsync('token')
.then((token) => { .then((token) => {
console.log(token); this.onLoginStateChange(true);
resolve(token); resolve(token);
}) })
.catch(error => { .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) { async saveLogin(email: string, token: string) {
console.log(token);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
SecureStore.setItemAsync('token', token) SecureStore.setItemAsync('token', token)
.then(() => { .then(() => {
this.#token = token; this.#token = token;
this.#email = email; this.#email = email;
this.onLoginStateChange(true);
resolve(true); resolve(true);
}) })
.catch(error => { .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) { async connect(email: string, password: string) {
let data = { let data = {
email: email, email: email,
@ -133,7 +159,6 @@ export default class ConnectionManager {
body: JSON.stringify({token: token}) body: JSON.stringify({token: token})
}).then(async (response) => response.json()) }).then(async (response) => response.json())
.then((data) => { .then((data) => {
console.log(data);
if (this.isRequestResponseValid(data)) { if (this.isRequestResponseValid(data)) {
if (data.state) if (data.state)
resolve(data.data); resolve(data.data);

View file

@ -16,6 +16,7 @@ import {createStackNavigator, TransitionPresets} from "@react-navigation/stack";
import HeaderButton from "../components/HeaderButton"; import HeaderButton from "../components/HeaderButton";
import i18n from "i18n-js"; import i18n from "i18n-js";
import LoginScreen from "../screens/Amicale/LoginScreen"; import LoginScreen from "../screens/Amicale/LoginScreen";
import ProfileScreen from "../screens/Amicale/ProfileScreen";
const defaultScreenOptions = { const defaultScreenOptions = {
gestureEnabled: true, gestureEnabled: true,
@ -211,6 +212,30 @@ function LoginStackComponent() {
); );
} }
const ProfileStack = createStackNavigator();
function ProfileStackComponent() {
return (
<ProfileStack.Navigator
initialRouteName="ProfileScreen"
headerMode="float"
screenOptions={defaultScreenOptions}
>
<ProfileStack.Screen
name="ProfileScreen"
component={ProfileScreen}
options={({navigation}) => {
const openDrawer = getDrawerButton.bind(this, navigation);
return {
title: 'PROFILE',
headerLeft: openDrawer
};
}}
/>
</ProfileStack.Navigator>
);
}
const Drawer = createDrawerNavigator(); const Drawer = createDrawerNavigator();
function getDrawerContent(props) { function getDrawerContent(props) {
@ -260,6 +285,10 @@ export default function DrawerNavigator() {
name="LoginScreen" name="LoginScreen"
component={LoginStackComponent} component={LoginStackComponent}
/> />
<Drawer.Screen
name="ProfileScreen"
component={ProfileStackComponent}
/>
</Drawer.Navigator> </Drawer.Navigator>
); );
} }

View file

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

View file

@ -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<Props, State> {
state = {
};
colors: Object;
constructor(props) {
super(props);
this.colors = props.theme.colors;
}
getScreen(data: Object) {
return (
<View>
<Text>PAGE</Text>
</View>
)
}
render() {
return (
<AuthenticatedScreen
{...this.props}
link={'https://www.amicale-insat.fr/api/user/profile'}
renderFunction={() => this.getScreen()}
/>
);
}
}
export default withTheme(ProfileScreen);