Compare commits

..

No commits in common. "5e40e271b7a3d9608f477154e2399c470851c7cf" and "c5e79f45c3f529ec54c404a5e40cd7347ac180a5" have entirely different histories.

8 changed files with 62 additions and 443 deletions

View file

@ -1,8 +1,7 @@
import * as React from 'react';
import {StyleSheet, View} from "react-native";
import {ActivityIndicator, Subheading, withTheme} from 'react-native-paper';
import ConnectionManager, {ERROR_TYPE} from "../managers/ConnectionManager";
import {MaterialCommunityIcons} from "@expo/vector-icons";
import {View} from "react-native";
import {Text} from 'react-native-paper';
import ConnectionManager from "../managers/ConnectionManager";
type Props = {
navigation: Object,
@ -15,150 +14,39 @@ type State = {
loading: boolean,
}
class AuthenticatedScreen extends React.Component<Props, State> {
export default class AuthenticatedScreen extends React.Component<Props, State> {
state = {
loading: true,
};
currentUserToken: string;
connectionManager: ConnectionManager;
errorCode: number;
data: Object;
colors: Object;
constructor(props) {
super(props);
this.colors = props.theme.colors;
this.connectionManager = ConnectionManager.getInstance();
this.props.navigation.addListener('focus', this.onScreenFocus.bind(this));
this.fetchData();
}
onScreenFocus() {
if (this.currentUserToken !== this.connectionManager.getToken())
this.fetchData();
}
fetchData() {
if (!this.state.loading)
this.setState({loading: true});
this.connectionManager.isLoggedIn()
.then(() => {
this.setState({loading: false});
this.connectionManager.authenticatedRequest(this.props.link)
.then((data) => {
this.onFinishedLoading(data);
console.log(data);
})
.catch((error) => {
this.onFinishedLoading(undefined, error);
console.log(error);
});
})
.catch((error) => {
this.onFinishedLoading(undefined, ERROR_TYPE.BAD_CREDENTIALS);
.catch(() => {
this.props.navigation.navigate('LoginScreen');
});
}
onFinishedLoading(data: Object, error: number) {
this.data = data;
this.currentUserToken = data !== undefined
? this.connectionManager.getToken()
: '';
this.errorCode = error;
this.setState({loading: false});
}
/**
* Gets the loading indicator
*
* @return {*}
*/
getRenderLoading() {
return (
<View style={{
backgroundColor: this.colors.background,
position: 'absolute',
top: 0,
right: 0,
width: '100%',
height: '100%',
flex: 1,
alignItems: 'center',
justifyContent: 'center'
}}>
<ActivityIndicator
animating={true}
size={'large'}
color={this.colors.primary}/>
</View>
);
}
getErrorRender() {
let message;
let icon;
switch (this.errorCode) {
case ERROR_TYPE.BAD_CREDENTIALS:
message = "BAD_CREDENTIALS";
icon = "account-alert-outline";
break;
case ERROR_TYPE.CONNECTION_ERROR:
message = "CONNECTION_ERROR";
icon = "access-point-network-off";
break;
default:
message = "UNKNOWN";
icon = "alert-circle-outline";
break;
}
return (
<View style={styles.outer}>
<View style={styles.inner}>
<View style={styles.iconContainer}>
<MaterialCommunityIcons
name={icon}
size={150}
color={this.colors.textDisabled}/>
</View>
<Subheading style={{
...styles.subheading,
color: this.colors.textDisabled
}}>
{message}
</Subheading>
</View>
</View>
);
}
render() {
return (
this.state.loading
? this.getRenderLoading()
: (this.data !== undefined
? this.props.renderFunction(this.data)
: this.getErrorRender())
? <View><Text>LOADING</Text></View>
: this.props.renderFunction()
);
}
}
const styles = StyleSheet.create({
outer: {
flex: 1,
},
inner: {
marginTop: 'auto',
marginBottom: 'auto',
},
iconContainer: {
marginLeft: 'auto',
marginRight: 'auto',
marginBottom: 20
},
subheading: {
textAlign: 'center',
}
});
export default withTheme(AuthenticatedScreen);

View file

@ -29,6 +29,11 @@ class SideBar extends React.PureComponent<Props, State> {
dataSet: Array<Object>;
state = {
active: 'Home',
isLoggedIn: false,
};
getRenderItem: Function;
colors: Object;
@ -51,22 +56,21 @@ class SideBar extends React.PureComponent<Props, State> {
route: "Divider4"
},
{
name: i18n.t('screens.login'),
name: 'LOGIN',
route: "LoginScreen",
icon: "login",
onlyWhenLoggedOut: true,
},
{
name: i18n.t('screens.profile'),
route: "ProfileScreen",
icon: "account",
name: 'DISCONNECT',
action: () => this.onClickDisconnect(),
icon: "logout",
onlyWhenLoggedIn: true,
},
{
name: i18n.t('screens.logout'),
route: 'disconnect',
action: () => this.onClickDisconnect(),
icon: "logout",
name: 'PROFILE',
route: "ProfileScreen",
icon: "circle",
onlyWhenLoggedIn: true,
},
{
@ -146,29 +150,15 @@ class SideBar extends React.PureComponent<Props, State> {
this.getRenderItem = this.getRenderItem.bind(this);
this.colors = props.theme.colors;
ConnectionManager.getInstance().setLoginCallback((value) => this.onLoginStateChange(value));
this.state = {
active: 'Home',
isLoggedIn: false,
};
ConnectionManager.getInstance().isLoggedIn()
}
onClickDisconnect() {
console.log('coucou');
Alert.alert(
'DISCONNECT',
'DISCONNECT?',
[
{
text: 'YES', onPress: () => {
ConnectionManager.getInstance().disconnect()
.then(() => {
this.props.navigation.reset({
index: 0,
routes: [{name: 'Main'}],
});
});
}
},
{text: 'YES', onPress: () => ConnectionManager.getInstance().disconnect()},
{text: 'NO', undefined},
],
{cancelable: false},
@ -186,6 +176,7 @@ class SideBar extends React.PureComponent<Props, State> {
* @param item The item pressed
*/
onListItemPress(item: Object) {
console.log(item.action);
if (item.link !== undefined)
openBrowser(item.link, this.colors.primary);
else if (item.action !== undefined)

View file

@ -7,7 +7,6 @@ export const ERROR_TYPE = {
CONNECTION_ERROR: 1,
SAVE_TOKEN: 2,
NO_TOKEN: 3,
NO_CONSENT: 4,
};
const AUTH_URL = "https://www.amicale-insat.fr/api/password";
@ -30,10 +29,6 @@ export default class ConnectionManager {
ConnectionManager.instance;
}
getToken() {
return this.#token;
}
onLoginStateChange(newState: boolean) {
this.loginCallback(newState);
}
@ -49,11 +44,8 @@ export default class ConnectionManager {
else {
SecureStore.getItemAsync('token')
.then((token) => {
if (token !== null) {
this.onLoginStateChange(true);
resolve(token);
} else
reject(false);
this.onLoginStateChange(true);
resolve(token);
})
.catch(error => {
reject(false);
@ -90,16 +82,8 @@ export default class ConnectionManager {
}
async disconnect() {
return new Promise((resolve, reject) => {
SecureStore.deleteItemAsync('token')
.then(() => {
this.onLoginStateChange(false);
resolve(true);
})
.catch((error) => {
reject(false);
});
});
SecureStore.deleteItemAsync('token'); // TODO use promise
this.onLoginStateChange(false);
}
async connect(email: string, password: string) {
@ -117,7 +101,6 @@ export default class ConnectionManager {
body: JSON.stringify(data)
}).then(async (response) => response.json())
.then((data) => {
console.log(data);
if (this.isConnectionResponseValid(data)) {
if (data.state) {
this.saveLogin(email, data.token)
@ -127,9 +110,7 @@ export default class ConnectionManager {
.catch(() => {
reject(ERROR_TYPE.SAVE_TOKEN);
});
} else if (data.data.consent !== undefined && !data.data.consent)
reject(ERROR_TYPE.NO_CONSENT);
else
} else
reject(ERROR_TYPE.BAD_CREDENTIALS);
} else
reject(ERROR_TYPE.CONNECTION_ERROR);

View file

@ -203,7 +203,7 @@ function LoginStackComponent() {
options={({navigation}) => {
const openDrawer = getDrawerButton.bind(this, navigation);
return {
title: i18n.t('screens.login'),
title: 'LOGIN',
headerLeft: openDrawer
};
}}
@ -227,7 +227,7 @@ function ProfileStackComponent() {
options={({navigation}) => {
const openDrawer = getDrawerButton.bind(this, navigation);
return {
title: i18n.t('screens.profile'),
title: 'PROFILE',
headerLeft: openDrawer
};
}}

View file

@ -13,7 +13,6 @@ import {
import {Avatar, Button, Card, HelperText, Text, TextInput, withTheme} from 'react-native-paper';
import ConnectionManager, {ERROR_TYPE} from "../../managers/ConnectionManager";
import {openBrowser} from "../../utils/WebBrowser";
import i18n from 'i18n-js';
type Props = {
navigation: Object,
@ -138,33 +137,28 @@ class LoginScreen extends React.Component<Props, State> {
}
handleErrors(error: number) {
const title = i18n.t("loginScreen.errors.title");
let message;
switch (error) {
case ERROR_TYPE.CONNECTION_ERROR:
message = i18n.t("loginScreen.errors.connection");
Alert.alert('ERREUR', 'PB DE CONNEXION');
break;
case ERROR_TYPE.BAD_CREDENTIALS:
message = i18n.t("loginScreen.errors.credentials");
Alert.alert('ERREUR', 'MDP OU MAIL INVALIDE');
break;
case ERROR_TYPE.SAVE_TOKEN:
message = i18n.t("loginScreen.errors.saveToken");
break;
case ERROR_TYPE.NO_CONSENT:
message = i18n.t("loginScreen.errors.consent");
Alert.alert('ERREUR', 'IMPOSSIBLE DE SAUVEGARDER INFOS CONNEXION');
break;
default:
message = i18n.t("loginScreen.errors.unknown");
Alert.alert('ERREUR', 'ERREUR INCONNUE. CONTACTER ARNAUD');
break;
}
Alert.alert(title, message);
}
getFormInput() {
return (
<View>
<TextInput
label={i18n.t("loginScreen.email")}
label='Email'
mode='outlined'
value={this.state.email}
onChangeText={this.onEmailChange}
@ -183,13 +177,13 @@ class LoginScreen extends React.Component<Props, State> {
type="error"
visible={this.shouldShowEmailError()}
>
{i18n.t("loginScreen.emailError")}
EMAIL INVALID
</HelperText>
<TextInput
ref={(ref) => {
this.passwordInputRef = ref;
}}
label={i18n.t("loginScreen.password")}
label='Password'
mode='outlined'
value={this.state.password}
onChangeText={this.onPasswordChange}
@ -208,7 +202,7 @@ class LoginScreen extends React.Component<Props, State> {
type="error"
visible={this.shouldShowPasswordError()}
>
{i18n.t("loginScreen.passwordError")}
PLS ENTER PASSWORD
</HelperText>
</View>
);
@ -218,8 +212,8 @@ class LoginScreen extends React.Component<Props, State> {
return (
<Card style={styles.card}>
<Card.Title
title={i18n.t("loginScreen.title")}
subtitle={i18n.t("loginScreen.subtitle")}
title="COUCOU"
subtitle="ENTREZ VOS IDENTIFIANTS"
left={(props) => <Avatar.Image
{...props}
source={ICON_AMICALE}
@ -235,7 +229,7 @@ class LoginScreen extends React.Component<Props, State> {
loading={this.state.loading}
onPress={this.onSubmit}
style={{marginLeft: 'auto'}}>
{i18n.t("loginScreen.login")}
LOGIN
</Button>
</Card.Actions>
</Card.Content>
@ -247,17 +241,17 @@ class LoginScreen extends React.Component<Props, State> {
return (
<Card style={styles.card}>
<Card.Content>
<Text>{i18n.t("loginScreen.forgotPassword")}</Text>
<Text>MDP OUBLIÉ ? t'es pas doué</Text>
<View style={styles.btnContainer}>
<Button
icon="reload"
mode="contained"
onPress={this.onResetPasswordClick}
style={{marginLeft: 'auto'}}>
{i18n.t("loginScreen.resetPassword")}
RESET MDP
</Button>
</View>
<Text>{i18n.t("loginScreen.noAccount")}</Text>
<Text>PAS DE COMPTE ? DOMMAGE PASSE À L'AMICALE</Text>
</Card.Content>
</Card>
);

View file

@ -1,235 +1,46 @@
import * as React from 'react';
import {FlatList, StyleSheet} from "react-native";
import {Avatar, Button, Card, Divider, List, withTheme} from 'react-native-paper';
import {View} from "react-native";
import {Text, withTheme} from 'react-native-paper';
import AuthenticatedScreen from "../../components/AuthenticatedScreen";
import {openBrowser} from "../../utils/WebBrowser";
import ConnectionManager from "../../managers/ConnectionManager";
import HeaderButton from "../../components/HeaderButton";
type Props = {
navigation: Object,
theme: Object,
}
type State = {}
type State = {
}
class ProfileScreen extends React.Component<Props, State> {
state = {};
state = {
};
colors: Object;
data: Object;
flatListData: Array<Object>;
constructor(props) {
super(props);
this.colors = props.theme.colors;
this.onClickDisconnect = this.onClickDisconnect.bind(this);
this.flatListData = [
{id: 0},
{id: 1},
{id: 2},
]
}
componentDidMount() {
const rightButton = this.getHeaderButtons.bind(this);
this.props.navigation.setOptions({
headerRight: rightButton,
});
}
getHeaderButtons() {
return <HeaderButton icon={'logout'} onPress={this.onClickDisconnect}/>;
}
onClickDisconnect() {
ConnectionManager.getInstance().disconnect()
.then(() => {
this.props.navigation.reset({
index: 0,
routes: [{name: 'Main'}],
});
});
}
getScreen(data: Object) {
this.data = data;
return (
<FlatList
renderItem={item => this.getRenderItem(item)}
keyExtractor={item => item.id}
data={this.flatListData}
/>
<View>
<Text>PAGE</Text>
</View>
)
}
getRenderItem({item}: Object) {
switch (item.id) {
case 0:
return this.getPersonalCard();
case 1:
return this.getClubCard();
case 2:
return this.getMembershipCar();
}
}
getPersonalCard() {
return (
<Card style={styles.card}>
<Card.Title
title={this.data.first_name + ' ' + this.data.last_name}
subtitle={this.data.email}
left={(props) => <Avatar.Icon
{...props}
icon="account"
color={this.colors.primary}
style={styles.icon}
/>}
/>
<Card.Content>
<Divider/>
<List.Section>
<List.Subheader>INFORMATIONS PERSONNELLES</List.Subheader>
<List.Item
title={this.getFieldValue(this.data.birthday)}
left={props => <List.Icon {...props} icon="cake-variant"/>}
/>
<List.Item
title={this.getFieldValue(this.data.phone)}
left={props => <List.Icon {...props} icon="phone"/>}
/>
<List.Item
title={this.getFieldValue(this.data.email)}
left={props => <List.Icon {...props} icon="email"/>}
/>
<List.Item
title={this.getFieldValue(this.data.branch)}
left={props => <List.Icon {...props} icon="school"/>}
/>
</List.Section>
<Divider/>
<Card.Actions>
<Button
icon="account-edit"
mode="contained"
onPress={() => openBrowser(this.data.link, this.colors.primary)}
style={styles.editButton}>
EDITER INFOS
</Button>
</Card.Actions>
</Card.Content>
</Card>
);
}
getClubCard() {
return (
<Card style={styles.card}>
<Card.Title
title="CLUBS"
subtitle="LISTE DE VOS CLUBS"
left={(props) => <Avatar.Icon
{...props}
icon="account-group"
color={this.colors.primary}
style={styles.icon}
/>}
/>
<Card.Content>
<Divider/>
{this.getClubList(this.data.clubs)}
</Card.Content>
</Card>
);
}
getMembershipCar() {
return (
<Card style={styles.card}>
<Card.Title
title="ÉTAT COTISATION"
subtitle="CA PERMET D'ETRE COOL"
left={(props) => <Avatar.Icon
{...props}
icon="credit-card"
color={this.colors.primary}
style={styles.icon}
/>}
/>
<Card.Content>
<List.Section>
{this.getMembershipItem(this.data.validity)}
</List.Section>
</Card.Content>
</Card>
);
}
getClubList(list: Array<string>) {
let dataset = [];
for (let i = 0; i < list.length; i++) {
dataset.push({name: list[i]});
}
return (
<FlatList
renderItem={({item}) =>
<List.Item
title={item.name}
left={props => <List.Icon {...props} icon="chevron-right"/>}
/>
}
keyExtractor={item => item.name}
data={dataset}
/>
);
}
getMembershipItem(state: boolean) {
return (
<List.Item
title={state ? 'PAYÉ' : 'NON PAYÉ'}
left={props => <List.Icon
{...props}
color={state ? this.colors.success : this.colors.danger}
icon={state ? 'check' : 'close'}
/>}
/>
);
}
getFieldValue(field: ?string) {
return field !== null
? field
: 'NON RENSEIGNÉ';
}
render() {
return (
<AuthenticatedScreen
{...this.props}
link={'https://www.amicale-insat.fr/api/user/profile'}
renderFunction={(data) => this.getScreen(data)}
renderFunction={() => this.getScreen()}
/>
);
}
}
const styles = StyleSheet.create({
card: {
margin: 10,
},
icon: {
backgroundColor: 'transparent'
},
editButton: {
marginLeft: 'auto'
}
});
export default withTheme(ProfileScreen);

View file

@ -12,10 +12,7 @@
"bluemind": "INSA Mails",
"ent": "INSA ENT",
"about": "About",
"debug": "Debug",
"login": "Login",
"logout": "Logout",
"profile": "Profile"
"debug": "Debug"
},
"sidenav": {
"divider1": "Student websites",
@ -210,26 +207,6 @@
"computerRoom": "Computer",
"bibRoom": "Bib'Box"
},
"loginScreen": {
"title": "Amicale account",
"subtitle": "Please enter your credentials",
"email": "Email",
"emailError": "Please enter a valid email",
"password": "Password",
"passwordError": "Please enter a password",
"login": "Login",
"forgotPassword": "Forgot your password? Click on the button below to get a new one.",
"resetPassword": "Reset Password",
"noAccount": "No Account? Go to the Amicale's building during open hours to create one.",
"errors": {
"title": "Error!",
"connection": "Network error. Please check your internet connection.",
"credentials": "Email or password invalid.",
"saveToken": "Failed to save connection information, please contact support.",
"consent": "You did not give your consent for data processing to the Amicale.",
"unknown": "Unknown error, please contact support."
}
},
"general": {
"loading": "Loading...",
"networkError": "Unable to contact servers. Make sure you are connected to Internet."

View file

@ -12,10 +12,7 @@
"bluemind": "Mails INSA",
"ent": "ENT INSA",
"about": "À Propos",
"debug": "Debug",
"login": "Se connecter",
"logout": "Se déconnecter",
"profile": "Profile"
"debug": "Debug"
},
"sidenav": {
"divider1": "Sites étudiants",
@ -211,26 +208,6 @@
"computerRoom": "Ordi",
"bibRoom": "Bib'Box"
},
"loginScreen": {
"title": "Compte Amicale",
"subtitle": "Entrez vos identifiants",
"email": "Email",
"emailError": "Merci d'entrer un email valide",
"password": "Mot de passe",
"passwordError": "Merci d'entrer un mot de passe",
"login": "Se Connecter",
"forgotPassword": "Mot de passe oublié ? Cliquez sur le bouton ci-dessous pour en créer un nouveau.",
"resetPassword": "Réinitialiser mot de passe",
"noAccount": "Pas de compte ? Passez à l'Amicale pendant une perm pour en créer un.",
"errors": {
"title": "Erreur !",
"connection": "Erreur de réseau. Merci de vérifier votre connexion Internet.",
"credentials": "Email ou mot de passe invalide.",
"saveToken": "Erreur de sauvegarde des informations de connexion, merci de contacter le support.",
"consent": "Vous n'avez pas donné votre consentement pour l'utilisation de vos données personnelles.",
"unknown": "Erreur inconnue, merci de contacter le support."
}
},
"general": {
"loading": "Chargement...",
"networkError": "Impossible de contacter les serveurs. Assurez-vous d'être connecté à internet."