diff options
Diffstat (limited to 'ui/app/components')
94 files changed, 1673 insertions, 1019 deletions
| diff --git a/ui/app/components/app/account-details/account-details.component.js b/ui/app/components/app/account-details/account-details.component.js new file mode 100644 index 000000000..ecf2f9428 --- /dev/null +++ b/ui/app/components/app/account-details/account-details.component.js @@ -0,0 +1,99 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import classnames from 'classnames' +import Identicon from '../../ui/identicon' +import Tooltip from '../../ui/tooltip-v2' +import copyToClipboard from 'copy-to-clipboard' + +export default class AccountDetails extends Component { +  static contextTypes = { +    t: PropTypes.func.isRequired, +    metricsEvent: PropTypes.func, +  } + +  static defaultProps = { +    hideSidebar: () => {}, +    showAccountDetailModal: () => {}, +  } + +  static propTypes = { +    hideSidebar: PropTypes.func, +    showAccountDetailModal: PropTypes.func, +    label: PropTypes.string.isRequired, +    checksummedAddress: PropTypes.string.isRequired, +    name: PropTypes.string.isRequired, +  } + +  state = { +    hasCopied: false, +    copyToClipboardPressed: false, +  } + +  copyAddress () { +    copyToClipboard(this.props.checksummedAddress) +    this.context.metricsEvent({ +      eventOpts: { +        category: 'Navigation', +        action: 'Home', +        name: 'Copied Address', +      }, +    }) +    this.setState({ hasCopied: true }) +    setTimeout(() => this.setState({ hasCopied: false }), 3000) +  } + +  render () { +    const { t } = this.context + +    const { +      hideSidebar, +      showAccountDetailModal, +      label, +      checksummedAddress, +      name, +    } = this.props + +    const { +      hasCopied, +      copyToClipboardPressed, +    } = this.state + +    return ( +      <div> +        <div className="flex-column account-details"> +          <div className="account-details__sidebar-close" onClick={hideSidebar} /> +          <div className="account-details__keyring-label allcaps"> +            {label} +          </div> +          <div className="flex-column flex-center account-details__name-container" onClick={showAccountDetailModal}> +            <Identicon diameter={54} address={checksummedAddress} /> +            <span className="account-details__account-name"> +              {name} +            </span> +            <button className="btn-secondary account-details__details-button"> +              {t('details')} +            </button> +          </div> +        </div> +        <Tooltip +          position={'bottom'} +          title={hasCopied ? t('copiedExclamation') : t('copyToClipboard')} +          wrapperClassName="account-details__tooltip" +        > +          <button +            className={classnames({ +              'account-details__address': true, +              'account-details__address__pressed': copyToClipboardPressed, +            })} +            onClick={() => this.copyAddress()} +            onMouseDown={() => this.setState({ copyToClipboardPressed: true })} +            onMouseUp={() => this.setState({ copyToClipboardPressed: false })} +          > +            {checksummedAddress.slice(0, 6)}...{checksummedAddress.slice(-4)} +            <i className="fa fa-clipboard" style={{ marginLeft: '8px' }} /> +          </button> +        </Tooltip> +      </div> +    ) +  } +} diff --git a/ui/app/components/app/account-details/account-details.container.js b/ui/app/components/app/account-details/account-details.container.js new file mode 100644 index 000000000..581ff1e2f --- /dev/null +++ b/ui/app/components/app/account-details/account-details.container.js @@ -0,0 +1,14 @@ +import { connect } from 'react-redux' +import { hideSidebar, showModal } from '../../../store/actions' +import AccountDetails from './account-details.component' + +function mapDispatchToProps (dispatch) { +  return { +    hideSidebar: () => dispatch(hideSidebar()), +    showAccountDetailModal: () => { +      dispatch(showModal({ name: 'ACCOUNT_DETAILS' })) +    }, +  } +} + +export default connect(null, mapDispatchToProps)(AccountDetails) diff --git a/ui/app/components/app/account-details/index.js b/ui/app/components/app/account-details/index.js new file mode 100644 index 000000000..dca244fee --- /dev/null +++ b/ui/app/components/app/account-details/index.js @@ -0,0 +1 @@ +export { default } from './account-details.container' diff --git a/ui/app/components/app/account-details/index.scss b/ui/app/components/app/account-details/index.scss new file mode 100644 index 000000000..b0a921df3 --- /dev/null +++ b/ui/app/components/app/account-details/index.scss @@ -0,0 +1,79 @@ +.account-details { +  flex: 0 0 auto; + +  &__keyring-label { +    height: 50px; +    color: $dusty-gray; +    font-family: Roboto; +    font-size: 10px; +    text-align: right; +    padding: 17px 20px 0; +    box-sizing: border-box; +  } + +  &__name-container { +    flex: 0 0 auto; +    cursor: pointer; +    width: 100%; +    margin: 0 auto; +  } + +  &__account-name { +    font-size: 24px; +    color: $black; +    margin-top: 8px; +    margin-bottom: .9rem; +    white-space: nowrap; +    text-overflow: ellipsis; +    overflow: hidden; +    width: 100%; +    padding: 0 8px; +    text-align: center; +  } + +  &__details-button { +    font-size: 10px; +    border-radius: 17px; +    background-color: transparent; +    margin: 0 auto; +    padding: 4px 12px; +    flex: 0 0 auto; +  } + +  &__tooltip { +    display: flex; +    justify-content: center; +    align-items: center; +    padding: 24px; +  } + +  &__address { +    border-radius: 3px; +    background-color: $alto; +    color: $scorpion; +    font-size: 14px; +    line-height: 12px; +    padding: 4px 12px; +    cursor: pointer; +    flex: 0 0 auto; + +    &__pressed { +      background-color: $manatee, +    } +  } + +  &__sidebar-close { + +    @media screen and (max-width: 575px) { +      &::after { +        content: '\00D7'; +        font-size: 40px; +        color: $tundora; +        position: absolute; +        top: 12px; +        left: 12px; +        cursor: pointer; +      } +    } +  } +} diff --git a/ui/app/components/app/app-header/app-header.component.js b/ui/app/components/app/app-header/app-header.component.js index 171a3499f..7bf7a39bd 100644 --- a/ui/app/components/app/app-header/app-header.component.js +++ b/ui/app/components/app/app-header/app-header.component.js @@ -2,6 +2,7 @@ import React, { PureComponent } from 'react'  import PropTypes from 'prop-types'  import classnames from 'classnames'  import Identicon from '../../ui/identicon' +import MetaFoxLogo from '../../ui/metafox-logo'  import { DEFAULT_ROUTE } from '../../../helpers/constants/routes'  const NetworkIndicator = require('../network') @@ -70,6 +71,7 @@ export default class AppHeader extends PureComponent {          <Identicon            address={selectedAddress}            diameter={32} +          addBorder={true}          />        </div>      ) @@ -89,20 +91,10 @@ export default class AppHeader extends PureComponent {        <div          className={classnames('app-header', { 'app-header--back-drop': isUnlocked })}>          <div className="app-header__contents"> -          <div -            className="app-header__logo-container" +          <MetaFoxLogo +            unsetIconHeight={true}              onClick={() => history.push(DEFAULT_ROUTE)} -          > -            <img -              className="app-header__metafox-logo app-header__metafox-logo--horizontal" -              src="/images/logo/metamask-logo-horizontal.svg" -              height={30} -            /> -            <img -              className="app-header__metafox-logo app-header__metafox-logo--icon" -              src="/images/logo/metamask-fox.svg" -            /> -          </div> +          />            <div className="app-header__account-menu-container">              {                !hideNetworkIndicator && ( diff --git a/ui/app/components/app/app-header/index.scss b/ui/app/components/app/app-header/index.scss index d3f37b7a2..0ea1793ca 100644 --- a/ui/app/components/app/app-header/index.scss +++ b/ui/app/components/app/app-header/index.scss @@ -10,7 +10,6 @@    @media screen and (max-width: 575px) {      padding: 1rem; -    box-shadow: 0 0 0 1px rgba(0, 0, 0, .08);      z-index: $mobile-header-z-index;    } @@ -24,7 +23,7 @@          position: absolute;          width: 100%;          height: 32px; -        background: $gallery; +        background: $Grey-000;          bottom: -32px;        }      } diff --git a/ui/app/components/app/confirm-page-container/confirm-detail-row/tests/confirm-detail-row.component.test.js b/ui/app/components/app/confirm-page-container/confirm-detail-row/tests/confirm-detail-row.component.test.js index c8507985d..95ca8144a 100644 --- a/ui/app/components/app/confirm-page-container/confirm-detail-row/tests/confirm-detail-row.component.test.js +++ b/ui/app/components/app/confirm-page-container/confirm-detail-row/tests/confirm-detail-row.component.test.js @@ -27,7 +27,7 @@ describe('Confirm Detail Row Component', function () {      )    }) -	describe('render', () => { +  describe('render', () => {      it('should render a div with a confirm-detail-row class', () => {        assert.equal(wrapper.find('div.confirm-detail-row').length, 1)      }) @@ -60,5 +60,5 @@ describe('Confirm Detail Row Component', function () {        wrapper.find('.confirm-detail-row__header-text').props().onClick()        assert.equal(assert.equal(propsMethodSpies.onHeaderClick.callCount, 1))      }) -	}) +  })  }) diff --git a/ui/app/components/app/confirm-page-container/confirm-page-container-navigation/confirm-page-container-navigation.component.js b/ui/app/components/app/confirm-page-container/confirm-page-container-navigation/confirm-page-container-navigation.component.js index 8327f997b..c24d24b17 100755 --- a/ui/app/components/app/confirm-page-container/confirm-page-container-navigation/confirm-page-container-navigation.component.js +++ b/ui/app/components/app/confirm-page-container/confirm-page-container-navigation/confirm-page-container-navigation.component.js @@ -6,9 +6,9 @@ const ConfirmPageContainerNavigation = props => {    return (      <div className="confirm-page-container-navigation" -        style={{ -          display: showNavigation ? 'flex' : 'none', -        }} +      style={{ +        display: showNavigation ? 'flex' : 'none', +      }}      >        <div className="confirm-page-container-navigation__container"          style={{ diff --git a/ui/app/components/app/confirm-page-container/confirm-page-container.component.js b/ui/app/components/app/confirm-page-container/confirm-page-container.component.js index 326e4f83e..1ff797fa1 100644 --- a/ui/app/components/app/confirm-page-container/confirm-page-container.component.js +++ b/ui/app/components/app/confirm-page-container/confirm-page-container.component.js @@ -106,16 +106,16 @@ export default class ConfirmPageContainer extends Component {      return (        <div className="page-container">          <ConfirmPageContainerNavigation -            totalTx={totalTx} -            positionOfCurrentTx={positionOfCurrentTx} -            nextTxId={nextTxId} -            prevTxId={prevTxId} -            showNavigation={showNavigation} -            onNextTx={(txId) => onNextTx(txId)} -            firstTx={firstTx} -            lastTx={lastTx} -            ofText={ofText} -            requestsWaitingText={requestsWaitingText} +          totalTx={totalTx} +          positionOfCurrentTx={positionOfCurrentTx} +          nextTxId={nextTxId} +          prevTxId={prevTxId} +          showNavigation={showNavigation} +          onNextTx={(txId) => onNextTx(txId)} +          firstTx={firstTx} +          lastTx={lastTx} +          ofText={ofText} +          requestsWaitingText={requestsWaitingText}          />          <ConfirmPageContainerHeader            showEdit={showEdit} diff --git a/ui/app/components/app/contact-list/contact-list.component.js b/ui/app/components/app/contact-list/contact-list.component.js new file mode 100644 index 000000000..ec9b5f8eb --- /dev/null +++ b/ui/app/components/app/contact-list/contact-list.component.js @@ -0,0 +1,114 @@ +import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' +import RecipientGroup from './recipient-group/recipient-group.component' + +export default class ContactList extends PureComponent { +  static propTypes = { +    searchForContacts: PropTypes.func, +    searchForRecents: PropTypes.func, +    searchForMyAccounts: PropTypes.func, +    selectRecipient: PropTypes.func, +    children: PropTypes.node, +    selectedAddress: PropTypes.string, +  } + +  static contextTypes = { +    t: PropTypes.func, +  } + +  state = { +    isShowingAllRecent: false, +  } + +  renderRecents () { +    const { t } = this.context +    const { isShowingAllRecent } = this.state +    const nonContacts = this.props.searchForRecents() + +    const showLoadMore = !isShowingAllRecent && nonContacts.length > 2 + +    return ( +      <div className="send__select-recipient-wrapper__recent-group-wrapper"> +        <RecipientGroup +          label={t('recents')} +          items={showLoadMore ? nonContacts.slice(0, 2) : nonContacts} +          onSelect={this.props.selectRecipient} +          selectedAddress={this.props.selectedAddress} +        /> +        { +          showLoadMore && ( +            <div +              className="send__select-recipient-wrapper__recent-group-wrapper__load-more" +              onClick={() => this.setState({ isShowingAllRecent: true })} +            > +              {t('loadMore')} +            </div> +          ) +        } +      </div> +    ) +  } + +  renderAddressBook () { +    const contacts = this.props.searchForContacts() + +    const contactGroups = contacts.reduce((acc, contact) => { +      const firstLetter = contact.name.slice(0, 1).toUpperCase() +      acc[firstLetter] = acc[firstLetter] || [] +      const bucket = acc[firstLetter] +      bucket.push(contact) +      return acc +    }, {}) + +    return Object +      .entries(contactGroups) +      .sort(([letter1], [letter2]) => { +        if (letter1 > letter2) { +          return 1 +        } else if (letter1 === letter2) { +          return 0 +        } else if (letter1 < letter2) { +          return -1 +        } +      }) +      .map(([letter, groupItems]) => ( +        <RecipientGroup +          key={`${letter}-contract-group`} +          label={letter} +          items={groupItems} +          onSelect={this.props.selectRecipient} +          selectedAddress={this.props.selectedAddress} +        /> +      )) +  } + +  renderMyAccounts () { +    const myAccounts = this.props.searchForMyAccounts() + +    return ( +      <RecipientGroup +        items={myAccounts} +        onSelect={this.props.selectRecipient} +        selectedAddress={this.props.selectedAddress} +      /> +    ) +  } + +  render () { +    const { +      children, +      searchForRecents, +      searchForContacts, +      searchForMyAccounts, +    } = this.props + +    return ( +      <div className="send__select-recipient-wrapper__list"> +        { children || null } +        { searchForRecents && this.renderRecents() } +        { searchForContacts && this.renderAddressBook() } +        { searchForMyAccounts && this.renderMyAccounts() } +      </div> +    ) +  } +} diff --git a/ui/app/components/app/contact-list/index.js b/ui/app/components/app/contact-list/index.js new file mode 100644 index 000000000..d90c29b2b --- /dev/null +++ b/ui/app/components/app/contact-list/index.js @@ -0,0 +1 @@ +export { default } from './contact-list.component' diff --git a/ui/app/components/app/contact-list/recipient-group/index.js b/ui/app/components/app/contact-list/recipient-group/index.js new file mode 100644 index 000000000..7d827523f --- /dev/null +++ b/ui/app/components/app/contact-list/recipient-group/index.js @@ -0,0 +1 @@ +export { default } from './recipient-group.component' diff --git a/ui/app/components/app/contact-list/recipient-group/recipient-group.component.js b/ui/app/components/app/contact-list/recipient-group/recipient-group.component.js new file mode 100644 index 000000000..a2248326e --- /dev/null +++ b/ui/app/components/app/contact-list/recipient-group/recipient-group.component.js @@ -0,0 +1,59 @@ +import React from 'react' +import PropTypes from 'prop-types' +import Identicon from '../../../ui/identicon' +import classnames from 'classnames' +import { ellipsify } from '../../../../pages/send/send.utils' + +function addressesEqual (address1, address2) { +  return String(address1).toLowerCase() === String(address2).toLowerCase() +} + +export default function RecipientGroup ({ label, items, onSelect, selectedAddress }) { +  if (!items || !items.length) { +    return null +  } + +  return ( +    <div className="send__select-recipient-wrapper__group"> +      {label && <div className="send__select-recipient-wrapper__group-label"> +        {label} +      </div>} +      { +        items.map(({ address, name }) => ( +          <div +            key={address} +            onClick={() => onSelect(address, name)} +            className={classnames({ +              'send__select-recipient-wrapper__group-item': !addressesEqual(address, selectedAddress), +              'send__select-recipient-wrapper__group-item--selected': addressesEqual(address, selectedAddress), +            })} +          > +            <Identicon address={address} diameter={28} /> +            <div className="send__select-recipient-wrapper__group-item__content"> +              <div className="send__select-recipient-wrapper__group-item__title"> +                {name || ellipsify(address)} +              </div> +              { +                name && ( +                  <div className="send__select-recipient-wrapper__group-item__subtitle"> +                    {ellipsify(address)} +                  </div> +                ) +              } +            </div> +          </div> +        )) +      } +    </div> +  ) +} + +RecipientGroup.propTypes = { +  label: PropTypes.string, +  items: PropTypes.arrayOf(PropTypes.shape({ +    address: PropTypes.string, +    name: PropTypes.string, +  })), +  onSelect: PropTypes.func.isRequired, +  selectedAddress: PropTypes.string, +} diff --git a/ui/app/components/app/dropdowns/account-details-dropdown.js b/ui/app/components/app/dropdowns/account-details-dropdown.js index a4c33620a..cf2aa8ae8 100644 --- a/ui/app/components/app/dropdowns/account-details-dropdown.js +++ b/ui/app/components/app/dropdowns/account-details-dropdown.js @@ -59,7 +59,7 @@ AccountDetailsDropdown.prototype.render = function () {      viewOnEtherscan,      showRemoveAccountConfirmationModal,      rpcPrefs, -   } = this.props +  } = this.props    const address = selectedIdentity.address diff --git a/ui/app/components/app/dropdowns/network-dropdown.js b/ui/app/components/app/dropdowns/network-dropdown.js index 378ad3ba6..e6a24ef11 100644 --- a/ui/app/components/app/dropdowns/network-dropdown.js +++ b/ui/app/components/app/dropdowns/network-dropdown.js @@ -32,9 +32,6 @@ function mapStateToProps (state) {  function mapDispatchToProps (dispatch) {    return { -    hideModal: () => { -      dispatch(actions.hideModal()) -    },      setProviderType: (type) => {        dispatch(actions.setProviderType(type))      }, @@ -47,7 +44,6 @@ function mapDispatchToProps (dispatch) {      delRpcTarget: (target) => {        dispatch(actions.delRpcTarget(target))      }, -    showNetworkDropdown: () => dispatch(actions.showNetworkDropdown()),      hideNetworkDropdown: () => dispatch(actions.hideNetworkDropdown()),      setNetworksTabAddMode: isInAddMode => dispatch(actions.setNetworksTabAddMode(isInAddMode)),    } @@ -357,12 +353,12 @@ NetworkDropdown.prototype.renderCommonRpc = function (rpcListDetail, provider) {              },            }, nickname || rpc),            h('i.fa.fa-times.delete', -          { -            onClick: (e) => { -              e.stopPropagation() -              props.delRpcTarget(rpc) -            }, -          }), +            { +              onClick: (e) => { +                e.stopPropagation() +                props.delRpcTarget(rpc) +              }, +            }),          ]        )      } diff --git a/ui/app/components/app/ens-input.js b/ui/app/components/app/ens-input.js deleted file mode 100644 index 5eea0dd90..000000000 --- a/ui/app/components/app/ens-input.js +++ /dev/null @@ -1,181 +0,0 @@ -const Component = require('react').Component -const PropTypes = require('prop-types') -const h = require('react-hyperscript') -const inherits = require('util').inherits -const extend = require('xtend') -const debounce = require('debounce') -const copyToClipboard = require('copy-to-clipboard') -const ENS = require('ethjs-ens') -const networkMap = require('ethjs-ens/lib/network-map.json') -const ensRE = /.+\..+$/ -const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' -const connect = require('react-redux').connect -const ToAutoComplete = require('../../pages/send/to-autocomplete').default -const log = require('loglevel') -const { isValidENSAddress } = require('../../helpers/utils/util') - -EnsInput.contextTypes = { -  t: PropTypes.func, -} - -module.exports = connect()(EnsInput) - - -inherits(EnsInput, Component) -function EnsInput () { -  Component.call(this) -} - -EnsInput.prototype.onChange = function (recipient) { - -  const network = this.props.network -  const networkHasEnsSupport = getNetworkEnsSupport(network) - -  this.props.onChange({ toAddress: recipient }) - -  if (!networkHasEnsSupport) return - -  if (recipient.match(ensRE) === null) { -    return this.setState({ -      loadingEns: false, -      ensResolution: null, -      ensFailure: null, -      toError: null, -    }) -  } - -  this.setState({ -    loadingEns: true, -  }) -  this.checkName(recipient) -} - -EnsInput.prototype.render = function () { -  const props = this.props -  const opts = extend(props, { -    list: 'addresses', -    onChange: this.onChange.bind(this), -    qrScanner: true, -  }) -  return h('div', { -    style: { width: '100%', position: 'relative' }, -  }, [ -    h(ToAutoComplete, { ...opts }), -    this.ensIcon(), -  ]) -} - -EnsInput.prototype.componentDidMount = function () { -  const network = this.props.network -  const networkHasEnsSupport = getNetworkEnsSupport(network) -  this.setState({ ensResolution: ZERO_ADDRESS }) - -  if (networkHasEnsSupport) { -    const provider = global.ethereumProvider -    this.ens = new ENS({ provider, network }) -    this.checkName = debounce(this.lookupEnsName.bind(this), 200) -  } -} - -EnsInput.prototype.lookupEnsName = function (recipient) { -  const { ensResolution } = this.state - -  log.info(`ENS attempting to resolve name: ${recipient}`) -  this.ens.lookup(recipient.trim()) -  .then((address) => { -    if (address === ZERO_ADDRESS) throw new Error(this.context.t('noAddressForName')) -    if (address !== ensResolution) { -      this.setState({ -        loadingEns: false, -        ensResolution: address, -        nickname: recipient.trim(), -        hoverText: address + '\n' + this.context.t('clickCopy'), -        ensFailure: false, -        toError: null, -      }) -    } -  }) -  .catch((reason) => { -    const setStateObj = { -      loadingEns: false, -      ensResolution: recipient, -      ensFailure: true, -      toError: null, -    } -    if (isValidENSAddress(recipient) && reason.message === 'ENS name not defined.') { -      setStateObj.hoverText = this.context.t('ensNameNotFound') -      setStateObj.toError = 'ensNameNotFound' -      setStateObj.ensFailure = false -    } else { -      log.error(reason) -      setStateObj.hoverText = reason.message -    } - -    return this.setState(setStateObj) -  }) -} - -EnsInput.prototype.componentDidUpdate = function (prevProps, prevState) { -  const state = this.state || {} -  const ensResolution = state.ensResolution -  // If an address is sent without a nickname, meaning not from ENS or from -  // the user's own accounts, a default of a one-space string is used. -  const nickname = state.nickname || ' ' -  if (prevProps.network !== this.props.network) { -    const provider = global.ethereumProvider -    this.ens = new ENS({ provider, network: this.props.network }) -    this.onChange(ensResolution) -  } -  if (prevState && ensResolution && this.props.onChange && -      ensResolution !== prevState.ensResolution) { -    this.props.onChange({ toAddress: ensResolution, nickname, toError: state.toError, toWarning: state.toWarning }) -  } -} - -EnsInput.prototype.ensIcon = function (recipient) { -  const { hoverText } = this.state || {} -  return h('span.#ensIcon', { -    title: hoverText, -    style: { -      position: 'absolute', -      top: '16px', -      left: '-25px', -    }, -  }, this.ensIconContents(recipient)) -} - -EnsInput.prototype.ensIconContents = function () { -  const { loadingEns, ensFailure, ensResolution, toError } = this.state || { ensResolution: ZERO_ADDRESS } - -  if (toError) return - -  if (loadingEns) { -    return h('img', { -      src: 'images/loading.svg', -      style: { -        width: '30px', -        height: '30px', -        transform: 'translateY(-6px)', -      }, -    }) -  } - -  if (ensFailure) { -    return h('i.fa.fa-warning.fa-lg.warning') -  } - -  if (ensResolution && (ensResolution !== ZERO_ADDRESS)) { -    return h('i.fa.fa-check-circle.fa-lg.cursor-pointer', { -      style: { color: 'green' }, -      onClick: (event) => { -        event.preventDefault() -        event.stopPropagation() -        copyToClipboard(ensResolution) -      }, -    }) -  } -} - -function getNetworkEnsSupport (network) { -  return Boolean(networkMap[network]) -} diff --git a/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js b/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js index d942fd150..7b87b3033 100644 --- a/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js +++ b/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js @@ -131,8 +131,8 @@ export default class AdvancedTabContent extends Component {          </div>          { isInError            ? <div className={`advanced-gas-inputs__gas-edit-row__${errorType}-text`}> -              { errorText } -            </div> +            { errorText } +          </div>            : null }        </div>      ) diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js index eab3434df..88d28b9ed 100644 --- a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js +++ b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js @@ -104,8 +104,8 @@ export default class AdvancedTabContent extends Component {          </div>          { isInError            ? <div className={`advanced-tab__gas-edit-row__${errorType}-text`}> -              { errorText } -            </div> +            { errorText } +          </div>            : null }        </div>      ) @@ -126,7 +126,7 @@ export default class AdvancedTabContent extends Component {            <div className="advanced-tab__transaction-data-summary__fee">              {transactionFee}            </div> -          <div className="time-remaining">{timeRemaining}</div> +          <div className="advanced-tab__transaction-data-summary__time-remaining">{timeRemaining}</div>          </div>        </div>      ) diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/index.scss b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/index.scss index 20a503018..e35b6d594 100644 --- a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/index.scss +++ b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/index.scss @@ -1,5 +1,3 @@ -@import './time-remaining/index'; -  .advanced-tab {    display: flex;    flex-flow: column; @@ -36,6 +34,24 @@        font-size: 16px;        color: #313A5E;      } + +    &__time-remaining { +      color: #313A5E; +      font-size: 16px; + +      .minutes-num, .seconds-num { +        font-size: 16px; +      } + +      .seconds-num { +        margin-left: 7px; +        font-size: 16px; +      } + +      .minutes-label, .seconds-label { +        font-size: 16px; +      } +    }    }    &__fee-chart { @@ -94,7 +110,7 @@        display: flex;        justify-content: space-between;        align-items: center; -       +        .fa-info-circle {          color: $silver;          margin-left: 10px; @@ -204,4 +220,4 @@        color: $dusty-gray;      }    } -}
\ No newline at end of file +} diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/tests/advanced-tab-content-component.test.js b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/tests/advanced-tab-content-component.test.js index 5f7d90922..683eeda9b 100644 --- a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/tests/advanced-tab-content-component.test.js +++ b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/tests/advanced-tab-content-component.test.js @@ -124,7 +124,7 @@ describe('AdvancedTabContent Component', function () {        const dataNode = dataSummary.children().at(1)        assert(dataNode.hasClass('advanced-tab__transaction-data-summary__container'))        assert.equal(dataNode.children().at(0).text(), 'mockTotalFee') -      assert(dataNode.children().at(1).hasClass('time-remaining')) +      assert(dataNode.children().at(1).hasClass('advanced-tab__transaction-data-summary__time-remaining'))        assert.equal(dataNode.children().at(1).text(), 'mockMsRemaining')      })    }) diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/index.js b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/index.js deleted file mode 100644 index 61b681e1a..000000000 --- a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './time-remaining.component' diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/index.scss b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/index.scss deleted file mode 100644 index e2115af7f..000000000 --- a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/index.scss +++ /dev/null @@ -1,17 +0,0 @@ -.time-remaining { -  color: #313A5E; -  font-size: 16px; - -  .minutes-num, .seconds-num { -    font-size: 16px; -  } - -  .seconds-num { -    margin-left: 7px; -    font-size: 16px; -  } - -  .minutes-label, .seconds-label { -    font-size: 16px; -  } -}
\ No newline at end of file diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/tests/time-remaining-component.test.js b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/tests/time-remaining-component.test.js deleted file mode 100644 index 17f0345d5..000000000 --- a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/tests/time-remaining-component.test.js +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react' -import assert from 'assert' -import shallow from '../../../../../../../../lib/shallow-with-context' -import TimeRemaining from '../time-remaining.component.js' - -describe('TimeRemaining Component', function () { -  let wrapper - -  beforeEach(() => { -    wrapper = shallow(<TimeRemaining -      milliseconds={495000} -    />) -  }) - -  describe('render()', () => { -    it('should render the time-remaining root node', () => { -      assert(wrapper.hasClass('time-remaining')) -    }) - -    it('should render minutes and seconds numbers and labels', () => { -      const timeRemainingChildren = wrapper.children() -      assert.equal(timeRemainingChildren.length, 4) -      assert.equal(timeRemainingChildren.at(0).text(), 8) -      assert.equal(timeRemainingChildren.at(1).text(), 'minutesShorthand') -      assert.equal(timeRemainingChildren.at(2).text(), 15) -      assert.equal(timeRemainingChildren.at(3).text(), 'secondsShorthand') -    }) -  }) - -}) diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/time-remaining.component.js b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/time-remaining.component.js deleted file mode 100644 index 826d41f9c..000000000 --- a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/time-remaining.component.js +++ /dev/null @@ -1,33 +0,0 @@ -import React, { Component } from 'react' -import PropTypes from 'prop-types' -import { getTimeBreakdown } from './time-remaining.utils' - -export default class TimeRemaining extends Component { -  static contextTypes = { -    t: PropTypes.func, -  } - -  static propTypes = { -    milliseconds: PropTypes.number, -  } - -  render () { -    const { -      milliseconds, -    } = this.props - -    const { -      minutes, -      seconds, -    } = getTimeBreakdown(milliseconds) - -    return ( -      <div className="time-remaining"> -        <span className="minutes-num">{minutes}</span> -        <span className="minutes-label">{this.context.t('minutesShorthand')}</span> -        <span className="seconds-num">{seconds}</span> -        <span className="seconds-label">{this.context.t('secondsShorthand')}</span> -      </div> -    ) -  } -} diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/time-remaining.utils.js b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/time-remaining.utils.js deleted file mode 100644 index cf43e0acb..000000000 --- a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/time-remaining/time-remaining.utils.js +++ /dev/null @@ -1,11 +0,0 @@ -function getTimeBreakdown (milliseconds) { -  return { -    hours: Math.floor(milliseconds / 3600000), -    minutes: Math.floor((milliseconds % 3600000) / 60000), -    seconds: Math.floor((milliseconds % 60000) / 1000), -  } -} - -module.exports = { -  getTimeBreakdown, -} diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/basic-tab-content.component.js b/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/basic-tab-content.component.js index 5f3925fa5..931611460 100644 --- a/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/basic-tab-content.component.js +++ b/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/basic-tab-content.component.js @@ -21,12 +21,12 @@ export default class BasicTabContent extends Component {          <div className="basic-tab-content__title">{ t('estimatedProcessingTimes') }</div>          <div className="basic-tab-content__blurb">{ t('selectAHigherGasFee') }</div>          {!gasPriceButtonGroupProps.loading -            ? <GasPriceButtonGroup -              className="gas-price-button-group--alt" -              showCheck={true} -              {...gasPriceButtonGroupProps} -            /> -            : <Loading /> +          ? <GasPriceButtonGroup +            className="gas-price-button-group--alt" +            showCheck={true} +            {...gasPriceButtonGroupProps} +          /> +          : <Loading />          }          <div className="basic-tab-content__footer-blurb">{ t('acceleratingATransaction') }</div>        </div> diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js b/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js index e18c1067e..5e557f660 100644 --- a/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js +++ b/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js @@ -47,7 +47,7 @@ export default class GasModalPageContainer extends Component {      const promise = this.props.hideBasic        ? Promise.resolve(this.props.blockTime)        : this.props.fetchBasicGasAndTimeEstimates() -          .then(basicEstimates => basicEstimates.blockTime) +        .then(basicEstimates => basicEstimates.blockTime)      promise        .then(blockTime => { @@ -144,11 +144,11 @@ export default class GasModalPageContainer extends Component {      return (        <Tabs>          {tabsToRender.map(({ name, content }, i) => <Tab name={this.context.t(name)} key={`gas-modal-tab-${i}`}> -            <div className="gas-modal-content"> -              { content } -              { this.renderInfoRows(newTotalFiat, newTotalEth, sendAmount, transactionFee) } -            </div> -          </Tab> +          <div className="gas-modal-content"> +            { content } +            { this.renderInfoRows(newTotalFiat, newTotalEth, sendAmount, transactionFee) } +          </div> +        </Tab>          )}        </Tabs>      ) diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-container.test.js b/ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-container.test.js index 03d254eee..d5f3837a9 100644 --- a/ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-container.test.js +++ b/ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-container.test.js @@ -341,44 +341,44 @@ describe('gas-modal-page-container container', () => {    })    describe('mergeProps', () => { -      let stateProps -      let dispatchProps -      let ownProps - -      beforeEach(() => { -        stateProps = { -          gasPriceButtonGroupProps: { -            someGasPriceButtonGroupProp: 'foo', -            anotherGasPriceButtonGroupProp: 'bar', -          }, -          isConfirm: true, -          someOtherStateProp: 'baz', -          transaction: {}, -        } -        dispatchProps = { -          updateCustomGasPrice: sinon.spy(), -          hideGasButtonGroup: sinon.spy(), -          setGasData: sinon.spy(), -          updateConfirmTxGasAndCalculate: sinon.spy(), -          someOtherDispatchProp: sinon.spy(), -          createSpeedUpTransaction: sinon.spy(), -          hideSidebar: sinon.spy(), -          hideModal: sinon.spy(), -          cancelAndClose: sinon.spy(), -        } -        ownProps = { someOwnProp: 123 } -      }) +    let stateProps +    let dispatchProps +    let ownProps -      afterEach(() => { -        dispatchProps.updateCustomGasPrice.resetHistory() -        dispatchProps.hideGasButtonGroup.resetHistory() -        dispatchProps.setGasData.resetHistory() -        dispatchProps.updateConfirmTxGasAndCalculate.resetHistory() -        dispatchProps.someOtherDispatchProp.resetHistory() -        dispatchProps.createSpeedUpTransaction.resetHistory() -        dispatchProps.hideSidebar.resetHistory() -        dispatchProps.hideModal.resetHistory() -      }) +    beforeEach(() => { +      stateProps = { +        gasPriceButtonGroupProps: { +          someGasPriceButtonGroupProp: 'foo', +          anotherGasPriceButtonGroupProp: 'bar', +        }, +        isConfirm: true, +        someOtherStateProp: 'baz', +        transaction: {}, +      } +      dispatchProps = { +        updateCustomGasPrice: sinon.spy(), +        hideGasButtonGroup: sinon.spy(), +        setGasData: sinon.spy(), +        updateConfirmTxGasAndCalculate: sinon.spy(), +        someOtherDispatchProp: sinon.spy(), +        createSpeedUpTransaction: sinon.spy(), +        hideSidebar: sinon.spy(), +        hideModal: sinon.spy(), +        cancelAndClose: sinon.spy(), +      } +      ownProps = { someOwnProp: 123 } +    }) + +    afterEach(() => { +      dispatchProps.updateCustomGasPrice.resetHistory() +      dispatchProps.hideGasButtonGroup.resetHistory() +      dispatchProps.setGasData.resetHistory() +      dispatchProps.updateConfirmTxGasAndCalculate.resetHistory() +      dispatchProps.someOtherDispatchProp.resetHistory() +      dispatchProps.createSpeedUpTransaction.resetHistory() +      dispatchProps.hideSidebar.resetHistory() +      dispatchProps.hideModal.resetHistory() +    })      it('should return the expected props when isConfirm is true', () => {        const result = mergeProps(stateProps, dispatchProps, ownProps) diff --git a/ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.utils.js b/ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.utils.js index 55512ce09..b941f1cf9 100644 --- a/ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.utils.js +++ b/ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.utils.js @@ -210,17 +210,17 @@ export function generateChart (gasPrices, estimatedTimes, gasPricesMax, estimate      },      padding: {left: 20, right: 15, top: 6, bottom: 10},      data: { -        x: 'x', -        columns: [ -            ['x', ...gasPrices], -            ['data1', ...estimatedTimes], -        ], -        types: { -          data1: 'area', -        }, -        selection: { -          enabled: false, -        }, +      x: 'x', +      columns: [ +        ['x', ...gasPrices], +        ['data1', ...estimatedTimes], +      ], +      types: { +        data1: 'area', +      }, +      selection: { +        enabled: false, +      },      },      color: {        data1: '#259de5', @@ -254,13 +254,13 @@ export function generateChart (gasPrices, estimatedTimes, gasPricesMax, estimate        },      },      legend: { -        show: false, +      show: false,      },      grid: { -        x: {}, -        lines: { -          front: false, -        }, +      x: {}, +      lines: { +        front: false, +      },      },      point: {        focus: { @@ -296,8 +296,8 @@ export function generateChart (gasPrices, estimatedTimes, gasPricesMax, estimate          const flipTooltip = circleY - circleWidth < chartYStart + 5          d3 -        .select('.tooltip-arrow') -        .style('margin-top', flipTooltip ? '-16px' : '4px') +          .select('.tooltip-arrow') +          .style('margin-top', flipTooltip ? '-16px' : '4px')          return {            top: bigNumMinus(circleY, chartYStart).minus(19).plus(flipTooltip ? circleWidth + 38 : 0).toNumber(), diff --git a/ui/app/components/app/home-notification/home-notification.component.js b/ui/app/components/app/home-notification/home-notification.component.js new file mode 100644 index 000000000..cc86ef6d8 --- /dev/null +++ b/ui/app/components/app/home-notification/home-notification.component.js @@ -0,0 +1,112 @@ +import React, { PureComponent } from 'react' +import classnames from 'classnames' +import {Tooltip as ReactTippy} from 'react-tippy' +import PropTypes from 'prop-types' +import Button from '../../ui/button' + +export default class HomeNotification extends PureComponent { +  static contextTypes = { +    metricsEvent: PropTypes.func, +  } + +  static defaultProps = { +    onAccept: null, +    ignoreText: null, +    onIgnore: null, +    infoText: null, +  } + +  static propTypes = { +    acceptText: PropTypes.string.isRequired, +    onAccept: PropTypes.func, +    ignoreText: PropTypes.string, +    onIgnore: PropTypes.func, +    descriptionText: PropTypes.string.isRequired, +    infoText: PropTypes.string, +    classNames: PropTypes.array, +  } + +  handleAccept = () => { +    this.props.onAccept() +  } + +  handleIgnore = () => { +    this.props.onIgnore() +  } + +  render () { +    const { descriptionText, acceptText, onAccept, ignoreText, onIgnore, infoText, classNames = [] } = this.props + +    return ( +      <div className={classnames('home-notification', ...classNames)}> +        <div className="home-notification__header"> +          <div className="home-notification__header-container"> +            <img +              className="home-notification__icon" +              alt="" +              src="images/icons/connect.svg" +            /> +            <div className="home-notification__text"> +              { descriptionText } +            </div> +          </div> +          { +            infoText ? ( +              <ReactTippy +                style={{ +                  display: 'flex', +                }} +                html={( +                  <p className="home-notification-tooltip__content"> +                    {infoText} +                  </p> +                )} +                offset={-36} +                distance={36} +                animation="none" +                position="top" +                arrow +                theme="info" +              > +                <img +                  alt="" +                  src="images/icons/info.svg" +                /> +              </ReactTippy> +            ) : ( +              null +            ) +          } +        </div> +        <div className="home-notification__buttons"> +          { +            (onAccept && acceptText) ? ( +              <Button +                type="primary" +                className="home-notification__accept-button" +                onClick={this.handleAccept} +              > +                { acceptText } +              </Button> +            ) : ( +              null +            ) +          } +          { +            (onIgnore && ignoreText) ? ( +              <Button +                type="secondary" +                className="home-notification__ignore-button" +                onClick={this.handleIgnore} +              > +                { ignoreText } +              </Button> +            ) : ( +              null +            ) +          } +        </div> +      </div> +    ) +  } +} diff --git a/ui/app/components/app/home-notification/index.js b/ui/app/components/app/home-notification/index.js new file mode 100644 index 000000000..918a35be2 --- /dev/null +++ b/ui/app/components/app/home-notification/index.js @@ -0,0 +1 @@ +export { default } from './home-notification.component' diff --git a/ui/app/components/app/home-notification/index.scss b/ui/app/components/app/home-notification/index.scss new file mode 100644 index 000000000..c855a0814 --- /dev/null +++ b/ui/app/components/app/home-notification/index.scss @@ -0,0 +1,118 @@ +.tippy-tooltip.info-theme { +  background: rgba(36, 41, 46, 0.9); +  color: $white; +  border-radius: 8px; +} + +.home-notification { +  background: rgba(36, 41, 46, 0.9); +  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.12); +  border-radius: 8px; +  height: 116px; +  padding: 16px; + +  @media screen and (min-width: 576px) { +    min-width: 472px; +  } + +  display: flex; +  flex-flow: column; +  justify-content: space-between; + +  &__header-container { +    display: flex; +  } + +  &__header { +    display: flex; +    align-items: center; +    justify-content: space-between; +  } + +  &__text { +    font-family: Roboto, 'sans-serif'; +    font-style: normal; +    font-weight: normal; +    font-size: 12px; +    color: $white; +    margin-left: 10px; +    margin-right: 8px; +  } + +  .fa-info-circle { +    color: #6A737D; +  } + +  &__ignore-button { +    border: 2px solid #6A737D; +    box-sizing: border-box; +    border-radius: 6px; +    color: $white; +    background-color: inherit; +    height: 34px; +    width: 155px; +    padding: 0; + +    @media screen and (max-width: 575px) { +      width: 135px; +    } + +    &:hover { +      border-color: #6A737D; +      background-color: #6A737D; +    } + +    &:active { +      background-color: #141618; +    } +  } + +  &__accept-button { +    border: 2px solid #6A737D; +    box-sizing: border-box; +    border-radius: 6px; +    color: $white; +    background-color: inherit; +    height: 34px; +    width: 155px; +    padding: 0; +    margin-left: 4px; + +    @media screen and (max-width: 575px) { +      width: 135px; +    } + +    &:hover { +      border-color: #6A737D; +      background-color: #6A737D; +    } + +    &:active { +      background-color: #141618; +    } +  } + +  &__buttons { +    display: flex; +    width: 100%; +    justify-content: flex-start; +    flex-direction: row-reverse; +  } +} + +.home-notification-tooltip { +  &__tooltip-container { +    display: flex; +  } + +  &__content { +    font-family: Roboto, 'sans-serif'; +    font-style: normal; +    font-weight: normal; +    font-size: 12px; +    color: $white; +    text-align: left; +    display: inline-block; +    width: 200px; +  } +} diff --git a/ui/app/components/app/index.scss b/ui/app/components/app/index.scss index e9bb4ac9f..1ccb6a94a 100644 --- a/ui/app/components/app/index.scss +++ b/ui/app/components/app/index.scss @@ -1,3 +1,5 @@ +@import 'account-details/index'; +  @import 'account-menu/index';  @import 'add-token-button/index'; @@ -78,4 +80,8 @@  @import 'gas-customization/gas-price-button-group/index'; -@import 'ui-migration-annoucement/index'; +@import '../ui/toggle-button/index'; + +@import 'home-notification/index'; + +@import 'multiple-notifications/index'; diff --git a/ui/app/components/app/menu-droppo.js b/ui/app/components/app/menu-droppo.js index c80bee2be..a88cad4b4 100644 --- a/ui/app/components/app/menu-droppo.js +++ b/ui/app/components/app/menu-droppo.js @@ -2,7 +2,7 @@ const Component = require('react').Component  const h = require('react-hyperscript')  const inherits = require('util').inherits  const findDOMNode = require('react-dom').findDOMNode -const ReactCSSTransitionGroup = require('react-addons-css-transition-group') +const ReactCSSTransitionGroup = require('react-transition-group/CSSTransitionGroup')  module.exports = MenuDroppoComponent diff --git a/ui/app/components/app/modals/account-details-modal/account-details-modal.component.js b/ui/app/components/app/modals/account-details-modal/account-details-modal.component.js index e3919edcf..1b9a6a718 100644 --- a/ui/app/components/app/modals/account-details-modal/account-details-modal.component.js +++ b/ui/app/components/app/modals/account-details-modal/account-details-modal.component.js @@ -72,14 +72,14 @@ export default class AccountDetailsModal extends Component {          </Button>          {exportPrivateKeyFeatureEnabled -            ? <Button -                type="secondary" -                className="account-modal__button" -                onClick={() => showExportPrivateKeyModal()} -              > -                {this.context.t('exportPrivateKey')} -              </Button> -            : null +          ? <Button +            type="secondary" +            className="account-modal__button" +            onClick={() => showExportPrivateKeyModal()} +          > +            {this.context.t('exportPrivateKey')} +          </Button> +          : null          }        </AccountModalContainer>      ) diff --git a/ui/app/components/app/modals/add-to-addressbook-modal/add-to-addressbook-modal.component.js b/ui/app/components/app/modals/add-to-addressbook-modal/add-to-addressbook-modal.component.js new file mode 100644 index 000000000..64161a632 --- /dev/null +++ b/ui/app/components/app/modals/add-to-addressbook-modal/add-to-addressbook-modal.component.js @@ -0,0 +1,79 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import Button from '../../../ui/button/button.component' + +export default class AddToAddressBookModal extends Component { + +  static contextTypes = { +    t: PropTypes.func, +  } + +  static propTypes = { +    hideModal: PropTypes.func.isRequired, +    addToAddressBook: PropTypes.func.isRequired, +    recipient: PropTypes.string.isRequired, +  } + +  state = { +    alias: '', +  } + +  onSave = () => { +    const { recipient, addToAddressBook, hideModal } = this.props +    addToAddressBook(recipient, this.state.alias) +    hideModal() +  } + +  onChange = e => { +    this.setState({ +      alias: e.target.value, +    }) +  } + +  onKeyPress = e => { +    if (e.key === 'Enter' && this.state.alias) { +      this.onSave() +    } +  } + +  render () { +    const { t } = this.context + +    return ( +      <div className="add-to-address-book-modal"> +        <div className="add-to-address-book-modal__content"> +          <div className="add-to-address-book-modal__content__header"> +            {t('addToAddressBook')} +          </div> +          <div className="add-to-address-book-modal__input-label"> +            {t('enterAnAlias')} +          </div> +          <input +            type="text" +            className="add-to-address-book-modal__input" +            placeholder={t('addToAddressBookModalPlaceholder')} +            onChange={this.onChange} +            onKeyPress={this.onKeyPress} +            value={this.state.alias} +            autoFocus +          /> +        </div> +        <div className="add-to-address-book-modal__footer"> +          <Button +            type="secondary" +            onClick={this.props.hideModal} +          > +            {t('cancel')} +          </Button> +          <Button +            type="primary" +            onClick={this.onSave} +            disabled={!this.state.alias} +          > +            {t('save')} +          </Button> +        </div> +      </div> +    ) +  } +} diff --git a/ui/app/components/app/modals/add-to-addressbook-modal/add-to-addressbook-modal.container.js b/ui/app/components/app/modals/add-to-addressbook-modal/add-to-addressbook-modal.container.js new file mode 100644 index 000000000..413d4aa4a --- /dev/null +++ b/ui/app/components/app/modals/add-to-addressbook-modal/add-to-addressbook-modal.container.js @@ -0,0 +1,18 @@ +import { connect } from 'react-redux' +import AddToAddressBookModal from './add-to-addressbook-modal.component' +import actions from '../../../../store/actions' + +function mapStateToProps (state) { +  return { +    ...state.appState.modal.modalState.props || {}, +  } +} + +function mapDispatchToProps (dispatch) { +  return { +    hideModal: () => dispatch(actions.hideModal()), +    addToAddressBook: (recipient, nickname) => dispatch(actions.addToAddressBook(recipient, nickname)), +  } +} + +export default connect(mapStateToProps, mapDispatchToProps)(AddToAddressBookModal) diff --git a/ui/app/components/app/modals/add-to-addressbook-modal/index.js b/ui/app/components/app/modals/add-to-addressbook-modal/index.js new file mode 100644 index 000000000..9ed4f018f --- /dev/null +++ b/ui/app/components/app/modals/add-to-addressbook-modal/index.js @@ -0,0 +1 @@ +export { default } from './add-to-addressbook-modal.container' diff --git a/ui/app/components/app/modals/add-to-addressbook-modal/index.scss b/ui/app/components/app/modals/add-to-addressbook-modal/index.scss new file mode 100644 index 000000000..f6bf85a0a --- /dev/null +++ b/ui/app/components/app/modals/add-to-addressbook-modal/index.scss @@ -0,0 +1,37 @@ +.add-to-address-book-modal { +  @extend %col-nowrap; +  @extend %modal; + +  &__content { +    @extend %col-nowrap; +    padding: 1.5rem; +    border-bottom: 1px solid $Grey-100; + +    &__header { +      @extend %h3; +    } +  } + +  &__input-label { +    color: $Grey-600; +    margin-top: 1.25rem; +  } + +  &__input { +    @extend %input; +    margin-top: 0.75rem; + +    &::placeholder { +      color: $Grey-300; +    } +  } + +  &__footer { +    @extend %row-nowrap; +    padding: 1rem; + +    button + button { +      margin-left: 1rem; +    } +  } +} diff --git a/ui/app/components/app/modals/confirm-delete-network/confirm-delete-network.component.js b/ui/app/components/app/modals/confirm-delete-network/confirm-delete-network.component.js new file mode 100644 index 000000000..ea92e340c --- /dev/null +++ b/ui/app/components/app/modals/confirm-delete-network/confirm-delete-network.component.js @@ -0,0 +1,43 @@ +import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' +import Modal, { ModalContent } from '../../modal' + +export default class ConfirmDeleteNetwork extends PureComponent { +  static propTypes = { +    hideModal: PropTypes.func.isRequired, +    delRpcTarget: PropTypes.func.isRequired, +    onConfirm: PropTypes.func.isRequired, +    target: PropTypes.string.isRequired, +  } + +  static contextTypes = { +    t: PropTypes.func, +  } + +  handleDelete = () => { +    this.props.delRpcTarget(this.props.target) +      .then(() => { +        this.props.onConfirm() +        this.props.hideModal() +      }) +  } + +  render () { +    const { t } = this.context + +    return ( +      <Modal +        onSubmit={this.handleDelete} +        onCancel={() => this.props.hideModal()} +        submitText={t('delete')} +        cancelText={t('cancel')} +        submitType="danger" +      > +        <ModalContent +          title={t('deleteNetwork')} +          description={t('deleteNetworkDescription')} +        /> +      </Modal> +    ) +  } +} diff --git a/ui/app/components/app/modals/confirm-delete-network/confirm-delete-network.container.js b/ui/app/components/app/modals/confirm-delete-network/confirm-delete-network.container.js new file mode 100644 index 000000000..4c9bb279f --- /dev/null +++ b/ui/app/components/app/modals/confirm-delete-network/confirm-delete-network.container.js @@ -0,0 +1,16 @@ +import { connect } from 'react-redux' +import { compose } from 'recompose' +import withModalProps from '../../../../helpers/higher-order-components/with-modal-props' +import ConfirmDeleteNetwork from './confirm-delete-network.component' +import { delRpcTarget } from '../../../../store/actions' + +const mapDispatchToProps = dispatch => { +  return { +    delRpcTarget: (target) => dispatch(delRpcTarget(target)), +  } +} + +export default compose( +  withModalProps, +  connect(null, mapDispatchToProps) +)(ConfirmDeleteNetwork) diff --git a/ui/app/components/app/modals/confirm-delete-network/index.js b/ui/app/components/app/modals/confirm-delete-network/index.js new file mode 100644 index 000000000..de9543eea --- /dev/null +++ b/ui/app/components/app/modals/confirm-delete-network/index.js @@ -0,0 +1 @@ +export { default } from './confirm-delete-network.container' diff --git a/ui/app/components/app/modals/deposit-ether-modal.js b/ui/app/components/app/modals/deposit-ether-modal.js index 20c4d018c..ff2411209 100644 --- a/ui/app/components/app/modals/deposit-ether-modal.js +++ b/ui/app/components/app/modals/deposit-ether-modal.js @@ -87,8 +87,8 @@ DepositEtherModal.prototype.renderRow = function ({    }    return h('div', { -      className: className || 'deposit-ether-modal__buy-row', -    }, [ +    className: className || 'deposit-ether-modal__buy-row', +  }, [      onBackClick && showBackButton && h('div.deposit-ether-modal__buy-row__back', {        onClick: onBackClick, @@ -100,22 +100,22 @@ DepositEtherModal.prototype.renderRow = function ({      h('div.deposit-ether-modal__buy-row__logo-container', [logo]), -      h('div.deposit-ether-modal__buy-row__description', [ +    h('div.deposit-ether-modal__buy-row__description', [ -        !hideTitle && h('div.deposit-ether-modal__buy-row__description__title', [title]), +      !hideTitle && h('div.deposit-ether-modal__buy-row__description__title', [title]), -        h('div.deposit-ether-modal__buy-row__description__text', [text]), +      h('div.deposit-ether-modal__buy-row__description__text', [text]), -      ]), +    ]), -      !hideButton && h('div.deposit-ether-modal__buy-row__button', [ -        h(Button, { -          type: 'secondary', -          className: 'deposit-ether-modal__deposit-button', -          large: true, -          onClick: onButtonClick, -        }, [buttonLabel]), -      ]), +    !hideButton && h('div.deposit-ether-modal__buy-row__button', [ +      h(Button, { +        type: 'secondary', +        className: 'deposit-ether-modal__deposit-button', +        large: true, +        onClick: onButtonClick, +      }, [buttonLabel]), +    ]),    ])  } diff --git a/ui/app/components/app/modals/export-private-key-modal.js b/ui/app/components/app/modals/export-private-key-modal.js index c3098a16c..43d7bcd74 100644 --- a/ui/app/components/app/modals/export-private-key-modal.js +++ b/ui/app/components/app/modals/export-private-key-modal.js @@ -86,12 +86,12 @@ ExportPrivateKeyModal.prototype.renderPasswordInput = function (privateKey) {    return privateKey      ? h(ReadOnlyInput, { -        wrapperClass: 'private-key-password-display-wrapper', -        inputClass: 'private-key-password-display-textarea', -        textarea: true, -        value: plainKey, -        onClick: () => copyToClipboard(plainKey), -      }) +      wrapperClass: 'private-key-password-display-wrapper', +      inputClass: 'private-key-password-display-textarea', +      textarea: true, +      value: plainKey, +      onClick: () => copyToClipboard(plainKey), +    })      : h('input.private-key-password-input', {        type: 'password',        onChange: event => this.setState({ password: event.target.value }), @@ -109,14 +109,14 @@ ExportPrivateKeyModal.prototype.renderButtons = function (privateKey, address, h      (privateKey        ? ( -          h(Button, { +        h(Button, {            type: 'secondary',            large: true,            className: 'export-private-key__button',            onClick: () => hideModal(),          }, this.context.t('done'))        ) : ( -          h(Button, { +        h(Button, {            type: 'secondary',            large: true,            className: 'export-private-key__button', @@ -149,29 +149,29 @@ ExportPrivateKeyModal.prototype.render = function () {      backButtonAction: () => showAccountDetailModal(),    }, [ -      h('span.account-name', name), +    h('span.account-name', name), -      h(ReadOnlyInput, { -        wrapperClass: 'ellip-address-wrapper', -        inputClass: 'qr-ellip-address ellip-address', -        value: checksumAddress(address), -      }), +    h(ReadOnlyInput, { +      wrapperClass: 'ellip-address-wrapper', +      inputClass: 'qr-ellip-address ellip-address', +      value: checksumAddress(address), +    }), -      h('div.account-modal-divider'), +    h('div.account-modal-divider'), -      h('span.modal-body-title', this.context.t('showPrivateKeys')), +    h('span.modal-body-title', this.context.t('showPrivateKeys')), -      h('div.private-key-password', {}, [ -        this.renderPasswordLabel(privateKey), +    h('div.private-key-password', {}, [ +      this.renderPasswordLabel(privateKey), -        this.renderPasswordInput(privateKey), +      this.renderPasswordInput(privateKey), -        showWarning && warning ? h('span.private-key-password-error', warning) : null, -      ]), +      showWarning && warning ? h('span.private-key-password-error', warning) : null, +    ]), -      h('div.private-key-password-warning', this.context.t('privateKeyWarning')), +    h('div.private-key-password-warning', this.context.t('privateKeyWarning')), -      this.renderButtons(privateKey, address, hideModal), +    this.renderButtons(privateKey, address, hideModal),    ])  } diff --git a/ui/app/components/app/modals/index.scss b/ui/app/components/app/modals/index.scss index 09b0bb73c..1bbfd2d07 100644 --- a/ui/app/components/app/modals/index.scss +++ b/ui/app/components/app/modals/index.scss @@ -9,3 +9,5 @@  @import 'transaction-confirmed/index';  @import 'metametrics-opt-in-modal/index'; + +@import './add-to-addressbook-modal/index'; diff --git a/ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.component.js b/ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.component.js index 0335991fc..1bf7c21b5 100644 --- a/ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.component.js +++ b/ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.component.js @@ -1,5 +1,6 @@  import React, { Component } from 'react'  import PropTypes from 'prop-types' +import MetaFoxLogo from '../../../ui/metafox-logo'  import PageContainerFooter from '../../../ui/page-container/page-container-footer'  export default class MetaMetricsOptInModal extends Component { @@ -20,19 +21,7 @@ export default class MetaMetricsOptInModal extends Component {        <div className="metametrics-opt-in metametrics-opt-in-modal">          <div className="metametrics-opt-in__main">            <div className="metametrics-opt-in__content"> -            <div className="app-header__logo-container"> -              <img -                className="app-header__metafox-logo app-header__metafox-logo--horizontal" -                src="/images/logo/metamask-logo-horizontal.svg" -                height={30} -              /> -              <img -                className="app-header__metafox-logo app-header__metafox-logo--icon" -                src="/images/logo/metamask-fox.svg" -                height={42} -                width={42} -              /> -            </div> +            <MetaFoxLogo />              <div className="metametrics-opt-in__body-graphic">                <img src="images/metrics-chart.svg" />              </div> diff --git a/ui/app/components/app/modals/modal.js b/ui/app/components/app/modals/modal.js index 394367c46..4044ded8c 100644 --- a/ui/app/components/app/modals/modal.js +++ b/ui/app/components/app/modals/modal.js @@ -29,6 +29,8 @@ import MetaMetricsOptInModal from './metametrics-opt-in-modal'  import RejectTransactions from './reject-transactions'  import ClearApprovedOrigins from './clear-approved-origins'  import ConfirmCustomizeGasModal from '../gas-customization/gas-modal-page-container' +import ConfirmDeleteNetwork from './confirm-delete-network' +import AddToAddressBookModal from './add-to-addressbook-modal'  const modalContainerBaseStyle = {    transform: 'translate3d(-50%, 0, 0px)', @@ -166,6 +168,35 @@ const MODALS = {      },    }, +  ADD_TO_ADDRESSBOOK: { +    contents: [ +      h(AddToAddressBookModal, {}, []), +    ], +    mobileModalStyle: { +      width: '95%', +      top: '10%', +      boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px', +      transform: 'none', +      left: '0', +      right: '0', +      margin: '0 auto', +      borderRadius: '10px', +    }, +    laptopModalStyle: { +      width: '375px', +      top: '10%', +      boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px', +      transform: 'none', +      left: '0', +      right: '0', +      margin: '0 auto', +      borderRadius: '10px', +    }, +    contentStyle: { +      borderRadius: '10px', +    }, +  }, +    ACCOUNT_DETAILS: {      contents: [        h(AccountDetailsModal, {}, []), @@ -301,6 +332,19 @@ const MODALS = {      },    }, +  CONFIRM_DELETE_NETWORK: { +    contents: h(ConfirmDeleteNetwork), +    mobileModalStyle: { +      ...modalContainerMobileStyle, +    }, +    laptopModalStyle: { +      ...modalContainerLaptopStyle, +    }, +    contentStyle: { +      borderRadius: '8px', +    }, +  }, +    NEW_ACCOUNT: {      contents: [        h(NewAccountModal, {}, []), @@ -452,7 +496,6 @@ module.exports = connect(mapStateToProps, mapDispatchToProps)(Modal)  Modal.prototype.render = function () {    const modal = MODALS[this.props.modalState.name || 'DEFAULT'] -    const { contents: children, disableBackdropClick = false } = modal    const modalStyle = modal[isMobileView() ? 'mobileModalStyle' : 'laptopModalStyle']    const contentStyle = modal.contentStyle || {} diff --git a/ui/app/components/app/modals/new-account-modal.js b/ui/app/components/app/modals/new-account-modal.js index 27c81a701..4b18c52ba 100644 --- a/ui/app/components/app/modals/new-account-modal.js +++ b/ui/app/components/app/modals/new-account-modal.js @@ -69,7 +69,7 @@ NewAccountModal.propTypes = {    showImportPage: PropTypes.func,    createAccount: PropTypes.func,    numberOfExistingAccounts: PropTypes.number, -    t: PropTypes.func, +  t: PropTypes.func,  }  const mapStateToProps = state => { diff --git a/ui/app/components/app/modals/notification-modal.js b/ui/app/components/app/modals/notification-modal.js index b8503ec1a..84d9004b7 100644 --- a/ui/app/components/app/modals/notification-modal.js +++ b/ui/app/components/app/modals/notification-modal.js @@ -62,7 +62,7 @@ NotificationModal.propTypes = {    showCancelButton: PropTypes.bool,    showConfirmButton: PropTypes.bool,    onConfirm: PropTypes.func, -    t: PropTypes.func, +  t: PropTypes.func,  }  const mapDispatchToProps = dispatch => { diff --git a/ui/app/components/app/modals/qr-scanner/qr-scanner.component.js b/ui/app/components/app/modals/qr-scanner/qr-scanner.component.js index a83ba8f8e..afeaef0da 100644 --- a/ui/app/components/app/modals/qr-scanner/qr-scanner.component.js +++ b/ui/app/components/app/modals/qr-scanner/qr-scanner.component.js @@ -1,7 +1,7 @@  import React, { Component } from 'react'  import PropTypes from 'prop-types'  import { BrowserQRCodeReader } from '@zxing/library' -import adapter from 'webrtc-adapter' // eslint-disable-line import/no-nodejs-modules, no-unused-vars +import 'webrtc-adapter'  import Spinner from '../../../ui/spinner'  import WebcamUtils from '../../../../../lib/webcam-utils'  import PageContainerFooter from '../../../ui/page-container/page-container-footer/page-container-footer.component' @@ -75,23 +75,23 @@ export default class QrScanner extends Component {          clearTimeout(this.permissionChecker)          this.checkPermisisions()          this.codeReader.decodeFromInputVideoDevice(undefined, 'video') -        .then(content => { -          const result = this.parseContent(content.text) -          if (result.type !== 'unknown') { -            this.props.qrCodeDetected(result) -            this.stopAndClose() -          } else { -            this.setState({msg: this.context.t('unknownQrCode')}) -          } -        }) -        .catch(err => { -          if (err && err.name === 'NotAllowedError') { -            this.setState({msg: this.context.t('youNeedToAllowCameraAccess')}) -            clearTimeout(this.permissionChecker) -            this.needsToReinit = true -            this.checkPermisisions() -          } -        }) +          .then(content => { +            const result = this.parseContent(content.text) +            if (result.type !== 'unknown') { +              this.props.qrCodeDetected(result) +              this.stopAndClose() +            } else { +              this.setState({msg: this.context.t('unknownQrCode')}) +            } +          }) +          .catch(err => { +            if (err && err.name === 'NotAllowedError') { +              this.setState({msg: this.context.t('youNeedToAllowCameraAccess')}) +              clearTimeout(this.permissionChecker) +              this.needsToReinit = true +              this.checkPermisisions() +            } +          })        }).catch(err => {          console.error('[QR-SCANNER]: getVideoInputDevices threw an exception: ', err)        }) diff --git a/ui/app/components/app/multiple-notifications/index.js b/ui/app/components/app/multiple-notifications/index.js new file mode 100644 index 000000000..a27a65187 --- /dev/null +++ b/ui/app/components/app/multiple-notifications/index.js @@ -0,0 +1 @@ +export { default } from './multiple-notifications.component' diff --git a/ui/app/components/app/multiple-notifications/index.scss b/ui/app/components/app/multiple-notifications/index.scss new file mode 100644 index 000000000..e8d064bc0 --- /dev/null +++ b/ui/app/components/app/multiple-notifications/index.scss @@ -0,0 +1,78 @@ +.home-notification-wrapper--show-all, +.home-notification-wrapper--show-first { +  display: flex; +  flex-direction: column; +  width: 472px; +  position: absolute; +  bottom: 0; +  right: 0; +  margin: 8px; + +  @media screen and (max-width: 576px) { +    width: 340px; +  } + +  .home-notification-wrapper__i-container { +    position: relative; +    width: 100%; +    height: 100%; +    visibility: none; + +    .fa-sm { +      display: initial; +      position: absolute; +      bottom: 14px; +      left: 16px; +      color: white; +      cursor: pointer; +      visibility: visible; + +      &:hover { +        color: #b0d7f2; +        font-size: 1.1rem; +      } +    } +  } +} + +.home-notification-wrapper--show-all { +  justify-content: flex-end; +  margin-bottom: 0; + +  .home-notification-wrapper__i-container { +    height: 0; +  } + +  > div { +    position: relative; +    margin-top: 8px; +  } + +  .fa-sm { +    margin-bottom: 8px; +  } + +} + +.home-notification-wrapper--show-first { +  > div { +    position: absolute; +    bottom: 0; +    right: 0; +    visibility: hidden; +  } + +  > div:first-of-type { +    visibility: visible; + +  } + +  .fa-sm { +    position: relative; +    display: initial; +  } +} + +.flipped { +  transform: rotate(180deg); +} diff --git a/ui/app/components/app/multiple-notifications/multiple-notifications.component.js b/ui/app/components/app/multiple-notifications/multiple-notifications.component.js new file mode 100644 index 000000000..040890e18 --- /dev/null +++ b/ui/app/components/app/multiple-notifications/multiple-notifications.component.js @@ -0,0 +1,44 @@ +import React, { PureComponent } from 'react' +import classnames from 'classnames' +import PropTypes from 'prop-types' + +export default class MultipleNotifications extends PureComponent { +  static propTypes = { +    notifications: PropTypes.array, +    classNames: PropTypes.array, +  } + +  state = { +    showAll: false, +  } + +  render () { +    const { showAll } = this.state +    const { notifications, classNames = [] } = this.props + +    const notificationsToBeRendered = notifications.filter(notificationConfig => notificationConfig.shouldBeRendered) + +    if (notificationsToBeRendered.length === 0) { +      return null +    } + +    return ( +      <div +        className={classnames(...classNames, { +          'home-notification-wrapper--show-all': showAll, +          'home-notification-wrapper--show-first': !showAll, +        })} +      > +        { notificationsToBeRendered.map(notificationConfig => notificationConfig.component) } +        <div +          className="home-notification-wrapper__i-container" +          onClick={() => this.setState({ showAll: !showAll })} +        > +          {notificationsToBeRendered.length > 1 ? <i className={classnames('fa fa-sm fa-sort-amount-asc', { +            'flipped': !showAll, +          })} /> : null} +        </div> +      </div> +    ) +  } +} diff --git a/ui/app/components/app/network-display/network-display.component.js b/ui/app/components/app/network-display/network-display.component.js index 9ef5341b0..266476267 100644 --- a/ui/app/components/app/network-display/network-display.component.js +++ b/ui/app/components/app/network-display/network-display.component.js @@ -39,12 +39,12 @@ export default class NetworkDisplay extends Component {      return networkClass        ? <div className={`network-display__icon network-display__icon--${networkClass}`} />        : <div -          className="i fa fa-question-circle fa-med" -          style={{ -            margin: '0 4px', -            color: 'rgb(125, 128, 130)', -          }} -        /> +        className="i fa fa-question-circle fa-med" +        style={{ +          margin: '0 4px', +          color: 'rgb(125, 128, 130)', +        }} +      />    }    render () { @@ -62,12 +62,12 @@ export default class NetworkDisplay extends Component {            networkClass              ? <div className={`network-display__icon network-display__icon--${networkClass}`} />              : <div -                className="i fa fa-question-circle fa-med" -                style={{ -                  margin: '0 4px', -                  color: 'rgb(125, 128, 130)', -                }} -              /> +              className="i fa fa-question-circle fa-med" +              style={{ +                margin: '0 4px', +                color: 'rgb(125, 128, 130)', +              }} +            />          }          <div className="network-display__name">            { type === 'rpc' && nickname ? nickname : this.context.t(type) } diff --git a/ui/app/components/app/network.js b/ui/app/components/app/network.js index e778700cd..d46906a66 100644 --- a/ui/app/components/app/network.js +++ b/ui/app/components/app/network.js @@ -127,19 +127,19 @@ Network.prototype.render = function () {            default:              return h('.network-indicator', [                networkNumber === 'loading' -              ? h('span.pointer.network-loading-spinner', { -                onClick: (event) => this.props.onClick(event), -              }, [ -                h('img', { -                  title: context.t('attemptingConnect'), -                  src: 'images/loading.svg', +                ? h('span.pointer.network-loading-spinner', { +                  onClick: (event) => this.props.onClick(event), +                }, [ +                  h('img', { +                    title: context.t('attemptingConnect'), +                    src: 'images/loading.svg', +                  }), +                ]) +                : h('i.fa.fa-question-circle.fa-lg', { +                  style: { +                    color: 'rgb(125, 128, 130)', +                  },                  }), -              ]) -              : h('i.fa.fa-question-circle.fa-lg', { -                style: { -                  color: 'rgb(125, 128, 130)', -                }, -              }),                h('.network-name', providerName === 'localhost' ? context.t('localhost') : providerNick || context.t('privateNetwork')),                h('.network-indicator__down-arrow'), diff --git a/ui/app/components/app/provider-page-container/provider-page-container-content/provider-page-container-content.component.js b/ui/app/components/app/provider-page-container/provider-page-container-content/provider-page-container-content.component.js index 0eb1d616a..7eda7f2b7 100644 --- a/ui/app/components/app/provider-page-container/provider-page-container-content/provider-page-container-content.component.js +++ b/ui/app/components/app/provider-page-container/provider-page-container-content/provider-page-container-content.component.js @@ -5,7 +5,7 @@ import Identicon from '../../../ui/identicon'  export default class ProviderPageContainerContent extends PureComponent {    static propTypes = {      origin: PropTypes.string.isRequired, -    selectedIdentity: PropTypes.string.isRequired, +    selectedIdentity: PropTypes.object.isRequired,      siteImage: PropTypes.string,      siteTitle: PropTypes.string.isRequired,    } diff --git a/ui/app/components/app/sidebars/sidebar.component.js b/ui/app/components/app/sidebars/sidebar.component.js index b9e0f9e81..e532ba7e5 100644 --- a/ui/app/components/app/sidebars/sidebar.component.js +++ b/ui/app/components/app/sidebars/sidebar.component.js @@ -1,6 +1,6 @@  import React, { Component } from 'react'  import PropTypes from 'prop-types' -import ReactCSSTransitionGroup from 'react-addons-css-transition-group' +import ReactCSSTransitionGroup from 'react-transition-group/CSSTransitionGroup'  import WalletView from '../wallet-view'  import { WALLET_VIEW_SIDEBAR } from './sidebar.constants'  import CustomizeGas from '../gas-customization/gas-modal-page-container/' @@ -26,7 +26,7 @@ export default class Sidebar extends Component {          onOverlayClose && onOverlayClose()          this.props.hideSidebar()        } -    } /> +      } />    }    renderSidebarContent () { diff --git a/ui/app/components/app/sidebars/tests/sidebars-component.test.js b/ui/app/components/app/sidebars/tests/sidebars-component.test.js index cee22aca8..e2daea9b6 100644 --- a/ui/app/components/app/sidebars/tests/sidebars-component.test.js +++ b/ui/app/components/app/sidebars/tests/sidebars-component.test.js @@ -2,7 +2,7 @@ import React from 'react'  import assert from 'assert'  import { shallow } from 'enzyme'  import sinon from 'sinon' -import ReactCSSTransitionGroup from 'react-addons-css-transition-group' +import ReactCSSTransitionGroup from 'react-transition-group/CSSTransitionGroup'  import Sidebar from '../sidebar.component.js'  import WalletView from '../../wallet-view' diff --git a/ui/app/components/app/signature-request.js b/ui/app/components/app/signature-request.js index fa237f1d1..9c0f53f57 100644 --- a/ui/app/components/app/signature-request.js +++ b/ui/app/components/app/signature-request.js @@ -12,7 +12,7 @@ const { compose } = require('recompose')  const { withRouter } = require('react-router-dom')  const { ObjectInspector } = require('react-inspector') -import AccountDropdownMini from '../ui/account-dropdown-mini' +import AccountListItem from '../../pages/send/account-list-item/account-list-item.component'  const actions = require('../../store/actions')  const { conversionUtil } = require('../../helpers/utils/conversion-util') @@ -21,7 +21,6 @@ const {    getSelectedAccount,    getCurrentAccountWithSendEtherInfo,    getSelectedAddress, -  accountsWithSendEtherInfoSelector,    conversionRateSelector,  } = require('../../selectors/selectors.js') @@ -37,7 +36,6 @@ function mapStateToProps (state) {      selectedAddress: getSelectedAddress(state),      requester: null,      requesterAddress: null, -    accounts: accountsWithSendEtherInfoSelector(state),      conversionRate: conversionRateSelector(state),    }  } @@ -76,9 +74,9 @@ function mergeProps (stateProps, dispatchProps, ownProps) {    }    return { +    ...ownProps,      ...stateProps,      ...dispatchProps, -    ...ownProps,      txData,      cancel,      sign, @@ -137,23 +135,19 @@ SignatureRequest.prototype.renderHeader = function () {    ])  } -SignatureRequest.prototype.renderAccountDropdown = function () { +SignatureRequest.prototype.renderAccount = function () {    const { selectedAccount } = this.state -  const { -    accounts, -  } = this.props -    return h('div.request-signature__account', [      h('div.request-signature__account-text', [this.context.t('account') + ':']), -    h(AccountDropdownMini, { -      selectedAccount, -      accounts, -      disabled: true, -    }), - +    h('div.request-signature__account-item', [ +      h(AccountListItem, { +        account: selectedAccount, +        displayBalance: false, +      }), +    ]),    ])  } @@ -180,7 +174,7 @@ SignatureRequest.prototype.renderBalance = function () {  SignatureRequest.prototype.renderAccountInfo = function () {    return h('div.request-signature__account-info', [ -    this.renderAccountDropdown(), +    this.renderAccount(),      this.renderRequestIcon(), @@ -257,7 +251,7 @@ SignatureRequest.prototype.renderBody = function () {              url: 'https://metamask.zendesk.com/hc/en-us/articles/360015488751',            })          }, -    }, this.context.t('learnMore'))] +      }, this.context.t('learnMore'))]    }    return h('div.request-signature__body', {}, [ diff --git a/ui/app/components/app/token-list.js b/ui/app/components/app/token-list.js index 2188e7020..000ca6b3f 100644 --- a/ui/app/components/app/token-list.js +++ b/ui/app/components/app/token-list.js @@ -67,8 +67,8 @@ TokenList.prototype.render = function () {          },          onClick: () => {            global.platform.openWindow({ -          url: `https://ethplorer.io/address/${userAddress}`, -        }) +            url: `https://ethplorer.io/address/${userAddress}`, +          })          },        }, this.context.t('here')),      ]) @@ -125,13 +125,13 @@ TokenList.prototype.createFreshTokenTracker = function () {    this.tracker.on('error', this.showError)    this.tracker.updateBalances() -  .then(() => { -    this.updateBalances(this.tracker.serialize()) -  }) -  .catch((reason) => { -    log.error(`Problem updating balances`, reason) -    this.setState({ isLoading: false }) -  }) +    .then(() => { +      this.updateBalances(this.tracker.serialize()) +    }) +    .catch((reason) => { +      log.error(`Problem updating balances`, reason) +      this.setState({ isLoading: false }) +    })  }  TokenList.prototype.componentDidUpdate = function (prevProps) { diff --git a/ui/app/components/app/transaction-activity-log/transaction-activity-log-icon/transaction-activity-log-icon.component.js b/ui/app/components/app/transaction-activity-log/transaction-activity-log-icon/transaction-activity-log-icon.component.js index 871716002..6124325be 100644 --- a/ui/app/components/app/transaction-activity-log/transaction-activity-log-icon/transaction-activity-log-icon.component.js +++ b/ui/app/components/app/transaction-activity-log/transaction-activity-log-icon/transaction-activity-log-icon.component.js @@ -40,15 +40,15 @@ export default class TransactionActivityLogIcon extends PureComponent {      return (        <div className={classnames('transaction-activity-log-icon', className)}> -      { -        imagePath && ( -          <img -            src={imagePath} -            height={9} -            width={9} -          /> -        ) -      } +        { +          imagePath && ( +            <img +              src={imagePath} +              height={9} +              width={9} +            /> +          ) +        }        </div>      )    } diff --git a/ui/app/components/app/transaction-list-item-details/tests/transaction-list-item-details.component.test.js b/ui/app/components/app/transaction-list-item-details/tests/transaction-list-item-details.component.test.js index c4e118b01..583980d26 100644 --- a/ui/app/components/app/transaction-list-item-details/tests/transaction-list-item-details.component.test.js +++ b/ui/app/components/app/transaction-list-item-details/tests/transaction-list-item-details.component.test.js @@ -78,4 +78,73 @@ describe('TransactionListItemDetails Component', () => {      assert.ok(wrapper.hasClass('transaction-list-item-details'))      assert.equal(wrapper.find(Button).length, 3)    }) + +  it('should disable the Copy Tx ID and View In Etherscan buttons when tx hash is missing', () => { +    const transaction = { +      history: [], +      id: 1, +      status: 'confirmed', +      txParams: { +        from: '0x1', +        gas: '0x5208', +        gasPrice: '0x3b9aca00', +        nonce: '0xa4', +        to: '0x2', +        value: '0x2386f26fc10000', +      }, +    } + +    const transactionGroup = { +      transactions: [transaction], +      primaryTransaction: transaction, +      initialTransaction: transaction, +    } + +    const wrapper = shallow( +      <TransactionListItemDetails +        transactionGroup={transactionGroup} +      />, +      { context: { t: (str1, str2) => str2 ? str1 + str2 : str1 } } +    ) + +    assert.ok(wrapper.hasClass('transaction-list-item-details')) +    const buttons = wrapper.find(Button) +    assert.strictEqual(buttons.at(0).prop('disabled'), true) +    assert.strictEqual(buttons.at(1).prop('disabled'), true) +  }) + +  it('should render functional Copy Tx ID and View In Etherscan buttons when tx hash exists', () => { +    const transaction = { +      history: [], +      id: 1, +      status: 'confirmed', +      hash: '0xaa', +      txParams: { +        from: '0x1', +        gas: '0x5208', +        gasPrice: '0x3b9aca00', +        nonce: '0xa4', +        to: '0x2', +        value: '0x2386f26fc10000', +      }, +    } + +    const transactionGroup = { +      transactions: [transaction], +      primaryTransaction: transaction, +      initialTransaction: transaction, +    } + +    const wrapper = shallow( +      <TransactionListItemDetails +        transactionGroup={transactionGroup} +      />, +      { context: { t: (str1, str2) => str2 ? str1 + str2 : str1 } } +    ) + +    assert.ok(wrapper.hasClass('transaction-list-item-details')) +    const buttons = wrapper.find(Button) +    assert.strictEqual(buttons.at(0).prop('disabled'), false) +    assert.strictEqual(buttons.at(1).prop('disabled'), false) +  })  }) diff --git a/ui/app/components/app/transaction-list-item-details/transaction-list-item-details.component.js b/ui/app/components/app/transaction-list-item-details/transaction-list-item-details.component.js index 72ca784e2..d8dd965fc 100644 --- a/ui/app/components/app/transaction-list-item-details/transaction-list-item-details.component.js +++ b/ui/app/components/app/transaction-list-item-details/transaction-list-item-details.component.js @@ -128,7 +128,7 @@ export default class TransactionListItemDetails extends PureComponent {        rpcPrefs: { blockExplorerUrl } = {},      } = this.props      const { primaryTransaction: transaction } = transactionGroup -    const { txParams: { to, from } = {} } = transaction +    const { hash, txParams: { to, from } = {} } = transaction      return (        <div className="transaction-list-item-details"> @@ -152,6 +152,7 @@ export default class TransactionListItemDetails extends PureComponent {                  type="raised"                  onClick={this.handleCopyTxId}                  className="transaction-list-item-details__header-button" +                disabled={!hash}                >                  <img                    className="transaction-list-item-details__header-button__copy-icon" @@ -164,7 +165,8 @@ export default class TransactionListItemDetails extends PureComponent {                  type="raised"                  onClick={this.handleEtherscanClick}                  className="transaction-list-item-details__header-button" -                > +                disabled={!hash} +              >                  <img src="/images/arrow-popout.svg" />                </Button>              </Tooltip> diff --git a/ui/app/components/app/transaction-list/transaction-list.component.js b/ui/app/components/app/transaction-list/transaction-list.component.js index fc5488884..157e7200b 100644 --- a/ui/app/components/app/transaction-list/transaction-list.component.js +++ b/ui/app/components/app/transaction-list/transaction-list.component.js @@ -10,11 +10,13 @@ export default class TransactionList extends PureComponent {    }    static defaultProps = { +    children: null,      pendingTransactions: [],      completedTransactions: [],    }    static propTypes = { +    children: PropTypes.node,      pendingTransactions: PropTypes.array,      completedTransactions: PropTypes.array,      selectedToken: PropTypes.object, @@ -39,7 +41,7 @@ export default class TransactionList extends PureComponent {      const { transactions = [], hasRetried } = transactionGroup      const [earliestTransaction = {}] = transactions      const { submittedTime } = earliestTransaction -    return Date.now() - submittedTime > 30000 && isEarliestNonce && !hasRetried +    return Date.now() - submittedTime > 5000 && isEarliestNonce && !hasRetried    }    shouldShowCancel (transactionGroup) { @@ -75,8 +77,8 @@ export default class TransactionList extends PureComponent {            {              completedTransactions.length > 0                ? completedTransactions.map((transactionGroup, index) => ( -                  this.renderTransaction(transactionGroup, index) -                )) +                this.renderTransaction(transactionGroup, index) +              ))                : this.renderEmpty()            }          </div> @@ -120,6 +122,7 @@ export default class TransactionList extends PureComponent {      return (        <div className="transaction-list">          { this.renderTransactions() } +        { this.props.children }        </div>      )    } diff --git a/ui/app/components/app/transaction-status/index.scss b/ui/app/components/app/transaction-status/index.scss index 024cbf2a1..99884d28c 100644 --- a/ui/app/components/app/transaction-status/index.scss +++ b/ui/app/components/app/transaction-status/index.scss @@ -43,4 +43,10 @@        border: 1px solid $monzo;      }    } + +  &__pending-spinner { +    height: 16px; +    width: 16px; +    margin-right: 6px; +  }  } diff --git a/ui/app/components/app/transaction-status/transaction-status.component.js b/ui/app/components/app/transaction-status/transaction-status.component.js index d3a239539..a97b79bde 100644 --- a/ui/app/components/app/transaction-status/transaction-status.component.js +++ b/ui/app/components/app/transaction-status/transaction-status.component.js @@ -2,6 +2,8 @@ import React, { PureComponent } from 'react'  import PropTypes from 'prop-types'  import classnames from 'classnames'  import Tooltip from '../../ui/tooltip-v2' +import Spinner from '../../ui/spinner' +  import {    UNAPPROVED_STATUS,    REJECTED_STATUS, @@ -51,6 +53,7 @@ export default class TransactionStatus extends PureComponent {      return (        <div className={classnames('transaction-status', className, statusToClassNameHash[statusKey])}> +        { statusToTextHash[statusKey] === 'pending' ? <Spinner className="transaction-status__pending-spinner" /> : null }          <Tooltip            position="top"            title={title} diff --git a/ui/app/components/app/transaction-view-balance/transaction-view-balance.component.js b/ui/app/components/app/transaction-view-balance/transaction-view-balance.component.js index 3f6abbb00..feb701dbe 100644 --- a/ui/app/components/app/transaction-view-balance/transaction-view-balance.component.js +++ b/ui/app/components/app/transaction-view-balance/transaction-view-balance.component.js @@ -43,38 +43,38 @@ export default class TransactionViewBalance extends PureComponent {            />          </div>        ) : ( -          <Tooltip position="top" title={this.context.t('balanceOutdated')} disabled={!balanceIsCached}> -            <div className="transaction-view-balance__balance"> -                <div className="transaction-view-balance__primary-container"> -                  <UserPreferencedCurrencyDisplay -                    className={classnames('transaction-view-balance__primary-balance', { -                      'transaction-view-balance__cached-balance': balanceIsCached, -                    })} -                    value={balance} -                    type={PRIMARY} -                    ethNumberOfDecimals={4} -                    hideTitle={true} -                  /> -                  { -                    balanceIsCached ? <span className="transaction-view-balance__cached-star">*</span> : null -                  } -                </div> -                { -                  showFiat && ( -                    <UserPreferencedCurrencyDisplay -                      className={classnames({ -                        'transaction-view-balance__cached-secondary-balance': balanceIsCached, -                        'transaction-view-balance__secondary-balance': !balanceIsCached, -                      })} -                      value={balance} -                      type={SECONDARY} -                      ethNumberOfDecimals={4} -                      hideTitle={true} -                    /> -                  ) -                } +        <Tooltip position="top" title={this.context.t('balanceOutdated')} disabled={!balanceIsCached}> +          <div className="transaction-view-balance__balance"> +            <div className="transaction-view-balance__primary-container"> +              <UserPreferencedCurrencyDisplay +                className={classnames('transaction-view-balance__primary-balance', { +                  'transaction-view-balance__cached-balance': balanceIsCached, +                })} +                value={balance} +                type={PRIMARY} +                ethNumberOfDecimals={4} +                hideTitle={true} +              /> +              { +                balanceIsCached ? <span className="transaction-view-balance__cached-star">*</span> : null +              }              </div> -          </Tooltip> +            { +              showFiat && ( +                <UserPreferencedCurrencyDisplay +                  className={classnames({ +                    'transaction-view-balance__cached-secondary-balance': balanceIsCached, +                    'transaction-view-balance__secondary-balance': !balanceIsCached, +                  })} +                  value={balance} +                  type={SECONDARY} +                  ethNumberOfDecimals={4} +                  hideTitle={true} +                /> +              ) +            } +          </div> +        </Tooltip>        )    } diff --git a/ui/app/components/app/transaction-view/transaction-view.component.js b/ui/app/components/app/transaction-view/transaction-view.component.js index 7014ca173..fb2c2145c 100644 --- a/ui/app/components/app/transaction-view/transaction-view.component.js +++ b/ui/app/components/app/transaction-view/transaction-view.component.js @@ -10,6 +10,14 @@ export default class TransactionView extends PureComponent {      t: PropTypes.func,    } +  static propTypes = { +    children: PropTypes.node, +  } + +  static defaultProps = { +    children: null, +  } +    render () {      return (        <div className="transaction-view"> @@ -20,7 +28,9 @@ export default class TransactionView extends PureComponent {          <div className="transaction-view__balance-wrapper">            <TransactionViewBalance />          </div> -        <TransactionList /> +        <TransactionList> +          { this.props.children } +        </TransactionList>        </div>      )    } diff --git a/ui/app/components/app/ui-migration-annoucement/index.js b/ui/app/components/app/ui-migration-annoucement/index.js deleted file mode 100644 index c6c8cc619..000000000 --- a/ui/app/components/app/ui-migration-annoucement/index.js +++ /dev/null @@ -1 +0,0 @@ -export {default} from './ui-migration-announcement.container' diff --git a/ui/app/components/app/ui-migration-annoucement/index.scss b/ui/app/components/app/ui-migration-annoucement/index.scss deleted file mode 100644 index 6138a3079..000000000 --- a/ui/app/components/app/ui-migration-annoucement/index.scss +++ /dev/null @@ -1,22 +0,0 @@ -.ui-migration-announcement { -  position: absolute; -  z-index: 9999; -  width: 100vw; -  height: 100vh; -  display: flex; -  flex-direction: column; -  align-items: center; -  background: $white; - -  p { -    box-sizing: border-box; -    padding: 1em; -    font-size: 12pt; -  } - -  p:last-of-type { -    cursor: pointer; -    text-decoration: underline; -    font-weight: bold; -  } -} diff --git a/ui/app/components/app/ui-migration-annoucement/ui-migration-annoucement.component.js b/ui/app/components/app/ui-migration-annoucement/ui-migration-annoucement.component.js deleted file mode 100644 index 7a4124972..000000000 --- a/ui/app/components/app/ui-migration-annoucement/ui-migration-annoucement.component.js +++ /dev/null @@ -1,33 +0,0 @@ -import PropTypes from 'prop-types' -import React, {PureComponent} from 'react' - -export default class UiMigrationAnnouncement extends PureComponent { -  static contextTypes = { -    t: PropTypes.func.isRequired, -  } - -  static defaultProps = { -    shouldShowAnnouncement: true, -  }; - -  static propTypes = { -    onClose: PropTypes.func.isRequired, -    shouldShowAnnouncement: PropTypes.bool, -  } - -  render () { -    const { t } = this.context -    const { onClose, shouldShowAnnouncement } = this.props - -    if (!shouldShowAnnouncement) { -      return null -    } - -    return ( -      <div className="ui-migration-announcement"> -        <p>{t('uiMigrationAnnouncement')}</p> -        <p onClick={onClose}>{t('close')}</p> -      </div> -    ) -  } -} diff --git a/ui/app/components/app/ui-migration-annoucement/ui-migration-announcement.container.js b/ui/app/components/app/ui-migration-annoucement/ui-migration-announcement.container.js deleted file mode 100644 index 55efd5a44..000000000 --- a/ui/app/components/app/ui-migration-annoucement/ui-migration-announcement.container.js +++ /dev/null @@ -1,21 +0,0 @@ -import { connect } from 'react-redux' -import UiMigrationAnnouncement from './ui-migration-annoucement.component' -import { setCompletedUiMigration } from '../../../store/actions' - -const mapStateToProps = (state) => { -  const shouldShowAnnouncement = !state.metamask.completedUiMigration - -  return { -    shouldShowAnnouncement, -  } -} - -const mapDispatchToProps = dispatch => { -  return { -    onClose () { -      dispatch(setCompletedUiMigration()) -    }, -  } -} - -export default connect(mapStateToProps, mapDispatchToProps)(UiMigrationAnnouncement) diff --git a/ui/app/components/app/wallet-view.js b/ui/app/components/app/wallet-view.js index b8bae5421..55aeec333 100644 --- a/ui/app/components/app/wallet-view.js +++ b/ui/app/components/app/wallet-view.js @@ -5,12 +5,8 @@ const h = require('react-hyperscript')  const { withRouter } = require('react-router-dom')  const { compose } = require('recompose')  const inherits = require('util').inherits -const classnames = require('classnames')  const { checksumAddress } = require('../../helpers/utils/util') -import Identicon from '../ui/identicon'  // const AccountDropdowns = require('./dropdowns/index.js').AccountDropdowns -const Tooltip = require('../ui/tooltip-v2.js').default -const copyToClipboard = require('copy-to-clipboard')  const actions = require('../../store/actions')  import BalanceComponent from '../ui/balance'  const TokenList = require('./token-list') @@ -18,6 +14,7 @@ const selectors = require('../../selectors/selectors')  const { ADD_TOKEN_ROUTE } = require('../../helpers/constants/routes')  import AddTokenButton from './add-token-button' +import AccountDetails from './account-details'  module.exports = compose(    withRouter, @@ -52,9 +49,6 @@ function mapDispatchToProps (dispatch) {      showSendPage: () => dispatch(actions.showSendPage()),      hideSidebar: () => dispatch(actions.hideSidebar()),      unsetSelectedToken: () => dispatch(actions.setSelectedToken()), -    showAccountDetailModal: () => { -      dispatch(actions.showModal({ name: 'ACCOUNT_DETAILS' })) -    },      showAddTokenPage: () => dispatch(actions.showAddTokenPage()),    }  } @@ -62,10 +56,6 @@ function mapDispatchToProps (dispatch) {  inherits(WalletView, Component)  function WalletView () {    Component.call(this) -  this.state = { -    hasCopied: false, -    copyToClipboardPressed: false, -  }  }  WalletView.prototype.renderWalletBalance = function () { @@ -130,8 +120,6 @@ WalletView.prototype.render = function () {      responsiveDisplayClassname,      selectedAddress,      keyrings, -    showAccountDetailModal, -    hideSidebar,      identities,      network,    } = this.props @@ -165,67 +153,11 @@ WalletView.prototype.render = function () {      className: responsiveDisplayClassname,    }, [ -    // TODO: Separate component: wallet account details -    h('div.flex-column.wallet-view-account-details', { -      style: {}, -    }, [ -      h('div.wallet-view__sidebar-close', { -        onClick: hideSidebar, -      }), - -      h('div.wallet-view__keyring-label.allcaps', label), - -      h('div.flex-column.flex-center.wallet-view__name-container', { -        style: { margin: '0 auto' }, -        onClick: showAccountDetailModal, -      }, [ -        h(Identicon, { -          diameter: 54, -          address: checksummedAddress, -        }), - -        h('span.account-name', { -          style: {}, -        }, [ -          identities[selectedAddress].name, -        ]), - -        h('button.btn-secondary.wallet-view__details-button', this.context.t('details')), -      ]), -    ]), - -    h(Tooltip, { -      position: 'bottom', -      title: this.state.hasCopied ? this.context.t('copiedExclamation') : this.context.t('copyToClipboard'), -      wrapperClassName: 'wallet-view__tooltip', -    }, [ -      h('button.wallet-view__address', { -        className: classnames({ -          'wallet-view__address__pressed': this.state.copyToClipboardPressed, -        }), -        onClick: () => { -          copyToClipboard(checksummedAddress) -          this.context.metricsEvent({ -            eventOpts: { -              category: 'Navigation', -              action: 'Home', -              name: 'Copied Address', -            }, -          }) -          this.setState({ hasCopied: true }) -          setTimeout(() => this.setState({ hasCopied: false }), 3000) -        }, -        onMouseDown: () => { -          this.setState({ copyToClipboardPressed: true }) -        }, -        onMouseUp: () => { -          this.setState({ copyToClipboardPressed: false }) -        }, -      }, [ -        `${checksummedAddress.slice(0, 6)}...${checksummedAddress.slice(-4)}`, -        h('i.fa.fa-clipboard', { style: { marginLeft: '8px' } }), -      ]), -    ]), +    h(AccountDetails, { +      label, +      checksummedAddress, +      name: identities[selectedAddress].name, +    }),      this.renderWalletBalance(), diff --git a/ui/app/components/ui/account-dropdown-mini/account-dropdown-mini.component.js b/ui/app/components/ui/account-dropdown-mini/account-dropdown-mini.component.js deleted file mode 100644 index d9627e31b..000000000 --- a/ui/app/components/ui/account-dropdown-mini/account-dropdown-mini.component.js +++ /dev/null @@ -1,84 +0,0 @@ -import React, { PureComponent } from 'react' -import PropTypes from 'prop-types' -import AccountListItem from '../../../pages/send/account-list-item/account-list-item.component' - -export default class AccountDropdownMini extends PureComponent { -  static propTypes = { -    accounts: PropTypes.array.isRequired, -    closeDropdown: PropTypes.func, -    disabled: PropTypes.bool, -    dropdownOpen: PropTypes.bool, -    onSelect: PropTypes.func, -    openDropdown: PropTypes.func, -    selectedAccount: PropTypes.object.isRequired, -  } - -  static defaultProps = { -    closeDropdown: () => {}, -    disabled: false, -    dropdownOpen: false, -    onSelect: () => {}, -    openDropdown: () => {}, -  } - -  getListItemIcon (currentAccount, selectedAccount) { -    return currentAccount.address === selectedAccount.address && ( -      <i -        className="fa fa-check fa-lg" -        style={{ color: '#02c9b1' }} -      /> -    ) -  } - -  renderDropdown () { -    const { accounts, selectedAccount, closeDropdown, onSelect } = this.props - -    return ( -      <div> -        <div -          className="account-dropdown-mini__close-area" -          onClick={closeDropdown} -        /> -        <div className="account-dropdown-mini__list"> -          { -            accounts.map(account => ( -              <AccountListItem -                key={account.address} -                account={account} -                displayBalance={false} -                displayAddress={false} -                handleClick={() => { -                  onSelect(account) -                  closeDropdown() -                }} -                icon={this.getListItemIcon(account, selectedAccount)} -              /> -            )) -          } -        </div> -      </div> -    ) -  } - -  render () { -    const { disabled, selectedAccount, openDropdown, dropdownOpen } = this.props - -    return ( -      <div className="account-dropdown-mini"> -        <AccountListItem -          account={selectedAccount} -          handleClick={() => !disabled && openDropdown()} -          displayBalance={false} -          displayAddress={false} -          icon={ -            !disabled && <i -              className="fa fa-caret-down fa-lg" -              style={{ color: '#dedede' }} -            /> -          } -        /> -        { !disabled && dropdownOpen && this.renderDropdown() } -      </div> -    ) -  } -} diff --git a/ui/app/components/ui/account-dropdown-mini/index.js b/ui/app/components/ui/account-dropdown-mini/index.js deleted file mode 100644 index cb0839e72..000000000 --- a/ui/app/components/ui/account-dropdown-mini/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './account-dropdown-mini.component' diff --git a/ui/app/components/ui/account-dropdown-mini/tests/account-dropdown-mini.component.test.js b/ui/app/components/ui/account-dropdown-mini/tests/account-dropdown-mini.component.test.js deleted file mode 100644 index 9691f38aa..000000000 --- a/ui/app/components/ui/account-dropdown-mini/tests/account-dropdown-mini.component.test.js +++ /dev/null @@ -1,107 +0,0 @@ -import React from 'react' -import assert from 'assert' -import { shallow } from 'enzyme' -import AccountDropdownMini from '../account-dropdown-mini.component' -import AccountListItem from '../../../../pages/send/account-list-item/account-list-item.component' - -describe('AccountDropdownMini', () => { -  it('should render an account with an icon', () => { -    const accounts = [ -      { -        address: '0x1', -        name: 'account1', -        balance: '0x1', -      }, -      { -        address: '0x2', -        name: 'account2', -        balance: '0x2', -      }, -      { -        address: '0x3', -        name: 'account3', -        balance: '0x3', -      }, -    ] - -    const wrapper = shallow( -      <AccountDropdownMini -        selectedAccount={{ address: '0x1', name: 'account1', balance: '0x1' }} -        accounts={accounts} -      /> -    ) - -    assert.ok(wrapper) -    assert.equal(wrapper.find(AccountListItem).length, 1) -    const accountListItemProps = wrapper.find(AccountListItem).at(0).props() -    assert.equal(accountListItemProps.account.address, '0x1') -    const iconProps = accountListItemProps.icon.props -    assert.equal(iconProps.className, 'fa fa-caret-down fa-lg') -  }) - -  it('should render a list of accounts', () => { -    const accounts = [ -      { -        address: '0x1', -        name: 'account1', -        balance: '0x1', -      }, -      { -        address: '0x2', -        name: 'account2', -        balance: '0x2', -      }, -      { -        address: '0x3', -        name: 'account3', -        balance: '0x3', -      }, -    ] - -    const wrapper = shallow( -      <AccountDropdownMini -        selectedAccount={{ address: '0x1', name: 'account1', balance: '0x1' }} -        accounts={accounts} -        dropdownOpen={true} -      /> -    ) - -    assert.ok(wrapper) -    assert.equal(wrapper.find(AccountListItem).length, 4) -  }) - -  it('should render a single account when disabled', () => { -    const accounts = [ -      { -        address: '0x1', -        name: 'account1', -        balance: '0x1', -      }, -      { -        address: '0x2', -        name: 'account2', -        balance: '0x2', -      }, -      { -        address: '0x3', -        name: 'account3', -        balance: '0x3', -      }, -    ] - -    const wrapper = shallow( -      <AccountDropdownMini -        selectedAccount={{ address: '0x1', name: 'account1', balance: '0x1' }} -        accounts={accounts} -        dropdownOpen={false} -        disabled={true} -      /> -    ) - -    assert.ok(wrapper) -    assert.equal(wrapper.find(AccountListItem).length, 1) -    const accountListItemProps = wrapper.find(AccountListItem).at(0).props() -    assert.equal(accountListItemProps.account.address, '0x1') -    assert.equal(accountListItemProps.icon, false) -  }) -}) diff --git a/ui/app/components/ui/alert/index.js b/ui/app/components/ui/alert/index.js index b1229f502..da2ca4b66 100644 --- a/ui/app/components/ui/alert/index.js +++ b/ui/app/components/ui/alert/index.js @@ -4,59 +4,59 @@ const h = require('react-hyperscript')  class Alert extends Component { -    constructor (props) { -      super(props) - -      this.state = { -        visble: false, -        msg: false, -        className: '', -      } -    } - -    componentWillReceiveProps (nextProps) { -      if (!this.props.visible && nextProps.visible) { -        this.animateIn(nextProps) -      } else if (this.props.visible && !nextProps.visible) { -        this.animateOut() -      } -    } +  constructor (props) { +    super(props) -    animateIn (props) { -      this.setState({ -        msg: props.msg, -        visible: true, -        className: '.visible', -      }) +    this.state = { +      visble: false, +      msg: false, +      className: '',      } +  } -    animateOut () { -      this.setState({ -        msg: null, -        className: '.hidden', -      }) - -      setTimeout(_ => { -        this.setState({visible: false}) -      }, 500) - +  componentWillReceiveProps (nextProps) { +    if (!this.props.visible && nextProps.visible) { +      this.animateIn(nextProps) +    } else if (this.props.visible && !nextProps.visible) { +      this.animateOut()      } - -    render () { -        if (this.state.visible) { -          return ( -              h(`div.global-alert${this.state.className}`, {}, -                h('a.msg', {}, this.state.msg) -              ) -          ) -        } -        return null +  } + +  animateIn (props) { +    this.setState({ +      msg: props.msg, +      visible: true, +      className: '.visible', +    }) +  } + +  animateOut () { +    this.setState({ +      msg: null, +      className: '.hidden', +    }) + +    setTimeout(_ => { +      this.setState({visible: false}) +    }, 500) + +  } + +  render () { +    if (this.state.visible) { +      return ( +        h(`div.global-alert${this.state.className}`, {}, +          h('a.msg', {}, this.state.msg) +        ) +      )      } +    return null +  }  }  Alert.propTypes = { -    visible: PropTypes.bool.isRequired, -    msg: PropTypes.string, +  visible: PropTypes.bool.isRequired, +  msg: PropTypes.string,  }  module.exports = Alert diff --git a/ui/app/components/ui/button-group/tests/button-group-component.test.js b/ui/app/components/ui/button-group/tests/button-group-component.test.js index 0bece90d6..f2e512445 100644 --- a/ui/app/components/ui/button-group/tests/button-group-component.test.js +++ b/ui/app/components/ui/button-group/tests/button-group-component.test.js @@ -59,40 +59,40 @@ describe('ButtonGroup Component', function () {    describe('renderButtons', () => {      it('should render a button for each child', () => { -        const childButtons = wrapper.find('.button-group__button') -        assert.equal(childButtons.length, 3) +      const childButtons = wrapper.find('.button-group__button') +      assert.equal(childButtons.length, 3)      })      it('should render the correct button with an active state', () => { -        const childButtons = wrapper.find('.button-group__button') -        const activeChildButton = wrapper.find('.button-group__button--active') -        assert.deepEqual(childButtons.get(1), activeChildButton.get(0)) +      const childButtons = wrapper.find('.button-group__button') +      const activeChildButton = wrapper.find('.button-group__button--active') +      assert.deepEqual(childButtons.get(1), activeChildButton.get(0))      })      it('should call handleButtonClick and the respective button\'s onClick method when a button is clicked', () => { -        assert.equal(ButtonGroup.prototype.handleButtonClick.callCount, 0) -        assert.equal(childButtonSpies.onClick.callCount, 0) -        const childButtons = wrapper.find('.button-group__button') -        childButtons.at(0).props().onClick() -        childButtons.at(1).props().onClick() -        childButtons.at(2).props().onClick() -        assert.equal(ButtonGroup.prototype.handleButtonClick.callCount, 3) -        assert.equal(childButtonSpies.onClick.callCount, 3) +      assert.equal(ButtonGroup.prototype.handleButtonClick.callCount, 0) +      assert.equal(childButtonSpies.onClick.callCount, 0) +      const childButtons = wrapper.find('.button-group__button') +      childButtons.at(0).props().onClick() +      childButtons.at(1).props().onClick() +      childButtons.at(2).props().onClick() +      assert.equal(ButtonGroup.prototype.handleButtonClick.callCount, 3) +      assert.equal(childButtonSpies.onClick.callCount, 3)      })      it('should render all child buttons as disabled if props.disabled is true', () => { -        const childButtons = wrapper.find('.button-group__button') -        childButtons.forEach(button => { -            assert.equal(button.props().disabled, undefined) -        }) -        wrapper.setProps({ disabled: true }) -        const disabledChildButtons = wrapper.find('[disabled=true]') -        assert.equal(disabledChildButtons.length, 3) +      const childButtons = wrapper.find('.button-group__button') +      childButtons.forEach(button => { +        assert.equal(button.props().disabled, undefined) +      }) +      wrapper.setProps({ disabled: true }) +      const disabledChildButtons = wrapper.find('[disabled=true]') +      assert.equal(disabledChildButtons.length, 3)      })      it('should render the children of the button', () => { -        const mockClass = wrapper.find('.mockClass') -        assert.equal(mockClass.length, 1) +      const mockClass = wrapper.find('.mockClass') +      assert.equal(mockClass.length, 1)      })    }) @@ -103,9 +103,9 @@ describe('ButtonGroup Component', function () {      })      it('should call renderButtons when rendering', () => { -        assert.equal(ButtonGroup.prototype.renderButtons.callCount, 1) -        wrapper.instance().render() -        assert.equal(ButtonGroup.prototype.renderButtons.callCount, 2) +      assert.equal(ButtonGroup.prototype.renderButtons.callCount, 1) +      wrapper.instance().render() +      assert.equal(ButtonGroup.prototype.renderButtons.callCount, 2)      })    })  }) diff --git a/ui/app/components/ui/button/buttons.scss b/ui/app/components/ui/button/buttons.scss index f1366cffe..f6388fa47 100644 --- a/ui/app/components/ui/button/buttons.scss +++ b/ui/app/components/ui/button/buttons.scss @@ -22,7 +22,6 @@ $hover-orange: #FFD3B5;    box-sizing: border-box;    border-radius: 6px;    width: 100%; -  outline: none;    transition: border-color .3s ease, background-color .3s ease;    &--disabled, diff --git a/ui/app/components/ui/currency-input/currency-input.component.js b/ui/app/components/ui/currency-input/currency-input.component.js index 1876c9591..f7db2b829 100644 --- a/ui/app/components/ui/currency-input/currency-input.component.js +++ b/ui/app/components/ui/currency-input/currency-input.component.js @@ -141,22 +141,22 @@ export default class CurrencyInput extends PureComponent {      const { decimalValue } = this.state      return ( -        <UnitInput -          {...restProps} -          suffix={this.shouldUseFiat() ? fiatSuffix : nativeSuffix} -          onChange={this.handleChange} -          onBlur={this.handleBlur} -          value={decimalValue} -          maxModeOn={maxModeOn} -          actionComponent={( -            <div -              className="currency-input__swap-component" -              onClick={this.swap} -            /> -          )} -        > -          { this.renderConversionComponent() } -        </UnitInput> +      <UnitInput +        {...restProps} +        suffix={this.shouldUseFiat() ? fiatSuffix : nativeSuffix} +        onChange={this.handleChange} +        onBlur={this.handleBlur} +        value={decimalValue} +        maxModeOn={maxModeOn} +        actionComponent={( +          <div +            className="currency-input__swap-component" +            onClick={this.swap} +          /> +        )} +      > +        { this.renderConversionComponent() } +      </UnitInput>      )    }  } diff --git a/ui/app/components/ui/dialog/dialog.scss b/ui/app/components/ui/dialog/dialog.scss new file mode 100644 index 000000000..68b5ce329 --- /dev/null +++ b/ui/app/components/ui/dialog/dialog.scss @@ -0,0 +1,26 @@ +.dialog { +  font-size: .75rem; +  line-height: 1rem; +  padding: 1rem; +  border: 1px solid $black; +  box-sizing: border-box; +  border-radius: 8px; + +  &--message { +    border-color: $Blue-200; +    color: $Blue-600; +    background-color: $Blue-000; +  } + +  &--error { +    border-color: $Red-300; +    color: $Red-600; +    background-color: $Red-000; +  } + +  &--warning { +    border-color: $Orange-300; +    color: $Orange-600; +    background-color: $Orange-000; +  } +} diff --git a/ui/app/components/ui/dialog/index.js b/ui/app/components/ui/dialog/index.js new file mode 100644 index 000000000..d7e522b22 --- /dev/null +++ b/ui/app/components/ui/dialog/index.js @@ -0,0 +1,26 @@ +import React from 'react' +import PropTypes from 'prop-types' +import c from 'classnames' + +export default function Dialog (props) { +  const { children, type, className, onClick } = props +  return ( +    <div +      className={c('dialog', className, { +        'dialog--message': type === 'message', +        'dialog--error': type === 'error', +        'dialog--warning': type === 'warning', +      })} +      onClick={onClick} +    > +      { children } +    </div> +  ) +} + +Dialog.propTypes = { +  className: PropTypes.string, +  children: PropTypes.node, +  type: PropTypes.oneOf(['message', 'error', 'warning']), +  onClick: PropTypes.func, +} diff --git a/ui/app/components/ui/identicon/identicon.component.js b/ui/app/components/ui/identicon/identicon.component.js index 88521247c..5582c7d12 100644 --- a/ui/app/components/ui/identicon/identicon.component.js +++ b/ui/app/components/ui/identicon/identicon.component.js @@ -16,6 +16,7 @@ const getStyles = diameter => (  export default class Identicon extends PureComponent {    static propTypes = { +    addBorder: PropTypes.bool,      address: PropTypes.string,      className: PropTypes.string,      diameter: PropTypes.number, @@ -70,7 +71,7 @@ export default class Identicon extends PureComponent {    }    render () { -    const { className, address, image, diameter, useBlockie } = this.props +    const { className, address, image, diameter, useBlockie, addBorder } = this.props      if (image) {        return this.renderImage() @@ -83,9 +84,11 @@ export default class Identicon extends PureComponent {          return this.renderJazzicon()        } -      return useBlockie -        ? this.renderBlockie() -        : this.renderJazzicon() +      return ( +        <div className={classnames({ 'identicon__address-wrapper': addBorder })}> +          { useBlockie ? this.renderBlockie() : this.renderJazzicon() } +        </div> +      )      }      return ( diff --git a/ui/app/components/ui/identicon/index.scss b/ui/app/components/ui/identicon/index.scss index 657afc48f..4c8213f01 100644 --- a/ui/app/components/ui/identicon/index.scss +++ b/ui/app/components/ui/identicon/index.scss @@ -4,4 +4,17 @@    align-items: center;    justify-content: center;    overflow: hidden; + +  &__address-wrapper { +    height: 40px; +    width: 40px; +    border-radius: 18px; +    display: flex; +    justify-content: center; +    align-items: center; +    border-style: solid; +    border-radius: 50%; +    border-width: 2px; +    border-color: $curious-blue; +  }  } diff --git a/ui/app/components/ui/metafox-logo/index.js b/ui/app/components/ui/metafox-logo/index.js new file mode 100644 index 000000000..0aeaed743 --- /dev/null +++ b/ui/app/components/ui/metafox-logo/index.js @@ -0,0 +1 @@ +export { default } from './metafox-logo.component' diff --git a/ui/app/components/ui/metafox-logo/metafox-logo.component.js b/ui/app/components/ui/metafox-logo/metafox-logo.component.js new file mode 100644 index 000000000..041e354ef --- /dev/null +++ b/ui/app/components/ui/metafox-logo/metafox-logo.component.js @@ -0,0 +1,31 @@ +import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' + +export default class MetaFoxLogo extends PureComponent { +  static propTypes = { +    onClick: PropTypes.func, +    unsetIconHeight: PropTypes.bool, +  } + +  render () { +    const iconProps = this.props.unsetIconHeight ? {} : { height: 42, width: 42 } + +    return ( +      <div +        onClick={this.props.onClick} +        className="app-header__logo-container" +      > +        <img +          height={30} +          src="/images/logo/metamask-logo-horizontal.svg" +          className="app-header__metafox-logo app-header__metafox-logo--horizontal" +        /> +        <img +          {...iconProps} +          src="/images/logo/metamask-fox.svg" +          className="app-header__metafox-logo app-header__metafox-logo--icon" +        /> +      </div> +    ) +  } +} diff --git a/ui/app/components/ui/metafox-logo/tests/metafox-logo.component.test.js b/ui/app/components/ui/metafox-logo/tests/metafox-logo.component.test.js new file mode 100644 index 000000000..c794a004f --- /dev/null +++ b/ui/app/components/ui/metafox-logo/tests/metafox-logo.component.test.js @@ -0,0 +1,25 @@ +import React from 'react' +import assert from 'assert' +import { mount } from 'enzyme' +import MetaFoxLogo from '../' + +describe('MetaFoxLogo', () => { + +  it('sets icon height and width to 42 by default', () => { +    const wrapper = mount( +      <MetaFoxLogo /> +    ) + +    assert.equal(wrapper.find('img.app-header__metafox-logo--icon').prop('width'), 42) +    assert.equal(wrapper.find('img.app-header__metafox-logo--icon').prop('height'), 42) +  }) + +  it('does not set icon height and width when unsetIconHeight is true', () => { +    const wrapper = mount( +      <MetaFoxLogo unsetIconHeight={true} /> +    ) + +    assert.equal(wrapper.find('img.app-header__metafox-logo--icon').prop('width'), null) +    assert.equal(wrapper.find('img.app-header__metafox-logo--icon').prop('height'), null) +  }) +}) diff --git a/ui/app/components/ui/page-container/page-container-header/page-container-header.component.js b/ui/app/components/ui/page-container/page-container-header/page-container-header.component.js index 08f9c7544..f1e15f10f 100644 --- a/ui/app/components/ui/page-container/page-container-header/page-container-header.component.js +++ b/ui/app/components/ui/page-container/page-container-header/page-container-header.component.js @@ -1,6 +1,6 @@  import React, { Component } from 'react'  import PropTypes from 'prop-types' -import classnames from 'classnames' +import c from 'classnames'  export default class PageContainerHeader extends Component {    static propTypes = { @@ -13,6 +13,7 @@ export default class PageContainerHeader extends Component {      backButtonString: PropTypes.string,      tabs: PropTypes.node,      headerCloseText: PropTypes.string, +    className: PropTypes.string,    }    renderTabs () { @@ -42,15 +43,14 @@ export default class PageContainerHeader extends Component {    }    render () { -    const { title, subtitle, onClose, tabs, headerCloseText } = this.props +    const { title, subtitle, onClose, tabs, headerCloseText, className } = this.props      return ( -      <div className={ -        classnames( -          'page-container__header', -          { 'page-container__header--no-padding-bottom': Boolean(tabs) } -        ) -      }> +      <div +        className={c('page-container__header', className, { +          'page-container__header--no-padding-bottom': Boolean(tabs), +        })} +      >          { this.renderHeaderRow() } diff --git a/ui/app/components/ui/sender-to-recipient/sender-to-recipient.component.js b/ui/app/components/ui/sender-to-recipient/sender-to-recipient.component.js index 57b595d48..a98a94101 100644 --- a/ui/app/components/ui/sender-to-recipient/sender-to-recipient.component.js +++ b/ui/app/components/ui/sender-to-recipient/sender-to-recipient.component.js @@ -64,10 +64,10 @@ export default class SenderToRecipient extends PureComponent {          containerClassName="sender-to-recipient__tooltip-container"          onHidden={() => this.setState({ senderAddressCopied: false })}        > -      <div className="sender-to-recipient__name"> -        { addressOnly ? `${t('from')}: ${checksummedSenderAddress}` : senderName } -      </div> -    </Tooltip> +        <div className="sender-to-recipient__name"> +          { addressOnly ? `${t('from')}: ${checksummedSenderAddress}` : senderName } +        </div> +      </Tooltip>      )    } diff --git a/ui/app/components/ui/text-field/text-field.component.js b/ui/app/components/ui/text-field/text-field.component.js index 1153a595b..ac7712c65 100644 --- a/ui/app/components/ui/text-field/text-field.component.js +++ b/ui/app/components/ui/text-field/text-field.component.js @@ -61,6 +61,9 @@ const styles = {      ...inputLabelBase,      fontSize: '.75rem',    }, +  inputMultiline: { +    lineHeight: 'initial !important', +  },  }  const TextField = props => { diff --git a/ui/app/components/ui/toggle-button/index.js b/ui/app/components/ui/toggle-button/index.js new file mode 100644 index 000000000..7948d3ca1 --- /dev/null +++ b/ui/app/components/ui/toggle-button/index.js @@ -0,0 +1,2 @@ +import ToggleButton from './toggle-button.component' +module.exports = ToggleButton diff --git a/ui/app/components/ui/toggle-button/index.scss b/ui/app/components/ui/toggle-button/index.scss new file mode 100644 index 000000000..868d416c8 --- /dev/null +++ b/ui/app/components/ui/toggle-button/index.scss @@ -0,0 +1,14 @@ +.toggle-button { +  display: flex; + +  &__status-label { +    font-family: Roboto; +    font-style: normal; +    font-weight: normal; +    font-size: 16px; +    line-height: 23px; +    display: flex; +    align-items: center; +    text-transform: uppercase; +  } +}
\ No newline at end of file diff --git a/ui/app/components/ui/toggle-button/toggle-button.component.js b/ui/app/components/ui/toggle-button/toggle-button.component.js new file mode 100644 index 000000000..3f13203a5 --- /dev/null +++ b/ui/app/components/ui/toggle-button/toggle-button.component.js @@ -0,0 +1,75 @@ +import React from 'react' +import PropTypes from 'prop-types' +import ReactToggleButton from 'react-toggle-button' + +const trackStyle = { +  width: '40px', +  height: '24px', +  padding: '0px', +  borderRadius: '26px', +  border: '2px solid rgb(3, 125, 214)', +  display: 'flex', +  alignItems: 'center', +  justifyContent: 'center', +} + +const offTrackStyle = { +  ...trackStyle, +  border: '2px solid #8E8E8E', +} + +const thumbStyle = { +  width: '18px', +  height: '18px', +  display: 'flex', +  boxShadow: 'none', +  alignSelf: 'center', +  borderRadius: '50%', +  position: 'relative', +} + +const colors = { +  activeThumb: { +    base: '#037DD6', +  }, +  inactiveThumb: { +    base: '#037DD6', +  }, +  active: { +    base: '#ffffff', +    hover: '#ffffff', +  }, +  inactive: { +    base: '#DADADA', +    hover: '#DADADA', +  }, +} + +const ToggleButton = props => { +  const { value, onToggle, offLabel, onLabel } = props + +  return ( +    <div className="toggle-button"> +      <ReactToggleButton +        value={value} +        onToggle={onToggle} +        activeLabel="" +        inactiveLabel="" +        trackStyle={value ? trackStyle : offTrackStyle} +        thumbStyle={thumbStyle} +        thumbAnimateRange={[3, 18]} +        colors={colors} +      /> +      <div className="toggle-button__status-label">{ value ? onLabel : offLabel }</div> +    </div> +  ) +} + +ToggleButton.propTypes = { +  value: PropTypes.bool, +  onToggle: PropTypes.func, +  offLabel: PropTypes.string, +  onLabel: PropTypes.string, +} + +export default ToggleButton diff --git a/ui/app/components/ui/unit-input/index.scss b/ui/app/components/ui/unit-input/index.scss index 58a10c9a1..338b3829f 100644 --- a/ui/app/components/ui/unit-input/index.scss +++ b/ui/app/components/ui/unit-input/index.scss @@ -38,7 +38,6 @@      font-size: 1rem;      font-family: Roboto;      border: none; -    outline: 0 !important;      max-width: 22ch;      height: 16px;      line-height: 18px; | 
