// @flow import * as React from 'react'; import {Image, KeyboardAvoidingView, StyleSheet, View} from "react-native"; import {Button, Card, HelperText, TextInput, withTheme} from 'react-native-paper'; import ConnectionManager from "../../managers/ConnectionManager"; import i18n from 'i18n-js'; import ErrorDialog from "../../components/Dialogs/ErrorDialog"; import type {CustomTheme} from "../../managers/ThemeManager"; import AsyncStorageManager from "../../managers/AsyncStorageManager"; import {StackNavigationProp} from "@react-navigation/stack"; import AvailableWebsites from "../../constants/AvailableWebsites"; import {MASCOT_STYLE} from "../../components/Mascot/Mascot"; import MascotPopup from "../../components/Mascot/MascotPopup"; import LinearGradient from "react-native-linear-gradient"; import CollapsibleScrollView from "../../components/Collapsible/CollapsibleScrollView"; type Props = { navigation: StackNavigationProp, route: { params: { nextScreen: string } }, theme: CustomTheme } type State = { email: string, password: string, isEmailValidated: boolean, isPasswordValidated: boolean, loading: boolean, dialogVisible: boolean, dialogError: number, mascotDialogVisible: boolean, } const ICON_AMICALE = require('../../../assets/amicale.png'); const RESET_PASSWORD_PATH = "https://www.amicale-insat.fr/password/reset"; const emailRegex = /^.+@.+\..+$/; class LoginScreen extends React.Component { state = { email: '', password: '', isEmailValidated: false, isPasswordValidated: false, loading: false, dialogVisible: false, dialogError: 0, mascotDialogVisible: AsyncStorageManager.getInstance().preferences.loginShowBanner.current === "1" }; onEmailChange: (value: string) => null; onPasswordChange: (value: string) => null; passwordInputRef: { current: null | TextInput }; nextScreen: string | null; constructor(props) { super(props); this.passwordInputRef = React.createRef(); this.onEmailChange = this.onInputChange.bind(this, true); this.onPasswordChange = this.onInputChange.bind(this, false); this.props.navigation.addListener('focus', this.onScreenFocus); } onScreenFocus = () => { this.handleNavigationParams(); }; /** * Saves the screen to navigate to after a successful login if one was provided in navigation parameters */ handleNavigationParams() { if (this.props.route.params != null) { if (this.props.route.params.nextScreen != null) this.nextScreen = this.props.route.params.nextScreen; else this.nextScreen = null; } } hideMascotDialog = () => { AsyncStorageManager.getInstance().savePref( AsyncStorageManager.getInstance().preferences.loginShowBanner.key, '0' ); this.setState({mascotDialogVisible: false}) }; showMascotDialog = () => { this.setState({mascotDialogVisible: true}) }; /** * Shows an error dialog with the corresponding login error * * @param error The error given by the login request */ showErrorDialog = (error: number) => this.setState({ dialogVisible: true, dialogError: error, }); hideErrorDialog = () => this.setState({dialogVisible: false}); /** * Navigates to the screen specified in navigation parameters or simply go back tha stack. * Saves in user preferences to not show the login banner again. */ handleSuccess = () => { // Do not show the login banner again AsyncStorageManager.getInstance().savePref( AsyncStorageManager.getInstance().preferences.homeShowBanner.key, '0' ); if (this.nextScreen == null) this.props.navigation.goBack(); else this.props.navigation.replace(this.nextScreen); }; /** * Navigates to the Amicale website screen with the reset password link as navigation parameters */ onResetPasswordClick = () => this.props.navigation.navigate("website", { host: AvailableWebsites.websites.AMICALE, path: RESET_PASSWORD_PATH, title: i18n.t('screens.websites.amicale') }); /** * The user has unfocused the input, his email is ready to be validated */ validateEmail = () => this.setState({isEmailValidated: true}); /** * Checks if the entered email is valid (matches the regex) * * @returns {boolean} */ isEmailValid() { return emailRegex.test(this.state.email); } /** * Checks if we should tell the user his email is invalid. * We should only show this if his email is invalid and has been checked when un-focusing the input * * @returns {boolean|boolean} */ shouldShowEmailError() { return this.state.isEmailValidated && !this.isEmailValid(); } /** * The user has unfocused the input, his password is ready to be validated */ validatePassword = () => this.setState({isPasswordValidated: true}); /** * Checks if the user has entered a password * * @returns {boolean} */ isPasswordValid() { return this.state.password !== ''; } /** * Checks if we should tell the user his password is invalid. * We should only show this if his password is invalid and has been checked when un-focusing the input * * @returns {boolean|boolean} */ shouldShowPasswordError() { return this.state.isPasswordValidated && !this.isPasswordValid(); } /** * If the email and password are valid, and we are not loading a request, then the login button can be enabled * * @returns {boolean} */ shouldEnableLogin() { return this.isEmailValid() && this.isPasswordValid() && !this.state.loading; } /** * Called when the user input changes in the email or password field. * This saves the new value in the State and disabled input validation (to prevent errors to show while typing) * * @param isEmail True if the field is the email field * @param value The new field value */ onInputChange(isEmail: boolean, value: string) { if (isEmail) { this.setState({ email: value, isEmailValidated: false, }); } else { this.setState({ password: value, isPasswordValidated: false, }); } } /** * Focuses the password field when the email field is done * * @returns {*} */ onEmailSubmit = () => { if (this.passwordInputRef.current != null) this.passwordInputRef.current.focus(); } /** * Called when the user clicks on login or finishes to type his password. * * Checks if we should allow the user to login, * then makes the login request and enters a loading state until the request finishes * */ onSubmit = () => { if (this.shouldEnableLogin()) { this.setState({loading: true}); ConnectionManager.getInstance().connect(this.state.email, this.state.password) .then(this.handleSuccess) .catch(this.showErrorDialog) .finally(() => { this.setState({loading: false}); }); } }; /** * Gets the form input * * @returns {*} */ getFormInput() { return ( {i18n.t("screens.login.emailError")} {i18n.t("screens.login.passwordError")} ); } /** * Gets the card containing the input form * @returns {*} */ getMainCard() { return ( } /> {this.getFormInput()} ); } render() { return ( {this.getMainCard()} ); } } const styles = StyleSheet.create({ container: { flex: 1, }, card: { marginTop: 'auto', marginBottom: 'auto', }, header: { fontSize: 36, marginBottom: 48 }, textInput: {}, btnContainer: { marginTop: 5, marginBottom: 10, } }); export default withTheme(LoginScreen);