1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-22 17:33:23 +01:00

Add react-router to allow use of the browser back button

This commit is contained in:
Alexander Tseung 2017-11-28 20:24:35 -08:00
parent 339eb7d1a6
commit e226b10a89
43 changed files with 2099 additions and 1502 deletions

View File

@ -3,7 +3,8 @@ module.exports = function isPopupOrNotification () {
// if (url.match(/popup.html$/) || url.match(/home.html$/)) {
// Below regexes needed for feature toggles (e.g. see line ~340 in ui/app/app.js)
// Revert below regexes to above commented out regexes before merge to master
if (url.match(/popup.html(?:\?.+)*$/) || url.match(/home.html(?:\?.+)*$/)) {
if (url.match(/popup.html(?:\?.+)*$/) ||
url.match(/home.html(?:\?.+)*$/) || url.match(/home.html(?:#.*)*$/)) {
return 'popup'
} else {
return 'notification'

View File

@ -145,6 +145,7 @@
"react-hyperscript": "^3.0.0",
"react-markdown": "^3.0.0",
"react-redux": "^5.0.5",
"react-router-dom": "^4.2.2",
"react-select": "^1.0.0",
"react-simple-file-input": "^2.0.0",
"react-toggle-button": "^2.2.0",

View File

@ -71,8 +71,8 @@ AccountDetailScreen.prototype.tabSections = function () {
{ content: 'Sent', key: 'history' },
{ content: 'Tokens', key: 'tokens' },
],
defaultTab: currentAccountTab || 'history',
tabSelected: (key) => {
selectedTab: currentAccountTab || 'history',
onSelect: key => {
this.props.dispatch(actions.setCurrentAccountTab(key))
},
}),

View File

@ -266,15 +266,19 @@ function tryUnlockMetamask (password) {
dispatch(actions.showLoadingIndication())
dispatch(actions.unlockInProgress())
log.debug(`background.submitPassword`)
background.submitPassword(password, (err) => {
return new Promise((resolve, reject) => {
background.submitPassword(password, err => {
dispatch(actions.hideLoadingIndication())
if (err) {
dispatch(actions.unlockFailed(err.message))
reject(err)
} else {
dispatch(actions.transitionForward())
forceUpdateMetamaskState(dispatch)
return forceUpdateMetamaskState(dispatch).then(resolve)
}
})
})
}
}
@ -291,7 +295,7 @@ function transitionBackward () {
}
function confirmSeedWords () {
return (dispatch) => {
return dispatch => {
dispatch(actions.showLoadingIndication())
log.debug(`background.clearSeedWordCache`)
return new Promise((resolve, reject) => {
@ -299,7 +303,7 @@ function confirmSeedWords () {
dispatch(actions.hideLoadingIndication())
if (err) {
dispatch(actions.displayWarning(err.message))
reject(err)
return reject(err)
}
log.info('Seed word cache cleared. ' + account)
@ -344,14 +348,14 @@ function createNewVaultAndKeychain (password) {
return reject(err)
}
log.debug(`background.placeSeedWords`)
background.placeSeedWords((err) => {
if (err) {
dispatch(actions.displayWarning(err.message))
return reject(err)
}
dispatch(actions.hideLoadingIndication())
forceUpdateMetamaskState(dispatch)
resolve()
forceUpdateMetamaskState(dispatch).then(resolve)
})
})
})
@ -696,16 +700,23 @@ function updateAndApproveTx (txData) {
log.info('actions: updateAndApproveTx: ' + JSON.stringify(txData))
return (dispatch) => {
log.debug(`actions calling background.updateAndApproveTx`)
background.updateAndApproveTransaction(txData, (err) => {
return new Promise((resolve, reject) => {
background.updateAndApproveTransaction(txData, err => {
dispatch(actions.hideLoadingIndication())
dispatch(actions.updateTransactionParams(txData.id, txData.txParams))
dispatch(actions.clearSend())
if (err) {
dispatch(actions.txError(err))
dispatch(actions.goHome())
return log.error(err.message)
log.error(err.message)
reject(err)
}
dispatch(actions.completedTx(txData.id))
resolve(txData)
})
})
}
}
@ -751,10 +762,13 @@ function cancelTypedMsg (msgData) {
}
function cancelTx (txData) {
return (dispatch) => {
return dispatch => {
log.debug(`background.cancelTransaction`)
return new Promise((resolve, reject) => {
background.cancelTransaction(txData.id, () => {
dispatch(actions.completedTx(txData.id))
resolve(txData)
})
})
}
}
@ -1585,11 +1599,17 @@ function callBackgroundThenUpdate (method, ...args) {
function forceUpdateMetamaskState (dispatch) {
log.debug(`background.getState`)
return new Promise((resolve, reject) => {
background.getState((err, newState) => {
if (err) {
reject(err)
return dispatch(actions.displayWarning(err.message))
}
dispatch(actions.updateMetamaskState(newState))
resolve(newState)
})
})
}

View File

@ -1,6 +1,7 @@
const inherits = require('util').inherits
const Component = require('react').Component
const connect = require('react-redux').connect
const { Component } = require('react')
const { connect } = require('react-redux')
const { Switch, Route, Redirect, withRouter } = require('react-router-dom')
const { compose } = require('recompose')
const h = require('react-hyperscript')
const actions = require('./actions')
// mascara
@ -14,23 +15,25 @@ const MainContainer = require('./main-container')
const SendTransactionScreen2 = require('./components/send/send-v2-container')
const ConfirmTxScreen = require('./conf-tx')
// notice
const NoticeScreen = require('./components/notice')
const generateLostAccountsNotice = require('../lib/lost-accounts-notice')
// slideout menu
const WalletView = require('./components/wallet-view')
// other views
const Settings = require('./settings')
const AddTokenScreen = require('./add-token')
const Import = require('./accounts/import')
const Authenticated = require('./components/pages/authenticated')
const Settings = require('./components/pages/settings')
const UnlockPage = require('./components/pages/unauthenticated/unlock')
const RestoreVaultPage = require('./components/pages/keychains/restore-vault')
const RevealSeedPage = require('./components/pages/keychains/reveal-seed')
const AddTokenPage = require('./components/pages/add-token')
const ImportAccountPage = require('./components/pages/import-account')
const NoticeScreen = require('./components/pages/notice')
const Loading = require('./components/loading')
const NetworkIndicator = require('./components/network')
const Identicon = require('./components/identicon')
const BuyView = require('./components/buy-button-subview')
const HDCreateVaultComplete = require('./keychains/hd/create-vault-complete')
const HDRestoreVaultScreen = require('./keychains/hd/restore-vault')
const RevealSeedConfirmation = require('./keychains/hd/recover-seed/confirmation')
const ReactCSSTransitionGroup = require('react-addons-css-transition-group')
const NetworkDropdown = require('./components/dropdowns/network-dropdown')
const AccountMenu = require('./components/account-menu')
@ -39,75 +42,57 @@ const QrView = require('./components/qr-code')
// Global Modals
const Modal = require('./components/modals/index').Modal
module.exports = connect(mapStateToProps, mapDispatchToProps)(App)
inherits(App, Component)
function App () { Component.call(this) }
function mapStateToProps (state) {
// Routes
const {
identities,
accounts,
address,
keyrings,
isInitialized,
noActiveNotices,
seedWords,
} = state.metamask
const selected = address || Object.keys(accounts)[0]
DEFAULT_ROUTE,
UNLOCK_ROUTE,
SETTINGS_ROUTE,
REVEAL_SEED_ROUTE,
RESTORE_VAULT_ROUTE,
ADD_TOKEN_ROUTE,
IMPORT_ACCOUNT_ROUTE,
SEND_ROUTE,
CONFIRM_TRANSACTION_ROUTE,
INITIALIZE_MENU_ROUTE,
NOTICE_ROUTE,
} = require('./routes')
return {
// state from plugin
networkDropdownOpen: state.appState.networkDropdownOpen,
sidebarOpen: state.appState.sidebarOpen,
isLoading: state.appState.isLoading,
loadingMessage: state.appState.loadingMessage,
noActiveNotices: state.metamask.noActiveNotices,
isInitialized: state.metamask.isInitialized,
isUnlocked: state.metamask.isUnlocked,
selectedAddress: state.metamask.selectedAddress,
currentView: state.appState.currentView,
activeAddress: state.appState.activeAddress,
transForward: state.appState.transForward,
isMascara: state.metamask.isMascara,
isOnboarding: Boolean(!noActiveNotices || seedWords || !isInitialized),
seedWords: state.metamask.seedWords,
unapprovedTxs: state.metamask.unapprovedTxs,
unapprovedMsgs: state.metamask.unapprovedMsgs,
menuOpen: state.appState.menuOpen,
network: state.metamask.network,
provider: state.metamask.provider,
forgottenPassword: state.appState.forgottenPassword,
lastUnreadNotice: state.metamask.lastUnreadNotice,
lostAccounts: state.metamask.lostAccounts,
frequentRpcList: state.metamask.frequentRpcList || [],
currentCurrency: state.metamask.currentCurrency,
class App extends Component {
constructor (props) {
super(props)
// state needed to get account dropdown temporarily rendering from app bar
identities,
selected,
keyrings,
this.renderPrimary = this.renderPrimary.bind(this)
}
componentWillMount () {
const { currentCurrency, setCurrentCurrency } = this.props
if (!currentCurrency) {
setCurrentCurrencyToUSD()
}
}
function mapDispatchToProps (dispatch, ownProps) {
return {
dispatch,
hideSidebar: () => dispatch(actions.hideSidebar()),
showNetworkDropdown: () => dispatch(actions.showNetworkDropdown()),
hideNetworkDropdown: () => dispatch(actions.hideNetworkDropdown()),
setCurrentCurrencyToUSD: () => dispatch(actions.setCurrentCurrency('usd')),
toggleAccountMenu: () => dispatch(actions.toggleAccountMenu()),
}
renderRoutes () {
const exact = true
return (
h(Switch, [
h(Route, { path: INITIALIZE_MENU_ROUTE, exact, component: InitializeMenuScreen }),
h(Route, { path: UNLOCK_ROUTE, exact, component: UnlockPage }),
h(Route, { path: SETTINGS_ROUTE, component: Settings }),
h(Route, { path: RESTORE_VAULT_ROUTE, exact, component: RestoreVaultPage }),
h(Route, { path: NOTICE_ROUTE, exact, component: NoticeScreen }),
h(Authenticated, { path: CONFIRM_TRANSACTION_ROUTE, exact, component: ConfirmTxScreen }),
h(Authenticated, { path: SEND_ROUTE, exact, component: SendTransactionScreen2 }),
h(Authenticated, { path: REVEAL_SEED_ROUTE, exact, component: RevealSeedPage }),
h(Authenticated, { path: ADD_TOKEN_ROUTE, exact, component: AddTokenPage }),
h(Authenticated, { path: IMPORT_ACCOUNT_ROUTE, exact, component: ImportAccountPage }),
h(Authenticated, { path: DEFAULT_ROUTE, exact, component: this.renderPrimary }),
])
)
}
App.prototype.componentWillMount = function () {
if (!this.props.currentCurrency) {
this.props.setCurrentCurrencyToUSD()
}
}
App.prototype.render = function () {
render () {
var props = this.props
const { isLoading, loadingMessage, network } = props
const isLoadingNetwork = network === 'loading' && props.currentView.name !== 'config'
@ -148,12 +133,13 @@ App.prototype.render = function () {
// this.renderLoadingIndicator({ isLoading, isLoadingNetwork, loadMessage }),
// content
this.renderPrimary(),
this.renderRoutes(),
// this.renderPrimary(),
])
)
}
App.prototype.renderGlobalModal = function () {
renderGlobalModal () {
return h(Modal, {
ref: 'modalRef',
}, [
@ -161,10 +147,8 @@ App.prototype.renderGlobalModal = function () {
])
}
App.prototype.renderSidebar = function () {
return h('div', {
}, [
renderSidebar () {
return h('div', [
h('style', `
.sidebar-enter {
transition: transform 300ms ease-in-out;
@ -208,7 +192,7 @@ App.prototype.renderSidebar = function () {
])
}
App.prototype.renderAppBar = function () {
renderAppBar () {
const {
isUnlocked,
network,
@ -217,22 +201,22 @@ App.prototype.renderAppBar = function () {
showNetworkDropdown,
hideNetworkDropdown,
currentView,
isMascara,
isOnboarding,
history,
} = this.props
if (window.METAMASK_UI_TYPE === 'notification') {
return null
}
const props = this.props
const {isMascara, isOnboarding} = props
// Do not render header if user is in mascara onboarding
if (isMascara && isOnboarding) {
return null
}
// Do not render header if user is in mascara buy ether
if (isMascara && props.currentView.name === 'buyEth') {
if (isMascara && currentView.name === 'buyEth') {
return null
}
@ -247,9 +231,7 @@ App.prototype.renderAppBar = function () {
}, [
h('div.app-header-contents', {}, [
h('div.left-menu-wrapper', {
onClick: () => {
props.dispatch(actions.backToAccountDetail(props.activeAddress))
},
onClick: () => history.push(DEFAULT_ROUTE),
}, [
// mini logo
h('img.metafox-icon', {
@ -298,13 +280,11 @@ App.prototype.renderAppBar = function () {
]),
]),
]),
])
)
}
App.prototype.renderLoadingIndicator = function ({ isLoading, isLoadingNetwork, loadMessage }) {
renderLoadingIndicator ({ isLoading, isLoadingNetwork, loadMessage }) {
const { isMascara } = this.props
return isMascara
@ -315,134 +295,166 @@ App.prototype.renderLoadingIndicator = function ({ isLoading, isLoadingNetwork,
})
}
App.prototype.renderBackButton = function (style, justArrow = false) {
var props = this.props
renderBackButton (style, justArrow = false) {
const { dispatch } = this.props
return (
h('.flex-row', {
key: 'leftArrow',
style: style,
onClick: () => props.dispatch(actions.goBackToInitView()),
onClick: () => dispatch(actions.goBackToInitView()),
}, [
h('i.fa.fa-arrow-left.cursor-pointer'),
justArrow ? null : h('div.cursor-pointer', {
style: {
marginLeft: '3px',
},
onClick: () => props.dispatch(actions.goBackToInitView()),
onClick: () => dispatch(actions.goBackToInitView()),
}, 'BACK'),
])
)
}
App.prototype.renderPrimary = function () {
renderPrimary () {
log.debug('rendering primary')
var props = this.props
const {isMascara, isOnboarding} = props
const {
isMascara,
isOnboarding,
noActiveNotices,
lostAccounts,
isInitialized,
forgottenPassword,
currentView,
activeAddress,
unapprovedTxs = {},
} = this.props
if (isMascara && isOnboarding) {
return h(MascaraFirstTime)
}
// notices
if (!props.noActiveNotices) {
log.debug('rendering notice screen for unread notices.')
return h(NoticeScreen, {
notice: props.lastUnreadNotice,
key: 'NoticeScreen',
onConfirm: () => props.dispatch(actions.markNoticeRead(props.lastUnreadNotice)),
})
} else if (props.lostAccounts && props.lostAccounts.length > 0) {
log.debug('rendering notice screen for lost accounts view.')
return h(NoticeScreen, {
notice: generateLostAccountsNotice(props.lostAccounts),
key: 'LostAccountsNotice',
onConfirm: () => props.dispatch(actions.markAccountsFound()),
if (!noActiveNotices || (lostAccounts && lostAccounts.length > 0)) {
return h(Redirect, {
to: {
pathname: NOTICE_ROUTE,
},
})
}
if (props.seedWords) {
log.debug('rendering seed words')
return h(HDCreateVaultComplete, {key: 'HDCreateVaultComplete'})
// unapprovedTxs
if (Object.keys(unapprovedTxs).length) {
return h(Redirect, {
to: {
pathname: CONFIRM_TRANSACTION_ROUTE,
},
})
}
// if (!props.noActiveNotices) {
// log.debug('rendering notice screen for unread notices.')
// return h(NoticeScreen, {
// notice: props.lastUnreadNotice,
// key: 'NoticeScreen',
// onConfirm: () => props.dispatch(actions.markNoticeRead(props.lastUnreadNotice)),
// })
// } else if (props.lostAccounts && props.lostAccounts.length > 0) {
// log.debug('rendering notice screen for lost accounts view.')
// return h(NoticeScreen, {
// notice: generateLostAccountsNotice(props.lostAccounts),
// key: 'LostAccountsNotice',
// onConfirm: () => props.dispatch(actions.markAccountsFound()),
// })
// }
// if (props.seedWords) {
// log.debug('rendering seed words')
// return h(HDCreateVaultComplete, {key: 'HDCreateVaultComplete'})
// }
// show initialize screen
if (!props.isInitialized || props.forgottenPassword) {
if (!isInitialized || forgottenPassword) {
// show current view
log.debug('rendering an initialize screen')
switch (props.currentView.name) {
// switch (props.currentView.name) {
case 'restoreVault':
log.debug('rendering restore vault screen')
return h(HDRestoreVaultScreen, {key: 'HDRestoreVaultScreen'})
// case 'restoreVault':
// log.debug('rendering restore vault screen')
// return h(HDRestoreVaultScreen, {key: 'HDRestoreVaultScreen'})
default:
log.debug('rendering menu screen')
return h(InitializeMenuScreen, {key: 'menuScreenInit'})
}
// default:
// log.debug('rendering menu screen')
// return h(InitializeMenuScreen, {key: 'menuScreenInit'})
// }
}
// show unlock screen
if (!props.isUnlocked) {
return h(MainContainer, {
currentViewName: props.currentView.name,
isUnlocked: props.isUnlocked,
})
}
// // show unlock screen
// if (!props.isUnlocked) {
// return h(MainContainer, {
// currentViewName: props.currentView.name,
// isUnlocked: props.isUnlocked,
// })
// }
// show current view
switch (props.currentView.name) {
switch (currentView.name) {
case 'accountDetail':
log.debug('rendering main container')
return h(MainContainer, {key: 'account-detail'})
case 'sendTransaction':
log.debug('rendering send tx screen')
// case 'sendTransaction':
// log.debug('rendering send tx screen')
// Going to leave this here until we are ready to delete SendTransactionScreen v1
// const SendComponentToRender = checkFeatureToggle('send-v2')
// ? SendTransactionScreen2
// : SendTransactionScreen
// // Going to leave this here until we are ready to delete SendTransactionScreen v1
// // const SendComponentToRender = checkFeatureToggle('send-v2')
// // ? SendTransactionScreen2
// // : SendTransactionScreen
return h(SendTransactionScreen2, {key: 'send-transaction'})
// return h(SendTransactionScreen2, {key: 'send-transaction'})
case 'sendToken':
log.debug('rendering send token screen')
// case 'sendToken':
// log.debug('rendering send token screen')
// Going to leave this here until we are ready to delete SendTransactionScreen v1
// const SendTokenComponentToRender = checkFeatureToggle('send-v2')
// ? SendTransactionScreen2
// : SendTokenScreen
// // Going to leave this here until we are ready to delete SendTransactionScreen v1
// // const SendTokenComponentToRender = checkFeatureToggle('send-v2')
// // ? SendTransactionScreen2
// // : SendTokenScreen
return h(SendTransactionScreen2, {key: 'sendToken'})
// return h(SendTransactionScreen2, {key: 'sendToken'})
case 'newKeychain':
log.debug('rendering new keychain screen')
return h(NewKeyChainScreen, {key: 'new-keychain'})
case 'confTx':
log.debug('rendering confirm tx screen')
return h(ConfirmTxScreen, {key: 'confirm-tx'})
// case 'confTx':
// log.debug('rendering confirm tx screen')
// return h(Redirect, {
// to: {
// pathname: CONFIRM_TRANSACTION_ROUTE,
// },
// })
// return h(ConfirmTxScreen, {key: 'confirm-tx'})
case 'add-token':
log.debug('rendering add-token screen from unlock screen.')
return h(AddTokenScreen, {key: 'add-token'})
// case 'add-token':
// log.debug('rendering add-token screen from unlock screen.')
// return h(AddTokenScreen, {key: 'add-token'})
case 'config':
log.debug('rendering config screen')
return h(Settings, {key: 'config'})
// case 'config':
// log.debug('rendering config screen')
// return h(Settings, {key: 'config'})
case 'import-menu':
log.debug('rendering import screen')
return h(Import, {key: 'import-menu'})
// case 'import-menu':
// log.debug('rendering import screen')
// return h(Import, {key: 'import-menu'})
case 'reveal-seed-conf':
log.debug('rendering reveal seed confirmation screen')
return h(RevealSeedConfirmation, {key: 'reveal-seed-conf'})
// case 'reveal-seed-conf':
// log.debug('rendering reveal seed confirmation screen')
// return h(RevealSeedConfirmation, {key: 'reveal-seed-conf'})
case 'info':
log.debug('rendering info screen')
return h(Settings, {key: 'info', tab: 'info'})
// case 'info':
// log.debug('rendering info screen')
// return h(Settings, {key: 'info', tab: 'info'})
case 'buyEth':
log.debug('rendering buy ether screen')
@ -463,7 +475,7 @@ App.prototype.renderPrimary = function () {
},
}, [
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer.color-orange', {
onClick: () => props.dispatch(actions.backToAccountDetail(props.activeAddress)),
onClick: () => this.props.dispatch(actions.backToAccountDetail(activeAddress)),
style: {
marginLeft: '10px',
marginTop: '50px',
@ -486,7 +498,7 @@ App.prototype.renderPrimary = function () {
}
}
App.prototype.toggleMetamaskActive = function () {
toggleMetamaskActive () {
if (!this.props.isUnlocked) {
// currently inactive: redirect to password box
var passwordBox = document.querySelector('input[type=password]')
@ -498,7 +510,7 @@ App.prototype.toggleMetamaskActive = function () {
}
}
App.prototype.getNetworkName = function () {
getNetworkName () {
const { provider } = this.props
const providerName = provider.type
@ -518,3 +530,77 @@ App.prototype.getNetworkName = function () {
return name
}
}
function mapStateToProps (state) {
const { appState, metamask } = state
const {
networkDropdownOpen,
sidebarOpen,
isLoading,
loadingMessage,
} = appState
const {
identities,
accounts,
address,
keyrings,
isInitialized,
noActiveNotices,
seedWords,
unapprovedTxs,
lastUnreadNotice,
lostAccounts,
} = metamask
const selected = address || Object.keys(accounts)[0]
return {
// state from plugin
networkDropdownOpen,
sidebarOpen,
isLoading,
loadingMessage,
noActiveNotices,
isInitialized,
isUnlocked: state.metamask.isUnlocked,
selectedAddress: state.metamask.selectedAddress,
currentView: state.appState.currentView,
activeAddress: state.appState.activeAddress,
transForward: state.appState.transForward,
isMascara: state.metamask.isMascara,
isOnboarding: Boolean(!noActiveNotices || seedWords || !isInitialized),
seedWords: state.metamask.seedWords,
unapprovedTxs,
unapprovedMsgs: state.metamask.unapprovedMsgs,
menuOpen: state.appState.menuOpen,
network: state.metamask.network,
provider: state.metamask.provider,
forgottenPassword: state.appState.forgottenPassword,
lastUnreadNotice,
lostAccounts,
frequentRpcList: state.metamask.frequentRpcList || [],
currentCurrency: state.metamask.currentCurrency,
// state needed to get account dropdown temporarily rendering from app bar
identities,
selected,
keyrings,
}
}
function mapDispatchToProps (dispatch, ownProps) {
return {
dispatch,
hideSidebar: () => dispatch(actions.hideSidebar()),
showNetworkDropdown: () => dispatch(actions.showNetworkDropdown()),
hideNetworkDropdown: () => dispatch(actions.hideNetworkDropdown()),
setCurrentCurrencyToUSD: () => dispatch(actions.setCurrentCurrency('usd')),
toggleAccountMenu: () => dispatch(actions.toggleAccountMenu()),
}
}
module.exports = compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(App)

View File

@ -1,13 +1,19 @@
const inherits = require('util').inherits
const Component = require('react').Component
const connect = require('react-redux').connect
const { compose } = require('recompose')
const { withRouter } = require('react-router-dom')
const h = require('react-hyperscript')
const actions = require('../../actions')
const { Menu, Item, Divider, CloseArea } = require('../dropdowns/components/menu')
const Identicon = require('../identicon')
const { formatBalance } = require('../../util')
const { SETTINGS_ROUTE, INFO_ROUTE, IMPORT_ACCOUNT_ROUTE } = require('../../routes')
module.exports = connect(mapStateToProps, mapDispatchToProps)(AccountMenu)
module.exports = compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(AccountMenu)
inherits(AccountMenu, Component)
function AccountMenu () { Component.call(this) }
@ -19,7 +25,6 @@ function mapStateToProps (state) {
keyrings: state.metamask.keyrings,
identities: state.metamask.identities,
accounts: state.metamask.accounts,
}
}
@ -63,6 +68,7 @@ AccountMenu.prototype.render = function () {
lockMetamask,
showConfigPage,
showInfoPage,
history,
} = this.props
return h(Menu, { className: 'account-menu', isShowing: isAccountMenuOpen }, [
@ -84,18 +90,27 @@ AccountMenu.prototype.render = function () {
text: 'Create Account',
}),
h(Item, {
onClick: showImportPage,
onClick: () => {
toggleAccountMenu()
history.push(IMPORT_ACCOUNT_ROUTE)
},
icon: h('img', { src: 'images/import-account.svg' }),
text: 'Import Account',
}),
h(Divider),
h(Item, {
onClick: showInfoPage,
onClick: () => {
toggleAccountMenu()
history.push(INFO_ROUTE)
},
icon: h('img', { src: 'images/mm-info-icon.svg' }),
text: 'Info & Help',
}),
h(Item, {
onClick: showConfigPage,
onClick: () => {
toggleAccountMenu()
history.push(SETTINGS_ROUTE)
},
icon: h('img', { src: 'images/settings.svg' }),
text: 'Settings',
}),

View File

@ -1,132 +0,0 @@
const inherits = require('util').inherits
const Component = require('react').Component
const h = require('react-hyperscript')
const ReactMarkdown = require('react-markdown')
const linker = require('extension-link-enabler')
const findDOMNode = require('react-dom').findDOMNode
module.exports = Notice
inherits(Notice, Component)
function Notice () {
Component.call(this)
}
Notice.prototype.render = function () {
const { notice, onConfirm } = this.props
const { title, date, body } = notice
const state = this.state || { disclaimerDisabled: true }
const disabled = state.disclaimerDisabled
return (
h('.flex-column.flex-center.flex-grow', {
style: {
width: '100%',
},
}, [
h('h3.flex-center.text-transform-uppercase.terms-header', {
style: {
background: '#EBEBEB',
color: '#AEAEAE',
width: '100%',
fontSize: '20px',
textAlign: 'center',
padding: 6,
},
}, [
title,
]),
h('h5.flex-center.text-transform-uppercase.terms-header', {
style: {
background: '#EBEBEB',
color: '#AEAEAE',
marginBottom: 24,
width: '100%',
fontSize: '20px',
textAlign: 'center',
padding: 6,
},
}, [
date,
]),
h('style', `
.markdown {
overflow-x: hidden;
}
.markdown h1, .markdown h2, .markdown h3 {
margin: 10px 0;
font-weight: bold;
}
.markdown strong {
font-weight: bold;
}
.markdown em {
font-style: italic;
}
.markdown p {
margin: 10px 0;
}
.markdown a {
color: #df6b0e;
}
`),
h('div.markdown', {
onScroll: (e) => {
var object = e.currentTarget
if (object.offsetHeight + object.scrollTop + 100 >= object.scrollHeight) {
this.setState({disclaimerDisabled: false})
}
},
style: {
background: 'rgb(235, 235, 235)',
height: '310px',
padding: '6px',
width: '90%',
overflowY: 'scroll',
scroll: 'auto',
},
}, [
h(ReactMarkdown, {
className: 'notice-box',
source: body,
skipHtml: true,
}),
]),
h('button.primary', {
disabled,
onClick: () => {
this.setState({disclaimerDisabled: true})
onConfirm()
},
style: {
marginTop: '18px',
},
}, 'Accept'),
])
)
}
Notice.prototype.componentDidMount = function () {
// eslint-disable-next-line react/no-find-dom-node
var node = findDOMNode(this)
linker.setupListener(node)
if (document.getElementsByClassName('notice-box')[0].clientHeight < 310) {
this.setState({disclaimerDisabled: false})
}
}
Notice.prototype.componentWillUnmount = function () {
// eslint-disable-next-line react/no-find-dom-node
var node = findDOMNode(this)
linker.teardownListener(node)
}

View File

@ -5,8 +5,8 @@ const h = require('react-hyperscript')
const connect = require('react-redux').connect
const Fuse = require('fuse.js')
const contractMap = require('eth-contract-metadata')
const TokenBalance = require('./components/token-balance')
const Identicon = require('./components/identicon')
const TokenBalance = require('../../components/token-balance')
const Identicon = require('../../components/identicon')
const contractList = Object.entries(contractMap)
.map(([ _, tokenData]) => tokenData)
.filter(tokenData => Boolean(tokenData.erc20))
@ -19,10 +19,12 @@ const fuse = new Fuse(contractList, {
minMatchCharLength: 1,
keys: ['address', 'name', 'symbol'],
})
const actions = require('./actions')
// const actions = require('./actions')
const actions = require('../../actions')
const ethUtil = require('ethereumjs-util')
const { tokenInfoGetter } = require('./token-util')
const { tokenInfoGetter } = require('../../token-util')
const R = require('ramda')
const { DEFAULT_ROUTE } = require('../../routes')
const emptyAddr = '0x0000000000000000000000000000000000000000'
@ -258,7 +260,7 @@ AddTokenScreen.prototype.renderConfirmation = function () {
selectedTokens,
} = this.state
const { addTokens, goHome } = this.props
const { addTokens, history } = this.props
const customToken = {
address,
@ -296,7 +298,7 @@ AddTokenScreen.prototype.renderConfirmation = function () {
]),
h('div.add-token__buttons', [
h('button.btn-secondary', {
onClick: () => addTokens(tokens).then(goHome),
onClick: () => addTokens(tokens).then(() => history.push(DEFAULT_ROUTE)),
}, 'Add Tokens'),
h('button.btn-tertiary', {
onClick: () => this.setState({ isShowingConfirmation: false }),
@ -308,7 +310,7 @@ AddTokenScreen.prototype.renderConfirmation = function () {
AddTokenScreen.prototype.render = function () {
const { isCollapsed, errors, isShowingConfirmation } = this.state
const { goHome } = this.props
const { history } = this.props
return isShowingConfirmation
? this.renderConfirmation()
@ -349,7 +351,7 @@ AddTokenScreen.prototype.render = function () {
onClick: this.onNext,
}, 'Next'),
h('button.btn-tertiary', {
onClick: goHome,
onClick: () => history.goBack(),
}, 'Cancel'),
]),
])

View File

@ -0,0 +1,42 @@
const { connect } = require('react-redux')
const PropTypes = require('prop-types')
const { Redirect, Route } = require('react-router-dom')
const h = require('react-hyperscript')
const { UNLOCK_ROUTE, INITIALIZE_MENU_ROUTE } = require('../../routes')
const Authenticated = ({ component: Component, isUnlocked, isInitialized, ...props }) => {
const render = props => {
switch (true) {
case isUnlocked:
return h(Component, { ...props })
case !isInitialized:
return h(Redirect, { to: { pathname: INITIALIZE_MENU_ROUTE } })
default:
return h(Redirect, { to: { pathname: UNLOCK_ROUTE } })
}
}
return (
h(Route, {
...props,
render,
})
)
}
Authenticated.propTypes = {
component: PropTypes.func,
isUnlocked: PropTypes.bool,
isInitialized: PropTypes.bool,
}
const mapStateToProps = state => {
const { metamask: { isUnlocked, isInitialized } } = state
return {
isUnlocked,
isInitialized,
}
}
module.exports = connect(mapStateToProps)(Authenticated)

View File

@ -0,0 +1,95 @@
const Component = require('react').Component
const h = require('react-hyperscript')
const PropTypes = require('prop-types')
import Select from 'react-select'
// Subviews
const JsonImportView = require('./json.js')
const PrivateKeyImportView = require('./private-key.js')
const PRIVATE_KEY_MENU_ITEM = 'Private Key'
const JSON_FILE_MENU_ITEM = 'JSON File'
class ImportAccount extends Component {
constructor (props) {
super(props)
this.state = {
current: PRIVATE_KEY_MENU_ITEM,
menuItems: [ PRIVATE_KEY_MENU_ITEM, JSON_FILE_MENU_ITEM ],
}
}
renderImportView () {
const { current } = this.state
switch (current) {
case 'Private Key':
return h(PrivateKeyImportView)
case 'JSON File':
return h(JsonImportView)
default:
return h(JsonImportView)
}
}
render () {
const { history } = this.props
const { current, menuItems } = this.state
return (
h('div.flex-center', {
style: {
flexDirection: 'column',
marginTop: '32px',
},
}, [
h('.section-title.flex-row.flex-center', [
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', {
onClick: history.goBack,
}),
h('h2.page-subtitle', 'Import Accounts'),
]),
h('div', {
style: {
padding: '10px 0',
width: '260px',
color: 'rgb(174, 174, 174)',
},
}, [
h('h3', { style: { padding: '3px' } }, 'SELECT TYPE'),
h('style', `
.has-value.Select--single > .Select-control .Select-value .Select-value-label, .Select-value-label {
color: rgb(174,174,174);
}
`),
h(Select, {
name: 'import-type-select',
clearable: false,
value: current,
options: menuItems.map(type => {
return {
value: type,
label: type,
}
}),
onChange: opt => {
this.setState({ current: opt.value })
},
}),
]),
this.renderImportView(),
])
)
}
}
ImportAccount.propTypes = {
history: PropTypes.object,
}
module.exports = ImportAccount

View File

@ -2,7 +2,7 @@ const inherits = require('util').inherits
const Component = require('react').Component
const h = require('react-hyperscript')
const connect = require('react-redux').connect
const actions = require('../../actions')
const actions = require('../../../actions')
const FileInput = require('react-simple-file-input').default
const HELP_LINK = 'https://support.metamask.io/kb/article/7-importing-accounts'

View File

@ -2,7 +2,7 @@ const inherits = require('util').inherits
const Component = require('react').Component
const h = require('react-hyperscript')
const connect = require('react-redux').connect
const actions = require('../../actions')
const actions = require('../../../actions')
module.exports = connect(mapStateToProps)(PrivateKeyImportView)

View File

@ -0,0 +1,167 @@
const { withRouter } = require('react-router-dom')
const PropTypes = require('prop-types')
const { compose } = require('recompose')
const PersistentForm = require('../../../../lib/persistent-form')
const { connect } = require('react-redux')
const h = require('react-hyperscript')
const { createNewVaultAndRestore } = require('../../../actions')
const { DEFAULT_ROUTE } = require('../../../routes')
class RestoreVaultPage extends PersistentForm {
constructor (props) {
super(props)
this.state = {
error: null,
}
}
createOnEnter (event) {
if (event.key === 'Enter') {
this.createNewVaultAndRestore()
}
}
createNewVaultAndRestore () {
this.setState({ error: null })
// check password
var passwordBox = document.getElementById('password-box')
var password = passwordBox.value
var passwordConfirmBox = document.getElementById('password-box-confirm')
var passwordConfirm = passwordConfirmBox.value
if (password.length < 8) {
this.setState({ error: 'Password not long enough' })
return
}
if (password !== passwordConfirm) {
this.setState({ error: 'Passwords don\'t match' })
return
}
// check seed
var seedBox = document.querySelector('textarea.twelve-word-phrase')
var seed = seedBox.value.trim()
if (seed.split(' ').length !== 12) {
this.setState({ error: 'Seed phrases are 12 words long' })
return
}
// submit
this.props.createNewVaultAndRestore(password, seed)
.then(() => history.push(DEFAULT_ROUTE))
.catch(({ message }) => this.setState({ error: message }))
}
render () {
const { error } = this.state
const { history } = this.props
this.persistentFormParentId = 'restore-vault-form'
return (
h('.initialize-screen.flex-column.flex-center.flex-grow', [
h('h3.flex-center.text-transform-uppercase', {
style: {
background: '#EBEBEB',
color: '#AEAEAE',
marginBottom: 24,
width: '100%',
fontSize: '20px',
padding: 6,
},
}, [
'Restore Vault',
]),
// wallet seed entry
h('h3', 'Wallet Seed'),
h('textarea.twelve-word-phrase.letter-spacey', {
dataset: {
persistentFormId: 'wallet-seed',
},
placeholder: 'Enter your secret twelve word phrase here to restore your vault.',
}),
// password
h('input.large-input.letter-spacey', {
type: 'password',
id: 'password-box',
placeholder: 'New Password (min 8 chars)',
dataset: {
persistentFormId: 'password',
},
style: {
width: 260,
marginTop: 12,
},
}),
// confirm password
h('input.large-input.letter-spacey', {
type: 'password',
id: 'password-box-confirm',
placeholder: 'Confirm Password',
onKeyPress: this.createOnEnter.bind(this),
dataset: {
persistentFormId: 'password-confirmation',
},
style: {
width: 260,
marginTop: 16,
},
}),
error && (
h('span.error.in-progress-notification', error)
),
// submit
h('.flex-row.flex-space-between', {
style: {
marginTop: 30,
width: '50%',
},
}, [
// cancel
h('button.primary', { onClick: () => history.goBack() }, 'CANCEL'),
// submit
h('button.primary', {
onClick: this.createNewVaultAndRestore.bind(this),
}, 'OK'),
]),
])
)
}
}
RestoreVaultPage.propTypes = {
history: PropTypes.object,
}
const mapStateToProps = state => {
const { appState: { warning, forgottenPassword } } = state
return {
warning,
forgottenPassword,
}
}
const mapDispatchToProps = dispatch => {
return {
createNewVaultAndRestore: (password, seed) => {
return dispatch(createNewVaultAndRestore(password, seed))
},
}
}
module.exports = compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(RestoreVaultPage)

View File

@ -0,0 +1,192 @@
const { Component } = require('react')
const { connect } = require('react-redux')
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const { exportAsFile } = require('../../../util')
const { requestRevealSeed, confirmSeedWords } = require('../../../actions')
const { DEFAULT_ROUTE } = require('../../../routes')
class RevealSeedPage extends Component {
componentDidMount () {
document.getElementById('password-box').focus()
}
checkConfirmation (event) {
if (event.key === 'Enter') {
event.preventDefault()
this.revealSeedWords()
}
}
revealSeedWords () {
const password = document.getElementById('password-box').value
this.props.requestRevealSeed(password)
}
renderSeed () {
const { seedWords, confirmSeedWords, history } = this.props
return (
h('.initialize-screen.flex-column.flex-center.flex-grow', [
h('h3.flex-center.text-transform-uppercase', {
style: {
background: '#EBEBEB',
color: '#AEAEAE',
marginTop: 36,
marginBottom: 8,
width: '100%',
fontSize: '20px',
padding: 6,
},
}, [
'Vault Created',
]),
h('div', {
style: {
fontSize: '1em',
marginTop: '10px',
textAlign: 'center',
},
}, [
h('span.error', 'These 12 words are the only way to restore your MetaMask accounts.\nSave them somewhere safe and secret.'),
]),
h('textarea.twelve-word-phrase', {
readOnly: true,
value: seedWords,
}),
h('button.primary', {
onClick: () => confirmSeedWords().then(() => history.push(DEFAULT_ROUTE)),
style: {
margin: '24px',
fontSize: '0.9em',
marginBottom: '10px',
},
}, 'I\'ve copied it somewhere safe'),
h('button.primary', {
onClick: () => exportAsFile(`MetaMask Seed Words`, seedWords),
style: {
margin: '10px',
fontSize: '0.9em',
},
}, 'Save Seed Words As File'),
])
)
}
renderConfirmation () {
const { history, warning, inProgress } = this.props
return (
h('.initialize-screen.flex-column.flex-center.flex-grow', {
style: { maxWidth: '420px' },
}, [
h('h3.flex-center.text-transform-uppercase', {
style: {
background: '#EBEBEB',
color: '#AEAEAE',
marginBottom: 24,
width: '100%',
fontSize: '20px',
padding: 6,
},
}, [
'Reveal Seed Words',
]),
h('.div', {
style: {
display: 'flex',
flexDirection: 'column',
padding: '20px',
justifyContent: 'center',
},
}, [
h('h4', 'Do not recover your seed words in a public place! These words can be used to steal all your accounts.'),
// confirmation
h('input.large-input.letter-spacey', {
type: 'password',
id: 'password-box',
placeholder: 'Enter your password to confirm',
onKeyPress: this.checkConfirmation.bind(this),
style: {
width: 260,
marginTop: '12px',
},
}),
h('.flex-row.flex-start', {
style: {
marginTop: 30,
width: '50%',
},
}, [
// cancel
h('button.primary', {
onClick: () => history.goBack(),
}, 'CANCEL'),
// submit
h('button.primary', {
style: { marginLeft: '10px' },
onClick: this.revealSeedWords.bind(this),
}, 'OK'),
]),
warning && (
h('span.error', {
style: {
margin: '20px',
},
}, warning.split('-'))
),
inProgress && (
h('span.in-progress-notification', 'Generating Seed...')
),
]),
])
)
}
render () {
return this.props.seedWords
? this.renderSeed()
: this.renderConfirmation()
}
}
RevealSeedPage.propTypes = {
requestRevealSeed: PropTypes.func,
confirmSeedWords: PropTypes.func,
seedWords: PropTypes.string,
inProgress: PropTypes.bool,
history: PropTypes.object,
warning: PropTypes.string,
}
const mapStateToProps = state => {
const { appState: { warning }, metamask: { seedWords } } = state
return {
warning,
seedWords,
}
}
const mapDispatchToProps = dispatch => {
return {
requestRevealSeed: password => dispatch(requestRevealSeed(password)),
confirmSeedWords: () => dispatch(confirmSeedWords()),
}
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(RevealSeedPage)

View File

@ -0,0 +1,219 @@
const { Component } = require('react')
const h = require('react-hyperscript')
const { connect } = require('react-redux')
const PropTypes = require('prop-types')
const ReactMarkdown = require('react-markdown')
const linker = require('extension-link-enabler')
const generateLostAccountsNotice = require('../../../lib/lost-accounts-notice')
const findDOMNode = require('react-dom').findDOMNode
const actions = require('../../actions')
const { DEFAULT_ROUTE } = require('../../routes')
class Notice extends Component {
constructor (props) {
super(props)
this.state = {
disclaimerDisabled: true,
}
}
componentWillMount () {
if (!this.props.notice) {
this.props.history.push(DEFAULT_ROUTE)
}
}
componentDidMount () {
// eslint-disable-next-line react/no-find-dom-node
var node = findDOMNode(this)
linker.setupListener(node)
if (document.getElementsByClassName('notice-box')[0].clientHeight < 310) {
this.setState({ disclaimerDisabled: false })
}
}
componentWillReceiveProps (nextProps) {
if (!nextProps.notice) {
this.props.history.push(DEFAULT_ROUTE)
}
}
componentWillUnmount () {
// eslint-disable-next-line react/no-find-dom-node
var node = findDOMNode(this)
linker.teardownListener(node)
}
handleAccept () {
this.setState({ disclaimerDisabled: true })
this.props.onConfirm()
}
render () {
const { notice = {} } = this.props
const { title, date, body } = notice
// const state = this.state || { disclaimerDisabled: true }
const { disclaimerDisabled } = this.state
return (
h('.flex-column.flex-center.flex-grow', {
style: {
width: '100%',
},
}, [
h('h3.flex-center.text-transform-uppercase.terms-header', {
style: {
background: '#EBEBEB',
color: '#AEAEAE',
width: '100%',
fontSize: '20px',
textAlign: 'center',
padding: 6,
},
}, [
title,
]),
h('h5.flex-center.text-transform-uppercase.terms-header', {
style: {
background: '#EBEBEB',
color: '#AEAEAE',
marginBottom: 24,
width: '100%',
fontSize: '20px',
textAlign: 'center',
padding: 6,
},
}, [
date,
]),
h('style', `
.markdown {
overflow-x: hidden;
}
.markdown h1, .markdown h2, .markdown h3 {
margin: 10px 0;
font-weight: bold;
}
.markdown strong {
font-weight: bold;
}
.markdown em {
font-style: italic;
}
.markdown p {
margin: 10px 0;
}
.markdown a {
color: #df6b0e;
}
`),
h('div.markdown', {
onScroll: (e) => {
var object = e.currentTarget
if (object.offsetHeight + object.scrollTop + 100 >= object.scrollHeight) {
this.setState({ disclaimerDisabled: false })
}
},
style: {
background: 'rgb(235, 235, 235)',
height: '310px',
padding: '6px',
width: '90%',
overflowY: 'scroll',
scroll: 'auto',
},
}, [
h(ReactMarkdown, {
className: 'notice-box',
source: body,
skipHtml: true,
}),
]),
h('button.primary', {
disabled: disclaimerDisabled,
onClick: () => this.handleAccept(),
style: {
marginTop: '18px',
},
}, 'Accept'),
])
)
}
}
const mapStateToProps = state => {
const { metamask } = state
const { noActiveNotices, lastUnreadNotice, lostAccounts } = metamask
// if (!props.noActiveNotices) {
// log.debug('rendering notice screen for unread notices.')
// return h(NoticeScreen, {
// notice: props.lastUnreadNotice,
// key: 'NoticeScreen',
// onConfirm: () => props.dispatch(actions.markNoticeRead(props.lastUnreadNotice)),
// })
// } else if (props.lostAccounts && props.lostAccounts.length > 0) {
// log.debug('rendering notice screen for lost accounts view.')
// return h(NoticeScreen, {
// notice: generateLostAccountsNotice(props.lostAccounts),
// key: 'LostAccountsNotice',
// onConfirm: () => props.dispatch(actions.markAccountsFound()),
// })
// }
return {
noActiveNotices,
lastUnreadNotice,
lostAccounts,
}
}
Notice.propTypes = {
notice: PropTypes.object,
onConfirm: PropTypes.func,
history: PropTypes.object,
}
const mapDispatchToProps = dispatch => {
return {
markNoticeRead: lastUnreadNotice => dispatch(actions.markNoticeRead(lastUnreadNotice)),
markAccountsFound: () => dispatch(actions.markAccountsFound()),
}
}
const mergeProps = (stateProps, dispatchProps, ownProps) => {
const { noActiveNotices, lastUnreadNotice, lostAccounts } = stateProps
const { markNoticeRead, markAccountsFound } = dispatchProps
let notice
let onConfirm
if (!noActiveNotices) {
notice = lastUnreadNotice
onConfirm = () => markNoticeRead(lastUnreadNotice)
} else if (lostAccounts && lostAccounts.length > 0) {
notice = generateLostAccountsNotice(lostAccounts)
onConfirm = () => markAccountsFound()
}
return {
...stateProps,
...dispatchProps,
...ownProps,
notice,
onConfirm,
}
}
module.exports = connect(mapStateToProps, mapDispatchToProps, mergeProps)(Notice)

View File

@ -0,0 +1,59 @@
const { Component } = require('react')
const { Switch, Route, matchPath } = require('react-router-dom')
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const TabBar = require('../../tab-bar')
const Settings = require('./settings')
const Info = require('./info')
const { SETTINGS_ROUTE, INFO_ROUTE } = require('../../../routes')
class Config extends Component {
renderTabs () {
const { history, location } = this.props
return h('div.settings__tabs', [
h(TabBar, {
tabs: [
{ content: 'Settings', key: SETTINGS_ROUTE },
{ content: 'Info', key: INFO_ROUTE },
],
isActive: key => matchPath(location.pathname, { path: key, exact: true }),
onSelect: key => history.push(key),
}),
])
}
render () {
const { history } = this.props
return (
h('.main-container.settings', {}, [
h('.settings__header', [
h('div.settings__close-button', {
onClick: () => history.push('/'),
}),
this.renderTabs(),
]),
h(Switch, [
h(Route, {
exact: true,
path: INFO_ROUTE,
component: Info,
}),
h(Route, {
exact: true,
path: SETTINGS_ROUTE,
component: Settings,
}),
]),
])
)
}
}
Config.propTypes = {
location: PropTypes.object,
history: PropTypes.object,
}
module.exports = Config

View File

@ -0,0 +1,108 @@
const { Component } = require('react')
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
class Info extends Component {
renderLogo () {
return (
h('div.settings__info-logo-wrapper', [
h('img.settings__info-logo', { src: 'images/info-logo.png' }),
])
)
}
renderInfoLinks () {
return (
h('div.settings__content-item.settings__content-item--without-height', [
h('div.settings__info-link-header', 'Links'),
h('div.settings__info-link-item', [
h('a', {
href: 'https://metamask.io/privacy.html',
target: '_blank',
}, [
h('span.settings__info-link', 'Privacy Policy'),
]),
]),
h('div.settings__info-link-item', [
h('a', {
href: 'https://metamask.io/terms.html',
target: '_blank',
}, [
h('span.settings__info-link', 'Terms of Use'),
]),
]),
h('div.settings__info-link-item', [
h('a', {
href: 'https://metamask.io/attributions.html',
target: '_blank',
}, [
h('span.settings__info-link', 'Attributions'),
]),
]),
h('hr.settings__info-separator'),
h('div.settings__info-link-item', [
h('a', {
href: 'https://support.metamask.io',
target: '_blank',
}, [
h('span.settings__info-link', 'Visit our Support Center'),
]),
]),
h('div.settings__info-link-item', [
h('a', {
href: 'https://metamask.io/',
target: '_blank',
}, [
h('span.settings__info-link', 'Visit our web site'),
]),
]),
h('div.settings__info-link-item', [
h('a', {
target: '_blank',
href: 'mailto:help@metamask.io?subject=Feedback',
}, [
h('span.settings__info-link', 'Email us!'),
]),
]),
])
)
}
render () {
return (
h('div.settings__content', [
h('div.settings__content-row', [
h('div.settings__content-item.settings__content-item--without-height', [
this.renderLogo(),
h('div.settings__info-item', [
h('div.settings__info-version-header', 'MetaMask Version'),
h('div.settings__info-version-number', '4.0.0'),
]),
h('div.settings__info-item', [
h(
'div.settings__info-about',
'MetaMask is designed and built in California.'
),
]),
]),
this.renderInfoLinks(),
]),
])
)
}
}
Info.propTypes = {
tab: PropTypes.string,
metamask: PropTypes.object,
setCurrentCurrency: PropTypes.func,
setRpcTarget: PropTypes.func,
displayWarning: PropTypes.func,
revealSeedConfirmation: PropTypes.func,
warning: PropTypes.string,
goHome: PropTypes.func,
location: PropTypes.object,
history: PropTypes.object,
}
module.exports = Info

View File

@ -1,14 +1,16 @@
const { Component } = require('react')
const { withRouter } = require('react-router-dom')
const { compose } = require('recompose')
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const { connect } = require('react-redux')
const actions = require('./actions')
const infuraCurrencies = require('./infura-conversion.json')
const actions = require('../../../actions')
const infuraCurrencies = require('../../../infura-conversion.json')
const validUrl = require('valid-url')
const { exportAsFile } = require('./util')
const TabBar = require('./components/tab-bar')
const SimpleDropdown = require('./components/dropdowns/simple-dropdown')
const { exportAsFile } = require('../../../util')
const SimpleDropdown = require('../../dropdowns/simple-dropdown')
const ToggleButton = require('react-toggle-button')
const { REVEAL_SEED_ROUTE } = require('../../../routes')
const getInfuraCurrencyOptions = () => {
const sortedCurrencies = infuraCurrencies.objects.sort((a, b) => {
@ -28,30 +30,11 @@ class Settings extends Component {
constructor (props) {
super(props)
const { tab } = props
const activeTab = tab === 'info' ? 'info' : 'settings'
this.state = {
activeTab,
newRpc: '',
}
}
renderTabs () {
const { activeTab } = this.state
return h('div.settings__tabs', [
h(TabBar, {
tabs: [
{ content: 'Settings', key: 'settings' },
{ content: 'Info', key: 'info' },
],
defaultTab: activeTab,
tabSelected: key => this.setState({ activeTab: key }),
}),
])
}
renderBlockieOptIn () {
const { metamask: { useBlockie }, setUseBlockie } = this.props
@ -210,7 +193,7 @@ class Settings extends Component {
}
renderSeedWords () {
const { revealSeedConfirmation } = this.props
const { history } = this.props
return (
h('div.settings__content-row', [
@ -218,10 +201,7 @@ class Settings extends Component {
h('div.settings__content-item', [
h('div.settings__content-item-col', [
h('button.settings__clear-button.settings__clear-button--red', {
onClick (event) {
event.preventDefault()
revealSeedConfirmation()
},
onClick: () => history.push(REVEAL_SEED_ROUTE),
}, 'Reveal Seed Words'),
]),
]),
@ -249,7 +229,7 @@ class Settings extends Component {
)
}
renderSettingsContent () {
render () {
const { warning } = this.props
return (
@ -265,118 +245,9 @@ class Settings extends Component {
])
)
}
renderLogo () {
return (
h('div.settings__info-logo-wrapper', [
h('img.settings__info-logo', { src: 'images/info-logo.png' }),
])
)
}
renderInfoLinks () {
return (
h('div.settings__content-item.settings__content-item--without-height', [
h('div.settings__info-link-header', 'Links'),
h('div.settings__info-link-item', [
h('a', {
href: 'https://metamask.io/privacy.html',
target: '_blank',
}, [
h('span.settings__info-link', 'Privacy Policy'),
]),
]),
h('div.settings__info-link-item', [
h('a', {
href: 'https://metamask.io/terms.html',
target: '_blank',
}, [
h('span.settings__info-link', 'Terms of Use'),
]),
]),
h('div.settings__info-link-item', [
h('a', {
href: 'https://metamask.io/attributions.html',
target: '_blank',
}, [
h('span.settings__info-link', 'Attributions'),
]),
]),
h('hr.settings__info-separator'),
h('div.settings__info-link-item', [
h('a', {
href: 'https://support.metamask.io',
target: '_blank',
}, [
h('span.settings__info-link', 'Visit our Support Center'),
]),
]),
h('div.settings__info-link-item', [
h('a', {
href: 'https://metamask.io/',
target: '_blank',
}, [
h('span.settings__info-link', 'Visit our web site'),
]),
]),
h('div.settings__info-link-item', [
h('a', {
target: '_blank',
href: 'mailto:help@metamask.io?subject=Feedback',
}, [
h('span.settings__info-link', 'Email us!'),
]),
]),
])
)
}
renderInfoContent () {
return (
h('div.settings__content', [
h('div.settings__content-row', [
h('div.settings__content-item.settings__content-item--without-height', [
this.renderLogo(),
h('div.settings__info-item', [
h('div.settings__info-version-header', 'MetaMask Version'),
h('div.settings__info-version-number', '4.0.0'),
]),
h('div.settings__info-item', [
h(
'div.settings__info-about',
'MetaMask is designed and built in California.'
),
]),
]),
this.renderInfoLinks(),
]),
])
)
}
render () {
const { goHome } = this.props
const { activeTab } = this.state
return (
h('.main-container.settings', {}, [
h('.settings__header', [
h('div.settings__close-button', {
onClick: goHome,
}),
this.renderTabs(),
]),
activeTab === 'settings'
? this.renderSettingsContent()
: this.renderInfoContent(),
])
)
}
}
Settings.propTypes = {
tab: PropTypes.string,
metamask: PropTypes.object,
setUseBlockie: PropTypes.func,
setCurrentCurrency: PropTypes.func,
@ -385,7 +256,7 @@ Settings.propTypes = {
revealSeedConfirmation: PropTypes.func,
setFeatureFlagToBeta: PropTypes.func,
warning: PropTypes.string,
goHome: PropTypes.func,
history: PropTypes.object,
}
const mapStateToProps = state => {
@ -397,7 +268,6 @@ const mapStateToProps = state => {
const mapDispatchToProps = dispatch => {
return {
goHome: () => dispatch(actions.goHome()),
setCurrentCurrency: currency => dispatch(actions.setCurrentCurrency(currency)),
setRpcTarget: newRpc => dispatch(actions.setRpcTarget(newRpc)),
displayWarning: warning => dispatch(actions.displayWarning(warning)),
@ -407,4 +277,7 @@ const mapDispatchToProps = dispatch => {
}
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(Settings)
module.exports = compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(Settings)

View File

@ -0,0 +1,172 @@
const { Component } = require('react')
const PropTypes = require('prop-types')
const { connect } = require('react-redux')
const h = require('react-hyperscript')
const { Redirect, withRouter } = require('react-router-dom')
const { compose } = require('recompose')
const { tryUnlockMetamask, forgotPassword } = require('../../../actions')
const getCaretCoordinates = require('textarea-caret')
const EventEmitter = require('events').EventEmitter
const Mascot = require('../../mascot')
const { DEFAULT_ROUTE, RESTORE_VAULT_ROUTE } = require('../../../routes')
class UnlockScreen extends Component {
constructor (props) {
super(props)
this.state = {
error: null,
}
this.animationEventEmitter = new EventEmitter()
}
componentDidMount () {
const passwordBox = document.getElementById('password-box')
if (passwordBox) {
passwordBox.focus()
}
}
tryUnlockMetamask (password) {
const { tryUnlockMetamask, history } = this.props
tryUnlockMetamask(password)
.then(() => history.push(DEFAULT_ROUTE))
.catch(({ message }) => this.setState({ error: message }))
}
onSubmit (event) {
const input = document.getElementById('password-box')
const password = input.value
this.tryUnlockMetamask(password)
}
onKeyPress (event) {
if (event.key === 'Enter') {
this.submitPassword(event)
}
}
submitPassword (event) {
var element = event.target
var password = element.value
// reset input
element.value = ''
this.tryUnlockMetamask(password)
}
inputChanged (event) {
// tell mascot to look at page action
var element = event.target
var boundingRect = element.getBoundingClientRect()
var coordinates = getCaretCoordinates(element, element.selectionEnd)
this.animationEventEmitter.emit('point', {
x: boundingRect.left + coordinates.left - element.scrollLeft,
y: boundingRect.top + coordinates.top - element.scrollTop,
})
}
render () {
const { error } = this.state
const { isUnlocked, history } = this.props
if (isUnlocked) {
return (
h(Redirect, {
to: {
pathname: DEFAULT_ROUTE,
},
})
)
}
return (
h('.unlock-page.main-container', [
h('.flex-column', {
style: {
width: 'inherit',
},
}, [
h('.unlock-screen.flex-column.flex-center.flex-grow', [
h(Mascot, {
animationEventEmitter: this.animationEventEmitter,
}),
h('h1', {
style: {
fontSize: '1.4em',
textTransform: 'uppercase',
color: '#7F8082',
},
}, 'MetaMask'),
h('input.large-input', {
type: 'password',
id: 'password-box',
placeholder: 'enter password',
style: {
background: 'white',
},
onKeyPress: this.onKeyPress.bind(this),
onInput: this.inputChanged.bind(this),
}),
h('.error', {
style: {
display: error ? 'block' : 'none',
padding: '0 20px',
textAlign: 'center',
},
}, error),
h('button.primary.cursor-pointer', {
onClick: this.onSubmit.bind(this),
style: {
margin: 10,
},
}, 'Unlock'),
h('.flex-row.flex-center.flex-grow', [
h('p.pointer', {
onClick: () => history.push(RESTORE_VAULT_ROUTE),
style: {
fontSize: '0.8em',
color: 'rgb(247, 134, 28)',
textDecoration: 'underline',
},
}, 'Restore from seed phrase'),
]),
]),
]),
])
)
}
}
UnlockScreen.propTypes = {
forgotPassword: PropTypes.func,
tryUnlockMetamask: PropTypes.func,
history: PropTypes.object,
isUnlocked: PropTypes.bool,
}
const mapStateToProps = state => {
const { metamask: { isUnlocked } } = state
return {
isUnlocked,
}
}
const mapDispatchToProps = dispatch => {
return {
forgotPassword: () => dispatch(forgotPassword()),
tryUnlockMetamask: password => dispatch(tryUnlockMetamask(password)),
}
}
module.exports = compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(UnlockScreen)

View File

@ -1,5 +1,7 @@
const Component = require('react').Component
const { connect } = require('react-redux')
const { withRouter } = require('react-router-dom')
const { compose } = require('recompose')
const h = require('react-hyperscript')
const inherits = require('util').inherits
const actions = require('../../actions')
@ -11,8 +13,12 @@ const hexToBn = require('../../../../app/scripts/lib/hex-to-bn')
const { conversionUtil, addCurrencies } = require('../../conversion-util')
const { MIN_GAS_PRICE_HEX } = require('../send/send-constants')
const { SEND_ROUTE, DEFAULT_ROUTE } = require('../../routes')
module.exports = connect(mapStateToProps, mapDispatchToProps)(ConfirmSendEther)
module.exports = compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(ConfirmSendEther)
function mapStateToProps (state) {
const {
@ -43,6 +49,7 @@ function mapDispatchToProps (dispatch) {
to,
value: amount,
} = txParams
dispatch(actions.updateSend({
gasLimit,
gasPrice,
@ -52,7 +59,6 @@ function mapDispatchToProps (dispatch) {
errors: { to: null, amount: null },
editingTransactionId: id,
}))
dispatch(actions.showSendPage())
},
cancelTransaction: ({ id }) => dispatch(actions.cancelTx({ id })),
}
@ -177,8 +183,14 @@ ConfirmSendEther.prototype.getData = function () {
}
}
ConfirmSendEther.prototype.editTransaction = function (txMeta) {
const { editTransaction, history } = this.props
editTransaction(txMeta)
history.push(SEND_ROUTE)
}
ConfirmSendEther.prototype.render = function () {
const { editTransaction, currentCurrency, clearSend } = this.props
const { currentCurrency, clearSend } = this.props
const txMeta = this.gatherTxMeta()
const txParams = txMeta.txParams || {}
@ -220,7 +232,7 @@ ConfirmSendEther.prototype.render = function () {
h('div.confirm-screen-wrapper.flex-column.flex-grow', [
h('h3.flex-center.confirm-screen-header', [
h('button.confirm-screen-back-button', {
onClick: () => editTransaction(txMeta),
onClick: () => this.editTransaction(txMeta),
}, 'EDIT'),
h('div.confirm-screen-title', 'Confirm Transaction'),
h('div.confirm-screen-header-tip'),
@ -422,6 +434,7 @@ ConfirmSendEther.prototype.onSubmit = function (event) {
ConfirmSendEther.prototype.cancel = function (event, txMeta) {
event.preventDefault()
this.props.cancelTransaction(txMeta)
.then(() => this.props.history.push(DEFAULT_ROUTE))
}
ConfirmSendEther.prototype.checkValidity = function () {

View File

@ -1,5 +1,7 @@
const Component = require('react').Component
const { connect } = require('react-redux')
const { withRouter } = require('react-router-dom')
const { compose } = require('recompose')
const h = require('react-hyperscript')
const inherits = require('util').inherits
const ethAbi = require('ethereumjs-abi')
@ -27,8 +29,12 @@ const {
getSelectedAddress,
getSelectedTokenContract,
} = require('../../selectors')
const { SEND_ROUTE, DEFAULT_ROUTE } = require('../../routes')
module.exports = connect(mapStateToProps, mapDispatchToProps)(ConfirmSendToken)
module.exports = compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(ConfirmSendToken)
function mapStateToProps (state, ownProps) {
const { token: { symbol }, txData } = ownProps
@ -99,6 +105,12 @@ function ConfirmSendToken () {
this.onSubmit = this.onSubmit.bind(this)
}
ConfirmSendToken.prototype.editTransaction = function (txMeta) {
const { editTransaction, history } = this.props
editTransaction(txMeta)
history.push(SEND_ROUTE)
}
ConfirmSendToken.prototype.componentWillMount = function () {
const { tokenContract, selectedAddress } = this.props
tokenContract && tokenContract
@ -293,7 +305,6 @@ ConfirmSendToken.prototype.renderTotalPlusGas = function () {
}
ConfirmSendToken.prototype.render = function () {
const { editTransaction } = this.props
const txMeta = this.gatherTxMeta()
const {
from: {
@ -316,7 +327,7 @@ ConfirmSendToken.prototype.render = function () {
h('div.confirm-screen-wrapper.flex-column.flex-grow', [
h('h3.flex-center.confirm-screen-header', [
h('button.confirm-screen-back-button', {
onClick: () => editTransaction(txMeta),
onClick: () => this.editTransaction(txMeta),
}, 'EDIT'),
h('div.confirm-screen-title', 'Confirm Transaction'),
h('div.confirm-screen-header-tip'),
@ -416,6 +427,7 @@ ConfirmSendToken.prototype.onSubmit = function (event) {
ConfirmSendToken.prototype.cancel = function (event, txMeta) {
event.preventDefault()
this.props.cancelTransaction(txMeta)
.then(() => this.props.history.push(DEFAULT_ROUTE))
}
ConfirmSendToken.prototype.checkValidity = function () {

View File

@ -2,6 +2,8 @@ const connect = require('react-redux').connect
const actions = require('../../actions')
const abi = require('ethereumjs-abi')
const SendEther = require('../../send-v2')
const { withRouter } = require('react-router-dom')
const { compose } = require('recompose')
const {
accountsWithSendEtherInfoSelector,
@ -16,7 +18,10 @@ const {
getSelectedTokenContract,
} = require('../../selectors')
module.exports = connect(mapStateToProps, mapDispatchToProps)(SendEther)
module.exports = compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(SendEther)
function mapStateToProps (state) {
const fromAccounts = accountsWithSendEtherInfoSelector(state)

View File

@ -4,31 +4,17 @@ const PropTypes = require('react').PropTypes
const classnames = require('classnames')
class TabBar extends Component {
constructor (props) {
super(props)
const { defaultTab, tabs } = props
this.state = {
subview: defaultTab || tabs[0].key,
}
}
render () {
const { tabs = [], tabSelected } = this.props
const { subview } = this.state
const { tabs = [], onSelect, isActive } = this.props
return (
h('.tab-bar', {}, [
tabs.map((tab) => {
const { key, content } = tab
tabs.map(({ key, content }) => {
return h('div', {
className: classnames('tab-bar__tab pointer', {
'tab-bar__tab--active': subview === key,
'tab-bar__tab--active': isActive(key, content),
}),
onClick: () => {
this.setState({ subview: key })
tabSelected(key)
},
onClick: () => onSelect(key),
key,
}, content)
}),
@ -39,9 +25,9 @@ class TabBar extends Component {
}
TabBar.propTypes = {
defaultTab: PropTypes.string,
isActive: PropTypes.func.isRequired,
tabs: PropTypes.array,
tabSelected: PropTypes.func,
onSelect: PropTypes.func,
}
module.exports = TabBar

View File

@ -10,8 +10,14 @@ const { formatDate } = require('../util')
const { showConfTxPage } = require('../actions')
const classnames = require('classnames')
const { tokenInfoGetter } = require('../token-util')
const { withRouter } = require('react-router-dom')
const { compose } = require('recompose')
const { CONFIRM_TRANSACTION_ROUTE } = require('../routes')
module.exports = connect(mapStateToProps, mapDispatchToProps)(TxList)
module.exports = compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(TxList)
function mapStateToProps (state) {
return {
@ -88,7 +94,7 @@ TxList.prototype.renderTransactionListItem = function (transaction, conversionRa
transactionHash,
transactionNetworkId,
} = props
const { showConfTxPage } = this.props
const { showConfTxPage, history } = this.props
const opts = {
key: transActionId || transactionHash,
@ -106,7 +112,7 @@ TxList.prototype.renderTransactionListItem = function (transaction, conversionRa
const isUnapproved = transactionStatus === 'unapproved'
if (isUnapproved) {
opts.onClick = () => showConfTxPage({id: transActionId})
opts.onClick = () => history.push(CONFIRM_TRANSACTION_ROUTE)
opts.transactionStatus = 'Not Started'
} else if (transactionHash) {
opts.onClick = () => this.view(transactionHash, transactionNetworkId)

View File

@ -3,14 +3,20 @@ const connect = require('react-redux').connect
const h = require('react-hyperscript')
const ethUtil = require('ethereumjs-util')
const inherits = require('util').inherits
const { withRouter } = require('react-router-dom')
const { compose } = require('recompose')
const actions = require('../actions')
const selectors = require('../selectors')
const { SEND_ROUTE } = require('../routes')
const BalanceComponent = require('./balance-component')
const TxList = require('./tx-list')
const Identicon = require('./identicon')
module.exports = connect(mapStateToProps, mapDispatchToProps)(TxView)
module.exports = compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(TxView)
function mapStateToProps (state) {
const sidebarOpen = state.appState.sidebarOpen
@ -63,7 +69,7 @@ TxView.prototype.renderHeroBalance = function () {
}
TxView.prototype.renderButtons = function () {
const {selectedToken, showModal, showSendPage, showSendTokenPage } = this.props
const {selectedToken, showModal, history } = this.props
return !selectedToken
? (
@ -82,7 +88,7 @@ TxView.prototype.renderButtons = function () {
textAlign: 'center',
marginLeft: '0.8em',
},
onClick: showSendPage,
onClick: () => history.push(SEND_ROUTE),
}, 'SEND'),
])
)
@ -93,7 +99,7 @@ TxView.prototype.renderButtons = function () {
textAlign: 'center',
marginLeft: '0.8em',
},
onClick: showSendTokenPage,
onClick: () => history.push(SEND_ROUTE),
}, 'SEND'),
])
)

View File

@ -1,6 +1,8 @@
const Component = require('react').Component
const connect = require('react-redux').connect
const h = require('react-hyperscript')
const { withRouter } = require('react-router-dom')
const { compose } = require('recompose')
const inherits = require('util').inherits
const Identicon = require('./identicon')
// const AccountDropdowns = require('./dropdowns/index.js').AccountDropdowns
@ -9,8 +11,12 @@ const actions = require('../actions')
const BalanceComponent = require('./balance-component')
const TokenList = require('./token-list')
const selectors = require('../selectors')
const { ADD_TOKEN_ROUTE } = require('../routes')
module.exports = connect(mapStateToProps, mapDispatchToProps)(WalletView)
module.exports = compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(WalletView)
function mapStateToProps (state) {
@ -89,6 +95,7 @@ WalletView.prototype.render = function () {
showAccountDetailModal,
hideSidebar,
showAddTokenPage,
history,
} = this.props
// temporary logs + fake extra wallets
// console.log('walletview, selectedAccount:', selectedAccount)
@ -152,10 +159,7 @@ WalletView.prototype.render = function () {
h(TokenList),
h('button.wallet-view__add-token-button', {
onClick: () => {
showAddTokenPage()
hideSidebar()
},
onClick: () => history.push(ADD_TOKEN_ROUTE),
}, 'Add Token'),
])
}

View File

@ -2,6 +2,8 @@ const inherits = require('util').inherits
const Component = require('react').Component
const h = require('react-hyperscript')
const connect = require('react-redux').connect
const { withRouter } = require('react-router-dom')
const { compose } = require('recompose')
const actions = require('./actions')
const txHelper = require('../lib/tx-helper')
@ -11,17 +13,12 @@ const SignatureRequest = require('./components/signature-request')
// const PendingPersonalMsg = require('./components/pending-personal-msg')
// const PendingTypedMsg = require('./components/pending-typed-msg')
const Loading = require('./components/loading')
const { DEFAULT_ROUTE } = require('./routes')
// const contentDivider = h('div', {
// style: {
// marginLeft: '16px',
// marginRight: '16px',
// height:'1px',
// background:'#E7E7E7',
// },
// })
module.exports = connect(mapStateToProps)(ConfirmTxScreen)
module.exports = compose(
withRouter,
connect(mapStateToProps)
)(ConfirmTxScreen)
function mapStateToProps (state) {
return {
@ -48,6 +45,20 @@ function ConfirmTxScreen () {
Component.call(this)
}
ConfirmTxScreen.prototype.componentWillMount = function () {
const { unapprovedTxs = {} } = this.props
if (Object.keys(unapprovedTxs).length === 0) {
this.props.history.push(DEFAULT_ROUTE)
}
}
ConfirmTxScreen.prototype.componentWillReceiveProps = function (nextProps) {
const { unapprovedTxs = {} } = nextProps
if (Object.keys(unapprovedTxs).length === 0) {
this.props.history.push(DEFAULT_ROUTE)
}
}
ConfirmTxScreen.prototype.render = function () {
const props = this.props
const {
@ -146,6 +157,7 @@ ConfirmTxScreen.prototype.buyEth = function (address, event) {
ConfirmTxScreen.prototype.sendTransaction = function (txData, event) {
this.stopPropagation(event)
this.props.dispatch(actions.updateAndApproveTx(txData))
.then(() => this.props.history.push(DEFAULT_ROUTE))
}
ConfirmTxScreen.prototype.cancelTransaction = function (txData, event) {

View File

@ -6,9 +6,15 @@
*/
@import './itcss/settings/index.scss';
@import './itcss/tools/index.scss';
@import './itcss/generic/index.scss';
@import './itcss/base/index.scss';
@import './itcss/objects/index.scss';
@import './itcss/components/index.scss';
@import './itcss/trumps/index.scss';

View File

@ -51,3 +51,5 @@
@import './account-dropdown-mini.scss';
@import './editable-label.scss';
@import './pages/index.scss';

View File

@ -0,0 +1 @@
@import './unlock.scss';

View File

@ -0,0 +1,9 @@
.unlock-page {
box-shadow: none;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: rgb(247, 247, 247);
width: 100%;
}

View File

@ -17,6 +17,12 @@ textarea.twelve-word-phrase {
resize: none;
}
.initialize-screen {
width: 100%;
z-index: $main-container-z-index;
background: #f7f7f7;
}
.initialize-screen hr {
width: 60px;
margin: 12px;

View File

@ -1,49 +1,40 @@
const inherits = require('util').inherits
const EventEmitter = require('events').EventEmitter
const Component = require('react').Component
const connect = require('react-redux').connect
const { EventEmitter } = require('events')
const { Component } = require('react')
const { connect } = require('react-redux')
const h = require('react-hyperscript')
const PropTypes = require('prop-types')
const Mascot = require('../components/mascot')
const actions = require('../actions')
const Tooltip = require('../components/tooltip')
const getCaretCoordinates = require('textarea-caret')
const { RESTORE_VAULT_ROUTE, DEFAULT_ROUTE } = require('../routes')
let isSubmitting = false
class InitializeMenuScreen extends Component {
constructor (props) {
super(props)
module.exports = connect(mapStateToProps)(InitializeMenuScreen)
inherits(InitializeMenuScreen, Component)
function InitializeMenuScreen () {
Component.call(this)
this.animationEventEmitter = new EventEmitter()
}
function mapStateToProps (state) {
return {
// state from plugin
currentView: state.appState.currentView,
warning: state.appState.warning,
this.state = {
warning: null,
}
}
InitializeMenuScreen.prototype.render = function () {
var state = this.props
switch (state.currentView.name) {
default:
return this.renderMenu(state)
componentWillMount () {
const { isInitialized, isUnlocked, history } = this.props
if (isInitialized || isUnlocked) {
history.push(DEFAULT_ROUTE)
}
}
// InitializeMenuScreen.prototype.componentDidMount = function(){
// document.getElementById('password-box').focus()
// }
componentDidMount () {
document.getElementById('password-box').focus()
}
render () {
const { history } = this.props
const { warning } = this.state
InitializeMenuScreen.prototype.renderMenu = function (state) {
return (
h('.initialize-screen.flex-column.flex-center.flex-grow', [
h(Mascot, {
@ -84,7 +75,7 @@ InitializeMenuScreen.prototype.renderMenu = function (state) {
]),
]),
h('span.in-progress-notification', state.warning),
h('span.error.in-progress-notification', warning),
// password
h('input.large-input.letter-spacey', {
@ -121,7 +112,7 @@ InitializeMenuScreen.prototype.renderMenu = function (state) {
h('.flex-row.flex-center.flex-grow', [
h('p.pointer', {
onClick: this.showRestoreVault.bind(this),
onClick: () => history.push(RESTORE_VAULT_ROUTE),
style: {
fontSize: '0.8em',
color: 'rgb(247, 134, 28)',
@ -134,45 +125,36 @@ InitializeMenuScreen.prototype.renderMenu = function (state) {
)
}
InitializeMenuScreen.prototype.createVaultOnEnter = function (event) {
createVaultOnEnter (event) {
if (event.key === 'Enter') {
event.preventDefault()
this.createNewVaultAndKeychain()
}
}
InitializeMenuScreen.prototype.componentDidMount = function () {
document.getElementById('password-box').focus()
}
InitializeMenuScreen.prototype.showRestoreVault = function () {
this.props.dispatch(actions.showRestoreVault())
}
InitializeMenuScreen.prototype.createNewVaultAndKeychain = function () {
createNewVaultAndKeychain () {
const { history } = this.props
var passwordBox = document.getElementById('password-box')
var password = passwordBox.value
var passwordConfirmBox = document.getElementById('password-box-confirm')
var passwordConfirm = passwordConfirmBox.value
this.setState({ warning: null })
if (password.length < 8) {
this.warning = 'password not long enough'
this.props.dispatch(actions.displayWarning(this.warning))
this.setState({ warning: 'password not long enough' })
return
}
if (password !== passwordConfirm) {
this.warning = 'passwords don\'t match'
this.props.dispatch(actions.displayWarning(this.warning))
this.setState({ warning: 'passwords don\'t match' })
return
}
if (!isSubmitting) {
isSubmitting = true
this.props.dispatch(actions.createNewVaultAndKeychain(password))
}
this.props.createNewVaultAndKeychain(password)
.then(() => history.push(DEFAULT_ROUTE))
}
InitializeMenuScreen.prototype.inputChanged = function (event) {
inputChanged (event) {
// tell mascot to look at page action
var element = event.target
var boundingRect = element.getBoundingClientRect()
@ -182,3 +164,28 @@ InitializeMenuScreen.prototype.inputChanged = function (event) {
y: boundingRect.top + coordinates.top - element.scrollTop,
})
}
}
InitializeMenuScreen.propTypes = {
history: PropTypes.object,
isInitialized: PropTypes.bool,
isUnlocked: PropTypes.bool,
createNewVaultAndKeychain: PropTypes.func,
}
const mapStateToProps = state => {
const { metamask: { isInitialized, isUnlocked } } = state
return {
isInitialized,
isUnlocked,
}
}
const mapDispatchToProps = dispatch => {
return {
createNewVaultAndKeychain: password => dispatch(actions.createNewVaultAndKeychain(password)),
}
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(InitializeMenuScreen)

View File

@ -1,91 +0,0 @@
const inherits = require('util').inherits
const Component = require('react').Component
const connect = require('react-redux').connect
const h = require('react-hyperscript')
const actions = require('../../actions')
const exportAsFile = require('../../util').exportAsFile
module.exports = connect(mapStateToProps)(CreateVaultCompleteScreen)
inherits(CreateVaultCompleteScreen, Component)
function CreateVaultCompleteScreen () {
Component.call(this)
}
function mapStateToProps (state) {
return {
seed: state.appState.currentView.seedWords,
cachedSeed: state.metamask.seedWords,
}
}
CreateVaultCompleteScreen.prototype.render = function () {
var state = this.props
var seed = state.seed || state.cachedSeed || ''
return (
h('.initialize-screen.flex-column.flex-center.flex-grow', [
// // subtitle and nav
// h('.section-title.flex-row.flex-center', [
// h('h2.page-subtitle', 'Vault Created'),
// ]),
h('h3.flex-center.text-transform-uppercase', {
style: {
background: '#EBEBEB',
color: '#AEAEAE',
marginTop: 36,
marginBottom: 8,
width: '100%',
fontSize: '20px',
padding: 6,
},
}, [
'Vault Created',
]),
h('div', {
style: {
fontSize: '1em',
marginTop: '10px',
textAlign: 'center',
},
}, [
h('span.error', 'These 12 words are the only way to restore your MetaMask accounts.\nSave them somewhere safe and secret.'),
]),
h('textarea.twelve-word-phrase', {
readOnly: true,
value: seed,
}),
h('button.primary', {
onClick: () => this.confirmSeedWords()
.then(account => this.showAccountDetail(account)),
style: {
margin: '24px',
fontSize: '0.9em',
marginBottom: '10px',
},
}, 'I\'ve copied it somewhere safe'),
h('button.primary', {
onClick: () => exportAsFile(`MetaMask Seed Words`, seed),
style: {
margin: '10px',
fontSize: '0.9em',
},
}, 'Save Seed Words As File'),
])
)
}
CreateVaultCompleteScreen.prototype.confirmSeedWords = function () {
return this.props.dispatch(actions.confirmSeedWords())
}
CreateVaultCompleteScreen.prototype.showAccountDetail = function (account) {
return this.props.dispatch(actions.showAccountDetail(account))
}

View File

@ -1,121 +0,0 @@
const inherits = require('util').inherits
const Component = require('react').Component
const connect = require('react-redux').connect
const h = require('react-hyperscript')
const actions = require('../../../actions')
module.exports = connect(mapStateToProps)(RevealSeedConfirmation)
inherits(RevealSeedConfirmation, Component)
function RevealSeedConfirmation () {
Component.call(this)
}
function mapStateToProps (state) {
return {
warning: state.appState.warning,
}
}
RevealSeedConfirmation.prototype.render = function () {
const props = this.props
return (
h('.initialize-screen.flex-column.flex-center.flex-grow', {
style: { maxWidth: '420px' },
}, [
h('h3.flex-center.text-transform-uppercase', {
style: {
background: '#EBEBEB',
color: '#AEAEAE',
marginBottom: 24,
width: '100%',
fontSize: '20px',
padding: 6,
},
}, [
'Reveal Seed Words',
]),
h('.div', {
style: {
display: 'flex',
flexDirection: 'column',
padding: '20px',
justifyContent: 'center',
},
}, [
h('h4', 'Do not recover your seed words in a public place! These words can be used to steal all your accounts.'),
// confirmation
h('input.large-input.letter-spacey', {
type: 'password',
id: 'password-box',
placeholder: 'Enter your password to confirm',
onKeyPress: this.checkConfirmation.bind(this),
style: {
width: 260,
marginTop: '12px',
},
}),
h('.flex-row.flex-start', {
style: {
marginTop: 30,
width: '50%',
},
}, [
// cancel
h('button.primary', {
onClick: this.goHome.bind(this),
}, 'CANCEL'),
// submit
h('button.primary', {
style: { marginLeft: '10px' },
onClick: this.revealSeedWords.bind(this),
}, 'OK'),
]),
(props.warning) && (
h('span.error', {
style: {
margin: '20px',
},
}, props.warning.split('-'))
),
props.inProgress && (
h('span.in-progress-notification', 'Generating Seed...')
),
]),
])
)
}
RevealSeedConfirmation.prototype.componentDidMount = function () {
document.getElementById('password-box').focus()
}
RevealSeedConfirmation.prototype.goHome = function () {
this.props.dispatch(actions.showConfigPage(false))
}
// create vault
RevealSeedConfirmation.prototype.checkConfirmation = function (event) {
if (event.key === 'Enter') {
event.preventDefault()
this.revealSeedWords()
}
}
RevealSeedConfirmation.prototype.revealSeedWords = function () {
var password = document.getElementById('password-box').value
this.props.dispatch(actions.requestRevealSeed(password))
}

View File

@ -1,152 +0,0 @@
const inherits = require('util').inherits
const PersistentForm = require('../../../lib/persistent-form')
const connect = require('react-redux').connect
const h = require('react-hyperscript')
const actions = require('../../actions')
module.exports = connect(mapStateToProps)(RestoreVaultScreen)
inherits(RestoreVaultScreen, PersistentForm)
function RestoreVaultScreen () {
PersistentForm.call(this)
}
function mapStateToProps (state) {
return {
warning: state.appState.warning,
forgottenPassword: state.appState.forgottenPassword,
}
}
RestoreVaultScreen.prototype.render = function () {
var state = this.props
this.persistentFormParentId = 'restore-vault-form'
return (
h('.initialize-screen.flex-column.flex-center.flex-grow', [
h('h3.flex-center.text-transform-uppercase', {
style: {
background: '#EBEBEB',
color: '#AEAEAE',
marginBottom: 24,
width: '100%',
fontSize: '20px',
padding: 6,
},
}, [
'Restore Vault',
]),
// wallet seed entry
h('h3', 'Wallet Seed'),
h('textarea.twelve-word-phrase.letter-spacey', {
dataset: {
persistentFormId: 'wallet-seed',
},
placeholder: 'Enter your secret twelve word phrase here to restore your vault.',
}),
// password
h('input.large-input.letter-spacey', {
type: 'password',
id: 'password-box',
placeholder: 'New Password (min 8 chars)',
dataset: {
persistentFormId: 'password',
},
style: {
width: 260,
marginTop: 12,
},
}),
// confirm password
h('input.large-input.letter-spacey', {
type: 'password',
id: 'password-box-confirm',
placeholder: 'Confirm Password',
onKeyPress: this.createOnEnter.bind(this),
dataset: {
persistentFormId: 'password-confirmation',
},
style: {
width: 260,
marginTop: 16,
},
}),
(state.warning) && (
h('span.error.in-progress-notification', state.warning)
),
// submit
h('.flex-row.flex-space-between', {
style: {
marginTop: 30,
width: '50%',
},
}, [
// cancel
h('button.primary', {
onClick: this.showInitializeMenu.bind(this),
}, 'CANCEL'),
// submit
h('button.primary', {
onClick: this.createNewVaultAndRestore.bind(this),
}, 'OK'),
]),
])
)
}
RestoreVaultScreen.prototype.showInitializeMenu = function () {
if (this.props.forgottenPassword) {
this.props.dispatch(actions.backToUnlockView())
} else {
this.props.dispatch(actions.showInitializeMenu())
}
}
RestoreVaultScreen.prototype.createOnEnter = function (event) {
if (event.key === 'Enter') {
this.createNewVaultAndRestore()
}
}
RestoreVaultScreen.prototype.createNewVaultAndRestore = function () {
// check password
var passwordBox = document.getElementById('password-box')
var password = passwordBox.value
var passwordConfirmBox = document.getElementById('password-box-confirm')
var passwordConfirm = passwordConfirmBox.value
if (password.length < 8) {
this.warning = 'Password not long enough'
this.props.dispatch(actions.displayWarning(this.warning))
return
}
if (password !== passwordConfirm) {
this.warning = 'Passwords don\'t match'
this.props.dispatch(actions.displayWarning(this.warning))
return
}
// check seed
var seedBox = document.querySelector('textarea.twelve-word-phrase')
var seed = seedBox.value.trim()
if (seed.split(' ').length !== 12) {
this.warning = 'seed phrases are 12 words long'
this.props.dispatch(actions.displayWarning(this.warning))
return
}
// submit
this.warning = null
this.props.dispatch(actions.displayWarning(this.warning))
this.props.dispatch(actions.createNewVaultAndRestore(password, seed))
}

View File

@ -2,9 +2,7 @@ const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
const AccountAndTransactionDetails = require('./account-and-transaction-details')
const HDRestoreVaultScreen = require('./keychains/hd/restore-vault')
const Settings = require('./settings')
const UnlockScreen = require('./unlock')
const UnlockScreen = require('./components/pages/unauthenticated/unlock')
module.exports = MainContainer
@ -26,36 +24,6 @@ MainContainer.prototype.render = function () {
style: {},
}
if (this.props.isUnlocked === false) {
switch (this.props.currentViewName) {
case 'restoreVault':
log.debug('rendering restore vault screen')
contents = {
component: HDRestoreVaultScreen,
key: 'HDRestoreVaultScreen',
}
break
case 'config':
log.debug('rendering config screen from unlock screen.')
return h(Settings, {key: 'config'})
default:
log.debug('rendering locked screen')
contents = {
component: UnlockScreen,
style: {
boxShadow: 'none',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: '#F7F7F7',
// must force 100%, because lock screen is full-width
width: '100%',
},
key: 'locked',
}
}
}
return h('div.main-container', {
style: contents.style,
}, [

View File

@ -1,22 +1,28 @@
const inherits = require('util').inherits
const Component = require('react').Component
const Provider = require('react-redux').Provider
const { Component } = require('react')
const PropTypes = require('prop-types')
const { Provider } = require('react-redux')
const h = require('react-hyperscript')
const SelectedApp = require('./select-app')
const { HashRouter } = require('react-router-dom')
module.exports = Root
class Root extends Component {
render () {
const { store } = this.props
inherits(Root, Component)
function Root () { Component.call(this) }
Root.prototype.render = function () {
return (
h(Provider, {
store: this.props.store,
h(Provider, { store }, [
h(HashRouter, {
hashType: 'noslash',
}, [
h(SelectedApp),
]),
])
)
}
}
Root.propTypes = {
store: PropTypes.object,
}
module.exports = Root

27
ui/app/routes.js Normal file
View File

@ -0,0 +1,27 @@
const DEFAULT_ROUTE = '/'
const UNLOCK_ROUTE = '/unlock'
const SETTINGS_ROUTE = '/settings'
const INFO_ROUTE = '/settings/info'
const REVEAL_SEED_ROUTE = '/reveal-seed-confirm'
const RESTORE_VAULT_ROUTE = '/restore-vault'
const ADD_TOKEN_ROUTE = '/add-token'
const IMPORT_ACCOUNT_ROUTE = '/import-account'
const SEND_ROUTE = '/send'
const CONFIRM_TRANSACTION_ROUTE = '/confirm-transaction'
const INITIALIZE_MENU_ROUTE = '/initialize-menu'
const NOTICE_ROUTE = '/notice'
module.exports = {
DEFAULT_ROUTE,
UNLOCK_ROUTE,
SETTINGS_ROUTE,
INFO_ROUTE,
REVEAL_SEED_ROUTE,
RESTORE_VAULT_ROUTE,
ADD_TOKEN_ROUTE,
IMPORT_ACCOUNT_ROUTE,
SEND_ROUTE,
CONFIRM_TRANSACTION_ROUTE,
INITIALIZE_MENU_ROUTE,
NOTICE_ROUTE,
}

View File

@ -28,6 +28,7 @@ const {
isTokenBalanceSufficient,
} = require('./components/send/send-utils')
const { isValidAddress } = require('./util')
const { CONFIRM_TRANSACTION_ROUTE } = require('./routes')
module.exports = SendTransactionScreen
@ -508,9 +509,9 @@ SendTransactionScreen.prototype.renderForm = function () {
SendTransactionScreen.prototype.renderFooter = function () {
const {
goHome,
clearSend,
errors: { amount: amountError, to: toError },
history,
} = this.props
const noErrors = !amountError && toError === null
@ -520,7 +521,7 @@ SendTransactionScreen.prototype.renderFooter = function () {
h('button.send-v2__cancel-btn', {
onClick: () => {
clearSend()
goHome()
history.goBack()
},
}, 'Cancel'),
h(`button.send-v2__next-btn${errorClass}`, {
@ -578,6 +579,7 @@ SendTransactionScreen.prototype.onSubmit = function (event) {
if (editingTransactionId) {
backToConfirmScreen(editingTransactionId)
this.props.history.push(CONFIRM_TRANSACTION_ROUTE)
return
}
@ -596,4 +598,6 @@ SendTransactionScreen.prototype.onSubmit = function (event) {
selectedToken
? signTokenTx(selectedToken.address, to, amount, txParams)
: signTx(txParams)
this.props.history.push(CONFIRM_TRANSACTION_ROUTE)
}

View File

@ -1,122 +0,0 @@
const inherits = require('util').inherits
const Component = require('react').Component
const h = require('react-hyperscript')
const connect = require('react-redux').connect
const actions = require('./actions')
const getCaretCoordinates = require('textarea-caret')
const EventEmitter = require('events').EventEmitter
const Mascot = require('./components/mascot')
module.exports = connect(mapStateToProps)(UnlockScreen)
inherits(UnlockScreen, Component)
function UnlockScreen () {
Component.call(this)
this.animationEventEmitter = new EventEmitter()
}
function mapStateToProps (state) {
return {
warning: state.appState.warning,
}
}
UnlockScreen.prototype.render = function () {
const state = this.props
const warning = state.warning
return (
h('.flex-column', {
style: {
width: 'inherit',
},
}, [
h('.unlock-screen.flex-column.flex-center.flex-grow', [
h(Mascot, {
animationEventEmitter: this.animationEventEmitter,
}),
h('h1', {
style: {
fontSize: '1.4em',
textTransform: 'uppercase',
color: '#7F8082',
},
}, 'MetaMask'),
h('input.large-input', {
type: 'password',
id: 'password-box',
placeholder: 'enter password',
style: {
background: 'white',
},
onKeyPress: this.onKeyPress.bind(this),
onInput: this.inputChanged.bind(this),
}),
h('.error', {
style: {
display: warning ? 'block' : 'none',
padding: '0 20px',
textAlign: 'center',
},
}, warning),
h('button.primary.cursor-pointer', {
onClick: this.onSubmit.bind(this),
style: {
margin: 10,
},
}, 'Unlock'),
]),
h('.flex-row.flex-center.flex-grow', [
h('p.pointer', {
onClick: () => this.props.dispatch(actions.forgotPassword()),
style: {
fontSize: '0.8em',
color: 'rgb(247, 134, 28)',
textDecoration: 'underline',
},
}, 'Restore from seed phrase'),
]),
])
)
}
UnlockScreen.prototype.componentDidMount = function () {
document.getElementById('password-box').focus()
}
UnlockScreen.prototype.onSubmit = function (event) {
const input = document.getElementById('password-box')
const password = input.value
this.props.dispatch(actions.tryUnlockMetamask(password))
}
UnlockScreen.prototype.onKeyPress = function (event) {
if (event.key === 'Enter') {
this.submitPassword(event)
}
}
UnlockScreen.prototype.submitPassword = function (event) {
var element = event.target
var password = element.value
// reset input
element.value = ''
this.props.dispatch(actions.tryUnlockMetamask(password))
}
UnlockScreen.prototype.inputChanged = function (event) {
// tell mascot to look at page action
var element = event.target
var boundingRect = element.getBoundingClientRect()
var coordinates = getCaretCoordinates(element, element.selectionEnd)
this.animationEventEmitter.emit('point', {
x: boundingRect.left + coordinates.left - element.scrollLeft,
y: boundingRect.top + coordinates.top - element.scrollTop,
})
}

View File

@ -20,8 +20,13 @@
through2 "^2.0.3"
"@types/node@*":
<<<<<<< HEAD
version "8.0.58"
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.58.tgz#5b3881c0be3a646874803fee3197ea7f1ed6df90"
=======
version "8.0.53"
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.53.tgz#396b35af826fa66aad472c8cb7b8d5e277f4e6d8"
>>>>>>> Add react-router to allow use of the browser back button
"@types/node@^6.0.46":
version "6.0.88"
@ -3155,6 +3160,7 @@ envify@^4.0.0:
enzyme-adapter-react-15@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/enzyme-adapter-react-15/-/enzyme-adapter-react-15-1.0.5.tgz#99f9a03ff2c2303e517342935798a6bdfbb75fac"
<<<<<<< HEAD
dependencies:
enzyme-adapter-utils "^1.1.0"
lodash "^4.17.4"
@ -3166,6 +3172,19 @@ enzyme-adapter-utils@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.2.0.tgz#7f4471ee0a70b91169ec8860d2bf0a6b551664b2"
dependencies:
=======
dependencies:
enzyme-adapter-utils "^1.1.0"
lodash "^4.17.4"
object.assign "^4.0.4"
object.values "^1.0.4"
prop-types "^15.5.10"
enzyme-adapter-utils@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.2.0.tgz#7f4471ee0a70b91169ec8860d2bf0a6b551664b2"
dependencies:
>>>>>>> Add react-router to allow use of the browser back button
lodash "^4.17.4"
object.assign "^4.0.4"
prop-types "^15.5.10"
@ -3813,11 +3832,19 @@ ethjs-query@^0.2.4, ethjs-query@^0.2.6, ethjs-query@^0.2.9:
ethjs-rpc "0.1.5"
ethjs-query@^0.3.1:
<<<<<<< HEAD
version "0.3.2"
resolved "https://registry.yarnpkg.com/ethjs-query/-/ethjs-query-0.3.2.tgz#f488a48ce1994cd4c77eccb7b52902c6f29cfd85"
dependencies:
ethjs-format "0.2.4"
ethjs-rpc "0.1.8"
=======
version "0.3.1"
resolved "https://registry.yarnpkg.com/ethjs-query/-/ethjs-query-0.3.1.tgz#aed5b60bdb7e73ad831d1218c8b067310013b86f"
dependencies:
ethjs-format "0.2.4"
ethjs-rpc "0.1.5"
>>>>>>> Add react-router to allow use of the browser back button
ethjs-rpc@0.1.5:
version "0.1.5"
@ -5044,6 +5071,16 @@ he@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
history@^4.7.2:
version "4.7.2"
resolved "https://registry.yarnpkg.com/history/-/history-4.7.2.tgz#22b5c7f31633c5b8021c7f4a8a954ac139ee8d5b"
dependencies:
invariant "^2.2.1"
loose-envify "^1.2.0"
resolve-pathname "^2.2.0"
value-equal "^0.4.0"
warning "^3.0.0"
hmac-drbg@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
@ -5060,7 +5097,7 @@ hoist-non-react-statics@^1.0.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz#aa448cf0986d55cc40773b17174b7dd066cb7cfb"
hoist-non-react-statics@^2.2.1:
hoist-non-react-statics@^2.2.1, hoist-non-react-statics@^2.3.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.3.1.tgz#343db84c6018c650778898240135a1420ee22ce0"
@ -5316,7 +5353,7 @@ interpret@^1.0.0:
version "1.0.4"
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.4.tgz#820cdd588b868ffb191a809506d6c9c8f212b1b0"
invariant@^2.0.0, invariant@^2.2.0, invariant@^2.2.2:
invariant@^2.0.0, invariant@^2.2.0, invariant@^2.2.1, invariant@^2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360"
dependencies:
@ -6435,7 +6472,7 @@ longest@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097"
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1:
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848"
dependencies:
@ -7919,7 +7956,11 @@ prop-types@^15.5.10, prop-types@^15.5.6, prop-types@^15.5.8:
fbjs "^0.8.9"
loose-envify "^1.3.1"
<<<<<<< HEAD
prop-types@^15.5.7, prop-types@^15.6.0:
=======
prop-types@^15.5.4, prop-types@^15.5.7, prop-types@^15.6.0:
>>>>>>> Add react-router to allow use of the browser back button
version "15.6.0"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856"
dependencies:
@ -8197,6 +8238,14 @@ react-motion@^0.5.2:
prop-types "^15.5.8"
raf "^3.1.0"
react-motion@^0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/react-motion/-/react-motion-0.5.2.tgz#0dd3a69e411316567927917c6626551ba0607316"
dependencies:
performance-now "^0.2.0"
prop-types "^15.5.8"
raf "^3.1.0"
react-redux@^5.0.5:
version "5.0.6"
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.0.6.tgz#23ed3a4f986359d68b5212eaaa681e60d6574946"
@ -8208,6 +8257,32 @@ react-redux@^5.0.5:
loose-envify "^1.1.0"
prop-types "^15.5.10"
<<<<<<< HEAD
=======
react-router-dom@^4.2.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.2.2.tgz#c8a81df3adc58bba8a76782e946cbd4eae649b8d"
dependencies:
history "^4.7.2"
invariant "^2.2.2"
loose-envify "^1.3.1"
prop-types "^15.5.4"
react-router "^4.2.0"
warning "^3.0.0"
react-router@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/react-router/-/react-router-4.2.0.tgz#61f7b3e3770daeb24062dae3eedef1b054155986"
dependencies:
history "^4.7.2"
hoist-non-react-statics "^2.3.0"
invariant "^2.2.2"
loose-envify "^1.3.1"
path-to-regexp "^1.7.0"
prop-types "^15.5.4"
warning "^3.0.0"
>>>>>>> Add react-router to allow use of the browser back button
react-select@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/react-select/-/react-select-1.1.0.tgz#626a2de839fdea2ade74dd1b143a9bde34be6c82"
@ -8668,6 +8743,10 @@ resolve-from@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748"
resolve-pathname@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-2.2.0.tgz#7e9ae21ed815fd63ab189adeee64dc831eefa879"
resolve-url@~0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
@ -10287,6 +10366,10 @@ validate-npm-package-license@^3.0.1:
spdx-correct "~1.0.0"
spdx-expression-parse "~1.0.0"
value-equal@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-0.4.0.tgz#c5bdd2f54ee093c04839d71ce2e4758a6890abc7"
varint@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/varint/-/varint-4.0.1.tgz#490829b942d248463b2b35097995c3bf737198e9"