// @flow import * as React from 'react'; import { Layout, Container, Header } from './Layout'; import AccountInput from './AccountInput'; import { formatAccount } from '../lib/formatters'; import ExternalLinkSVG from '../assets/images/icon-extLink.svg'; import LoginArrowSVG from '../assets/images/icon-arrow.svg'; import RemoveAccountSVG from '../assets/images/icon-close-sml.svg'; import type { AccountReduxState } from '../redux/account/reducers'; import type { AccountToken } from '../lib/ipc-facade'; export type LoginPropTypes = { account: AccountReduxState, onLogin: (accountToken: AccountToken) => void, onSettings: ?(() => void), onFirstChangeAfterFailure: () => void, onExternalLink: (type: string) => void, onAccountTokenChange: (accountToken: AccountToken) => void, onRemoveAccountTokenFromHistory: (accountToken: AccountToken) => void, }; type State = { notifyOnFirstChangeAfterFailure: boolean, isActive: boolean, dropdownHeight: number }; export default class Login extends React.Component { state = { notifyOnFirstChangeAfterFailure: false, isActive: false, dropdownHeight: 0 }; constructor(props: LoginPropTypes) { super(props); if(props.account.status === 'failed') { this.state.notifyOnFirstChangeAfterFailure = true; } } componentDidMount() { this._updateDropdownHeight(); } componentDidUpdate() { this._updateDropdownHeight(); } componentWillReceiveProps(nextProps: LoginPropTypes) { const prev = this.props.account || {}; const next = nextProps.account || {}; if(prev.status !== next.status && next.status === 'failed') { this.setState({ notifyOnFirstChangeAfterFailure: true }); } } render() { const footerClass = this._shouldShowFooter() ? '' : 'login-footer--invisible'; return (
{ this._getStatusIcon() }
{ this._formTitle() }
{this._shouldShowLoginForm() &&
{ this._createLoginForm() }
}
{ 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 && this._isWithinDropdown(relatedTarget)) { e.target.focus(); return; } this.setState({ isActive: false }); } _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.state.notifyOnFirstChangeAfterFailure) { this.setState({ 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 './assets/images/icon-spinner.svg'; case 'failed': return './assets/images/icon-fail.svg'; case 'ok': return './assets/images/icon-success.svg'; default: return undefined; } } _accountInputGroupClass(): string { const classes = ['login-form__account-input-group']; if(this.state.isActive) { classes.push('login-form__account-input-group--active'); } switch(this.props.account.status) { case 'logging in': classes.push('login-form__account-input-group--inactive'); break; case 'failed': classes.push('login-form__account-input-group--error'); break; } return classes.join(' '); } _accountInputButtonClass(): string { const { accountToken, status } = this.props.account; const classes = ['login-form__account-input-button']; if(accountToken && accountToken.length > 0) { classes.push('login-form__account-input-button--active'); } if(status === 'logging in') { classes.push('login-form__account-input-button--invisible'); } return classes.join(' '); } _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(); } // helper function to calculate and save dropdown element's height // this is a no-op of the height didn't change since last update _updateDropdownHeight() { const element = this._accountDropdownElement; if(element && this.state.dropdownHeight !== element.clientHeight) { this.setState({ dropdownHeight: element.clientHeight }); } } // returns true if DOM node is within dropdown hierarchy _isWithinDropdown(relatedTarget) { const dropdownElement = this._accountDropdownElement; return dropdownElement && dropdownElement.contains(relatedTarget); } // container element used for measuring the height of the accounts dropdown _accountDropdownElement: ?HTMLElement; _onAccountDropdownContainerRef = ref => this._accountDropdownElement = ref; _onSelectAccountFromHistory = (accountToken) => { this.props.onAccountTokenChange(accountToken); this.props.onLogin(accountToken); } _createLoginForm() { const { accountHistory, accountToken } = this.props.account; const dropdownStyles = { height: this._shouldShowAccountHistory() ? this.state.dropdownHeight : 0 }; // auto-focus on account input when failed to log in // do not refactor this into instance method, // it has to be new function each time to be called on each render const autoFocusOnFailure = (input) => { if(this.props.account.status === 'failed' && input) { input.focus(); } }; return
{ this._formSubtitle() }
{ }
; } _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 (
); } }