diff options
Diffstat (limited to 'ui/app/components/account-menu/account-menu.component.js')
-rw-r--r-- | ui/app/components/account-menu/account-menu.component.js | 301 |
1 files changed, 301 insertions, 0 deletions
diff --git a/ui/app/components/account-menu/account-menu.component.js b/ui/app/components/account-menu/account-menu.component.js new file mode 100644 index 000000000..b2fec647a --- /dev/null +++ b/ui/app/components/account-menu/account-menu.component.js @@ -0,0 +1,301 @@ +import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' +import debounce from 'lodash.debounce' +import { Menu, Item, Divider, CloseArea } from '../dropdowns/components/menu' +import { ENVIRONMENT_TYPE_POPUP } from '../../../../app/scripts/lib/enums' +import { getEnvironmentType } from '../../../../app/scripts/lib/util' +import Tooltip from '../tooltip' +import Identicon from '../identicon' +import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display' +import { PRIMARY } from '../../constants/common' +import { + SETTINGS_ROUTE, + INFO_ROUTE, + NEW_ACCOUNT_ROUTE, + IMPORT_ACCOUNT_ROUTE, + CONNECT_HARDWARE_ROUTE, + DEFAULT_ROUTE, +} from '../../routes' + +export default class AccountMenu extends PureComponent { + static contextTypes = { + t: PropTypes.func, + } + + static propTypes = { + accounts: PropTypes.object, + history: PropTypes.object, + identities: PropTypes.object, + isAccountMenuOpen: PropTypes.bool, + keyrings: PropTypes.array, + lockMetamask: PropTypes.func, + selectedAddress: PropTypes.string, + showAccountDetail: PropTypes.func, + showRemoveAccountConfirmationModal: PropTypes.func, + toggleAccountMenu: PropTypes.func, + } + + state = { + atAccountListBottom: false, + } + + componentDidUpdate (prevProps) { + const { prevIsAccountMenuOpen } = prevProps + const { isAccountMenuOpen } = this.props + + if (!prevIsAccountMenuOpen && isAccountMenuOpen) { + this.setAtAccountListBottom() + } + } + + renderAccounts () { + const { + identities, + accounts, + selectedAddress, + keyrings, + showAccountDetail, + } = this.props + + const accountOrder = keyrings.reduce((list, keyring) => list.concat(keyring.accounts), []) + + return accountOrder.filter(address => !!identities[address]).map(address => { + const identity = identities[address] + const isSelected = identity.address === selectedAddress + + const balanceValue = accounts[address] ? accounts[address].balance : '' + const simpleAddress = identity.address.substring(2).toLowerCase() + + const keyring = keyrings.find(kr => { + return kr.accounts.includes(simpleAddress) || kr.accounts.includes(identity.address) + }) + + return ( + <div + className="account-menu__account menu__item--clickable" + onClick={() => showAccountDetail(identity.address)} + key={identity.address} + > + <div className="account-menu__check-mark"> + { isSelected && <div className="account-menu__check-mark-icon" /> } + </div> + <Identicon + address={identity.address} + diameter={24} + /> + <div className="account-menu__account-info"> + <div className="account-menu__name"> + { identity.name || '' } + </div> + <UserPreferencedCurrencyDisplay + className="account-menu__balance" + value={balanceValue} + type={PRIMARY} + /> + </div> + { this.renderKeyringType(keyring) } + { this.renderRemoveAccount(keyring, identity) } + </div> + ) + }) + } + + renderRemoveAccount (keyring, identity) { + const { t } = this.context + // Any account that's not from the HD wallet Keyring can be removed + const { type } = keyring + const isRemovable = type !== 'HD Key Tree' + + return isRemovable && ( + <Tooltip + title={t('removeAccount')} + position="bottom" + > + <a + className="remove-account-icon" + onClick={e => this.removeAccount(e, identity)} + /> + </Tooltip> + ) + } + + removeAccount (e, identity) { + e.preventDefault() + e.stopPropagation() + const { showRemoveAccountConfirmationModal } = this.props + showRemoveAccountConfirmationModal(identity) + } + + renderKeyringType (keyring) { + const { t } = this.context + + // Sometimes keyrings aren't loaded yet + if (!keyring) { + return null + } + + const { type } = keyring + let label + + switch (type) { + case 'Trezor Hardware': + case 'Ledger Hardware': + label = t('hardware') + break + case 'Simple Key Pair': + label = t('imported') + break + } + + return label && ( + <div className="keyring-label allcaps"> + { label } + </div> + ) + } + + setAtAccountListBottom = () => { + const target = document.querySelector('.account-menu__accounts') + const { scrollTop, offsetHeight, scrollHeight } = target + const atAccountListBottom = scrollTop + offsetHeight >= scrollHeight + this.setState({ atAccountListBottom }) + } + + onScroll = debounce(this.setAtAccountListBottom, 25) + + handleScrollDown = e => { + e.stopPropagation() + const target = document.querySelector('.account-menu__accounts') + const { scrollHeight } = target + target.scroll({ left: 0, top: scrollHeight, behavior: 'smooth' }) + this.setAtAccountListBottom() + } + + renderScrollButton () { + const { accounts } = this.props + const { atAccountListBottom } = this.state + + return !atAccountListBottom && Object.keys(accounts).length > 3 && ( + <div + className="account-menu__scroll-button" + onClick={this.handleScrollDown} + > + <img + src="./images/icons/down-arrow.svg" + width={28} + height={28} + /> + </div> + ) + } + + render () { + const { t } = this.context + const { + isAccountMenuOpen, + toggleAccountMenu, + lockMetamask, + history, + } = this.props + + return ( + <Menu + className="account-menu" + isShowing={isAccountMenuOpen} + > + <CloseArea onClick={toggleAccountMenu} /> + <Item className="account-menu__header"> + { t('myAccounts') } + <button + className="account-menu__logout-button" + onClick={() => { + lockMetamask() + history.push(DEFAULT_ROUTE) + }} + > + { t('logout') } + </button> + </Item> + <Divider /> + <div className="account-menu__accounts-container"> + <div + className="account-menu__accounts" + onScroll={this.onScroll} + > + { this.renderAccounts() } + </div> + { this.renderScrollButton() } + </div> + <Divider /> + <Item + onClick={() => { + toggleAccountMenu() + history.push(NEW_ACCOUNT_ROUTE) + }} + icon={ + <img + className="account-menu__item-icon" + src="images/plus-btn-white.svg" + /> + } + text={t('createAccount')} + /> + <Item + onClick={() => { + toggleAccountMenu() + history.push(IMPORT_ACCOUNT_ROUTE) + }} + icon={ + <img + className="account-menu__item-icon" + src="images/import-account.svg" + /> + } + text={t('importAccount')} + /> + <Item + onClick={() => { + toggleAccountMenu() + + if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP) { + global.platform.openExtensionInBrowser(CONNECT_HARDWARE_ROUTE) + } else { + history.push(CONNECT_HARDWARE_ROUTE) + } + }} + icon={ + <img + className="account-menu__item-icon" + src="images/connect-icon.svg" + /> + } + text={t('connectHardwareWallet')} + /> + <Divider /> + <Item + onClick={() => { + toggleAccountMenu() + history.push(INFO_ROUTE) + }} + icon={ + <img src="images/mm-info-icon.svg" /> + } + text={t('infoHelp')} + /> + <Item + onClick={() => { + toggleAccountMenu() + history.push(SETTINGS_ROUTE) + }} + icon={ + <img + className="account-menu__item-icon" + src="images/settings.svg" + /> + } + text={t('settings')} + /> + </Menu> + ) + } +} |