Compare commits

..

No commits in common. "5aa3afd38351f9daf5ea12b3729c593a1ec372c3" and "5bf1e7586bd32faa3d0af8755eafc0b4fdf113ad" have entirely different histories.

7 changed files with 73 additions and 417 deletions

View file

@ -1,143 +1,15 @@
import React from 'react'; import React from 'react';
import ConnectionManager, {ERROR_TYPE} from "../../managers/ConnectionManager"; import ConnectionManager, {ERROR_TYPE} from "../../managers/ConnectionManager";
import * as SecureStore from 'expo-secure-store';
let fetch = require('isomorphic-fetch'); // fetch is not implemented in nodeJS but in react-native
const fetch = require('isomorphic-fetch'); // fetch is not implemented in nodeJS but in react-native
const c = ConnectionManager.getInstance(); const c = ConnectionManager.getInstance();
test("connect bad credentials", () => { test("connect bad credentials", () => {
jest.spyOn(global, 'fetch').mockImplementationOnce(() => { return expect(c.connect('truc', 'chose'))
return Promise.resolve({
json: () => {
return {
state: false,
message: 'Adresse mail ou mot de passe incorrect',
token: ''
}
},
})
});
return expect(c.connect('email', 'password'))
.rejects.toBe(ERROR_TYPE.BAD_CREDENTIALS); .rejects.toBe(ERROR_TYPE.BAD_CREDENTIALS);
}); });
test("connect good credentials", () => { test("connect good credentials", () => {
jest.spyOn(global, 'fetch').mockImplementationOnce(() => { return expect(c.connect('vergnet@etud.insa-toulouse.fr', 'Coucoù512'))
return Promise.resolve({ .resolves.toBe('test');
json: () => {
return {
state: true,
message: 'Connexion confirmée',
token: 'token'
}
},
})
});
jest.spyOn(SecureStore, 'setItemAsync').mockImplementationOnce(() => {
return Promise.resolve(true);
});
return expect(c.connect('email', 'password')).resolves.toBeTruthy();
});
test("connect good credentials, fail save token", () => {
jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
return Promise.resolve({
json: () => {
return {
state: true,
message: 'Connexion confirmée',
token: 'token'
}
},
})
});
jest.spyOn(SecureStore, 'setItemAsync').mockImplementationOnce(() => {
return Promise.reject(false);
});
return expect(c.connect('email', 'password')).rejects.toBe(ERROR_TYPE.SAVE_TOKEN);
});
test("connect connection error", () => {
jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
return Promise.reject();
});
return expect(c.connect('email', 'password'))
.rejects.toBe(ERROR_TYPE.CONNECTION_ERROR);
});
test("connect bogus response 1", () => {
jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
return Promise.resolve({
json: () => {
return {
thing: true,
wrong: '',
}
},
})
});
return expect(c.connect('email', 'password'))
.rejects.toBe(ERROR_TYPE.CONNECTION_ERROR);
});
test("connect bogus response 2", () => {
jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
return Promise.resolve({
json: () => {
return {
state: true,
message: '',
}
},
})
});
return expect(c.connect('email', 'password'))
.rejects.toBe(ERROR_TYPE.CONNECTION_ERROR);
});
test("connect bogus response 3", () => {
jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
return Promise.resolve({
json: () => {
return {
state: false,
message: '',
}
},
})
});
return expect(c.connect('email', 'password'))
.rejects.toBe(ERROR_TYPE.BAD_CREDENTIALS);
});
test("connect bogus response 4", () => {
jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
return Promise.resolve({
json: () => {
return {
message: '',
token: '',
}
},
})
});
return expect(c.connect('email', 'password'))
.rejects.toBe(ERROR_TYPE.CONNECTION_ERROR);
});
test("connect bogus response 5", () => {
jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
return Promise.resolve({
json: () => {
return {
state: true,
message: 'Connexion confirmée',
token: ''
}
},
})
});
return expect(c.connect('email', 'password'))
.rejects.toBe(ERROR_TYPE.CONNECTION_ERROR);
}); });

View file

