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
|
// @flow
|
||||||
|
|
||||||
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";
|
||||||
import ConnectionManager from "../managers/ConnectionManager";
|
|
||||||
import LogoutDialog from "./LogoutDialog";
|
|
||||||
|
|
||||||
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 = {
|
||||||
active: string,
|
active: string,
|
||||||
isLoggedIn: boolean,
|
|
||||||
dialogVisible: boolean,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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>;
|
||||||
|
|
||||||
|
state = {
|
||||||
|
active: 'Home',
|
||||||
|
};
|
||||||
|
|
||||||
getRenderItem: Function;
|
getRenderItem: Function;
|
||||||
colors: Object;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate the dataset
|
* Generate the dataset
|
||||||
|
|
@ -48,29 +46,6 @@ class SideBar extends React.PureComponent<Props, State> {
|
||||||
route: "Main",
|
route: "Main",
|
||||||
icon: "home",
|
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'),
|
name: i18n.t('sidenav.divider2'),
|
||||||
route: "Divider2"
|
route: "Divider2"
|
||||||
|
|
@ -146,23 +121,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;
|
|
||||||
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
|
* @param item The item pressed
|
||||||
*/
|
*/
|
||||||
onListItemPress(item: Object) {
|
onListItemPress(item: Object) {
|
||||||
if (item.link !== undefined)
|
if (item.link === undefined)
|
||||||
openBrowser(item.link, this.colors.primary);
|
|
||||||
else if (item.action !== undefined)
|
|
||||||
item.action();
|
|
||||||
else
|
|
||||||
this.props.navigation.navigate(item.route);
|
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) {
|
getRenderItem({item}: Object) {
|
||||||
const onListItemPress = this.onListItemPress.bind(this, item);
|
const onListItemPress = this.onListItemPress.bind(this, item);
|
||||||
const onlyWhenLoggedOut = item.onlyWhenLoggedOut !== undefined && item.onlyWhenLoggedOut === true;
|
if (item.icon !== undefined) {
|
||||||
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}
|
||||||
|
|
@ -236,11 +188,6 @@ class SideBar extends React.PureComponent<Props, State> {
|
||||||
keyExtractor={this.listKeyExtractor}
|
keyExtractor={this.listKeyExtractor}
|
||||||
renderItem={this.getRenderItem}
|
renderItem={this.getRenderItem}
|
||||||
/>
|
/>
|
||||||
<LogoutDialog
|
|
||||||
{...this.props}
|
|
||||||
visible={this.state.dialogVisible}
|
|
||||||
onDismiss={this.hideDisconnectDialog}
|
|
||||||
/>
|
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -266,5 +213,3 @@ const styles = StyleSheet.create({
|
||||||
marginTop: Platform.OS === "android" ? -3 : undefined
|
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 {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 ProfileScreen from "../screens/Amicale/ProfileScreen";
|
|
||||||
|
|
||||||
const defaultScreenOptions = {
|
const defaultScreenOptions = {
|
||||||
gestureEnabled: true,
|
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();
|
const Drawer = createDrawerNavigator();
|
||||||
|
|
||||||
function getDrawerContent(props) {
|
function getDrawerContent(props) {
|
||||||
|
|
@ -281,14 +231,6 @@ export default function DrawerNavigator() {
|
||||||
name="TetrisScreen"
|
name="TetrisScreen"
|
||||||
component={TetrisStackComponent}
|
component={TetrisStackComponent}
|
||||||
/>
|
/>
|
||||||
<Drawer.Screen
|
|
||||||
name="LoginScreen"
|
|
||||||
component={LoginStackComponent}
|
|
||||||
/>
|
|
||||||
<Drawer.Screen
|
|
||||||
name="ProfileScreen"
|
|
||||||
component={ProfileStackComponent}
|
|
||||||
/>
|
|
||||||
</Drawer.Navigator>
|
</Drawer.Navigator>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
10
package.json
10
package.json
|
|
@ -12,12 +12,7 @@
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
"preset": "react-native",
|
"preset": "react-native",
|
||||||
"transformIgnorePatterns": [
|
"setupFilesAfterEnv": ["jest-extended"]
|
||||||
"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"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/vector-icons": "~10.0.0",
|
"@expo/vector-icons": "~10.0.0",
|
||||||
|
|
@ -47,8 +42,7 @@
|
||||||
"react-native-screens": "2.0.0-alpha.12",
|
"react-native-screens": "2.0.0-alpha.12",
|
||||||
"react-native-webview": "7.4.3",
|
"react-native-webview": "7.4.3",
|
||||||
"react-native-appearance": "~0.3.1",
|
"react-native-appearance": "~0.3.1",
|
||||||
"expo-linear-gradient": "~8.0.0",
|
"expo-linear-gradient": "~8.0.0"
|
||||||
"expo-secure-store": "~8.0.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-preset-expo": "^8.0.0",
|
"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 {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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -12,16 +12,12 @@
|
||||||
"bluemind": "INSA Mails",
|
"bluemind": "INSA Mails",
|
||||||
"ent": "INSA ENT",
|
"ent": "INSA ENT",
|
||||||
"about": "About",
|
"about": "About",
|
||||||
"debug": "Debug",
|
"debug": "Debug"
|
||||||
"login": "Login",
|
|
||||||
"logout": "Logout",
|
|
||||||
"profile": "Profile"
|
|
||||||
},
|
},
|
||||||
"sidenav": {
|
"sidenav": {
|
||||||
"divider1": "Student websites",
|
"divider1": "Student websites",
|
||||||
"divider2": "Services",
|
"divider2": "Services",
|
||||||
"divider3": "Personalisation",
|
"divider3": "Personalisation"
|
||||||
"divider4": "Amicale"
|
|
||||||
},
|
},
|
||||||
"intro": {
|
"intro": {
|
||||||
"slide1": {
|
"slide1": {
|
||||||
|
|
@ -211,50 +207,8 @@
|
||||||
"computerRoom": "Computer",
|
"computerRoom": "Computer",
|
||||||
"bibRoom": "Bib'Box"
|
"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": {
|
"general": {
|
||||||
"loading": "Loading...",
|
"loading": "Loading...",
|
||||||
"retry": "Retry",
|
|
||||||
"networkError": "Unable to contact servers. Make sure you are connected to Internet."
|
"networkError": "Unable to contact servers. Make sure you are connected to Internet."
|
||||||
},
|
},
|
||||||
"date": {
|
"date": {
|
||||||
|
|
|
||||||
|
|
@ -12,16 +12,12 @@
|
||||||
"bluemind": "Mails INSA",
|
"bluemind": "Mails INSA",
|
||||||
"ent": "ENT INSA",
|
"ent": "ENT INSA",
|
||||||
"about": "À Propos",
|
"about": "À Propos",
|
||||||
"debug": "Debug",
|
"debug": "Debug"
|
||||||
"login": "Se Connecter",
|
|
||||||
"logout": "Se Déconnecter",
|
|
||||||
"profile": "Profil"
|
|
||||||
},
|
},
|
||||||
"sidenav": {
|
"sidenav": {
|
||||||
"divider1": "Sites étudiants",
|
"divider1": "Sites étudiants",
|
||||||
"divider2": "Services",
|
"divider2": "Services",
|
||||||
"divider3": "Personnalisation",
|
"divider3": "Personnalisation"
|
||||||
"divider4": "Amicale"
|
|
||||||
},
|
},
|
||||||
"intro": {
|
"intro": {
|
||||||
"slide1": {
|
"slide1": {
|
||||||
|
|
@ -212,50 +208,8 @@
|
||||||
"computerRoom": "Ordi",
|
"computerRoom": "Ordi",
|
||||||
"bibRoom": "Bib'Box"
|
"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": {
|
"general": {
|
||||||
"loading": "Chargement...",
|
"loading": "Chargement...",
|
||||||
"retry": "Réessayer",
|
|
||||||
"networkError": "Impossible de contacter les serveurs. Assurez-vous d'être connecté à internet."
|
"networkError": "Impossible de contacter les serveurs. Assurez-vous d'être connecté à internet."
|
||||||
},
|
},
|
||||||
"date": {
|
"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