// @flow
import React, { Component } 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,
};
export default class Login extends Component {
props: LoginPropTypes;
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 ;
}
_createFooter() {
return
{ 'Don\'t have an account number?' }
;
}
}
class AccountDropdown extends Component {
props: {
items: Array,
onSelect: ((value: AccountToken) => void),
onRemove: ((value: AccountToken) => void)
};
render() {
const uniqueItems = [...new Set(this.props.items)];
return (
{ uniqueItems.map(token => (
)) }
);
}
}
class AccountDropdownItem extends Component {
props: {
label: string,
value: AccountToken,
onRemove: (value: AccountToken) => void,
onSelect: (value: AccountToken) => void
};
render() {
return (
);
}
}