@ -3,17 +3,16 @@
import * as React from 'react'; import * as React from 'react';
import {Dimensions, FlatList, Image, Platform, StyleSheet, View} from 'react-native'; import {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 * as WebBrowser from 'expo-web-browser';
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} from "react-native-paper";
const deviceWidth = Dimensions.get("window").width; const deviceWidth = Dimensions.get("window").width;
type Props = { type Props = {
navigation: Object, navigation: Object,
state: Object, state: Object,
theme: Object,
}; };
type State = { type State = {
@ -23,7 +22,7 @@ type State = {
/** /**
* Component used to render the drawer menu content * Component used to render the drawer menu content
*/ */
class SideBar extends React.PureComponent<Props, State> { export default class SideBar extends React.PureComponent<Props, State> {
dataSet: Array<Object>; dataSet: Array<Object>;
@ -32,7 +31,6 @@ class SideBar extends React.PureComponent<Props, State> {
}; };
getRenderItem: Function; getRenderItem: Function;
colors: Object;
/** /**
* Generate the dataset * Generate the dataset
@ -128,7 +126,6 @@ class SideBar extends React.PureComponent<Props, State> {
}, },
]; ];
this.getRenderItem = this.getRenderItem.bind(this); this.getRenderItem = this.getRenderItem.bind(this);
this.colors = props.theme.colors;
} }
/** /**
@ -141,7 +138,7 @@ class SideBar extends React.PureComponent<Props, State> {
if (item.link === undefined) if (item.link === undefined)
this.props.navigation.navigate(item.route); this.props.navigation.navigate(item.route);
else else
openBrowser(item.link, this.colors.primary); WebBrowser.openBrowserAsync(item.link);
} }
/** /**
@ -221,5 +218,3 @@ const styles = StyleSheet.create({
marginTop: Platform.OS === "android" ? -3 : undefined marginTop: Platform.OS === "android" ? -3 : undefined
} }
}); });
export default withTheme(SideBar);

View file

@ -1,11 +1,8 @@
// @flow // @flow
import * as SecureStore from 'expo-secure-store';
export const ERROR_TYPE = { export const ERROR_TYPE = {
BAD_CREDENTIALS: 0, BAD_CREDENTIALS: 0,
CONNECTION_ERROR: 1, CONNECTION_ERROR: 1
SAVE_TOKEN: 2,
}; };
const AUTH_URL = "https://www.amicale-insat.fr/api/password"; const AUTH_URL = "https://www.amicale-insat.fr/api/password";
@ -30,20 +27,6 @@ export default class ConnectionManager {
ConnectionManager.instance; ConnectionManager.instance;
} }
async saveLogin(email: string, token: string) {
this.#token = token;
this.#email = email;
return new Promise((resolve, reject) => {
SecureStore.setItemAsync('token', token)
.then(() => {
resolve(true);
})
.catch(error => {
reject(false);
});
});
}
async connect(email: string, password: string) { async connect(email: string, password: string) {
let data = { let data = {
email: email, email: email,
@ -59,31 +42,24 @@ export default class ConnectionManager {
body: JSON.stringify(data) body: JSON.stringify(data)
}).then(async (response) => response.json()) }).then(async (response) => response.json())
.then((data) => { .then((data) => {
if (this.isResponseValid(data)) { console.log(data);
if (data.state) { if (this.isResponseValid(data))
this.saveLogin(email, data.token) resolve({success: data.success, token: data.token});
.then(() => { else
resolve(true);
})
.catch(() => {
reject(ERROR_TYPE.SAVE_TOKEN);
});
} else
reject(ERROR_TYPE.BAD_CREDENTIALS); reject(ERROR_TYPE.BAD_CREDENTIALS);
} else
reject(ERROR_TYPE.CONNECTION_ERROR);
}) })
.catch((error) => { .catch((error) => {
console.log(error);
reject(ERROR_TYPE.CONNECTION_ERROR); reject(ERROR_TYPE.CONNECTION_ERROR);
}); });
}); });
} }
isResponseValid(response: Object) { isResponseValid(response: Object) {
let valid = response !== undefined && response.state !== undefined; return response !== undefined
if (valid && response.state) && response.success !== undefined
valid = valid && response.token !== undefined && response.token !== ''; && response.success
return valid; && response.token !== undefined;
} }
} }

View file

@ -12,9 +12,6 @@
}, },
"jest": { "jest": {
"preset": "react-native", "preset": "react-native",
"transformIgnorePatterns": [
"node_modules/(?!(jest-)?react-native|react-clone-referenced-element|@react-native-community|expo(nent)?|@expo(nent)?/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base)"
],
"setupFilesAfterEnv": [ "setupFilesAfterEnv": [
"jest-extended" "jest-extended"
] ]

View file

@ -1,18 +1,9 @@
// @flow // @flow
import * as React from 'react'; import * as React from 'react';
import { import {Keyboard, KeyboardAvoidingView, StyleSheet, TouchableWithoutFeedback, View} from "react-native";
Alert, import {Button, Text, TextInput, Title} from 'react-native-paper';
Keyboard,
KeyboardAvoidingView,
ScrollView,
StyleSheet,
TouchableWithoutFeedback,
View
} from "react-native";
import {Avatar, Button, Card, HelperText, Text, TextInput, withTheme} from 'react-native-paper';
import ConnectionManager from "../../managers/ConnectionManager"; import ConnectionManager from "../../managers/ConnectionManager";
import {openBrowser} from "../../utils/WebBrowser";
type Props = { type Props = {
navigation: Object, navigation: Object,
@ -21,239 +12,79 @@ type Props = {
type State = { type State = {
email: string, email: string,
password: string, password: string,
isEmailValidated: boolean,
isPasswordValidated: boolean,
loading: boolean,
} }
const ICON_AMICALE = require('../../assets/amicale.png');
const RESET_PASSWORD_LINK = "https://www.amicale-insat.fr/password/reset"; export default class LoginScreen extends React.Component<Props, State> {
const emailRegex = /^.+@.+\..+$/;
class LoginScreen extends React.Component<Props, State> {
state = { state = {
email: '', email: '',
password: '', password: '',
isEmailValidated: false,
isPasswordValidated: false,
loading: false,
}; };
colors: Object;
onEmailChange: Function; onEmailChange: Function;
onPasswordChange: Function; onPasswordChange: Function;
validateEmail: Function;
validatePassword: Function;
onSubmit: Function;
onEmailSubmit: Function;
onResetPasswordClick: Function;
passwordInputRef: Object; constructor() {
super();
constructor(props) {
super(props);
this.onEmailChange = this.onInputChange.bind(this, true); this.onEmailChange = this.onInputChange.bind(this, true);
this.onPasswordChange = this.onInputChange.bind(this, false); this.onPasswordChange = this.onInputChange.bind(this, false);
this.validateEmail = this.validateEmail.bind(this);
this.validatePassword = this.validatePassword.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.onEmailSubmit = this.onEmailSubmit.bind(this);
this.onResetPasswordClick = this.onResetPasswordClick.bind(this);
this.colors = props.theme.colors;
}
onResetPasswordClick() {
openBrowser(RESET_PASSWORD_LINK, this.colors.primary);
}
validateEmail() {
this.setState({isEmailValidated: true});
}
isEmailValid() {
return emailRegex.test(this.state.email);
}
shouldShowEmailError() {
return this.state.isEmailValidated && !this.isEmailValid();
}
validatePassword() {
this.setState({isPasswordValidated: true});
}
isPasswordValid() {
return this.state.password !== '';
}
shouldShowPasswordError() {
return this.state.isPasswordValidated && !this.isPasswordValid();
}
shouldEnableLogin() {
return this.isEmailValid() && this.isPasswordValid();
} }
onInputChange(isEmail: boolean, value: string) { onInputChange(isEmail: boolean, value: string) {
if (isEmail) { if (isEmail)
this.setState({ this.setState({email: value});
email: value, else
isEmailValidated: false, this.setState({password: value});
});
} else {
this.setState({
password: value,
isPasswordValidated: false,
});
}
}
onEmailSubmit() {
this.passwordInputRef.focus();
} }
onSubmit() { onSubmit() {
if (this.shouldEnableLogin()) { console.log('pressed');
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); console.log(data);
Alert.alert('COOL', 'ÇA MARCHE');
}) })
.catch((error) => { .catch((error) => {
console.log(error); console.log(error);
Alert.alert('ERREUR', 'MDP OU MAIL INVALIDE');
})
.finally(() => {
this.setState({loading: false});
}); });
} }
}
getFormInput() {
return (
<View>
<TextInput
label='Email'
mode='outlined'
value={this.state.email}
onChangeText={this.onEmailChange}
onBlur={this.validateEmail}
onSubmitEditing={this.onEmailSubmit}
error={this.shouldShowEmailError()}
textContentType={'emailAddress'}
autoCapitalize={'none'}
autoCompleteType={'email'}
autoCorrect={false}
keyboardType={'email-address'}
returnKeyType={'next'}
secureTextEntry={false}
/>
<HelperText
type="error"
visible={this.shouldShowEmailError()}
>
EMAIL INVALID
</HelperText>
<TextInput
ref={(ref) => {
this.passwordInputRef = ref;
}}
label='Password'
mode='outlined'
value={this.state.password}
onChangeText={this.onPasswordChange}
onBlur={this.validatePassword}
onSubmitEditing={this.onSubmit}
error={this.shouldShowPasswordError()}
textContentType={'password'}
autoCapitalize={'none'}
autoCompleteType={'password'}
autoCorrect={false}
keyboardType={'default'}
returnKeyType={'done'}
secureTextEntry={true}
/>
<HelperText
type="error"
visible={this.shouldShowPasswordError()}
>
PLS ENTER PASSWORD
</HelperText>
</View>
);
}
getMainCard() {
return (
<Card style={styles.card}>
<Card.Title
title="COUCOU"
subtitle="ENTREZ VOS IDENTIFIANTS"
left={(props) => <Avatar.Image
{...props}
source={ICON_AMICALE}
style={{backgroundColor: 'transparent'}}/>}
/>
<Card.Content>
{this.getFormInput()}
<Card.Actions>
<Button
icon="send"
mode="contained"
disabled={!this.shouldEnableLogin()}
loading={this.state.loading}
onPress={this.onSubmit}
style={{marginLeft: 'auto'}}>
LOGIN
</Button>
</Card.Actions>
</Card.Content>
</Card>
);
}
getSecondaryCard() {
return (
<Card style={styles.card}>
<Card.Content>
<Text>MDP OUBLIÉ ? t'es pas doué</Text>
<View style={styles.btnContainer}>
<Button
icon="reload"
mode="contained"
onPress={this.onResetPasswordClick}
style={{marginLeft: 'auto'}}>
RESET MDP
</Button>
</View>
<Text>PAS DE COMPTE ? DOMMAGE PASSE À L'AMICALE</Text>
</Card.Content>
</Card>
);
}
render() { render() {
return ( return (
<KeyboardAvoidingView <KeyboardAvoidingView
behavior={"height"} behavior={"padding"}
contentContainerStyle={styles.container} contentContainerStyle={styles.container}
style={styles.container} style={styles.container}
enabled
keyboardVerticalOffset={100}
> >
<ScrollView>
<TouchableWithoutFeedback onPress={Keyboard.dismiss}> <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
<View> <View style={styles.inner}>
{this.getMainCard()} <Title>COUCOU</Title>
{this.getSecondaryCard()} <Text>entrez vos identifiants</Text>
<TextInput
label='Email'
mode='outlined'
value={this.state.email}
onChangeText={this.onEmailChange}
/>
<TextInput
label='Password'
mode='outlined'
value={this.state.password}
onChangeText={this.onPasswordChange}
/>
<View style={styles.btnContainer}>
<Button icon="send" onPress={() => this.onSubmit()}>
LOGIN
</Button>
</View>
<Text>Pas de compte, dommage !</Text>
<View style={styles.btnContainer}>
<Button icon="send" onPress={() => console.log('Pressed')}>
Créer un compte
</Button>
</View>
</View> </View>
</TouchableWithoutFeedback> </TouchableWithoutFeedback>
</ScrollView>
</KeyboardAvoidingView> </KeyboardAvoidingView>
); );
} }
@ -261,12 +92,11 @@ class LoginScreen extends React.Component<Props, State> {
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
flex: 1, flex: 1
flexDirection: 'column',
justifyContent: 'center',
}, },
card: { inner: {
margin: 10, padding: 24,
flex: 1,
}, },
header: { header: {
fontSize: 36, fontSize: 36,
@ -274,9 +104,6 @@ const styles = StyleSheet.create({
}, },
textInput: {}, textInput: {},
btnContainer: { btnContainer: {
marginTop: 5, marginTop: 12
marginBottom: 10,
} }
}); });
export default withTheme(LoginScreen);

View file

@ -4,13 +4,13 @@ import * as React from 'react';
import {View} from 'react-native'; import {View} from 'react-native';
import i18n from "i18n-js"; import i18n from "i18n-js";
import DashboardItem from "../components/EventDashboardItem"; import DashboardItem from "../components/EventDashboardItem";
import * as WebBrowser from 'expo-web-browser';
import WebSectionList from "../components/WebSectionList"; import WebSectionList from "../components/WebSectionList";
import {Text, withTheme} from 'react-native-paper'; import {Text, withTheme} from 'react-native-paper';
import FeedItem from "../components/FeedItem"; import FeedItem from "../components/FeedItem";
import SquareDashboardItem from "../components/SquareDashboardItem"; import SquareDashboardItem from "../components/SquareDashboardItem";
import PreviewEventDashboardItem from "../components/PreviewEventDashboardItem"; import PreviewEventDashboardItem from "../components/PreviewEventDashboardItem";
import {stringToDate} from "../utils/Planning"; import {stringToDate} from "../utils/Planning";
import {openBrowser} from "../utils/WebBrowser";
// import DATA from "../dashboard_data.json"; // import DATA from "../dashboard_data.json";
@ -70,7 +70,7 @@ class HomeScreen extends React.Component<Props> {
} }
onTutorInsaClick() { onTutorInsaClick() {
openBrowser("https://www.etud.insa-toulouse.fr/~tutorinsa/", this.colors.primary); WebBrowser.openBrowserAsync("https://www.etud.insa-toulouse.fr/~tutorinsa/");
} }
onProximoClick() { onProximoClick() {
@ -402,7 +402,7 @@ class HomeScreen extends React.Component<Props> {
} }
openLink(link: string) { openLink(link: string) {
openBrowser(link, this.colors.primary); WebBrowser.openBrowserAsync(link);
} }
/** /**

View file

@ -1,11 +0,0 @@
// @flow
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
export function openBrowser(url: string, color: string) {
WebBrowser.openBrowserAsync(url, {
toolbarColor: color,
enableBarCollapsing: true,
});
}