import React from 'react'; import PropTypes from 'prop-types'; import { Keyboard, Text, ScrollView, View, StyleSheet, Alert, LayoutAnimation, Dimensions } from 'react-native'; import { connect } from 'react-redux'; import { Answers } from 'react-native-fabric'; import SafeAreaView from 'react-native-safe-area-view'; import equal from 'deep-equal'; import Navigation from '../lib/Navigation'; import KeyboardView from '../presentation/KeyboardView'; import TextInput from '../containers/TextInput'; import Button from '../containers/Button'; import sharedStyles from './Styles'; import scrollPersistTaps from '../utils/scrollPersistTaps'; import LoggedView from './View'; import I18n from '../i18n'; import { DARK_HEADER } from '../constants/headerOptions'; import { loginRequest as loginRequestAction } from '../actions/login'; const styles = StyleSheet.create({ buttonsContainer: { flexDirection: 'column', marginTop: 5 }, bottomContainer: { flexDirection: 'column', alignItems: 'center', marginTop: 10 }, dontHaveAccount: { ...sharedStyles.textRegular, color: '#9ea2a8', fontSize: 13 }, createAccount: { ...sharedStyles.textSemibold, color: '#1d74f5', fontSize: 13 }, loginTitle: { marginVertical: 0, marginTop: 15 } }); @connect(state => ({ isFetching: state.login.isFetching, failure: state.login.failure, error: state.login.error && state.login.error.data, Site_Name: state.settings.Site_Name, Accounts_EmailOrUsernamePlaceholder: state.settings.Accounts_EmailOrUsernamePlaceholder, Accounts_PasswordPlaceholder: state.settings.Accounts_PasswordPlaceholder }), dispatch => ({ loginRequest: params => dispatch(loginRequestAction(params)) })) /** @extends React.Component */ export default class LoginView extends LoggedView { static options() { return { ...DARK_HEADER, topBar: { ...DARK_HEADER.topBar, rightButtons: [{ id: 'more', icon: { uri: 'more', scale: Dimensions.get('window').scale }, testID: 'login-view-more' }] } }; } static propTypes = { componentId: PropTypes.string, loginRequest: PropTypes.func.isRequired, error: PropTypes.object, Site_Name: PropTypes.string, Accounts_EmailOrUsernamePlaceholder: PropTypes.string, Accounts_PasswordPlaceholder: PropTypes.string, isFetching: PropTypes.bool, failure: PropTypes.bool } constructor(props) { super('LoginView', props); this.state = { user: '', password: '', code: '', showTOTP: false }; Navigation.events().bindComponent(this); const { componentId, Site_Name } = this.props; this.setTitle(componentId, Site_Name); } componentDidMount() { this.timeout = setTimeout(() => { this.usernameInput.focus(); }, 600); } componentWillReceiveProps(nextProps) { const { componentId, Site_Name, error } = this.props; if (Site_Name && nextProps.Site_Name !== Site_Name) { this.setTitle(componentId, nextProps.Site_Name); } else if (nextProps.failure && !equal(error, nextProps.error)) { if (nextProps.error && nextProps.error.error === 'totp-required') { LayoutAnimation.easeInEaseOut(); this.setState({ showTOTP: true }); setTimeout(() => { if (this.codeInput && this.codeInput.focus) { this.codeInput.focus(); } }, 300); return; } Alert.alert(I18n.t('Oops'), I18n.t('Login_error')); } } shouldComponentUpdate(nextProps, nextState) { const { user, password, code, showTOTP } = this.state; const { isFetching, failure, error, Site_Name, Accounts_EmailOrUsernamePlaceholder, Accounts_PasswordPlaceholder } = this.props; if (nextState.user !== user) { return true; } if (nextState.password !== password) { return true; } if (nextState.code !== code) { return true; } if (nextState.showTOTP !== showTOTP) { return true; } if (nextProps.isFetching !== isFetching) { return true; } if (nextProps.failure !== failure) { return true; } if (nextProps.Site_Name !== Site_Name) { return true; } if (nextProps.Accounts_EmailOrUsernamePlaceholder !== Accounts_EmailOrUsernamePlaceholder) { return true; } if (nextProps.Accounts_PasswordPlaceholder !== Accounts_PasswordPlaceholder) { return true; } if (!equal(nextProps.error, error)) { return true; } return false; } componentWillUnmount() { if (this.timeout) { clearTimeout(this.timeout); } } setTitle = (componentId, title) => { Navigation.mergeOptions(componentId, { topBar: { title: { text: title } } }); } navigationButtonPressed = ({ buttonId }) => { if (buttonId === 'more') { Navigation.showModal({ stack: { children: [{ component: { name: 'LegalView' } }] } }); } } valid = () => { const { user, password, code, showTOTP } = this.state; if (showTOTP) { return code.trim(); } return user.trim() && password.trim(); } submit = () => { if (!this.valid()) { return; } const { user, password, code } = this.state; const { loginRequest } = this.props; Keyboard.dismiss(); loginRequest({ user, password, code }); Answers.logLogin('Email', true); } register = () => { const { componentId, Site_Name } = this.props; Navigation.push(componentId, { component: { name: 'RegisterView', options: { topBar: { title: { text: Site_Name } } } } }); } forgotPassword = () => { const { componentId, Site_Name } = this.props; Navigation.push(componentId, { component: { name: 'ForgotPasswordView', options: { topBar: { title: { text: Site_Name } } } } }); } renderTOTP = () => { const { isFetching } = this.props; return ( {I18n.t('Two_Factor_Authentication')} {I18n.t('Whats_your_2fa')} this.codeInput = ref} onChangeText={value => this.setState({ code: value })} keyboardType='numeric' returnKeyType='send' autoCapitalize='none' onSubmitEditing={this.submit} testID='login-view-totp' containerStyle={sharedStyles.inputLastChild} />