Compare commits
No commits in common. "2010170e8ba3d57295147e1749bdd4545054c1c8" and "14856616df9f48d5814b64edae5108d4404a1ca3" have entirely different histories.
2010170e8b
...
14856616df
15 changed files with 21 additions and 1656 deletions
|
|
@ -1,292 +0,0 @@
|
|||
import React from 'react';
|
||||
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 c = ConnectionManager.getInstance();
|
||||
|
||||
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();
|
||||
});
|
||||
return expect(c.recoverLogin()).rejects.toBe(false);
|
||||
});
|
||||
|
||||
test('recoverLogin success crypto', () => {
|
||||
jest.spyOn(SecureStore, 'getItemAsync').mockImplementationOnce(() => {
|
||||
return Promise.resolve('token1');
|
||||
});
|
||||
return expect(c.recoverLogin()).resolves.toBe('token1');
|
||||
});
|
||||
|
||||
test('saveLogin success', () => {
|
||||
jest.spyOn(SecureStore, 'setItemAsync').mockImplementationOnce(() => {
|
||||
return Promise.resolve();
|
||||
});
|
||||
return expect(c.saveLogin('email', 'token2')).resolves.toBeTruthy();
|
||||
});
|
||||
|
||||
test('saveLogin error', () => {
|
||||
jest.spyOn(SecureStore, 'setItemAsync').mockImplementationOnce(() => {
|
||||
return Promise.reject();
|
||||
});
|
||||
return expect(c.saveLogin('email', 'token3')).rejects.toBeFalsy();
|
||||
});
|
||||
|
||||
test('recoverLogin error crypto with saved token', () => {
|
||||
jest.spyOn(SecureStore, 'getItemAsync').mockImplementationOnce(() => {
|
||||
return Promise.reject();
|
||||
});
|
||||
return expect(c.recoverLogin()).resolves.toBe('token2');
|
||||
});
|
||||
|
||||
test('recoverLogin success saved', () => {
|
||||
return expect(c.recoverLogin()).resolves.toBe('token2');
|
||||
});
|
||||
|
||||
test('isRequestResponseValid', () => {
|
||||
let json = {
|
||||
state: true,
|
||||
data: {}
|
||||
};
|
||||
expect(c.isRequestResponseValid(json)).toBeTrue();
|
||||
json = {
|
||||
state: false,
|
||||
data: {}
|
||||
};
|
||||
expect(c.isRequestResponseValid(json)).toBeTrue();
|
||||
json = {
|
||||
state: false,
|
||||
message: 'coucou',
|
||||
data: {truc: 'machin'}
|
||||
};
|
||||
expect(c.isRequestResponseValid(json)).toBeTrue();
|
||||
json = {
|
||||
message: 'coucou'
|
||||
};
|
||||
expect(c.isRequestResponseValid(json)).toBeFalse();
|
||||
json = {
|
||||
state: 'coucou'
|
||||
};
|
||||
expect(c.isRequestResponseValid(json)).toBeFalse();
|
||||
json = {
|
||||
state: true,
|
||||
};
|
||||
expect(c.isRequestResponseValid(json)).toBeFalse();
|
||||
});
|
||||
|
||||
test("isConnectionResponseValid", () => {
|
||||
let json = {
|
||||
state: true,
|
||||
message: 'Connexion confirmée',
|
||||
token: 'token'
|
||||
};
|
||||
expect(c.isConnectionResponseValid(json)).toBeTrue();
|
||||
json = {
|
||||
state: true,
|
||||
token: 'token'
|
||||
};
|
||||
expect(c.isConnectionResponseValid(json)).toBeTrue();
|
||||
json = {
|
||||
state: false,
|
||||
};
|
||||
expect(c.isConnectionResponseValid(json)).toBeTrue();
|
||||
|
||||
json = {
|
||||
state: true,
|
||||
message: 'Connexion confirmée',
|
||||
token: ''
|
||||
};
|
||||
expect(c.isConnectionResponseValid(json)).toBeFalse();
|
||||
json = {
|
||||
state: true,
|
||||
message: 'Connexion confirmée',
|
||||
};
|
||||
expect(c.isConnectionResponseValid(json)).toBeFalse();
|
||||
json = {
|
||||
state: 'coucou',
|
||||
message: 'Connexion confirmée',
|
||||
token: 'token'
|
||||
};
|
||||
expect(c.isConnectionResponseValid(json)).toBeFalse();
|
||||
json = {
|
||||
state: true,
|
||||
message: 'Connexion confirmée',
|
||||
token: 2
|
||||
};
|
||||
expect(c.isConnectionResponseValid(json)).toBeFalse();
|
||||
json = {
|
||||
coucou: 'coucou',
|
||||
message: 'Connexion confirmée',
|
||||
token: 'token'
|
||||
};
|
||||
expect(c.isConnectionResponseValid(json)).toBeFalse();
|
||||
});
|
||||
|
||||
|
||||
test("connect bad credentials", () => {
|
||||
jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
|
||||
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);
|
||||
});
|
||||
|
||||
test("connect good credentials", () => {
|
||||
jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
|
||||
return Promise.resolve({
|
||||
json: () => {
|
||||
return {
|
||||
state: true,
|
||||
message: 'Connexion confirmée',
|
||||
token: 'token'
|
||||
}
|
||||
},
|
||||
})
|
||||
});
|
||||
jest.spyOn(ConnectionManager.prototype, 'saveLogin').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(ConnectionManager.prototype, 'saveLogin').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("authenticatedRequest success", () => {
|
||||
jest.spyOn(ConnectionManager.prototype, 'recoverLogin').mockImplementationOnce(() => {
|
||||
return Promise.resolve('token');
|
||||
});
|
||||
jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
|
||||
return Promise.resolve({
|
||||
json: () => {
|
||||
return {state: true, message: 'Connexion vérifiée', data: {coucou: 'toi'}}
|
||||
},
|
||||
})
|
||||
});
|
||||
return expect(c.authenticatedRequest('https://www.amicale-insat.fr/api/token/check'))
|
||||
.resolves.toStrictEqual({coucou: 'toi'});
|
||||
});
|
||||
|
||||
test("authenticatedRequest error wrong token", () => {
|
||||
jest.spyOn(ConnectionManager.prototype, 'recoverLogin').mockImplementationOnce(() => {
|
||||
return Promise.resolve('token');
|
||||
});
|
||||
jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
|
||||
return Promise.resolve({
|
||||
json: () => {
|
||||
return {state: false, message: 'Le champ token sélectionné est invalide.'}
|
||||
},
|
||||
})
|
||||
});
|
||||
return expect(c.authenticatedRequest('https://www.amicale-insat.fr/api/token/check'))
|
||||
.rejects.toBe(ERROR_TYPE.BAD_CREDENTIALS);
|
||||
});
|
||||
|
||||
test("authenticatedRequest error bogus response", () => {
|
||||
jest.spyOn(ConnectionManager.prototype, 'recoverLogin').mockImplementationOnce(() => {
|
||||
return Promise.resolve('token');
|
||||
});
|
||||
jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
|
||||
return Promise.resolve({
|
||||
json: () => {
|
||||
return {state: true, message: 'Connexion vérifiée'}
|
||||
},
|
||||
})
|
||||
});
|
||||
return expect(c.authenticatedRequest('https://www.amicale-insat.fr/api/token/check'))
|
||||
.rejects.toBe(ERROR_TYPE.CONNECTION_ERROR);
|
||||
});
|
||||
|
||||
test("authenticatedRequest connection error", () => {
|
||||
jest.spyOn(ConnectionManager.prototype, 'recoverLogin').mockImplementationOnce(() => {
|
||||
return Promise.resolve('token');
|
||||
});
|
||||
jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
|
||||
return Promise.reject()
|
||||
});
|
||||
return expect(c.authenticatedRequest('https://www.amicale-insat.fr/api/token/check'))
|
||||
.rejects.toBe(ERROR_TYPE.CONNECTION_ERROR);
|
||||
});
|
||||
|
||||
test("authenticatedRequest error no token", () => {
|
||||
jest.spyOn(ConnectionManager.prototype, 'recoverLogin').mockImplementationOnce(() => {
|
||||
return Promise.reject(false);
|
||||
});
|
||||
jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
|
||||
return Promise.resolve({
|
||||
json: () => {
|
||||
return {state: false, message: 'Le champ token sélectionné est invalide.'}
|
||||
},
|
||||
})
|
||||
});
|
||||
return expect(c.authenticatedRequest('https://www.amicale-insat.fr/api/token/check'))
|
||||
.rejects.toBe(ERROR_TYPE.NO_TOKEN);
|
||||
});
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
import * as React from 'react';
|
||||
import {Button, Dialog, Paragraph, Portal, withTheme} from 'react-native-paper';
|
||||
|
||||
type Props = {
|
||||
navigation: Object,
|
||||
visible: boolean,
|
||||
onDismiss: Function,
|
||||
title: string,
|
||||
message: string,
|
||||
}
|
||||
|
||||
class AlertDialog extends React.PureComponent<Props> {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Portal>
|
||||
<Dialog
|
||||
visible={this.props.visible}
|
||||
onDismiss={this.props.onDismiss}>
|
||||
<Dialog.Title>{this.props.title}</Dialog.Title>
|
||||
<Dialog.Content>
|
||||
<Paragraph>{this.props.message}</Paragraph>
|
||||
</Dialog.Content>
|
||||
<Dialog.Actions>
|
||||
<Button onPress={this.props.onDismiss}>OK</Button>
|
||||
</Dialog.Actions>
|
||||
</Dialog>
|
||||
</Portal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withTheme(AlertDialog);
|
||||
|
|
@ -1,139 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {View} from "react-native";
|
||||
import {ActivityIndicator, withTheme} from 'react-native-paper';
|
||||
import ConnectionManager, {ERROR_TYPE} from "../managers/ConnectionManager";
|
||||
import NetworkErrorComponent from "./NetworkErrorComponent";
|
||||
import i18n from 'i18n-js';
|
||||
|
||||
type Props = {
|
||||
navigation: Object,
|
||||
theme: Object,
|
||||
link: string,
|
||||
renderFunction: Function,
|
||||
}
|
||||
|
||||
type State = {
|
||||
loading: boolean,
|
||||
}
|
||||
|
||||
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.connectionManager.authenticatedRequest(this.props.link)
|
||||
.then((data) => {
|
||||
this.onFinishedLoading(data, -1);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.onFinishedLoading(undefined, error);
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
this.onFinishedLoading(undefined, ERROR_TYPE.BAD_CREDENTIALS);
|
||||
});
|
||||
};
|
||||
|
||||
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 = i18n.t("loginScreen.errors.credentials");
|
||||
icon = "account-alert-outline";
|
||||
break;
|
||||
case ERROR_TYPE.CONNECTION_ERROR:
|
||||
message = i18n.t("loginScreen.errors.connection");
|
||||
icon = "access-point-network-off";
|
||||
break;
|
||||
default:
|
||||
message = i18n.t("loginScreen.errors.unknown");
|
||||
icon = "alert-circle-outline";
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<NetworkErrorComponent
|
||||
{...this.props}
|
||||
icon={icon}
|
||||
message={message}
|
||||
onRefresh={this.fetchData}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
this.state.loading
|
||||
? this.getRenderLoading()
|
||||
: (this.data !== undefined
|
||||
? this.props.renderFunction(this.data)
|
||||
: this.getErrorRender())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withTheme(AuthenticatedScreen);
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {ActivityIndicator, Button, Dialog, Paragraph, Portal, withTheme} from 'react-native-paper';
|
||||
import ConnectionManager from "../managers/ConnectionManager";
|
||||
import i18n from 'i18n-js';
|
||||
|
||||
type Props = {
|
||||
navigation: Object,
|
||||
visible: boolean,
|
||||
onDismiss: Function,
|
||||
}
|
||||
|
||||
type State = {
|
||||
loading: boolean,
|
||||
}
|
||||
|
||||
class LogoutDialog extends React.PureComponent<Props, State> {
|
||||
|
||||
colors: Object;
|
||||
|
||||
state = {
|
||||
loading: false,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.colors = props.theme.colors;
|
||||
}
|
||||
|
||||
onClickAccept = () => {
|
||||
this.setState({loading: true});
|
||||
ConnectionManager.getInstance().disconnect()
|
||||
.then(() => {
|
||||
this.props.onDismiss();
|
||||
this.setState({loading: false});
|
||||
this.props.navigation.reset({
|
||||
index: 0,
|
||||
routes: [{name: 'Main'}],
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
onDismiss = () => {
|
||||
if (!this.state.loading)
|
||||
this.props.onDismiss();
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Portal>
|
||||
<Dialog
|
||||
visible={this.props.visible}
|
||||
onDismiss={this.onDismiss}>
|
||||
<Dialog.Title>
|
||||
{this.state.loading
|
||||
? i18n.t("dialog.disconnect.titleLoading")
|
||||
: i18n.t("dialog.disconnect.title")}
|
||||
</Dialog.Title>
|
||||
<Dialog.Content>
|
||||
{this.state.loading
|
||||
? <ActivityIndicator
|
||||
animating={true}
|
||||
size={'large'}
|
||||
color={this.colors.primary}/>
|
||||
: <Paragraph>{i18n.t("dialog.disconnect.message")}</Paragraph>
|
||||
}
|
||||
</Dialog.Content>
|
||||
{this.state.loading
|
||||
? null
|
||||
: <Dialog.Actions>
|
||||
<Button onPress={this.onDismiss} style={{marginRight: 10}}>{i18n.t("dialog.cancel")}</Button>
|
||||
<Button onPress={this.onClickAccept}>{i18n.t("dialog.yes")}</Button>
|
||||
</Dialog.Actions>
|
||||
}
|
||||
|
||||
</Dialog>
|
||||
</Portal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withTheme(LogoutDialog);
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Button, Subheading, withTheme} from 'react-native-paper';
|
||||
import {StyleSheet, View} from "react-native";
|
||||
import {MaterialCommunityIcons} from "@expo/vector-icons";
|
||||
import i18n from 'i18n-js';
|
||||
|
||||
type Props = {
|
||||
navigation: Object,
|
||||
message: string,
|
||||
icon: string,
|
||||
onRefresh: Function,
|
||||
}
|
||||
|
||||
type State = {
|
||||
refreshing: boolean,
|
||||
}
|
||||
|
||||
class NetworkErrorComponent extends React.PureComponent<Props, State> {
|
||||
|
||||
colors: Object;
|
||||
|
||||
state = {
|
||||
refreshing: false,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.colors = props.theme.colors;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.outer}>
|
||||
<View style={styles.inner}>
|
||||
<View style={styles.iconContainer}>
|
||||
<MaterialCommunityIcons
|
||||
name={this.props.icon}
|
||||
size={150}
|
||||
color={this.colors.textDisabled}/>
|
||||
</View>
|
||||
<Subheading style={{
|
||||
...styles.subheading,
|
||||
color: this.colors.textDisabled
|
||||
}}>
|
||||
{this.props.message}
|
||||
</Subheading>
|
||||
<Button
|
||||
mode={'contained'}
|
||||
icon={'refresh'}
|
||||
onPress={this.props.onRefresh}
|
||||
style={styles.button}
|
||||
>
|
||||
{i18n.t("general.retry")}
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
outer: {
|
||||
flex: 1,
|
||||
},
|
||||
inner: {
|
||||
marginTop: 'auto',
|
||||
marginBottom: 'auto',
|
||||
},
|
||||
iconContainer: {
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
marginBottom: 20
|
||||
},
|
||||
subheading: {
|
||||
textAlign: 'center',
|
||||
},
|
||||
button: {
|
||||
marginTop: 10,
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
export default withTheme(NetworkErrorComponent);
|
||||
|
|
@ -1,38 +1,36 @@
|
|||
// @flow
|
||||
|
||||
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 {openBrowser} from "../utils/WebBrowser";
|
||||
import * as WebBrowser from 'expo-web-browser';
|
||||
import SidebarDivider from "./SidebarDivider";
|
||||
import SidebarItem from "./SidebarItem";
|
||||
import {TouchableRipple, withTheme} from "react-native-paper";
|
||||
import ConnectionManager from "../managers/ConnectionManager";
|
||||
import LogoutDialog from "./LogoutDialog";
|
||||
import {TouchableRipple} from "react-native-paper";
|
||||
|
||||
const deviceWidth = Dimensions.get("window").width;
|
||||
|
||||
type Props = {
|
||||
navigation: Object,
|
||||
state: Object,
|
||||
theme: Object,
|
||||
};
|
||||
|
||||
type State = {
|
||||
active: string,
|
||||
isLoggedIn: boolean,
|
||||
dialogVisible: boolean,
|
||||
};
|
||||
|
||||
/**
|
||||
* 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>;
|
||||
|
||||
state = {
|
||||
active: 'Home',
|
||||
};
|
||||
|
||||
getRenderItem: Function;
|
||||
colors: Object;
|
||||
|
||||
/**
|
||||
* Generate the dataset
|
||||
|
|
@ -48,29 +46,6 @@ class SideBar extends React.PureComponent<Props, State> {
|
|||
route: "Main",
|
||||
icon: "home",
|
||||
},
|
||||
{
|
||||
name: i18n.t('sidenav.divider4'),
|
||||
route: "Divider4"
|
||||
},
|
||||
{
|
||||
name: i18n.t('screens.login'),
|
||||
route: "LoginScreen",
|
||||
icon: "login",
|
||||
onlyWhenLoggedOut: true,
|
||||
},
|
||||
{
|
||||
name: i18n.t('screens.profile'),
|
||||
route: "ProfileScreen",
|
||||
icon: "account",
|
||||
onlyWhenLoggedIn: true,
|
||||
},
|
||||
{
|
||||
name: i18n.t('screens.logout'),
|
||||
route: 'disconnect',
|
||||
action: this.showDisconnectDialog,
|
||||
icon: "logout",
|
||||
onlyWhenLoggedIn: true,
|
||||
},
|
||||
{
|
||||
name: i18n.t('sidenav.divider2'),
|
||||
route: "Divider2"
|
||||
|
|
@ -146,23 +121,6 @@ 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,
|
||||
dialogVisible: false,
|
||||
};
|
||||
ConnectionManager.getInstance().isLoggedIn().then(data => undefined).catch(error => undefined);
|
||||
}
|
||||
|
||||
showDisconnectDialog = () => this.setState({ dialogVisible: true });
|
||||
|
||||
hideDisconnectDialog = () => this.setState({ dialogVisible: false });
|
||||
|
||||
|
||||
onLoginStateChange(isLoggedIn: boolean) {
|
||||
this.setState({isLoggedIn: isLoggedIn});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -172,12 +130,10 @@ class SideBar extends React.PureComponent<Props, State> {
|
|||
* @param item The item pressed
|
||||
*/
|
||||
onListItemPress(item: Object) {
|
||||
if (item.link !== undefined)
|
||||
openBrowser(item.link, this.colors.primary);
|
||||
else if (item.action !== undefined)
|
||||
item.action();
|
||||
else
|
||||
if (item.link === undefined)
|
||||
this.props.navigation.navigate(item.route);
|
||||
else
|
||||
WebBrowser.openBrowserAsync(item.link);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -198,11 +154,7 @@ class SideBar extends React.PureComponent<Props, State> {
|
|||
*/
|
||||
getRenderItem({item}: Object) {
|
||||
const onListItemPress = this.onListItemPress.bind(this, item);
|
||||
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) {
|
||||
if (item.icon !== undefined) {
|
||||
return (
|
||||
<SidebarItem
|
||||
title={item.name}
|
||||
|
|
@ -236,11 +188,6 @@ class SideBar extends React.PureComponent<Props, State> {
|
|||
keyExtractor={this.listKeyExtractor}
|
||||
renderItem={this.getRenderItem}
|
||||
/>
|
||||
<LogoutDialog
|
||||
{...this.props}
|
||||
visible={this.state.dialogVisible}
|
||||
onDismiss={this.hideDisconnectDialog}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
@ -266,5 +213,3 @@ const styles = StyleSheet.create({
|
|||
marginTop: Platform.OS === "android" ? -3 : undefined
|
||||
}
|
||||
});
|
||||
|
||||
export default withTheme(SideBar);
|
||||
|
|
|
|||
|
|
@ -1,197 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import * as SecureStore from 'expo-secure-store';
|
||||
|
||||
export const ERROR_TYPE = {
|
||||
BAD_CREDENTIALS: 0,
|
||||
CONNECTION_ERROR: 1,
|
||||
SAVE_TOKEN: 2,
|
||||
NO_TOKEN: 3,
|
||||
NO_CONSENT: 4,
|
||||
};
|
||||
|
||||
const AUTH_URL = "https://www.amicale-insat.fr/api/password";
|
||||
|
||||
export default class ConnectionManager {
|
||||
static instance: ConnectionManager | null = null;
|
||||
|
||||
#email: string;
|
||||
#token: string;
|
||||
|
||||
loginCallback: Function;
|
||||
|
||||
/**
|
||||
* Get this class instance or create one if none is found
|
||||
* @returns {ConnectionManager}
|
||||
*/
|
||||
static getInstance(): ConnectionManager {
|
||||
return ConnectionManager.instance === null ?
|
||||
ConnectionManager.instance = new ConnectionManager() :
|
||||
ConnectionManager.instance;
|
||||
}
|
||||
|
||||
getToken() {
|
||||
return this.#token;
|
||||
}
|
||||
|
||||
onLoginStateChange(newState: boolean) {
|
||||
this.loginCallback(newState);
|
||||
}
|
||||
|
||||
setLoginCallback(callback: Function) {
|
||||
this.loginCallback = callback;
|
||||
}
|
||||
|
||||
async recoverLogin() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.#token !== undefined)
|
||||
resolve(this.#token);
|
||||
else {
|
||||
SecureStore.getItemAsync('token')
|
||||
.then((token) => {
|
||||
if (token !== null) {
|
||||
this.onLoginStateChange(true);
|
||||
resolve(token);
|
||||
} else
|
||||
reject(false);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(false);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async isLoggedIn() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.recoverLogin()
|
||||
.then(() => {
|
||||
resolve(true);
|
||||
})
|
||||
.catch(() => {
|
||||
reject(false);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
async saveLogin(email: string, token: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
SecureStore.setItemAsync('token', token)
|
||||
.then(() => {
|
||||
this.#token = token;
|
||||
this.#email = email;
|
||||
this.onLoginStateChange(true);
|
||||
resolve(true);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async disconnect() {
|
||||
return new Promise((resolve, reject) => {
|
||||
SecureStore.deleteItemAsync('token')
|
||||
.then(() => {
|
||||
this.onLoginStateChange(false);
|
||||
resolve(true);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async connect(email: string, password: string) {
|
||||
let data = {
|
||||
email: email,
|
||||
password: password,
|
||||
};
|
||||
return new Promise((resolve, reject) => {
|
||||
fetch(AUTH_URL, {
|
||||
method: 'POST',
|
||||
headers: new Headers({
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: JSON.stringify(data)
|
||||
}).then(async (response) => response.json())
|
||||
.then((data) => {
|
||||
if (this.isConnectionResponseValid(data)) {
|
||||
if (data.state) {
|
||||
this.saveLogin(email, data.token)
|
||||
.then(() => {
|
||||
resolve(true);
|
||||
})
|
||||
.catch(() => {
|
||||
reject(ERROR_TYPE.SAVE_TOKEN);
|
||||
});
|
||||
} else if (data.data.consent !== undefined && !data.data.consent)
|
||||
reject(ERROR_TYPE.NO_CONSENT);
|
||||
else
|
||||
reject(ERROR_TYPE.BAD_CREDENTIALS);
|
||||
} else
|
||||
reject(ERROR_TYPE.CONNECTION_ERROR);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(ERROR_TYPE.CONNECTION_ERROR);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
isRequestResponseValid(response: Object) {
|
||||
let valid = response !== undefined
|
||||
&& response.state !== undefined
|
||||
&& typeof response.state === "boolean";
|
||||
|
||||
if (valid && response.state)
|
||||
valid = valid
|
||||
&& response.data !== undefined
|
||||
&& typeof response.data === "object";
|
||||
return valid;
|
||||
}
|
||||
|
||||
isConnectionResponseValid(response: Object) {
|
||||
let valid = response !== undefined
|
||||
&& response.state !== undefined
|
||||
&& typeof response.state === "boolean";
|
||||
|
||||
if (valid && response.state)
|
||||
valid = valid
|
||||
&& response.token !== undefined
|
||||
&& response.token !== ''
|
||||
&& typeof response.token === "string";
|
||||
return valid;
|
||||
}
|
||||
|
||||
async authenticatedRequest(url: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.recoverLogin()
|
||||
.then(token => {
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
headers: new Headers({
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: JSON.stringify({token: token})
|
||||
}).then(async (response) => response.json())
|
||||
.then((data) => {
|
||||
if (this.isRequestResponseValid(data)) {
|
||||
if (data.state)
|
||||
resolve(data.data);
|
||||
else
|
||||
reject(ERROR_TYPE.BAD_CREDENTIALS);
|
||||
} else
|
||||
reject(ERROR_TYPE.CONNECTION_ERROR);
|
||||
})
|
||||
.catch(() => {
|
||||
reject(ERROR_TYPE.CONNECTION_ERROR);
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
reject(ERROR_TYPE.NO_TOKEN);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -15,8 +15,6 @@ import Sidebar from "../components/Sidebar";
|
|||
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,
|
||||
|
|
@ -188,54 +186,6 @@ function TetrisStackComponent() {
|
|||
);
|
||||
}
|
||||
|
||||
const LoginStack = createStackNavigator();
|
||||
|
||||
function LoginStackComponent() {
|
||||
return (
|
||||
<LoginStack.Navigator
|
||||
initialRouteName="LoginScreen"
|
||||
headerMode="float"
|
||||
screenOptions={defaultScreenOptions}
|
||||
>
|
||||
<LoginStack.Screen
|
||||
name="LoginScreen"
|
||||
component={LoginScreen}
|
||||
options={({navigation}) => {
|
||||
const openDrawer = getDrawerButton.bind(this, navigation);
|
||||
return {
|
||||
title: i18n.t('screens.login'),
|
||||
headerLeft: openDrawer
|
||||
};
|
||||
}}
|
||||
/>
|
||||
</LoginStack.Navigator>
|
||||
);
|
||||
}
|
||||
|
||||
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: i18n.t('screens.profile'),
|
||||
headerLeft: openDrawer
|
||||
};
|
||||
}}
|
||||
/>
|
||||
</ProfileStack.Navigator>
|
||||
);
|
||||
}
|
||||
|
||||
const Drawer = createDrawerNavigator();
|
||||
|
||||
function getDrawerContent(props) {
|
||||
|
|
@ -281,14 +231,6 @@ export default function DrawerNavigator() {
|
|||
name="TetrisScreen"
|
||||
component={TetrisStackComponent}
|
||||
/>
|
||||
<Drawer.Screen
|
||||
name="LoginScreen"
|
||||
component={LoginStackComponent}
|
||||
/>
|
||||
<Drawer.Screen
|
||||
name="ProfileScreen"
|
||||
component={ProfileStackComponent}
|
||||
/>
|
||||
</Drawer.Navigator>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
10
package.json
10
package.json
|
|
@ -12,12 +12,7 @@
|
|||
},
|
||||
"jest": {
|
||||
"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": [
|
||||
"jest-extended"
|
||||
]
|
||||
"setupFilesAfterEnv": ["jest-extended"]
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo/vector-icons": "~10.0.0",
|
||||
|
|
@ -47,8 +42,7 @@
|
|||
"react-native-screens": "2.0.0-alpha.12",
|
||||
"react-native-webview": "7.4.3",
|
||||
"react-native-appearance": "~0.3.1",
|
||||
"expo-linear-gradient": "~8.0.0",
|
||||
"expo-secure-store": "~8.0.0"
|
||||
"expo-linear-gradient": "~8.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-preset-expo": "^8.0.0",
|
||||
|
|
|
|||
|
|
@ -1,323 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Keyboard, KeyboardAvoidingView, ScrollView, StyleSheet, TouchableWithoutFeedback, View} from "react-native";
|
||||
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';
|
||||
import AlertDialog from "../../components/AlertDialog";
|
||||
|
||||
type Props = {
|
||||
navigation: Object,
|
||||
}
|
||||
|
||||
type State = {
|
||||
email: string,
|
||||
password: string,
|
||||
isEmailValidated: boolean,
|
||||
isPasswordValidated: boolean,
|
||||
loading: boolean,
|
||||
dialogVisible: boolean,
|
||||
dialogTitle: string,
|
||||
dialogMessage: string,
|
||||
}
|
||||
|
||||
const ICON_AMICALE = require('../../assets/amicale.png');
|
||||
|
||||
const RESET_PASSWORD_LINK = "https://www.amicale-insat.fr/password/reset";
|
||||
|
||||
const emailRegex = /^.+@.+\..+$/;
|
||||
|
||||
class LoginScreen extends React.Component<Props, State> {
|
||||
|
||||
state = {
|
||||
email: '',
|
||||
password: '',
|
||||
isEmailValidated: false,
|
||||
isPasswordValidated: false,
|
||||
loading: false,
|
||||
dialogVisible: false,
|
||||
dialogTitle: '',
|
||||
dialogMessage: '',
|
||||
};
|
||||
|
||||
colors: Object;
|
||||
|
||||
onEmailChange: Function;
|
||||
onPasswordChange: Function;
|
||||
validateEmail: Function;
|
||||
validatePassword: Function;
|
||||
onSubmit: Function;
|
||||
onEmailSubmit: Function;
|
||||
onResetPasswordClick: Function;
|
||||
|
||||
passwordInputRef: Object;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onEmailChange = this.onInputChange.bind(this, true);
|
||||
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;
|
||||
}
|
||||
|
||||
showErrorDialog = (title: string, message: string) =>
|
||||
this.setState({
|
||||
dialogTitle: title,
|
||||
dialogMessage: message,
|
||||
dialogVisible: true
|
||||
});
|
||||
|
||||
hideErrorDialog = () => this.setState({ dialogVisible: false });
|
||||
|
||||
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() && !this.state.loading;
|
||||
}
|
||||
|
||||
onInputChange(isEmail: boolean, value: string) {
|
||||
if (isEmail) {
|
||||
this.setState({
|
||||
email: value,
|
||||
isEmailValidated: false,
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
password: value,
|
||||
isPasswordValidated: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onEmailSubmit() {
|
||||
this.passwordInputRef.focus();
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
if (this.shouldEnableLogin()) {
|
||||
this.setState({loading: true});
|
||||
ConnectionManager.getInstance().connect(this.state.email, this.state.password)
|
||||
.then((data) => {
|
||||
this.handleSuccess();
|
||||
})
|
||||
.catch((error) => {
|
||||
this.handleErrors(error);
|
||||
})
|
||||
.finally(() => {
|
||||
this.setState({loading: false});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
handleSuccess() {
|
||||
this.props.navigation.navigate('ProfileScreen');
|
||||
}
|
||||
|
||||
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");
|
||||
break;
|
||||
case ERROR_TYPE.BAD_CREDENTIALS:
|
||||
message = i18n.t("loginScreen.errors.credentials");
|
||||
break;
|
||||
case ERROR_TYPE.SAVE_TOKEN:
|
||||
message = i18n.t("loginScreen.errors.saveToken");
|
||||
break;
|
||||
case ERROR_TYPE.NO_CONSENT:
|
||||
message = i18n.t("loginScreen.errors.consent");
|
||||
break;
|
||||
default:
|
||||
message = i18n.t("loginScreen.errors.unknown");
|
||||
break;
|
||||
}
|
||||
this.showErrorDialog(title, message);
|
||||
}
|
||||
|
||||
getFormInput() {
|
||||
return (
|
||||
<View>
|
||||
<TextInput
|
||||
label={i18n.t("loginScreen.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()}
|
||||
>
|
||||
{i18n.t("loginScreen.emailError")}
|
||||
</HelperText>
|
||||
<TextInput
|
||||
ref={(ref) => {
|
||||
this.passwordInputRef = ref;
|
||||
}}
|
||||
label={i18n.t("loginScreen.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()}
|
||||
>
|
||||
{i18n.t("loginScreen.passwordError")}
|
||||
</HelperText>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
getMainCard() {
|
||||
return (
|
||||
<Card style={styles.card}>
|
||||
<Card.Title
|
||||
title={i18n.t("loginScreen.title")}
|
||||
subtitle={i18n.t("loginScreen.subtitle")}
|
||||
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'}}>
|
||||
{i18n.t("loginScreen.login")}
|
||||
</Button>
|
||||
</Card.Actions>
|
||||
</Card.Content>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
getSecondaryCard() {
|
||||
return (
|
||||
<Card style={styles.card}>
|
||||
<Card.Content>
|
||||
<Text>{i18n.t("loginScreen.forgotPassword")}</Text>
|
||||
<View style={styles.btnContainer}>
|
||||
<Button
|
||||
icon="reload"
|
||||
mode="contained"
|
||||
onPress={this.onResetPasswordClick}
|
||||
style={{marginLeft: 'auto'}}>
|
||||
{i18n.t("loginScreen.resetPassword")}
|
||||
</Button>
|
||||
</View>
|
||||
<Text>{i18n.t("loginScreen.noAccount")}</Text>
|
||||
</Card.Content>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<KeyboardAvoidingView
|
||||
behavior={"height"}
|
||||
contentContainerStyle={styles.container}
|
||||
style={styles.container}
|
||||
enabled
|
||||
keyboardVerticalOffset={100}
|
||||
>
|
||||
<ScrollView>
|
||||
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
|
||||
<View>
|
||||
{this.getMainCard()}
|
||||
{this.getSecondaryCard()}
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
<AlertDialog
|
||||
{...this.props}
|
||||
visible={this.state.dialogVisible}
|
||||
title={this.state.dialogTitle}
|
||||
message={this.state.dialogMessage}
|
||||
onDismiss={this.hideErrorDialog}
|
||||
/>
|
||||
</ScrollView>
|
||||
</KeyboardAvoidingView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
card: {
|
||||
margin: 10,
|
||||
},
|
||||
header: {
|
||||
fontSize: 36,
|
||||
marginBottom: 48
|
||||
},
|
||||
textInput: {},
|
||||
btnContainer: {
|
||||
marginTop: 5,
|
||||
marginBottom: 10,
|
||||
}
|
||||
});
|
||||
|
||||
export default withTheme(LoginScreen);
|
||||
|
|
@ -1,255 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {FlatList, StyleSheet, View} from "react-native";
|
||||
import {Avatar, Button, Card, Divider, List, withTheme} from 'react-native-paper';
|
||||
import AuthenticatedScreen from "../../components/AuthenticatedScreen";
|
||||
import {openBrowser} from "../../utils/WebBrowser";
|
||||
import HeaderButton from "../../components/HeaderButton";
|
||||
import i18n from 'i18n-js';
|
||||
import LogoutDialog from "../../components/LogoutDialog";
|
||||
|
||||
type Props = {
|
||||
navigation: Object,
|
||||
theme: Object,
|
||||
}
|
||||
|
||||
type State = {
|
||||
dialogVisible: boolean,
|
||||
}
|
||||
|
||||
class ProfileScreen extends React.Component<Props, State> {
|
||||
|
||||
state = {
|
||||
dialogVisible: false,
|
||||
};
|
||||
|
||||
colors: Object;
|
||||
|
||||
data: Object;
|
||||
|
||||
flatListData: Array<Object>;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.colors = props.theme.colors;
|
||||
this.flatListData = [
|
||||
{id: '0'},
|
||||
{id: '1'},
|
||||
{id: '2'},
|
||||
]
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const rightButton = this.getHeaderButtons.bind(this);
|
||||
this.props.navigation.setOptions({
|
||||
headerRight: rightButton,
|
||||
});
|
||||
}
|
||||
|
||||
showDisconnectDialog = () => this.setState({ dialogVisible: true });
|
||||
|
||||
hideDisconnectDialog = () => this.setState({ dialogVisible: false });
|
||||
|
||||
getHeaderButtons() {
|
||||
return <HeaderButton icon={'logout'} onPress={this.showDisconnectDialog}/>;
|
||||
}
|
||||
|
||||
getScreen(data: Object) {
|
||||
this.data = data;
|
||||
return (
|
||||
<View>
|
||||
<FlatList
|
||||
renderItem={item => this.getRenderItem(item)}
|
||||
keyExtractor={item => item.id}
|
||||
data={this.flatListData}
|
||||
/>
|
||||
<LogoutDialog
|
||||
{...this.props}
|
||||
visible={this.state.dialogVisible}
|
||||
onDismiss={this.hideDisconnectDialog}
|
||||
/>
|
||||
</View>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
getRenderItem({item}: Object): any {
|
||||
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>{i18n.t("profileScreen.personalInformation")}</List.Subheader>
|
||||
{this.getPersonalListItem(this.data.birthday, "cake-variant")}
|
||||
{this.getPersonalListItem(this.data.phone, "phone")}
|
||||
{this.getPersonalListItem(this.data.email, "email")}
|
||||
{this.getPersonalListItem(this.data.branch, "school")}
|
||||
</List.Section>
|
||||
<Divider/>
|
||||
<Card.Actions>
|
||||
<Button
|
||||
icon="account-edit"
|
||||
mode="contained"
|
||||
onPress={() => openBrowser(this.data.link, this.colors.primary)}
|
||||
style={styles.editButton}>
|
||||
{i18n.t("profileScreen.editInformation")}
|
||||
</Button>
|
||||
</Card.Actions>
|
||||
</Card.Content>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
getClubCard() {
|
||||
return (
|
||||
<Card style={styles.card}>
|
||||
<Card.Title
|
||||
title={i18n.t("profileScreen.clubs")}
|
||||
subtitle={i18n.t("profileScreen.clubsSubtitle")}
|
||||
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={i18n.t("profileScreen.membership")}
|
||||
subtitle={i18n.t("profileScreen.membershipSubtitle")}
|
||||
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 ? i18n.t("profileScreen.membershipPayed") : i18n.t("profileScreen.membershipNotPayed")}
|
||||
left={props => <List.Icon
|
||||
{...props}
|
||||
color={state ? this.colors.success : this.colors.danger}
|
||||
icon={state ? 'check' : 'close'}
|
||||
/>}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
isFieldAvailable(field: ?string) {
|
||||
return field !== null;
|
||||
}
|
||||
|
||||
getFieldValue(field: ?string) {
|
||||
return this.isFieldAvailable(field)
|
||||
? field
|
||||
: i18n.t("profileScreen.noData");
|
||||
}
|
||||
|
||||
getFieldColor(field: ?string) {
|
||||
return this.isFieldAvailable(field)
|
||||
? this.colors.text
|
||||
: this.colors.textDisabled;
|
||||
}
|
||||
|
||||
getPersonalListItem(field: ?string, icon: string) {
|
||||
return (
|
||||
<List.Item
|
||||
title={this.getFieldValue(field)}
|
||||
left={props => <List.Icon
|
||||
{...props}
|
||||
icon={icon}
|
||||
color={this.getFieldColor(field)}
|
||||
/>}
|
||||
titleStyle={{color: this.getFieldColor(field)}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<AuthenticatedScreen
|
||||
{...this.props}
|
||||
link={'https://www.amicale-insat.fr/api/user/profile'}
|
||||
renderFunction={(data) => this.getScreen(data)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
card: {
|
||||
margin: 10,
|
||||
},
|
||||
icon: {
|
||||
backgroundColor: 'transparent'
|
||||
},
|
||||
editButton: {
|
||||
marginLeft: 'auto'
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
export default withTheme(ProfileScreen);
|
||||
|
|
@ -4,13 +4,13 @@ import * as React from 'react';
|
|||
import {View} from 'react-native';
|
||||
import i18n from "i18n-js";
|
||||
import DashboardItem from "../components/EventDashboardItem";
|
||||
import * as WebBrowser from 'expo-web-browser';
|
||||
import WebSectionList from "../components/WebSectionList";
|
||||
import {Text, withTheme} from 'react-native-paper';
|
||||
import FeedItem from "../components/FeedItem";
|
||||
import SquareDashboardItem from "../components/SquareDashboardItem";
|
||||
import PreviewEventDashboardItem from "../components/PreviewEventDashboardItem";
|
||||
import {stringToDate} from "../utils/Planning";
|
||||
import {openBrowser} from "../utils/WebBrowser";
|
||||
// import DATA from "../dashboard_data.json";
|
||||
|
||||
|
||||
|
|
@ -70,7 +70,7 @@ class HomeScreen extends React.Component<Props> {
|
|||
}
|
||||
|
||||
onTutorInsaClick() {
|
||||
openBrowser("https://www.etud.insa-toulouse.fr/~tutorinsa/", this.colors.primary);
|
||||
WebBrowser.openBrowserAsync("https://www.etud.insa-toulouse.fr/~tutorinsa/");
|
||||
}
|
||||
|
||||
onProximoClick() {
|
||||
|
|
@ -402,7 +402,7 @@ class HomeScreen extends React.Component<Props> {
|
|||
}
|
||||
|
||||
openLink(link: string) {
|
||||
openBrowser(link, this.colors.primary);
|
||||
WebBrowser.openBrowserAsync(link);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -12,16 +12,12 @@
|
|||
"bluemind": "INSA Mails",
|
||||
"ent": "INSA ENT",
|
||||
"about": "About",
|
||||
"debug": "Debug",
|
||||
"login": "Login",
|
||||
"logout": "Logout",
|
||||
"profile": "Profile"
|
||||
"debug": "Debug"
|
||||
},
|
||||
"sidenav": {
|
||||
"divider1": "Student websites",
|
||||
"divider2": "Services",
|
||||
"divider3": "Personalisation",
|
||||
"divider4": "Amicale"
|
||||
"divider3": "Personalisation"
|
||||
},
|
||||
"intro": {
|
||||
"slide1": {
|
||||
|
|
@ -211,50 +207,8 @@
|
|||
"computerRoom": "Computer",
|
||||
"bibRoom": "Bib'Box"
|
||||
},
|
||||
"profileScreen": {
|
||||
"personalInformation": "Personal information",
|
||||
"noData": "No data",
|
||||
"editInformation": "Edit Information",
|
||||
"clubs": "Clubs",
|
||||
"clubsSubtitle": "Clubs you are part of",
|
||||
"membership": "Membership Fee",
|
||||
"membershipSubtitle": "Allows you to take part in various activities",
|
||||
"membershipPayed": "Payed",
|
||||
"membershipNotPayed": "not Payed"
|
||||
},
|
||||
"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."
|
||||
}
|
||||
},
|
||||
"dialog": {
|
||||
"ok": "OK",
|
||||
"yes": "Yes",
|
||||
"cancel": "Cancel",
|
||||
"disconnect": {
|
||||
"title": "Disconnect",
|
||||
"titleLoading": "Disconnecting...",
|
||||
"message": "Are you sure you want to disconnect from your Amicale account?"
|
||||
}
|
||||
},
|
||||
"general": {
|
||||
"loading": "Loading...",
|
||||
"retry": "Retry",
|
||||
"networkError": "Unable to contact servers. Make sure you are connected to Internet."
|
||||
},
|
||||
"date": {
|
||||
|
|
|
|||
|
|
@ -12,16 +12,12 @@
|
|||
"bluemind": "Mails INSA",
|
||||
"ent": "ENT INSA",
|
||||
"about": "À Propos",
|
||||
"debug": "Debug",
|
||||
"login": "Se Connecter",
|
||||
"logout": "Se Déconnecter",
|
||||
"profile": "Profil"
|
||||
"debug": "Debug"
|
||||
},
|
||||
"sidenav": {
|
||||
"divider1": "Sites étudiants",
|
||||
"divider2": "Services",
|
||||
"divider3": "Personnalisation",
|
||||
"divider4": "Amicale"
|
||||
"divider3": "Personnalisation"
|
||||
},
|
||||
"intro": {
|
||||
"slide1": {
|
||||
|
|
@ -212,50 +208,8 @@
|
|||
"computerRoom": "Ordi",
|
||||
"bibRoom": "Bib'Box"
|
||||
},
|
||||
"profileScreen": {
|
||||
"personalInformation": "Informations Personnelles",
|
||||
"noData": "Pas de données",
|
||||
"editInformation": "Modifier les informations",
|
||||
"clubs": "Clubs",
|
||||
"clubsSubtitle": "Liste de vos clubs",
|
||||
"membership": "Cotisation",
|
||||
"membershipSubtitle": "Permet de participer à diverses activités",
|
||||
"membershipPayed": "Payée",
|
||||
"membershipNotPayed": "Non payée"
|
||||
},
|
||||
"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."
|
||||
}
|
||||
},
|
||||
"dialog": {
|
||||
"ok": "OK",
|
||||
"yes": "Oui",
|
||||
"cancel": "Annuler",
|
||||
"disconnect": {
|
||||
"title": "Déconnexion",
|
||||
"titleLoading": "Déconnexion...",
|
||||
"message": "Voulez vous vraiment vous déconnecter de votre compte Amicale ??"
|
||||
}
|
||||
},
|
||||
"general": {
|
||||
"loading": "Chargement...",
|
||||
"retry": "Réessayer",
|
||||
"networkError": "Impossible de contacter les serveurs. Assurez-vous d'être connecté à internet."
|
||||
},
|
||||
"date": {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
Loading…
Reference in a new issue