// @flow import * as React from 'react'; import { Component, Text, View, Animated, Styles, UserInterface } from 'reactxp'; import { Layout, Container, Header } from './Layout'; import AccountInput from './AccountInput'; import Accordion from './Accordion'; import { formatAccount } from '../lib/formatters'; import Img from './Img'; import { BlueButton, Label, CellButton } from './styled'; import styles from './LoginStyles'; import { colors } from '../config'; import type { AccountReduxState } from '../redux/account/reducers'; import type { AccountToken } from '../lib/ipc-facade'; export type Props = { account: AccountReduxState, onLogin: (accountToken: AccountToken) => void, onSettings: ?() => void, onFirstChangeAfterFailure: () => void, onExternalLink: (type: string) => void, onAccountTokenChange: (accountToken: AccountToken) => void, onRemoveAccountTokenFromHistory: (accountToken: AccountToken) => void, }; type State = { isActive: boolean, }; export default class Login extends Component { state = { isActive: true, }; _accountInput: ?AccountInput; _notifyOnFirstChangeAfterFailure = false; _showsFooter = true; _footerAnimatedValue = Animated.createValue(0); _footerAnimation: ?Animated.Animation; _footerAnimationStyle: Animated.Style; _footerRef: ?React.Node; _isLoginButtonActive = false; _loginButtonAnimatedValue = Animated.createValue(0); _loginButtonAnimation: ?Animated.Animation; _loginButtonAnimationStyle: Animated.Style; constructor(props: Props) { super(props); if (props.account.status === 'failed') { this._notifyOnFirstChangeAfterFailure = true; } this._footerAnimationStyle = Styles.createAnimatedViewStyle({ transform: [{ translateY: this._footerAnimatedValue }], }); this._loginButtonAnimationStyle = Styles.createAnimatedViewStyle({ backgroundColor: Animated.interpolate( this._loginButtonAnimatedValue, [0.0, 1.0], [colors.white, colors.green], ), }); } componentDidUpdate(prevProps: Props, _prevState: State) { if ( this.props.account.status !== prevProps.account.status && this.props.account.status === 'failed' && !this._notifyOnFirstChangeAfterFailure ) { this._notifyOnFirstChangeAfterFailure = true; // focus on login field when failed to log in const accountInput = this._accountInput; if (accountInput) { accountInput.focus(); } } this._setLoginButtonActive(this._shouldActivateLoginButton()); this._setFooterVisibility(this._shouldShowFooter()); } render() { return (
{this._getStatusIcon()} {this._formTitle()} {this._shouldShowLoginForm() && {this._createLoginForm()}} { this._footerRef = ref; }} style={[styles.login_footer, this._footerAnimationStyle]} testName={'footerVisibility ' + this._shouldShowFooter().toString()}> {this._createFooter()} ); } _onCreateAccount = () => this.props.onExternalLink('createAccount'); _onFocus = () => { this.setState({ isActive: true }); }; _onBlur = (e) => { const relatedTarget = e.relatedTarget; // restore focus if click happened within dropdown if (relatedTarget) { e.target.focus(); return; } this.setState({ isActive: false }); }; async _setLoginButtonActive(isActive: boolean) { if (this._isLoginButtonActive === isActive) { return; } const animation = Animated.timing(this._loginButtonAnimatedValue, { toValue: isActive ? 1 : 0, easing: Animated.Easing.Linear(), duration: 250, }); const oldAnimation = this._loginButtonAnimation; if (oldAnimation) { oldAnimation.stop(); } animation.start(); this._loginButtonAnimation = animation; this._isLoginButtonActive = isActive; } async _setFooterVisibility(show: boolean) { if (this._showsFooter === show) { return; } this._showsFooter = show; const layout = await UserInterface.measureLayoutRelativeToWindow(this._footerRef); const value = show ? 0 : layout.height; const animation = Animated.timing(this._footerAnimatedValue, { toValue: value, easing: Animated.Easing.InOut(), duration: 250, }); const oldAnimation = this._footerAnimation; if (oldAnimation) { oldAnimation.stop(); } animation.start(); this._footerAnimation = animation; } _onLogin = () => { const accountToken = this.props.account.accountToken; if (accountToken && accountToken.length > 0) { this.props.onLogin(accountToken); } }; _onInputChange = (value: string) => { // notify delegate on first change after login failure if (this._notifyOnFirstChangeAfterFailure) { this._notifyOnFirstChangeAfterFailure = false; this.props.onFirstChangeAfterFailure(); } this.props.onAccountTokenChange(value); }; _formTitle() { switch (this.props.account.status) { case 'logging in': return 'Logging in...'; case 'failed': return 'Login failed'; case 'ok': return 'Login successful'; default: return 'Login'; } } _formSubtitle() { const { status, error } = this.props.account; switch (status) { case 'failed': return (error && error.message) || 'Unknown error'; case 'logging in': return 'Checking account number'; default: return 'Enter your account number'; } } _getStatusIcon() { const statusIconPath = this._getStatusIconPath(); return ( {statusIconPath ? : null} ); } _getStatusIconPath(): ?string { switch (this.props.account.status) { case 'logging in': return 'icon-spinner'; case 'failed': return 'icon-fail'; case 'ok': return 'icon-success'; default: return undefined; } } _accountInputGroupStyles(): Array { const classes = [styles.account_input_group]; if (this.state.isActive) { classes.push(styles.account_input_group__active); } switch (this.props.account.status) { case 'logging in': classes.push(styles.account_input_group__inactive); break; case 'failed': classes.push(styles.account_input_group__error); break; } return classes; } _accountInputButtonStyles(): Array { const { status } = this.props.account; const classes = [styles.input_button]; if (status === 'logging in') { classes.push(styles.input_button__invisible); } classes.push(this._loginButtonAnimationStyle); return classes; } _accountInputArrowStyles(): Array { const { accountToken, status } = this.props.account; const classes = [styles.input_arrow]; if (accountToken && accountToken.length > 0) { classes.push(styles.input_arrow__active); } if (status === 'logging in') { classes.push(styles.input_arrow__invisible); } return classes; } _shouldActivateLoginButton() { const { accountToken } = this.props.account; return accountToken && accountToken.length > 0; } _shouldEnableAccountInput() { // enable account input always except when "logging in" return this.props.account.status !== 'logging in'; } _shouldShowAccountHistory() { return ( this._shouldEnableAccountInput() && this.state.isActive && this.props.account.accountHistory.length > 0 ); } _shouldShowLoginForm() { return this.props.account.status !== 'ok'; } _shouldShowFooter() { const { status } = this.props.account; return (status === 'none' || status === 'failed') && !this._shouldShowAccountHistory(); } _onSelectAccountFromHistory = (accountToken) => { this.props.onAccountTokenChange(accountToken); this.props.onLogin(accountToken); }; _createLoginForm() { const { accountHistory, accountToken } = this.props.account; return ( {this._formSubtitle()} (this._accountInput = ref)} testName="AccountInput" /> { } ); } _createFooter() { return ( {"Don't have an account number?"} ); } } type AccountDropdownProps = { items: Array, onSelect: (value: AccountToken) => void, onRemove: (value: AccountToken) => void, }; class AccountDropdown extends React.Component { render() { const uniqueItems = [...new Set(this.props.items)]; return ( {uniqueItems.map((token) => ( ))} ); } } type AccountDropdownItemProps = { label: string, value: AccountToken, onRemove: (value: AccountToken) => void, onSelect: (value: AccountToken) => void, }; class AccountDropdownItem extends React.Component { render() { return ( this.props.onRemove(this.props.value)} /> ); } }