aboutsummaryrefslogtreecommitdiffstats
path: root/ui/app/components/modals
diff options
context:
space:
mode:
authorCsaba S <csaba.solya@gmail.com>2018-02-22 21:55:46 +0800
committerGitHub <noreply@github.com>2018-02-22 21:55:46 +0800
commit2f3463048685759319eb320c916cc7bc23a41649 (patch)
treee53ac24a1a127f1c684327bca00d23f51b56c542 /ui/app/components/modals
parent5ec311ba3e01bd9b0a9ff447fd7639d22a7b3d9c (diff)
parenteed75735b8e90d121537249cf147c1ece60f1e47 (diff)
downloadtangerine-wallet-browser-2f3463048685759319eb320c916cc7bc23a41649.tar.gz
tangerine-wallet-browser-2f3463048685759319eb320c916cc7bc23a41649.tar.zst
tangerine-wallet-browser-2f3463048685759319eb320c916cc7bc23a41649.zip
Merge branch 'master' into edge-support
Diffstat (limited to 'ui/app/components/modals')
-rw-r--r--ui/app/components/modals/account-details-modal.js75
-rw-r--r--ui/app/components/modals/account-modal-container.js74
-rw-r--r--ui/app/components/modals/buy-options-modal.js95
-rw-r--r--ui/app/components/modals/deposit-ether-modal.js184
-rw-r--r--ui/app/components/modals/edit-account-name-modal.js77
-rw-r--r--ui/app/components/modals/export-private-key-modal.js141
-rw-r--r--ui/app/components/modals/hide-token-confirmation-modal.js74
-rw-r--r--ui/app/components/modals/index.js5
-rw-r--r--ui/app/components/modals/modal.js344
-rw-r--r--ui/app/components/modals/new-account-modal.js106
-rw-r--r--ui/app/components/modals/notification-modal.js75
-rw-r--r--ui/app/components/modals/notification-modals/confirm-reset-account.js46
-rw-r--r--ui/app/components/modals/shapeshift-deposit-tx-modal.js40
13 files changed, 1336 insertions, 0 deletions
diff --git a/ui/app/components/modals/account-details-modal.js b/ui/app/components/modals/account-details-modal.js
new file mode 100644
index 000000000..c1f7a3236
--- /dev/null
+++ b/ui/app/components/modals/account-details-modal.js
@@ -0,0 +1,75 @@
+const Component = require('react').Component
+const h = require('react-hyperscript')
+const inherits = require('util').inherits
+const connect = require('react-redux').connect
+const actions = require('../../actions')
+const AccountModalContainer = require('./account-modal-container')
+const { getSelectedIdentity } = require('../../selectors')
+const genAccountLink = require('../../../lib/account-link.js')
+const QrView = require('../qr-code')
+const EditableLabel = require('../editable-label')
+
+function mapStateToProps (state) {
+ return {
+ network: state.metamask.network,
+ selectedIdentity: getSelectedIdentity(state),
+ }
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ // Is this supposed to be used somewhere?
+ showQrView: (selected, identity) => dispatch(actions.showQrView(selected, identity)),
+ showExportPrivateKeyModal: () => {
+ dispatch(actions.showModal({ name: 'EXPORT_PRIVATE_KEY' }))
+ },
+ hideModal: () => dispatch(actions.hideModal()),
+ saveAccountLabel: (address, label) => dispatch(actions.saveAccountLabel(address, label)),
+ }
+}
+
+inherits(AccountDetailsModal, Component)
+function AccountDetailsModal () {
+ Component.call(this)
+}
+
+module.exports = connect(mapStateToProps, mapDispatchToProps)(AccountDetailsModal)
+
+// Not yet pixel perfect todos:
+ // fonts of qr-header
+
+AccountDetailsModal.prototype.render = function () {
+ const {
+ selectedIdentity,
+ network,
+ showExportPrivateKeyModal,
+ saveAccountLabel,
+ } = this.props
+ const { name, address } = selectedIdentity
+
+ return h(AccountModalContainer, {}, [
+ h(EditableLabel, {
+ className: 'account-modal__name',
+ defaultValue: name,
+ onSubmit: label => saveAccountLabel(address, label),
+ }),
+
+ h(QrView, {
+ Qr: {
+ data: address,
+ },
+ }),
+
+ h('div.account-modal-divider'),
+
+ h('button.btn-clear.account-modal__button', {
+ onClick: () => global.platform.openWindow({ url: genAccountLink(address, network) }),
+ }, 'View account on Etherscan'),
+
+ // Holding on redesign for Export Private Key functionality
+ h('button.btn-clear.account-modal__button', {
+ onClick: () => showExportPrivateKeyModal(),
+ }, 'Export private key'),
+
+ ])
+}
diff --git a/ui/app/components/modals/account-modal-container.js b/ui/app/components/modals/account-modal-container.js
new file mode 100644
index 000000000..c548fb7b3
--- /dev/null
+++ b/ui/app/components/modals/account-modal-container.js
@@ -0,0 +1,74 @@
+const Component = require('react').Component
+const h = require('react-hyperscript')
+const inherits = require('util').inherits
+const connect = require('react-redux').connect
+const actions = require('../../actions')
+const { getSelectedIdentity } = require('../../selectors')
+const Identicon = require('../identicon')
+
+function mapStateToProps (state) {
+ return {
+ selectedIdentity: getSelectedIdentity(state),
+ }
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ hideModal: () => {
+ dispatch(actions.hideModal())
+ },
+ }
+}
+
+inherits(AccountModalContainer, Component)
+function AccountModalContainer () {
+ Component.call(this)
+}
+
+module.exports = connect(mapStateToProps, mapDispatchToProps)(AccountModalContainer)
+
+AccountModalContainer.prototype.render = function () {
+ const {
+ selectedIdentity,
+ showBackButton = false,
+ backButtonAction,
+ } = this.props
+ let { children } = this.props
+
+ if (children.constructor !== Array) {
+ children = [children]
+ }
+
+ return h('div', { style: { borderRadius: '4px' }}, [
+ h('div.account-modal-container', [
+
+ h('div', [
+
+ // Needs a border; requires changes to svg
+ h(Identicon, {
+ address: selectedIdentity.address,
+ diameter: 64,
+ style: {},
+ }),
+
+ ]),
+
+ showBackButton && h('div.account-modal-back', {
+ onClick: backButtonAction,
+ }, [
+
+ h('i.fa.fa-angle-left.fa-lg'),
+
+ h('span.account-modal-back__text', ' Back'),
+
+ ]),
+
+ h('div.account-modal-close', {
+ onClick: this.props.hideModal,
+ }),
+
+ ...children,
+
+ ]),
+ ])
+}
diff --git a/ui/app/components/modals/buy-options-modal.js b/ui/app/components/modals/buy-options-modal.js
new file mode 100644
index 000000000..74a7a847e
--- /dev/null
+++ b/ui/app/components/modals/buy-options-modal.js
@@ -0,0 +1,95 @@
+const Component = require('react').Component
+const h = require('react-hyperscript')
+const inherits = require('util').inherits
+const connect = require('react-redux').connect
+const actions = require('../../actions')
+const networkNames = require('../../../../app/scripts/config.js').networkNames
+
+function mapStateToProps (state) {
+ return {
+ network: state.metamask.network,
+ address: state.metamask.selectedAddress,
+ }
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ toCoinbase: (address) => {
+ dispatch(actions.buyEth({ network: '1', address, amount: 0 }))
+ },
+ hideModal: () => {
+ dispatch(actions.hideModal())
+ },
+ showAccountDetailModal: () => {
+ dispatch(actions.showModal({ name: 'ACCOUNT_DETAILS' }))
+ },
+ toFaucet: network => dispatch(actions.buyEth({ network })),
+ }
+}
+
+inherits(BuyOptions, Component)
+function BuyOptions () {
+ Component.call(this)
+}
+
+module.exports = connect(mapStateToProps, mapDispatchToProps)(BuyOptions)
+
+BuyOptions.prototype.renderModalContentOption = function (title, header, onClick) {
+ return h('div.buy-modal-content-option', {
+ onClick,
+ }, [
+ h('div.buy-modal-content-option-title', {}, title),
+ h('div.buy-modal-content-option-subtitle', {}, header),
+ ])
+}
+
+BuyOptions.prototype.render = function () {
+ const { network, toCoinbase, address, toFaucet } = this.props
+ const isTestNetwork = ['3', '4', '42'].find(n => n === network)
+ const networkName = networkNames[network]
+
+ return h('div', {}, [
+ h('div.buy-modal-content.transfers-subview', {
+ }, [
+ h('div.buy-modal-content-title-wrapper.flex-column.flex-center', {
+ style: {},
+ }, [
+ h('div.buy-modal-content-title', {
+ style: {},
+ }, 'Transfers'),
+ h('div', {}, 'How would you like to deposit Ether?'),
+ ]),
+
+ h('div.buy-modal-content-options.flex-column.flex-center', {}, [
+
+ isTestNetwork
+ ? this.renderModalContentOption(networkName, 'Test Faucet', () => toFaucet(network))
+ : this.renderModalContentOption('Coinbase', 'Deposit with Fiat', () => toCoinbase(address)),
+
+ // h('div.buy-modal-content-option', {}, [
+ // h('div.buy-modal-content-option-title', {}, 'Shapeshift'),
+ // h('div.buy-modal-content-option-subtitle', {}, 'Trade any digital asset for any other'),
+ // ]),,
+
+ this.renderModalContentOption(
+ 'Direct Deposit',
+ 'Deposit from another account',
+ () => this.goToAccountDetailsModal()
+ ),
+
+ ]),
+
+ h('button', {
+ style: {
+ background: 'white',
+ },
+ onClick: () => { this.props.hideModal() },
+ }, h('div.buy-modal-content-footer#buy-modal-content-footer-text', {}, 'Cancel')),
+ ]),
+ ])
+}
+
+BuyOptions.prototype.goToAccountDetailsModal = function () {
+ this.props.hideModal()
+ this.props.showAccountDetailModal()
+}
diff --git a/ui/app/components/modals/deposit-ether-modal.js b/ui/app/components/modals/deposit-ether-modal.js
new file mode 100644
index 000000000..532d66653
--- /dev/null
+++ b/ui/app/components/modals/deposit-ether-modal.js
@@ -0,0 +1,184 @@
+const Component = require('react').Component
+const h = require('react-hyperscript')
+const inherits = require('util').inherits
+const connect = require('react-redux').connect
+const actions = require('../../actions')
+const networkNames = require('../../../../app/scripts/config.js').networkNames
+const ShapeshiftForm = require('../shapeshift-form')
+
+const DIRECT_DEPOSIT_ROW_TITLE = 'Directly Deposit Ether'
+const DIRECT_DEPOSIT_ROW_TEXT = `If you already have some Ether, the quickest way to get Ether in
+your new wallet by direct deposit.`
+const COINBASE_ROW_TITLE = 'Buy on Coinbase'
+const COINBASE_ROW_TEXT = `Coinbase is the world’s most popular way to buy and sell bitcoin,
+ethereum, and litecoin.`
+const SHAPESHIFT_ROW_TITLE = 'Deposit with ShapeShift'
+const SHAPESHIFT_ROW_TEXT = `If you own other cryptocurrencies, you can trade and deposit Ether
+directly into your MetaMask wallet. No Account Needed.`
+const FAUCET_ROW_TITLE = 'Test Faucet'
+const facuetRowText = networkName => `Get Ether from a faucet for the ${networkName}`
+
+function mapStateToProps (state) {
+ return {
+ network: state.metamask.network,
+ address: state.metamask.selectedAddress,
+ }
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ toCoinbase: (address) => {
+ dispatch(actions.buyEth({ network: '1', address, amount: 0 }))
+ },
+ hideModal: () => {
+ dispatch(actions.hideModal())
+ },
+ showAccountDetailModal: () => {
+ dispatch(actions.showModal({ name: 'ACCOUNT_DETAILS' }))
+ },
+ toFaucet: network => dispatch(actions.buyEth({ network })),
+ }
+}
+
+inherits(DepositEtherModal, Component)
+function DepositEtherModal () {
+ Component.call(this)
+
+ this.state = {
+ buyingWithShapeshift: false,
+ }
+}
+
+module.exports = connect(mapStateToProps, mapDispatchToProps)(DepositEtherModal)
+
+DepositEtherModal.prototype.renderRow = function ({
+ logo,
+ title,
+ text,
+ buttonLabel,
+ onButtonClick,
+ hide,
+ className,
+ hideButton,
+ hideTitle,
+ onBackClick,
+ showBackButton,
+}) {
+ if (hide) {
+ return null
+ }
+
+ return h('div', {
+ className: className || 'deposit-ether-modal__buy-row',
+ }, [
+
+ onBackClick && showBackButton && h('div.deposit-ether-modal__buy-row__back', {
+ onClick: onBackClick,
+ }, [
+
+ h('i.fa.fa-arrow-left.cursor-pointer'),
+
+ ]),
+
+ h('div.deposit-ether-modal__buy-row__logo', [logo]),
+
+ h('div.deposit-ether-modal__buy-row__description', [
+
+ !hideTitle && h('div.deposit-ether-modal__buy-row__description__title', [title]),
+
+ h('div.deposit-ether-modal__buy-row__description__text', [text]),
+
+ ]),
+
+ !hideButton && h('div.deposit-ether-modal__buy-row__button', [
+ h('button.deposit-ether-modal__deposit-button', {
+ onClick: onButtonClick,
+ }, [buttonLabel]),
+ ]),
+
+ ])
+}
+
+DepositEtherModal.prototype.render = function () {
+ const { network, toCoinbase, address, toFaucet } = this.props
+ const { buyingWithShapeshift } = this.state
+
+ const isTestNetwork = ['3', '4', '42'].find(n => n === network)
+ const networkName = networkNames[network]
+
+ return h('div.deposit-ether-modal', {}, [
+
+ h('div.deposit-ether-modal__header', [
+
+ h('div.deposit-ether-modal__header__title', ['Deposit Ether']),
+
+ h('div.deposit-ether-modal__header__description', [
+ 'To interact with decentralized applications using MetaMask, you’ll need Ether in your wallet.',
+ ]),
+
+ h('div.deposit-ether-modal__header__close', {
+ onClick: () => {
+ this.setState({ buyingWithShapeshift: false })
+ this.props.hideModal()
+ },
+ }),
+
+ ]),
+
+ h('div.deposit-ether-modal__buy-rows', [
+
+ this.renderRow({
+ logo: h('img.deposit-ether-modal__buy-row__eth-logo', { src: '../../../images/eth_logo.svg' }),
+ title: DIRECT_DEPOSIT_ROW_TITLE,
+ text: DIRECT_DEPOSIT_ROW_TEXT,
+ buttonLabel: 'View Account',
+ onButtonClick: () => this.goToAccountDetailsModal(),
+ hide: buyingWithShapeshift,
+ }),
+
+ this.renderRow({
+ logo: h('i.fa.fa-tint.fa-2x'),
+ title: FAUCET_ROW_TITLE,
+ text: facuetRowText(networkName),
+ buttonLabel: 'Get Ether',
+ onButtonClick: () => toFaucet(network),
+ hide: !isTestNetwork || buyingWithShapeshift,
+ }),
+
+ this.renderRow({
+ logo: h('img.deposit-ether-modal__buy-row__coinbase-logo', {
+ src: '../../../images/coinbase logo.png',
+ }),
+ title: COINBASE_ROW_TITLE,
+ text: COINBASE_ROW_TEXT,
+ buttonLabel: 'Continue to Coinbase',
+ onButtonClick: () => toCoinbase(address),
+ hide: isTestNetwork || buyingWithShapeshift,
+ }),
+
+ this.renderRow({
+ logo: h('img.deposit-ether-modal__buy-row__shapeshift-logo', {
+ src: '../../../images/shapeshift logo.png',
+ }),
+ title: SHAPESHIFT_ROW_TITLE,
+ text: SHAPESHIFT_ROW_TEXT,
+ buttonLabel: 'Buy with Shapeshift',
+ onButtonClick: () => this.setState({ buyingWithShapeshift: true }),
+ hide: isTestNetwork,
+ hideButton: buyingWithShapeshift,
+ hideTitle: buyingWithShapeshift,
+ onBackClick: () => this.setState({ buyingWithShapeshift: false }),
+ showBackButton: this.state.buyingWithShapeshift,
+ className: buyingWithShapeshift && 'deposit-ether-modal__buy-row__shapeshift-buy',
+ }),
+
+ buyingWithShapeshift && h(ShapeshiftForm),
+
+ ]),
+ ])
+}
+
+DepositEtherModal.prototype.goToAccountDetailsModal = function () {
+ this.props.hideModal()
+ this.props.showAccountDetailModal()
+}
diff --git a/ui/app/components/modals/edit-account-name-modal.js b/ui/app/components/modals/edit-account-name-modal.js
new file mode 100644
index 000000000..e2361140d
--- /dev/null
+++ b/ui/app/components/modals/edit-account-name-modal.js
@@ -0,0 +1,77 @@
+const Component = require('react').Component
+const h = require('react-hyperscript')
+const inherits = require('util').inherits
+const connect = require('react-redux').connect
+const actions = require('../../actions')
+const { getSelectedAccount } = require('../../selectors')
+
+function mapStateToProps (state) {
+ return {
+ selectedAccount: getSelectedAccount(state),
+ identity: state.appState.modal.modalState.identity,
+ }
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ hideModal: () => {
+ dispatch(actions.hideModal())
+ },
+ saveAccountLabel: (account, label) => {
+ dispatch(actions.saveAccountLabel(account, label))
+ },
+ }
+}
+
+inherits(EditAccountNameModal, Component)
+function EditAccountNameModal (props) {
+ Component.call(this)
+
+ this.state = {
+ inputText: props.identity.name,
+ }
+}
+
+module.exports = connect(mapStateToProps, mapDispatchToProps)(EditAccountNameModal)
+
+EditAccountNameModal.prototype.render = function () {
+ const { hideModal, saveAccountLabel, identity } = this.props
+
+ return h('div', {}, [
+ h('div.flex-column.edit-account-name-modal-content', {
+ }, [
+
+ h('div.edit-account-name-modal-cancel', {
+ onClick: () => {
+ hideModal()
+ },
+ }, [
+ h('i.fa.fa-times'),
+ ]),
+
+ h('div.edit-account-name-modal-title', {
+ }, ['Edit Account Name']),
+
+ h('input.edit-account-name-modal-input', {
+ placeholder: identity.name,
+ onChange: (event) => {
+ this.setState({ inputText: event.target.value })
+ },
+ value: this.state.inputText,
+ }, []),
+
+ h('button.btn-clear.edit-account-name-modal-save-button', {
+ onClick: () => {
+ if (this.state.inputText.length !== 0) {
+ saveAccountLabel(identity.address, this.state.inputText)
+ hideModal()
+ }
+ },
+ disabled: this.state.inputText.length === 0,
+ }, [
+ 'SAVE',
+ ]),
+
+ ]),
+ ])
+}
diff --git a/ui/app/components/modals/export-private-key-modal.js b/ui/app/components/modals/export-private-key-modal.js
new file mode 100644
index 000000000..422f23f44
--- /dev/null
+++ b/ui/app/components/modals/export-private-key-modal.js
@@ -0,0 +1,141 @@
+const Component = require('react').Component
+const h = require('react-hyperscript')
+const inherits = require('util').inherits
+const connect = require('react-redux').connect
+const ethUtil = require('ethereumjs-util')
+const actions = require('../../actions')
+const AccountModalContainer = require('./account-modal-container')
+const { getSelectedIdentity } = require('../../selectors')
+const ReadOnlyInput = require('../readonly-input')
+const copyToClipboard = require('copy-to-clipboard')
+
+function mapStateToProps (state) {
+ return {
+ warning: state.appState.warning,
+ privateKey: state.appState.accountDetail.privateKey,
+ network: state.metamask.network,
+ selectedIdentity: getSelectedIdentity(state),
+ previousModalState: state.appState.modal.previousModalState.name,
+ }
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ exportAccount: (password, address) => dispatch(actions.exportAccount(password, address)),
+ showAccountDetailModal: () => dispatch(actions.showModal({ name: 'ACCOUNT_DETAILS' })),
+ hideModal: () => dispatch(actions.hideModal()),
+ }
+}
+
+inherits(ExportPrivateKeyModal, Component)
+function ExportPrivateKeyModal () {
+ Component.call(this)
+
+ this.state = {
+ password: '',
+ privateKey: null,
+ }
+}
+
+module.exports = connect(mapStateToProps, mapDispatchToProps)(ExportPrivateKeyModal)
+
+ExportPrivateKeyModal.prototype.exportAccountAndGetPrivateKey = function (password, address) {
+ const { exportAccount } = this.props
+
+ exportAccount(password, address)
+ .then(privateKey => this.setState({ privateKey }))
+}
+
+ExportPrivateKeyModal.prototype.renderPasswordLabel = function (privateKey) {
+ return h('span.private-key-password-label', privateKey
+ ? 'This is your private key (click to copy)'
+ : 'Type Your Password'
+ )
+}
+
+ExportPrivateKeyModal.prototype.renderPasswordInput = function (privateKey) {
+ const plainKey = privateKey && ethUtil.stripHexPrefix(privateKey)
+
+ return privateKey
+ ? h(ReadOnlyInput, {
+ 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 }),
+ })
+}
+
+ExportPrivateKeyModal.prototype.renderButton = function (className, onClick, label) {
+ return h('button', {
+ className,
+ onClick,
+ }, label)
+}
+
+ExportPrivateKeyModal.prototype.renderButtons = function (privateKey, password, address, hideModal) {
+ return h('div.export-private-key-buttons', {}, [
+ !privateKey && this.renderButton(
+ 'btn-cancel export-private-key__button export-private-key__button--cancel',
+ () => hideModal(),
+ 'Cancel'
+ ),
+
+ (privateKey
+ ? this.renderButton('btn-clear export-private-key__button', () => hideModal(), 'Done')
+ : this.renderButton('btn-clear export-private-key__button', () => this.exportAccountAndGetPrivateKey(this.state.password, address), 'Confirm')
+ ),
+
+ ])
+}
+
+ExportPrivateKeyModal.prototype.render = function () {
+ const {
+ selectedIdentity,
+ warning,
+ showAccountDetailModal,
+ hideModal,
+ previousModalState,
+ } = this.props
+ const { name, address } = selectedIdentity
+
+ const { privateKey } = this.state
+
+ return h(AccountModalContainer, {
+ showBackButton: previousModalState === 'ACCOUNT_DETAILS',
+ backButtonAction: () => showAccountDetailModal(),
+ }, [
+
+ h('span.account-name', name),
+
+ h(ReadOnlyInput, {
+ wrapperClass: 'ellip-address-wrapper',
+ inputClass: 'qr-ellip-address ellip-address',
+ value: address,
+ }),
+
+ h('div.account-modal-divider'),
+
+ h('span.modal-body-title', 'Show Private Keys'),
+
+ h('div.private-key-password', {}, [
+ this.renderPasswordLabel(privateKey),
+
+ this.renderPasswordInput(privateKey),
+
+ !warning ? null : h('span.private-key-password-error', warning),
+ ]),
+
+ h('div.private-key-password-warning', `Warning: Never disclose this key.
+ Anyone with your private keys can take steal any assets held in your
+ account.`
+ ),
+
+ this.renderButtons(privateKey, this.state.password, address, hideModal),
+
+ ])
+}
diff --git a/ui/app/components/modals/hide-token-confirmation-modal.js b/ui/app/components/modals/hide-token-confirmation-modal.js
new file mode 100644
index 000000000..56c7ba299
--- /dev/null
+++ b/ui/app/components/modals/hide-token-confirmation-modal.js
@@ -0,0 +1,74 @@
+const Component = require('react').Component
+const h = require('react-hyperscript')
+const inherits = require('util').inherits
+const connect = require('react-redux').connect
+const actions = require('../../actions')
+const Identicon = require('../identicon')
+
+function mapStateToProps (state) {
+ return {
+ network: state.metamask.network,
+ token: state.appState.modal.modalState.token,
+ }
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ hideModal: () => dispatch(actions.hideModal()),
+ hideToken: address => {
+ dispatch(actions.removeToken(address))
+ .then(() => {
+ dispatch(actions.hideModal())
+ })
+ },
+ }
+}
+
+inherits(HideTokenConfirmationModal, Component)
+function HideTokenConfirmationModal () {
+ Component.call(this)
+
+ this.state = {}
+}
+
+module.exports = connect(mapStateToProps, mapDispatchToProps)(HideTokenConfirmationModal)
+
+HideTokenConfirmationModal.prototype.render = function () {
+ const { token, network, hideToken, hideModal } = this.props
+ const { symbol, address } = token
+
+ return h('div.hide-token-confirmation', {}, [
+ h('div.hide-token-confirmation__container', {
+ }, [
+ h('div.hide-token-confirmation__title', {}, [
+ 'Hide Token?',
+ ]),
+
+ h(Identicon, {
+ className: 'hide-token-confirmation__identicon',
+ diameter: 45,
+ address,
+ network,
+ }),
+
+ h('div.hide-token-confirmation__symbol', {}, symbol),
+
+ h('div.hide-token-confirmation__copy', {}, [
+ 'You can add this token back in the future by going go to “Add token” in your accounts options menu.',
+ ]),
+
+ h('div.hide-token-confirmation__buttons', {}, [
+ h('button.btn-cancel.hide-token-confirmation__button', {
+ onClick: () => hideModal(),
+ }, [
+ 'CANCEL',
+ ]),
+ h('button.btn-clear.hide-token-confirmation__button', {
+ onClick: () => hideToken(address),
+ }, [
+ 'HIDE',
+ ]),
+ ]),
+ ]),
+ ])
+}
diff --git a/ui/app/components/modals/index.js b/ui/app/components/modals/index.js
new file mode 100644
index 000000000..1db1d33d4
--- /dev/null
+++ b/ui/app/components/modals/index.js
@@ -0,0 +1,5 @@
+const Modal = require('./modal')
+
+module.exports = {
+ Modal,
+}
diff --git a/ui/app/components/modals/modal.js b/ui/app/components/modals/modal.js
new file mode 100644
index 000000000..97fe38292
--- /dev/null
+++ b/ui/app/components/modals/modal.js
@@ -0,0 +1,344 @@
+const Component = require('react').Component
+const h = require('react-hyperscript')
+const inherits = require('util').inherits
+const connect = require('react-redux').connect
+const FadeModal = require('boron').FadeModal
+const actions = require('../../actions')
+const isMobileView = require('../../../lib/is-mobile-view')
+const isPopupOrNotification = require('../../../../app/scripts/lib/is-popup-or-notification')
+
+// Modal Components
+const BuyOptions = require('./buy-options-modal')
+const DepositEtherModal = require('./deposit-ether-modal')
+const AccountDetailsModal = require('./account-details-modal')
+const EditAccountNameModal = require('./edit-account-name-modal')
+const ExportPrivateKeyModal = require('./export-private-key-modal')
+const NewAccountModal = require('./new-account-modal')
+const ShapeshiftDepositTxModal = require('./shapeshift-deposit-tx-modal.js')
+const HideTokenConfirmationModal = require('./hide-token-confirmation-modal')
+const CustomizeGasModal = require('../customize-gas-modal')
+const NotifcationModal = require('./notification-modal')
+const ConfirmResetAccount = require('./notification-modals/confirm-reset-account')
+
+const accountModalStyle = {
+ mobileModalStyle: {
+ width: '95%',
+ // top: isPopupOrNotification() === 'popup' ? '52vh' : '36.5vh',
+ boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px',
+ borderRadius: '4px',
+ top: '10%',
+ transform: 'none',
+ left: '0',
+ right: '0',
+ margin: '0 auto',
+ },
+ laptopModalStyle: {
+ width: '360px',
+ // top: 'calc(33% + 45px)',
+ boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px',
+ borderRadius: '4px',
+ top: '10%',
+ transform: 'none',
+ left: '0',
+ right: '0',
+ margin: '0 auto',
+ },
+ contentStyle: {
+ borderRadius: '4px',
+ },
+}
+
+const MODALS = {
+ BUY: {
+ contents: [
+ h(BuyOptions, {}, []),
+ ],
+ mobileModalStyle: {
+ width: '95%',
+ // top: isPopupOrNotification() === 'popup' ? '48vh' : '36.5vh',
+ transform: 'none',
+ left: '0',
+ right: '0',
+ margin: '0 auto',
+ boxShadow: '0 0 7px 0 rgba(0,0,0,0.08)',
+ top: '10%',
+ },
+ laptopModalStyle: {
+ width: '66%',
+ maxWidth: '550px',
+ top: 'calc(10% + 10px)',
+ left: '0',
+ right: '0',
+ margin: '0 auto',
+ boxShadow: '0 0 7px 0 rgba(0,0,0,0.08)',
+ transform: 'none',
+ },
+ },
+
+ DEPOSIT_ETHER: {
+ contents: [
+ h(DepositEtherModal, {}, []),
+ ],
+ mobileModalStyle: {
+ width: '100%',
+ height: '100%',
+ transform: 'none',
+ left: '0',
+ right: '0',
+ margin: '0 auto',
+ boxShadow: '0 0 7px 0 rgba(0,0,0,0.08)',
+ top: '0',
+ display: 'flex',
+ },
+ laptopModalStyle: {
+ width: '900px',
+ maxWidth: '900px',
+ top: 'calc(10% + 10px)',
+ left: '0',
+ right: '0',
+ margin: '0 auto',
+ boxShadow: '0 0 6px 0 rgba(0,0,0,0.3)',
+ borderRadius: '8px',
+ transform: 'none',
+ },
+ contentStyle: {
+ borderRadius: '8px',
+ },
+ },
+
+ EDIT_ACCOUNT_NAME: {
+ contents: [
+ h(EditAccountNameModal, {}, []),
+ ],
+ mobileModalStyle: {
+ width: '95%',
+ // top: isPopupOrNotification() === 'popup' ? '48vh' : '36.5vh',
+ top: '10%',
+ boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px',
+ transform: 'none',
+ left: '0',
+ right: '0',
+ margin: '0 auto',
+ },
+ laptopModalStyle: {
+ width: '375px',
+ // top: 'calc(30% + 10px)',
+ top: '10%',
+ boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px',
+ transform: 'none',
+ left: '0',
+ right: '0',
+ margin: '0 auto',
+ },
+ },
+
+ ACCOUNT_DETAILS: {
+ contents: [
+ h(AccountDetailsModal, {}, []),
+ ],
+ ...accountModalStyle,
+ },
+
+ EXPORT_PRIVATE_KEY: {
+ contents: [
+ h(ExportPrivateKeyModal, {}, []),
+ ],
+ ...accountModalStyle,
+ },
+
+ SHAPESHIFT_DEPOSIT_TX: {
+ contents: [
+ h(ShapeshiftDepositTxModal),
+ ],
+ ...accountModalStyle,
+ },
+
+ HIDE_TOKEN_CONFIRMATION: {
+ contents: [
+ h(HideTokenConfirmationModal, {}, []),
+ ],
+ mobileModalStyle: {
+ width: '95%',
+ top: isPopupOrNotification() === 'popup' ? '52vh' : '36.5vh',
+ },
+ laptopModalStyle: {
+ width: '449px',
+ top: 'calc(33% + 45px)',
+ },
+ },
+
+ BETA_UI_NOTIFICATION_MODAL: {
+ contents: [
+ h(NotifcationModal, {
+ header: 'Welcome to the New UI (Beta)',
+ message: `You are now using the new Metamask UI. Take a look around, try out new features like sending tokens,
+ and let us know if you have any issues.`,
+ }),
+ ],
+ mobileModalStyle: {
+ width: '95%',
+ top: isPopupOrNotification() === 'popup' ? '52vh' : '36.5vh',
+ },
+ laptopModalStyle: {
+ width: '449px',
+ top: 'calc(33% + 45px)',
+ },
+ },
+
+ OLD_UI_NOTIFICATION_MODAL: {
+ contents: [
+ h(NotifcationModal, {
+ header: 'Old UI',
+ message: `You have returned to the old UI. You can switch back to the New UI through the option in the top
+ right dropdown menu.`,
+ }),
+ ],
+ mobileModalStyle: {
+ width: '95%',
+ top: isPopupOrNotification() === 'popup' ? '52vh' : '36.5vh',
+ },
+ laptopModalStyle: {
+ width: '449px',
+ top: 'calc(33% + 45px)',
+ },
+ },
+
+ CONFIRM_RESET_ACCOUNT: {
+ contents: h(ConfirmResetAccount),
+ mobileModalStyle: {
+ width: '95%',
+ top: isPopupOrNotification() === 'popup' ? '52vh' : '36.5vh',
+ },
+ laptopModalStyle: {
+ width: '473px',
+ top: 'calc(33% + 45px)',
+ },
+ },
+
+ NEW_ACCOUNT: {
+ contents: [
+ h(NewAccountModal, {}, []),
+ ],
+ mobileModalStyle: {
+ width: '95%',
+ // top: isPopupOrNotification() === 'popup' ? '52vh' : '36.5vh',
+ top: '10%',
+ transform: 'none',
+ left: '0',
+ right: '0',
+ margin: '0 auto',
+ },
+ laptopModalStyle: {
+ width: '449px',
+ // top: 'calc(33% + 45px)',
+ top: '10%',
+ transform: 'none',
+ left: '0',
+ right: '0',
+ margin: '0 auto',
+ },
+ },
+
+ CUSTOMIZE_GAS: {
+ contents: [
+ h(CustomizeGasModal, {}, []),
+ ],
+ mobileModalStyle: {
+ width: '100vw',
+ height: '100vh',
+ top: '0',
+ transform: 'none',
+ left: '0',
+ right: '0',
+ margin: '0 auto',
+ },
+ laptopModalStyle: {
+ width: '720px',
+ height: '377px',
+ top: '80px',
+ transform: 'none',
+ left: '0',
+ right: '0',
+ margin: '0 auto',
+ },
+ },
+
+ DEFAULT: {
+ contents: [],
+ mobileModalStyle: {},
+ laptopModalStyle: {},
+ },
+}
+
+const BACKDROPSTYLE = {
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
+}
+
+function mapStateToProps (state) {
+ return {
+ active: state.appState.modal.open,
+ modalState: state.appState.modal.modalState,
+ }
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ hideModal: () => {
+ dispatch(actions.hideModal())
+ },
+ }
+}
+
+// Global Modal Component
+inherits(Modal, Component)
+function Modal () {
+ Component.call(this)
+}
+
+module.exports = connect(mapStateToProps, mapDispatchToProps)(Modal)
+
+Modal.prototype.render = function () {
+ const modal = MODALS[this.props.modalState.name || 'DEFAULT']
+
+ const children = modal.contents
+ const modalStyle = modal[isMobileView() ? 'mobileModalStyle' : 'laptopModalStyle']
+ const contentStyle = modal.contentStyle || {}
+
+ return h(FadeModal,
+ {
+ className: 'modal',
+ keyboard: false,
+ onHide: () => { this.onHide() },
+ ref: (ref) => {
+ this.modalRef = ref
+ },
+ modalStyle,
+ contentStyle,
+ backdropStyle: BACKDROPSTYLE,
+ },
+ children,
+ )
+}
+
+Modal.prototype.componentWillReceiveProps = function (nextProps) {
+ if (nextProps.active) {
+ this.show()
+ } else if (this.props.active) {
+ this.hide()
+ }
+}
+
+Modal.prototype.onHide = function () {
+ if (this.props.onHideCallback) {
+ this.props.onHideCallback()
+ }
+ this.props.hideModal()
+}
+
+Modal.prototype.hide = function () {
+ this.modalRef.hide()
+}
+
+Modal.prototype.show = function () {
+ this.modalRef.show()
+}
diff --git a/ui/app/components/modals/new-account-modal.js b/ui/app/components/modals/new-account-modal.js
new file mode 100644
index 000000000..fc1fd413d
--- /dev/null
+++ b/ui/app/components/modals/new-account-modal.js
@@ -0,0 +1,106 @@
+const { Component } = require('react')
+const PropTypes = require('prop-types')
+const h = require('react-hyperscript')
+const { connect } = require('react-redux')
+const actions = require('../../actions')
+
+class NewAccountModal extends Component {
+ constructor (props) {
+ super(props)
+ const { numberOfExistingAccounts = 0 } = props
+ const newAccountNumber = numberOfExistingAccounts + 1
+
+ this.state = {
+ newAccountName: `Account ${newAccountNumber}`,
+ }
+ }
+
+ render () {
+ const { newAccountName } = this.state
+
+ return h('div', [
+ h('div.new-account-modal-wrapper', {
+ }, [
+ h('div.new-account-modal-header', {}, [
+ 'New Account',
+ ]),
+
+ h('div.modal-close-x', {
+ onClick: this.props.hideModal,
+ }),
+
+ h('div.new-account-modal-content', {}, [
+ 'Account Name',
+ ]),
+
+ h('div.new-account-input-wrapper', {}, [
+ h('input.new-account-input', {
+ value: this.state.newAccountName,
+ placeholder: 'E.g. My new account',
+ onChange: event => this.setState({ newAccountName: event.target.value }),
+ }, []),
+ ]),
+
+ h('div.new-account-modal-content.after-input', {}, [
+ 'or',
+ ]),
+
+ h('div.new-account-modal-content.after-input.pointer', {
+ onClick: () => {
+ this.props.hideModal()
+ this.props.showImportPage()
+ },
+ }, 'Import an account'),
+
+ h('div.new-account-modal-content.button', {}, [
+ h('button.btn-clear', {
+ onClick: () => this.props.createAccount(newAccountName),
+ }, [
+ 'SAVE',
+ ]),
+ ]),
+ ]),
+ ])
+ }
+}
+
+NewAccountModal.propTypes = {
+ hideModal: PropTypes.func,
+ showImportPage: PropTypes.func,
+ createAccount: PropTypes.func,
+ numberOfExistingAccounts: PropTypes.number,
+}
+
+const mapStateToProps = state => {
+ const { metamask: { network, selectedAddress, identities = {} } } = state
+ const numberOfExistingAccounts = Object.keys(identities).length
+
+ return {
+ network,
+ address: selectedAddress,
+ numberOfExistingAccounts,
+ }
+}
+
+const mapDispatchToProps = dispatch => {
+ return {
+ toCoinbase: (address) => {
+ dispatch(actions.buyEth({ network: '1', address, amount: 0 }))
+ },
+ hideModal: () => {
+ dispatch(actions.hideModal())
+ },
+ createAccount: (newAccountName) => {
+ dispatch(actions.addNewAccount())
+ .then((newAccountAddress) => {
+ if (newAccountName) {
+ dispatch(actions.saveAccountLabel(newAccountAddress, newAccountName))
+ }
+ dispatch(actions.hideModal())
+ })
+ },
+ showImportPage: () => dispatch(actions.showImportPage()),
+ }
+}
+
+module.exports = connect(mapStateToProps, mapDispatchToProps)(NewAccountModal)
diff --git a/ui/app/components/modals/notification-modal.js b/ui/app/components/modals/notification-modal.js
new file mode 100644
index 000000000..621a974d0
--- /dev/null
+++ b/ui/app/components/modals/notification-modal.js
@@ -0,0 +1,75 @@
+const { Component } = require('react')
+const PropTypes = require('prop-types')
+const h = require('react-hyperscript')
+const { connect } = require('react-redux')
+const actions = require('../../actions')
+
+class NotificationModal extends Component {
+ render () {
+ const {
+ header,
+ message,
+ showCancelButton = false,
+ showConfirmButton = false,
+ hideModal,
+ onConfirm,
+ } = this.props
+
+ const showButtons = showCancelButton || showConfirmButton
+
+ return h('div', [
+ h('div.notification-modal__wrapper', {
+ }, [
+
+ h('div.notification-modal__header', {}, [
+ header,
+ ]),
+
+ h('div.notification-modal__message-wrapper', {}, [
+ h('div.notification-modal__message', {}, [
+ message,
+ ]),
+ ]),
+
+ h('div.modal-close-x', {
+ onClick: hideModal,
+ }),
+
+ showButtons && h('div.notification-modal__buttons', [
+
+ showCancelButton && h('div.btn-cancel.notification-modal__buttons__btn', {
+ onClick: hideModal,
+ }, 'Cancel'),
+
+ showConfirmButton && h('div.btn-clear.notification-modal__buttons__btn', {
+ onClick: () => {
+ onConfirm()
+ hideModal()
+ },
+ }, 'Confirm'),
+
+ ]),
+
+ ]),
+ ])
+ }
+}
+
+NotificationModal.propTypes = {
+ hideModal: PropTypes.func,
+ header: PropTypes.string,
+ message: PropTypes.node,
+ showCancelButton: PropTypes.bool,
+ showConfirmButton: PropTypes.bool,
+ onConfirm: PropTypes.func,
+}
+
+const mapDispatchToProps = dispatch => {
+ return {
+ hideModal: () => {
+ dispatch(actions.hideModal())
+ },
+ }
+}
+
+module.exports = connect(null, mapDispatchToProps)(NotificationModal)
diff --git a/ui/app/components/modals/notification-modals/confirm-reset-account.js b/ui/app/components/modals/notification-modals/confirm-reset-account.js
new file mode 100644
index 000000000..e1bc91b24
--- /dev/null
+++ b/ui/app/components/modals/notification-modals/confirm-reset-account.js
@@ -0,0 +1,46 @@
+const { Component } = require('react')
+const PropTypes = require('prop-types')
+const h = require('react-hyperscript')
+const { connect } = require('react-redux')
+const actions = require('../../../actions')
+const NotifcationModal = require('../notification-modal')
+
+class ConfirmResetAccount extends Component {
+ render () {
+ const { resetAccount } = this.props
+
+ return h(NotifcationModal, {
+ header: 'Are you sure you want to reset account?',
+ message: h('div', [
+
+ h('span', `Resetting is for developer use only. This button wipes the current account's transaction history,
+ which is used to calculate the current account nonce. `),
+
+ h('a.notification-modal__link', {
+ href: 'http://metamask.helpscoutdocs.com/article/36-resetting-an-account',
+ target: '_blank',
+ onClick (event) { global.platform.openWindow({ url: event.target.href }) },
+ }, 'Read more.'),
+
+ ]),
+ showCancelButton: true,
+ showConfirmButton: true,
+ onConfirm: resetAccount,
+
+ })
+ }
+}
+
+ConfirmResetAccount.propTypes = {
+ resetAccount: PropTypes.func,
+}
+
+const mapDispatchToProps = dispatch => {
+ return {
+ resetAccount: () => {
+ dispatch(actions.resetAccount())
+ },
+ }
+}
+
+module.exports = connect(null, mapDispatchToProps)(ConfirmResetAccount)
diff --git a/ui/app/components/modals/shapeshift-deposit-tx-modal.js b/ui/app/components/modals/shapeshift-deposit-tx-modal.js
new file mode 100644
index 000000000..24af5a0de
--- /dev/null
+++ b/ui/app/components/modals/shapeshift-deposit-tx-modal.js
@@ -0,0 +1,40 @@
+const Component = require('react').Component
+const h = require('react-hyperscript')
+const inherits = require('util').inherits
+const connect = require('react-redux').connect
+const actions = require('../../actions')
+const QrView = require('../qr-code')
+const AccountModalContainer = require('./account-modal-container')
+
+function mapStateToProps (state) {
+ return {
+ Qr: state.appState.modal.modalState.Qr,
+ }
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ hideModal: () => {
+ dispatch(actions.hideModal())
+ },
+ }
+}
+
+inherits(ShapeshiftDepositTxModal, Component)
+function ShapeshiftDepositTxModal () {
+ Component.call(this)
+
+}
+
+module.exports = connect(mapStateToProps, mapDispatchToProps)(ShapeshiftDepositTxModal)
+
+ShapeshiftDepositTxModal.prototype.render = function () {
+ const { Qr } = this.props
+
+ return h(AccountModalContainer, {
+ }, [
+ h('div', {}, [
+ h(QrView, {key: 'qr', Qr}),
+ ]),
+ ])
+}