Merge pull request #314 from poanetwork/develop

NW release 4.11.10
This commit is contained in:
Victor Baranov 2020-02-04 19:37:41 +03:00 committed by GitHub
commit 5fbd256ad9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
554 changed files with 9039 additions and 46558 deletions

View File

@ -11,9 +11,9 @@ workflows:
- prep-docs:
requires:
- prep-deps-npm
- prep-scss:
requires:
- prep-deps-npm
# - prep-scss:
# requires:
# - prep-deps-npm
- test-lint:
requires:
- prep-deps-npm
@ -42,11 +42,11 @@ workflows:
- test-integration-flat-chrome:
requires:
- prep-deps-npm
- prep-scss
# - prep-scss
- test-integration-flat-firefox:
requires:
- prep-deps-npm
- prep-scss
# - prep-scss
- all-tests-pass:
requires:
- test-lint
@ -369,12 +369,6 @@ jobs:
command: ./.circleci/scripts/firefox-install
- restore_cache:
key: dependency-cache-{{ .Revision }}
- run:
name: Get Scss Cache key
# this allows us to checksum against a whole directory
command: find ui/app/css -type f -exec md5sum {} \; | sort -k 2 > scss_checksum
- restore_cache:
key: scss-cache-{{ checksum "scss_checksum" }}
- run:
name: test:integration:flat
command: npm run test:flat
@ -388,12 +382,6 @@ jobs:
- checkout
- restore_cache:
key: dependency-cache-{{ .Revision }}
- run:
name: Get Scss Cache key
# this allows us to checksum against a whole directory
command: find ui/app/css -type f -exec md5sum {} \; | sort -k 2 > scss_checksum
- restore_cache:
key: scss-cache-{{ checksum "scss_checksum" }}
- run:
name: test:integration:flat
command: npm run test:flat
@ -412,12 +400,6 @@ jobs:
command: ./.circleci/scripts/firefox-install
- restore_cache:
key: dependency-cache-{{ .Revision }}
- run:
name: Get Scss Cache key
# this allows us to checksum against a whole directory
command: find ui/app/css -type f -exec md5sum {} \; | sort -k 2 > scss_checksum
- restore_cache:
key: scss-cache-{{ checksum "scss_checksum" }}
- run:
name: test:integration:mascara
command: npm run test:mascara
@ -431,12 +413,6 @@ jobs:
- checkout
- restore_cache:
key: dependency-cache-{{ .Revision }}
- run:
name: Get Scss Cache key
# this allows us to checksum against a whole directory
command: find ui/app/css -type f -exec md5sum {} \; | sort -k 2 > scss_checksum
- restore_cache:
key: scss-cache-{{ checksum "scss_checksum" }}
- run:
name: test:integration:mascara
command: npm run test:mascara

View File

@ -2,6 +2,10 @@
## Current Master
## 4.11.10 Tue Feb 04 2020
- [#313](https://github.com/poanetwork/nifty-wallet/pull/313) - Change Ethereum classic RPC endpoint
## 4.11.9 Thu Aug 22 2019
- [#303](https://github.com/poanetwork/nifty-wallet/pull/303): (Feature) Add Pocket Network

View File

@ -1,7 +1,7 @@
{
"name": "__MSG_appName__",
"short_name": "__MSG_appName__",
"version": "4.11.9",
"version": "4.11.10",
"manifest_version": 2,
"author": "POA Network",
"description": "__MSG_appDescription__",

View File

@ -1,6 +1,5 @@
const injectCss = require('inject-css')
const OldMetaMaskUiCss = require('../../old-ui/css')
const NewMetaMaskUiCss = require('../../ui/css')
const startPopup = require('./popup-core')
const PortStream = require('extension-port-stream')
const { getEnvironmentType } = require('./lib/util')
@ -44,27 +43,11 @@ async function start () {
// Code commented out until we begin auto adding users to NewUI
// const { isMascara, identities = {}, featureFlags = {} } = store.getState().metamask
// const firstTime = Object.keys(identities).length === 0
const { isMascara, featureFlags = {} } = store.getState().metamask
let betaUIState = featureFlags.betaUI
// Code commented out until we begin auto adding users to NewUI
// const useBetaCss = isMascara || firstTime || betaUIState
const useBetaCss = isMascara || betaUIState
let css = useBetaCss ? NewMetaMaskUiCss() : OldMetaMaskUiCss()
let deleteInjectedCss = injectCss(css)
let newBetaUIState
store.subscribe(() => {
const state = store.getState()
newBetaUIState = state.metamask.featureFlags.betaUI
if (newBetaUIState !== betaUIState) {
deleteInjectedCss()
betaUIState = newBetaUIState
css = betaUIState ? NewMetaMaskUiCss() : OldMetaMaskUiCss()
deleteInjectedCss = injectCss(css)
}
})
const css = OldMetaMaskUiCss()
injectCss(css)
})

View File

@ -17,8 +17,6 @@ const fs = require('fs')
const path = require('path')
const manifest = require('./app/manifest.json')
const mkdirp = require('mkdirp')
const sass = require('gulp-sass')
const autoprefixer = require('gulp-autoprefixer')
const gulpStylelint = require('gulp-stylelint')
const stylefmt = require('gulp-stylefmt')
const uglify = require('gulp-uglify-es').default
@ -214,51 +212,6 @@ gulp.task('dev:copy',
)
)
// scss compilation and autoprefixing tasks
gulp.task('build:scss', createScssBuildTask({
src: 'ui/app/css/index.scss',
dest: 'ui/app/css/output',
devMode: false,
}))
gulp.task('dev:scss', createScssBuildTask({
src: 'ui/app/css/index.scss',
dest: 'ui/app/css/output',
devMode: true,
pattern: 'ui/app/**/*.scss',
}))
function createScssBuildTask ({ src, dest, devMode, pattern }) {
return function () {
if (devMode) {
watch(pattern, async (event) => {
const stream = buildScss()
await endOfStream(stream)
livereload.changed(event.path)
})
return buildScssWithSourceMaps()
}
return buildScss()
}
function buildScssWithSourceMaps () {
return gulp.src(src)
.pipe(sourcemaps.init())
.pipe(sass().on('error', sass.logError))
.pipe(sourcemaps.write())
.pipe(autoprefixer())
.pipe(gulp.dest(dest))
}
function buildScss () {
return gulp.src(src)
.pipe(sass().on('error', sass.logError))
.pipe(autoprefixer())
.pipe(gulp.dest(dest))
}
}
gulp.task('lint-scss', function () {
return gulp
.src('ui/app/css/itcss/**/*.scss')
@ -371,7 +324,6 @@ gulp.task('zip', gulp.parallel('zip:chrome', 'zip:firefox', 'zip:edge', 'zip:ope
gulp.task('dev',
gulp.series(
'clean',
'dev:scss',
gulp.parallel(
'dev:extension:js',
'dev:mascara:js',
@ -384,7 +336,6 @@ gulp.task('dev',
gulp.task('dev:extension',
gulp.series(
'clean',
'dev:scss',
gulp.parallel(
'dev:extension:js',
'dev:copy',
@ -396,7 +347,6 @@ gulp.task('dev:extension',
gulp.task('dev:mascara',
gulp.series(
'clean',
'dev:scss',
gulp.parallel(
'dev:mascara:js',
'dev:copy',
@ -408,7 +358,6 @@ gulp.task('dev:mascara',
gulp.task('build',
gulp.series(
'clean',
'build:scss',
gulpParallel(
'build:extension:js',
'build:mascara:js',
@ -420,7 +369,6 @@ gulp.task('build',
gulp.task('build:extension',
gulp.series(
'clean',
'build:scss',
gulp.parallel(
'build:extension:js',
'copy'
@ -431,7 +379,6 @@ gulp.task('build:extension',
gulp.task('build:mascara',
gulp.series(
'clean',
'build:scss',
gulp.parallel(
'build:mascara:js',
'copy'

View File

@ -1,7 +1,7 @@
const injectCss = require('inject-css')
const SwController = require('sw-controller')
const SwStream = require('sw-stream')
const MetaMaskUiCss = require('../../ui/css')
const MetaMaskUiCss = require('../../old-ui/css')
const MetamascaraPlatform = require('../../app/scripts/platforms/window')
const startPopup = require('../../app/scripts/popup-core')

View File

@ -180,26 +180,6 @@ class ConfigScreen extends Component {
},
}),
h('p', {
style: {
fontFamily: 'Nunito Regular',
fontSize: '14px',
lineHeight: '18px',
},
}, [
'Switch to Decentralized Provider (Pocket)',
]),
h('input', {
type: 'checkbox',
name: 'pocket-checkbox',
checked: this.state.dProvider,
onChange: (event) => {
event.preventDefault()
this.toggleProvider()
},
}),
h('p.config-title', `Provider`),
h('div', {

15104
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -117,7 +117,7 @@
"eth-keychain-controller": "github:vbaranov/KeyringController#simple-address",
"eth-ledger-bridge-keyring": "github:vbaranov/eth-ledger-bridge-keyring#0.1.0-clear-accounts-flag",
"eth-method-registry": "^1.0.0",
"eth-net-props": "^1.0.24",
"eth-net-props": "^1.0.25",
"eth-phishing-detect": "^1.1.4",
"eth-query": "^2.1.2",
"eth-sig-util": "^2.2.0",
@ -141,7 +141,6 @@
"fuse.js": "^3.2.0",
"gulp": "github:gulpjs/gulp#v4.0.0",
"gulp-autoprefixer": "^5.0.0",
"gulp-sass": "^4.0.0",
"human-standard-token-abi": "^2.0.0",
"idb-global": "^2.1.0",
"iframe-stream": "^3.0.0",
@ -232,7 +231,7 @@
"brfs": "^1.6.1",
"browserify": "^16.2.3",
"chai": "^4.1.0",
"chromedriver": "^76.0.0",
"chromedriver": "^2.41.0",
"clipboardy": "^1.2.3",
"compression": "^1.7.1",
"coveralls": "^3.0.0",

File diff suppressed because one or more lines are too long

View File

@ -1,36 +0,0 @@
<html>
<head>
<title>E2E Test Dapp</title>
</head>
<body>
<div style="display: flex; flex-flow: column;">
<div style="display: flex; font-size: 1.25rem;">Contract</div>
<div style="display: flex;">
<button id="deployButton">Deploy Contract</button>
<button id="depositButton">Deposit</button>
<button id="withdrawButton">Withdraw</button>
</div>
<div id="contractStatus" style="display: flex; font-size: 1rem;">
Not clicked
</div>
</div>
<div style="display: flex; flex-flow: column;">
<div style="display: flex; font-size: 1.25rem;">Send eth</div>
<div style="display: flex;">
<button id="sendButton">Send</button>
</div>
</div>
<div style="display: flex; flex-flow: column;">
<div style="display: flex; font-size: 1.25rem;">Send tokens</div>
<div id="tokenAddress"></div>
<div style="display: flex;">
<button id="createToken">Create Token</button>
<button id="transferTokens">Transfer Tokens</button>
<button id="approveTokens">Approve Tokens</button>
</div>
</div>
<script src="contract.js"></script>
</body>
</html>

View File

@ -1,286 +0,0 @@
const path = require('path')
const assert = require('assert')
const webdriver = require('selenium-webdriver')
const { By, until } = webdriver
const {
delay,
buildChromeWebDriver,
buildFirefoxWebdriver,
installWebExt,
getExtensionIdChrome,
getExtensionIdFirefox,
} = require('../func')
const {
checkBrowserForConsoleErrors,
closeAllWindowHandlesExcept,
findElement,
findElements,
loadExtension,
openNewPage,
verboseReportOnFailure,
waitUntilXWindowHandles,
} = require('./helpers')
describe('MetaMask', function () {
let extensionId
let driver
const tinyDelayMs = 200
const regularDelayMs = tinyDelayMs * 2
const largeDelayMs = regularDelayMs * 2
this.timeout(0)
this.bail(true)
before(async function () {
switch (process.env.SELENIUM_BROWSER) {
case 'chrome': {
const extPath = path.resolve('dist/chrome')
driver = buildChromeWebDriver(extPath)
extensionId = await getExtensionIdChrome(driver)
await driver.get(`chrome-extension://${extensionId}/popup.html`)
break
}
case 'firefox': {
const extPath = path.resolve('dist/firefox')
driver = buildFirefoxWebdriver()
await installWebExt(driver, extPath)
await delay(700)
extensionId = await getExtensionIdFirefox(driver)
await driver.get(`moz-extension://${extensionId}/popup.html`)
}
}
})
afterEach(async function () {
if (process.env.SELENIUM_BROWSER === 'chrome') {
const errors = await checkBrowserForConsoleErrors(driver)
if (errors.length) {
const errorReports = errors.map(err => err.message)
const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}`
console.error(new Error(errorMessage))
}
}
if (this.currentTest.state === 'failed') {
await verboseReportOnFailure(driver, this.currentTest)
}
})
after(async function () {
await driver.quit()
})
describe('New UI setup', async function () {
it('switches to first tab', async function () {
await delay(tinyDelayMs)
const [firstTab] = await driver.getAllWindowHandles()
await driver.switchTo().window(firstTab)
await delay(regularDelayMs)
})
it('selects the new UI option', async () => {
try {
const overlay = await findElement(driver, By.css('.full-flex-height'))
await driver.wait(until.stalenessOf(overlay))
} catch (e) {}
let button
try {
button = await findElement(driver, By.xpath("//button[contains(text(), 'Try it now')]"))
} catch (e) {
await loadExtension(driver, extensionId)
await delay(largeDelayMs)
button = await findElement(driver, By.xpath("//button[contains(text(), 'Try it now')]"))
}
await button.click()
await delay(regularDelayMs)
// Close all other tabs
const [tab0, tab1, tab2] = await driver.getAllWindowHandles()
await driver.switchTo().window(tab0)
await delay(tinyDelayMs)
let selectedUrl = await driver.getCurrentUrl()
await delay(tinyDelayMs)
if (tab0 && selectedUrl.match(/popup.html/)) {
await closeAllWindowHandlesExcept(driver, tab0)
} else if (tab1) {
await driver.switchTo().window(tab1)
selectedUrl = await driver.getCurrentUrl()
await delay(tinyDelayMs)
if (selectedUrl.match(/popup.html/)) {
await closeAllWindowHandlesExcept(driver, tab1)
} else if (tab2) {
await driver.switchTo().window(tab2)
selectedUrl = await driver.getCurrentUrl()
selectedUrl.match(/popup.html/) && await closeAllWindowHandlesExcept(driver, tab2)
}
} else {
throw new Error('popup.html not found')
}
await delay(regularDelayMs)
const [appTab] = await driver.getAllWindowHandles()
await driver.switchTo().window(appTab)
await delay(tinyDelayMs)
await loadExtension(driver, extensionId)
await delay(regularDelayMs)
const continueBtn = await findElement(driver, By.css('.welcome-screen__button'))
await continueBtn.click()
await delay(regularDelayMs)
})
})
describe('Going through the first time flow', () => {
it('accepts a secure password', async () => {
const passwordBox = await findElement(driver, By.css('.create-password #create-password'))
const passwordBoxConfirm = await findElement(driver, By.css('.create-password #confirm-password'))
const button = await findElement(driver, By.css('.create-password button'))
await passwordBox.sendKeys('correct horse battery staple')
await passwordBoxConfirm.sendKeys('correct horse battery staple')
await button.click()
await delay(regularDelayMs)
})
it('clicks through the unique image screen', async () => {
const nextScreen = await findElement(driver, By.css('.unique-image button'))
await nextScreen.click()
await delay(regularDelayMs)
})
it('clicks through the ToS', async () => {
// terms of use
const canClickThrough = await driver.findElement(By.css('.tou button')).isEnabled()
assert.equal(canClickThrough, false, 'disabled continue button')
const bottomOfTos = await findElement(driver, By.linkText('Attributions'))
await driver.executeScript('arguments[0].scrollIntoView(true)', bottomOfTos)
await delay(regularDelayMs)
const acceptTos = await findElement(driver, By.css('.tou button'))
driver.wait(until.elementIsEnabled(acceptTos))
await acceptTos.click()
await delay(regularDelayMs)
})
it('clicks through the privacy notice', async () => {
// privacy notice
const nextScreen = await findElement(driver, By.css('.tou button'))
await nextScreen.click()
await delay(regularDelayMs)
})
it('clicks through the phishing notice', async () => {
// phishing notice
const noticeElement = await driver.findElement(By.css('.markdown'))
await driver.executeScript('arguments[0].scrollTop = arguments[0].scrollHeight', noticeElement)
await delay(regularDelayMs)
const nextScreen = await findElement(driver, By.css('.tou button'))
await nextScreen.click()
await delay(regularDelayMs)
})
let seedPhrase
it('reveals the seed phrase', async () => {
const byRevealButton = By.css('.backup-phrase__secret-blocker .backup-phrase__reveal-button')
await driver.wait(until.elementLocated(byRevealButton, 10000))
const revealSeedPhraseButton = await findElement(driver, byRevealButton, 10000)
await revealSeedPhraseButton.click()
await delay(regularDelayMs)
seedPhrase = await driver.findElement(By.css('.backup-phrase__secret-words')).getText()
assert.equal(seedPhrase.split(' ').length, 12)
await delay(regularDelayMs)
const nextScreen = await findElement(driver, By.css('.backup-phrase button'))
await nextScreen.click()
await delay(regularDelayMs)
})
async function clickWordAndWait (word) {
const xpathClass = 'backup-phrase__confirm-seed-option backup-phrase__confirm-seed-option--unselected'
const xpath = `//button[@class='${xpathClass}' and contains(text(), '${word}')]`
const word0 = await findElement(driver, By.xpath(xpath), 10000)
await word0.click()
await delay(tinyDelayMs)
}
async function retypeSeedPhrase (words, wasReloaded, count = 0) {
try {
if (wasReloaded) {
const byRevealButton = By.css('.backup-phrase__secret-blocker .backup-phrase__reveal-button')
await driver.wait(until.elementLocated(byRevealButton, 10000))
const revealSeedPhraseButton = await findElement(driver, byRevealButton, 10000)
await revealSeedPhraseButton.click()
await delay(regularDelayMs)
const nextScreen = await findElement(driver, By.css('.backup-phrase button'))
await nextScreen.click()
await delay(regularDelayMs)
}
for (let i = 0; i < 12; i++) {
await clickWordAndWait(words[i])
}
} catch (e) {
if (count > 2) {
throw e
} else {
await loadExtension(driver, extensionId)
await retypeSeedPhrase(words, true, count + 1)
}
}
}
it('can retype the seed phrase', async () => {
const words = seedPhrase.split(' ')
await retypeSeedPhrase(words)
const confirm = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`))
await confirm.click()
await delay(regularDelayMs)
})
it('clicks through the deposit modal', async () => {
const byBuyModal = By.css('span .modal')
const buyModal = await driver.wait(until.elementLocated(byBuyModal))
const closeModal = await findElement(driver, By.css('.page-container__header-close'))
await closeModal.click()
await driver.wait(until.stalenessOf(buyModal))
await delay(regularDelayMs)
})
it('switches to localhost', async () => {
const networkDropdown = await findElement(driver, By.css('.network-name'))
await networkDropdown.click()
await delay(regularDelayMs)
const [localhost] = await findElements(driver, By.xpath(`//span[contains(text(), 'Localhost')]`))
await localhost.click()
await delay(largeDelayMs * 2)
})
})
describe('Drizzle', () => {
it('should be able to detect our eth address', async () => {
await openNewPage(driver, 'http://127.0.0.1:3000/')
await delay(regularDelayMs)
await waitUntilXWindowHandles(driver, 2)
const windowHandles = await driver.getAllWindowHandles()
const dapp = windowHandles[1]
await driver.switchTo().window(dapp)
await delay(regularDelayMs)
const addressElement = await findElement(driver, By.css(`.pure-u-1-1 h4`))
const addressText = await addressElement.getText()
assert(addressText.match(/^0x[a-fA-F0-9]{40}$/))
})
})
})

View File

@ -1,408 +0,0 @@
const path = require('path')
const assert = require('assert')
const webdriver = require('selenium-webdriver')
const { By, Key, until } = webdriver
const {
delay,
buildChromeWebDriver,
buildFirefoxWebdriver,
installWebExt,
getExtensionIdChrome,
getExtensionIdFirefox,
} = require('../func')
const {
checkBrowserForConsoleErrors,
closeAllWindowHandlesExcept,
verboseReportOnFailure,
findElement,
findElements,
loadExtension,
} = require('./helpers')
describe('Using MetaMask with an existing account', function () {
let extensionId
let driver
const testSeedPhrase = 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent'
const testAddress = '0xE18035BF8712672935FDB4e5e431b1a0183d2DFC'
const testPrivateKey2 = '14abe6f4aab7f9f626fe981c864d0adeb5685f289ac9270c27b8fd790b4235d6'
const tinyDelayMs = 500
const regularDelayMs = 1000
const largeDelayMs = regularDelayMs * 2
this.timeout(0)
this.bail(true)
before(async function () {
switch (process.env.SELENIUM_BROWSER) {
case 'chrome': {
const extensionPath = path.resolve('dist/chrome')
driver = buildChromeWebDriver(extensionPath)
extensionId = await getExtensionIdChrome(driver)
await driver.get(`chrome-extension://${extensionId}/popup.html`)
await delay(regularDelayMs)
break
}
case 'firefox': {
const extensionPath = path.resolve('dist/firefox')
driver = buildFirefoxWebdriver()
await installWebExt(driver, extensionPath)
await delay(regularDelayMs)
extensionId = await getExtensionIdFirefox(driver)
await driver.get(`moz-extension://${extensionId}/popup.html`)
await delay(regularDelayMs)
break
}
}
})
afterEach(async function () {
if (process.env.SELENIUM_BROWSER === 'chrome') {
const errors = await checkBrowserForConsoleErrors(driver)
if (errors.length) {
const errorReports = errors.map(err => err.message)
const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}`
console.error(new Error(errorMessage))
}
}
if (this.currentTest.state === 'failed') {
await verboseReportOnFailure(driver, this.currentTest)
}
})
after(async function () {
await driver.quit()
})
describe('New UI setup', async function () {
it('switches to first tab', async function () {
await delay(tinyDelayMs)
const [firstTab] = await driver.getAllWindowHandles()
await driver.switchTo().window(firstTab)
await delay(regularDelayMs)
})
it('selects the new UI option', async () => {
try {
const overlay = await findElement(driver, By.css('.full-flex-height'))
await driver.wait(until.stalenessOf(overlay))
} catch (e) {}
let button
try {
button = await findElement(driver, By.xpath("//button[contains(text(), 'Try it now')]"))
} catch (e) {
await loadExtension(driver, extensionId)
await delay(largeDelayMs)
button = await findElement(driver, By.xpath("//button[contains(text(), 'Try it now')]"))
}
await button.click()
await delay(regularDelayMs)
// Close all other tabs
const [tab0, tab1, tab2] = await driver.getAllWindowHandles()
await driver.switchTo().window(tab0)
await delay(tinyDelayMs)
let selectedUrl = await driver.getCurrentUrl()
await delay(tinyDelayMs)
if (tab0 && selectedUrl.match(/popup.html/)) {
await closeAllWindowHandlesExcept(driver, tab0)
} else if (tab1) {
await driver.switchTo().window(tab1)
selectedUrl = await driver.getCurrentUrl()
await delay(tinyDelayMs)
if (selectedUrl.match(/popup.html/)) {
await closeAllWindowHandlesExcept(driver, tab1)
} else if (tab2) {
await driver.switchTo().window(tab2)
selectedUrl = await driver.getCurrentUrl()
selectedUrl.match(/popup.html/) && await closeAllWindowHandlesExcept(driver, tab2)
}
} else {
throw new Error('popup.html not found')
}
await delay(regularDelayMs)
const [appTab] = await driver.getAllWindowHandles()
await driver.switchTo().window(appTab)
await delay(tinyDelayMs)
await loadExtension(driver, extensionId)
await delay(regularDelayMs)
const continueBtn = await findElement(driver, By.css('.welcome-screen__button'))
await continueBtn.click()
await delay(regularDelayMs)
})
})
describe('First time flow starting from an existing seed phrase', () => {
it('imports a seed phrase', async () => {
const [seedPhrase] = await findElements(driver, By.xpath(`//a[contains(text(), 'Import with seed phrase')]`))
await seedPhrase.click()
await delay(regularDelayMs)
const [seedTextArea] = await findElements(driver, By.css('textarea.import-account__secret-phrase'))
await seedTextArea.sendKeys(testSeedPhrase)
await delay(regularDelayMs)
const [password] = await findElements(driver, By.id('password'))
await password.sendKeys('correct horse battery staple')
const [confirmPassword] = await findElements(driver, By.id('confirm-password'))
confirmPassword.sendKeys('correct horse battery staple')
const [importButton] = await findElements(driver, By.xpath(`//button[contains(text(), 'Import')]`))
await importButton.click()
await delay(regularDelayMs)
})
it('clicks through the ToS', async () => {
// terms of use
const canClickThrough = await driver.findElement(By.css('.tou button')).isEnabled()
assert.equal(canClickThrough, false, 'disabled continue button')
const bottomOfTos = await findElement(driver, By.linkText('Attributions'))
await driver.executeScript('arguments[0].scrollIntoView(true)', bottomOfTos)
await delay(regularDelayMs)
const acceptTos = await findElement(driver, By.css('.tou button'))
await acceptTos.click()
await delay(regularDelayMs)
})
it('clicks through the privacy notice', async () => {
// privacy notice
const nextScreen = await findElement(driver, By.css('.tou button'))
await nextScreen.click()
await delay(regularDelayMs)
})
it('clicks through the phishing notice', async () => {
// phishing notice
const noticeElement = await driver.findElement(By.css('.markdown'))
await driver.executeScript('arguments[0].scrollTop = arguments[0].scrollHeight', noticeElement)
await delay(regularDelayMs)
const nextScreen = await findElement(driver, By.css('.tou button'))
await nextScreen.click()
await delay(regularDelayMs)
})
})
describe('Show account information', () => {
it('shows the correct account address', async () => {
await driver.findElement(By.css('.wallet-view__details-button')).click()
await driver.findElement(By.css('.qr-wrapper')).isDisplayed()
await delay(regularDelayMs)
const [address] = await findElements(driver, By.css('input.qr-ellip-address'))
assert.equal(await address.getAttribute('value'), testAddress)
await driver.executeScript("document.querySelector('.account-modal-close').click()")
await delay(largeDelayMs)
})
it('shows a QR code for the account', async () => {
await driver.findElement(By.css('.wallet-view__details-button')).click()
await driver.findElement(By.css('.qr-wrapper')).isDisplayed()
const detailModal = await driver.findElement(By.css('span .modal'))
await delay(regularDelayMs)
await driver.executeScript("document.querySelector('.account-modal-close').click()")
await driver.wait(until.stalenessOf(detailModal))
await delay(regularDelayMs)
})
})
describe('Log out and log back in', () => {
it('logs out of the account', async () => {
const accountIdenticon = driver.findElement(By.css('.account-menu__icon .identicon'))
accountIdenticon.click()
await delay(regularDelayMs)
const [logoutButton] = await findElements(driver, By.css('.account-menu__logout-button'))
assert.equal(await logoutButton.getText(), 'Log out')
await logoutButton.click()
await delay(regularDelayMs)
})
it('accepts the account password after lock', async () => {
await driver.findElement(By.id('password')).sendKeys('correct horse battery staple')
await driver.findElement(By.id('password')).sendKeys(Key.ENTER)
await delay(largeDelayMs)
})
})
describe('Add an account', () => {
it('switches to localhost', async () => {
const networkDropdown = await findElement(driver, By.css('.network-name'))
await networkDropdown.click()
await delay(regularDelayMs)
const [localhost] = await findElements(driver, By.xpath(`//span[contains(text(), 'Localhost')]`))
await localhost.click()
await delay(largeDelayMs)
})
it('choose Create Account from the account menu', async () => {
await driver.findElement(By.css('.account-menu__icon')).click()
await delay(regularDelayMs)
const [createAccount] = await findElements(driver, By.xpath(`//div[contains(text(), 'Create Account')]`))
await createAccount.click()
await delay(regularDelayMs)
})
it('set account name', async () => {
const [accountName] = await findElements(driver, By.css('.new-account-create-form input'))
await accountName.sendKeys('2nd account')
await delay(regularDelayMs)
const [createButton] = await findElements(driver, By.xpath(`//button[contains(text(), 'Create')]`))
await createButton.click()
await delay(regularDelayMs)
})
it('should show the correct account name', async () => {
const [accountName] = await findElements(driver, By.css('.account-name'))
assert.equal(await accountName.getText(), '2nd account')
await delay(regularDelayMs)
})
})
describe('Switch back to original account', () => {
it('chooses the original account from the account menu', async () => {
await driver.findElement(By.css('.account-menu__icon')).click()
await delay(regularDelayMs)
const [originalAccountMenuItem] = await findElements(driver, By.css('.account-menu__name'))
await originalAccountMenuItem.click()
await delay(regularDelayMs)
})
})
describe('Send ETH from inside MetaMask', () => {
it('starts to send a transaction', async function () {
const sendButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Send')]`))
await sendButton.click()
await delay(regularDelayMs)
const inputAddress = await findElement(driver, By.css('input[placeholder="Recipient Address"]'))
const inputAmount = await findElement(driver, By.css('.unit-input__input'))
await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970')
await inputAmount.sendKeys('1')
// Set the gas limit
const configureGas = await findElement(driver, By.css('.send-v2__gas-fee-display button'))
await configureGas.click()
await delay(regularDelayMs)
const gasModal = await driver.findElement(By.css('span .modal'))
const save = await findElement(driver, By.xpath(`//button[contains(text(), 'Save')]`))
await save.click()
await driver.wait(until.stalenessOf(gasModal))
await delay(regularDelayMs)
// Continue to next screen
const nextScreen = await findElement(driver, By.xpath(`//button[contains(text(), 'Next')]`))
await nextScreen.click()
await delay(regularDelayMs)
})
it('confirms the transaction', async function () {
const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`))
await confirmButton.click()
await delay(regularDelayMs)
})
it('finds the transaction in the transactions list', async function () {
const transactions = await findElements(driver, By.css('.transaction-list-item'))
assert.equal(transactions.length, 1)
const txValues = await findElements(driver, By.css('.transaction-list-item__amount--primary'))
assert.equal(txValues.length, 1)
assert.equal(await txValues[0].getText(), '-1 ETH')
})
})
describe('Imports an account with private key', () => {
it('choose Create Account from the account menu', async () => {
await driver.findElement(By.css('.account-menu__icon')).click()
await delay(regularDelayMs)
const [importAccount] = await findElements(driver, By.xpath(`//div[contains(text(), 'Import Account')]`))
await importAccount.click()
await delay(regularDelayMs)
})
it('enter private key', async () => {
const privateKeyInput = await findElement(driver, By.css('#private-key-box'))
await privateKeyInput.sendKeys(testPrivateKey2)
await delay(regularDelayMs)
const importButtons = await findElements(driver, By.xpath(`//button[contains(text(), 'Import')]`))
await importButtons[0].click()
await delay(regularDelayMs)
})
it('should show the correct account name', async () => {
const [accountName] = await findElements(driver, By.css('.account-name'))
assert.equal(await accountName.getText(), 'Account 3')
await delay(regularDelayMs)
})
it('should show the imported label', async () => {
const [importedLabel] = await findElements(driver, By.css('.wallet-view__keyring-label'))
assert.equal(await importedLabel.getText(), 'IMPORTED')
await delay(regularDelayMs)
})
})
describe('Connects to a Hardware wallet', () => {
it('choose Connect Hardware Wallet from the account menu', async () => {
await driver.findElement(By.css('.account-menu__icon')).click()
await delay(regularDelayMs)
const [connectAccount] = await findElements(driver, By.xpath(`//div[contains(text(), 'Connect Hardware Wallet')]`))
await connectAccount.click()
await delay(regularDelayMs)
})
it('should open the TREZOR Connect popup', async () => {
const trezorButton = await findElements(driver, By.css('.hw-connect__btn'))
await trezorButton[1].click()
await delay(regularDelayMs)
const connectButtons = await findElements(driver, By.xpath(`//button[contains(text(), 'Connect')]`))
await connectButtons[0].click()
await delay(regularDelayMs)
const allWindows = await driver.getAllWindowHandles()
switch (process.env.SELENIUM_BROWSER) {
case 'chrome':
assert.equal(allWindows.length, 2)
break
default:
assert.equal(allWindows.length, 1)
}
})
it('should show the "Browser not supported" screen for non Chrome browsers', async () => {
if (process.env.SELENIUM_BROWSER !== 'chrome') {
const title = await findElements(driver, By.xpath(`//h3[contains(text(), 'Your Browser is not supported...')]`))
assert.equal(title.length, 1)
const downloadChromeButtons = await findElements(driver, By.xpath(`//button[contains(text(), 'Download Google Chrome')]`))
assert.equal(downloadChromeButtons.length, 1)
await downloadChromeButtons[0].click()
await delay(regularDelayMs)
const [newUITab, downloadChromeTab] = await driver.getAllWindowHandles()
await driver.switchTo().window(downloadChromeTab)
await delay(regularDelayMs)
const tabUrl = await driver.getCurrentUrl()
assert.equal(tabUrl, 'https://www.google.com/chrome/')
await driver.close()
await delay(regularDelayMs)
await driver.switchTo().window(newUITab)
}
})
})
})

View File

@ -1,132 +0,0 @@
const fs = require('fs')
const mkdirp = require('mkdirp')
const pify = require('pify')
const assert = require('assert')
const { delay } = require('../func')
const { until } = require('selenium-webdriver')
module.exports = {
assertElementNotPresent,
checkBrowserForConsoleErrors,
closeAllWindowHandlesExcept,
findElement,
findElements,
loadExtension,
openNewPage,
switchToWindowWithTitle,
verboseReportOnFailure,
waitUntilXWindowHandles,
}
async function loadExtension (driver, extensionId) {
switch (process.env.SELENIUM_BROWSER) {
case 'chrome': {
await driver.get(`chrome-extension://${extensionId}/home.html`)
break
}
case 'firefox': {
await driver.get(`moz-extension://${extensionId}/home.html`)
break
}
}
}
async function checkBrowserForConsoleErrors (driver) {
const ignoredLogTypes = ['WARNING']
const ignoredErrorMessages = [
// React throws error warnings on "dataset", but still sets the data-* properties correctly
'Warning: Unknown prop `dataset` on ',
// Third-party Favicon 404s show up as errors
'favicon.ico - Failed to load resource: the server responded with a status of 404 (Not Found)',
// React Development build - known issue blocked by test build sys
'Warning: It looks like you\'re using a minified copy of the development build of React.',
// Redux Development build - known issue blocked by test build sys
'This means that you are running a slower development build of Redux.',
]
const browserLogs = await driver.manage().logs().get('browser')
const errorEntries = browserLogs.filter(entry => !ignoredLogTypes.includes(entry.level.toString()))
const errorObjects = errorEntries.map(entry => entry.toJSON())
return errorObjects.filter(entry => !ignoredErrorMessages.some(message => entry.message.includes(message)))
}
async function verboseReportOnFailure (driver, test) {
let artifactDir
if (process.env.SELENIUM_BROWSER === 'chrome') {
artifactDir = `./test-artifacts/chrome/${test.title}`
} else if (process.env.SELENIUM_BROWSER === 'firefox') {
artifactDir = `./test-artifacts/firefox/${test.title}`
}
const filepathBase = `${artifactDir}/test-failure`
await pify(mkdirp)(artifactDir)
const screenshot = await driver.takeScreenshot()
await pify(fs.writeFile)(`${filepathBase}-screenshot.png`, screenshot, { encoding: 'base64' })
const htmlSource = await driver.getPageSource()
await pify(fs.writeFile)(`${filepathBase}-dom.html`, htmlSource)
}
async function findElement (driver, by, timeout = 10000) {
return driver.wait(until.elementLocated(by), timeout)
}
async function findElements (driver, by, timeout = 10000) {
return driver.wait(until.elementsLocated(by), timeout)
}
async function openNewPage (driver, url) {
await driver.executeScript('window.open()')
await delay(1000)
const handles = await driver.getAllWindowHandles()
const lastHandle = handles[handles.length - 1]
await driver.switchTo().window(lastHandle)
await driver.get(url)
await delay(1000)
}
async function waitUntilXWindowHandles (driver, x) {
const windowHandles = await driver.getAllWindowHandles()
if (windowHandles.length === x) return
await delay(1000)
return await waitUntilXWindowHandles(driver, x)
}
async function switchToWindowWithTitle (driver, title, windowHandles) {
if (!windowHandles) {
windowHandles = await driver.getAllWindowHandles()
} else if (windowHandles.length === 0) {
throw new Error('No window with title: ' + title)
}
const firstHandle = windowHandles[0]
await driver.switchTo().window(firstHandle)
const handleTitle = await driver.getTitle()
if (handleTitle === title) {
return firstHandle
} else {
return await switchToWindowWithTitle(driver, title, windowHandles.slice(1))
}
}
async function closeAllWindowHandlesExcept (driver, exceptions, windowHandles) {
exceptions = typeof exceptions === 'string' ? [ exceptions ] : exceptions
windowHandles = windowHandles || await driver.getAllWindowHandles()
const lastWindowHandle = windowHandles.pop()
if (!exceptions.includes(lastWindowHandle)) {
await driver.switchTo().window(lastWindowHandle)
await delay(1000)
await driver.close()
await delay(1000)
}
return windowHandles.length && await closeAllWindowHandlesExcept(driver, exceptions, windowHandles)
}
async function assertElementNotPresent (webdriver, driver, by) {
let dataTab
try {
dataTab = await findElement(driver, by, 4000)
} catch (err) {
assert(err instanceof webdriver.error.NoSuchElementError || err instanceof webdriver.error.TimeoutError)
}
assert.ok(!dataTab, 'Found element that should not be present')
}

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +0,0 @@
#!/usr/bin/env bash
set -e
set -u
set -o pipefail
export PATH="$PATH:./node_modules/.bin"
shell-parallel -s 'npm run ganache:start -- -b 2' -x 'sleep 5 && static-server test/e2e/beta/contract-test --port 8080' -x 'sleep 5 && mocha test/e2e/beta/metamask-beta-ui.spec'
shell-parallel -s 'npm run ganache:start -- -d -b 2' -x 'sleep 5 && mocha test/e2e/beta/from-import-beta-ui.spec'

View File

@ -1,20 +0,0 @@
#!/usr/bin/env bash
set -e
set -u
set -o pipefail
export PATH="$PATH:./node_modules/.bin"
npm run ganache:start -- -b 2 >> /dev/null 2>&1 &
sleep 5
cd test/e2e/beta/
rm -rf drizzle-test
mkdir drizzle-test && cd drizzle-test
npm install truffle
truffle unbox drizzle
echo "Deploying contracts for Drizzle test..."
truffle compile && truffle migrate
BROWSER=none npm start >> /dev/null 2>&1 &
cd ../../../../
mocha test/e2e/beta/drizzle.spec

View File

@ -271,14 +271,14 @@ module.exports = {
iconCopy: By.className('clipboard cursor-pointer'),
},
settings: {
currentNetwork: By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-justify-center.flex-grow.select-none > div > div:nth-child(1) > span:nth-child(2)'),
currentNetwork: By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-justify-center.flex-grow.select-none > div > div:nth-child(1) > div.config-description'),
fieldNewRPC: By.id('new_rpc'),
buttonSave: By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-justify-center.flex-grow.select-none > div > div:nth-child(2) > button'),
buttonSave: By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-justify-center.flex-grow.select-none > div > button'),
titleText: 'Settings',
title: By.css('#app-content > div > div.app-primary.from-right > div > div.section-title.flex-row.flex-center > h2'),
buttons: {
arrow: By.className('fa fa-arrow-left fa-lg cursor-pointer'),
changePassword: By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-justify-center.flex-grow.select-none > div > div:nth-child(10) > button:nth-child(5)'),
changePassword: By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-justify-center.flex-grow.select-none > div > div:nth-child(14) > button:nth-child(5)'),
delete: By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-justify-center.flex-grow.select-none > div > div:nth-child(1) > button'),
},
error: By.className('error'),

View File

@ -53,15 +53,19 @@ class Functions {
return { driver, extensionId, extensionUri }
}
static async buildChromeWebDriver (extPath) {
static async buildChromeWebDriver (extPath, opts = {}) {
const tmpProfile = fs.mkdtempSync(path.join(os.tmpdir(), 'mm-chrome-profile'))
const args = [
`load-extension=${extPath}`,
`user-data-dir=${tmpProfile}`,
]
if (opts.responsive) {
args.push('--auto-open-devtools-for-tabs')
}
return new webdriver.Builder()
.withCapabilities({
chromeOptions: {
args: [
`load-extension=${extPath}`,
`user-data-dir=${tmpProfile}`,
],
args,
binary: process.env.SELENIUM_CHROME_BINARY,
},
})

View File

@ -1,146 +1,146 @@
// const path = require('path')
// const Func = require('./func').Functions
// const account1 = '0x2E428ABd9313D256d64D1f69fe3929C3BE18fD1f'
// const account1RSK = '0x2E428aBd9313D256d64D1f69fe3929c3Be18Fd1F'
// const account2 = '0xd7b7AFeCa35e32594e29504771aC847E2a803742'
// const testsFolder = './test-cases'
// const setup = require(`${testsFolder}/setup.spec`)
// const login = require(`${testsFolder}/login.spec`)
// const { accountCreation, getCreatedAccounts } = require(`${testsFolder}/account-creation.spec`)
// const connectHDWallet = require(`${testsFolder}/connect-hd-wallet.spec`)
// const importAccount = require(`${testsFolder}/import-account.spec`)
// const importContractAccount = require(`${testsFolder}/import-contract-account.spec`)
// const deleteImportedAccount = require(`${testsFolder}/delete-imported-account.spec`)
// const signData = require(`${testsFolder}/sign-data.spec`)
// const exportPrivateKey = require(`${testsFolder}/export-private-key.spec`)
// const importGanacheSeedPhrase = require(`${testsFolder}/import-ganache-seed-phrase.spec`)
// const RSKNetworkTests = require(`${testsFolder}/RSK-network-tests.js`)
// const checkEmittedEvents = require(`${testsFolder}/check-emitted-events.spec`)
// // const addCustomToken = require(`${testsFolder}/add-token-custom.spec`)
// const changePassword = require(`${testsFolder}/change-password.spec`)
// const addTokeFromSearch = require(`${testsFolder}/add-token-search.spec`)
// const customRPC = require(`${testsFolder}/custom-rpc.spec`)
const path = require('path')
const Func = require('./func').Functions
const account1 = '0x2E428ABd9313D256d64D1f69fe3929C3BE18fD1f'
const account1RSK = '0x2E428aBd9313D256d64D1f69fe3929c3Be18Fd1F'
const account2 = '0xd7b7AFeCa35e32594e29504771aC847E2a803742'
const testsFolder = './test-cases'
const setup = require(`${testsFolder}/setup.spec`)
const login = require(`${testsFolder}/login.spec`)
const { accountCreation, getCreatedAccounts } = require(`${testsFolder}/account-creation.spec`)
const connectHDWallet = require(`${testsFolder}/connect-hd-wallet.spec`)
const importAccount = require(`${testsFolder}/import-account.spec`)
const importContractAccount = require(`${testsFolder}/import-contract-account.spec`)
const deleteImportedAccount = require(`${testsFolder}/delete-imported-account.spec`)
const signData = require(`${testsFolder}/sign-data.spec`)
const exportPrivateKey = require(`${testsFolder}/export-private-key.spec`)
const importGanacheSeedPhrase = require(`${testsFolder}/import-ganache-seed-phrase.spec`)
const RSKNetworkTests = require(`${testsFolder}/RSK-network-tests.js`)
const checkEmittedEvents = require(`${testsFolder}/check-emitted-events.spec`)
// const addCustomToken = require(`${testsFolder}/add-token-custom.spec`)
const changePassword = require(`${testsFolder}/change-password.spec`)
const addTokeFromSearch = require(`${testsFolder}/add-token-search.spec`)
const customRPC = require(`${testsFolder}/custom-rpc.spec`)
// describe('Metamask popup page', async function () {
describe('Metamask popup page', async function () {
// this.timeout(15 * 60 * 1000)
// const f = new Func()
// let driver, extensionId
// const password = '123456789'
// const newPassword = {
// correct: 'abcDEF123!@#',
// short: '123',
// incorrect: '1234567890',
// }
this.timeout(15 * 60 * 1000)
const f = new Func()
let driver, extensionId
const password = '123456789'
const newPassword = {
correct: 'abcDEF123!@#',
short: '123',
incorrect: '1234567890',
}
// before(async function () {
// if (process.env.SELENIUM_BROWSER === 'chrome') {
// const extPath = path.resolve('dist/chrome')
// driver = await Func.buildChromeWebDriver(extPath)
// f.driver = driver
// extensionId = await f.getExtensionIdChrome()
// f.extensionId = extensionId
// await driver.get(`chrome-extension://${extensionId}/popup.html`)
before(async function () {
if (process.env.SELENIUM_BROWSER === 'chrome') {
const extPath = path.resolve('dist/chrome')
driver = await Func.buildChromeWebDriver(extPath)
f.driver = driver
extensionId = await f.getExtensionIdChrome()
f.extensionId = extensionId
await driver.get(`chrome-extension://${extensionId}/popup.html`)
// } else if (process.env.SELENIUM_BROWSER === 'firefox') {
// const extPath = path.resolve('dist/firefox')
// driver = await Func.buildFirefoxWebdriver()
// f.driver = driver
// await f.installWebExt(extPath)
// await f.delay(700)
// extensionId = await f.getExtensionIdFirefox()
// f.extensionId = extensionId
// await driver.get(`moz-extension://${extensionId}/popup.html`)
// }
} else if (process.env.SELENIUM_BROWSER === 'firefox') {
const extPath = path.resolve('dist/firefox')
driver = await Func.buildFirefoxWebdriver()
f.driver = driver
await f.installWebExt(extPath)
await f.delay(700)
extensionId = await f.getExtensionIdFirefox()
f.extensionId = extensionId
await driver.get(`moz-extension://${extensionId}/popup.html`)
}
// })
})
// afterEach(async function () {
// // logs command not supported in firefox
// // https://github.com/SeleniumHQ/selenium/issues/2910
// if (process.env.SELENIUM_BROWSER === 'chrome') {
// // check for console errors
// const errors = await f.checkBrowserForConsoleErrors(driver)
// if (errors.length) {
// const errorReports = errors.map(err => err.message)
// const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}`
// console.log(errorMessage)
// }
// }
// // gather extra data if test failed
// if (this.currentTest.state === 'failed') {
// await f.verboseReportOnFailure(this.currentTest)
// }
// })
afterEach(async function () {
// logs command not supported in firefox
// https://github.com/SeleniumHQ/selenium/issues/2910
if (process.env.SELENIUM_BROWSER === 'chrome') {
// check for console errors
const errors = await f.checkBrowserForConsoleErrors(driver)
if (errors.length) {
const errorReports = errors.map(err => err.message)
const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}`
console.log(errorMessage)
}
}
// gather extra data if test failed
if (this.currentTest.state === 'failed') {
await f.verboseReportOnFailure(this.currentTest)
}
})
// after(async function () {
// await driver.quit()
// })
after(async function () {
await driver.quit()
})
// describe('Setup', async () => {
// await setup(f)
// })
describe('Setup', async () => {
await setup(f)
})
// describe('Log In', async () => {
// await login(f, password)
// })
describe('Log In', async () => {
await login(f, password)
})
// describe('Account Creation', async () => {
// await accountCreation(f, password)
// })
describe('Account Creation', async () => {
await accountCreation(f, password)
})
// describe('Connect Hardware Wallet', async () => {
// await connectHDWallet(f)
// })
describe('Connect Hardware Wallet', async () => {
await connectHDWallet(f)
})
// describe('Import Account', async () => {
// await importAccount(f)
// })
describe('Import Account', async () => {
await importAccount(f)
})
// describe('Import Contract account', async () => {
// await importContractAccount(f, account1, getCreatedAccounts)
// })
describe('Import Contract account', async () => {
await importContractAccount(f, account1, getCreatedAccounts)
})
// describe('Delete Imported Account', async () => {
// await deleteImportedAccount(f)
// })
describe('Delete Imported Account', async () => {
await deleteImportedAccount(f)
})
// describe('Sign Data', async () => {
// await signData(f)
// })
describe('Sign Data', async () => {
await signData(f)
})
// describe('Export private key', async () => {
// await exportPrivateKey(f, password)
// })
describe('Export private key', async () => {
await exportPrivateKey(f, password)
})
// describe('Import Ganache seed phrase', async () => {
// await importGanacheSeedPhrase(f, account2, password)
// })
describe('Import Ganache seed phrase', async () => {
await importGanacheSeedPhrase(f, account2, password)
})
// describe('RSK network tests', async () => {
// await RSKNetworkTests(f, account1RSK)
// })
describe('RSK network tests', async () => {
await RSKNetworkTests(f, account1RSK)
})
// describe('Check the filter of emitted events', async () => {
// await checkEmittedEvents(f, account1, account2)
// })
describe('Check the filter of emitted events', async () => {
await checkEmittedEvents(f, account1, account2)
})
// // todo: it works locally, but doesn't work in CI
// // describe('Add Token: Custom', async () => {
// // await addCustomToken(f, account1, account2)
// // })
// todo: it works locally, but doesn't work in CI
// describe('Add Token: Custom', async () => {
// await addCustomToken(f, account1, account2)
// })
// describe('Change password', async () => {
// await changePassword(f, password, newPassword)
// })
describe('Change password', async () => {
await changePassword(f, password, newPassword)
})
// describe('Add Token:Search', async () => {
// await addTokeFromSearch(f)
// })
describe('Add Token:Search', async () => {
await addTokeFromSearch(f)
})
// describe('Custom RPC', async () => {
// await customRPC(f)
// })
// })
describe('Custom RPC', async () => {
await customRPC(f)
})
})

View File

@ -45,7 +45,7 @@ const { screens, elements, NETWORKS } = require('../elements')
const [tab0, tab1] = await f.driver.getAllWindowHandles()
await f.driver.switchTo().window(tab1)
const faucetLink = await f.driver.getCurrentUrl()
assert.equal(faucetLink, 'https://faucet.testnet.rsk.co/', 'Incorrect faucet link for RSK network')
assert.equal(faucetLink, 'https://faucet.rsk.co/', 'Incorrect faucet link for RSK network')
await f.driver.close()
await f.driver.switchTo().window(tab0)
const arrow = await f.waitUntilShowUp(elements.buttonArrow)

View File

@ -1,6 +1,6 @@
const assert = require('assert')
const currencyFormatter = require('currency-formatter')
const infuraConversion = require('../../ui/app/infura-conversion.json')
const infuraConversion = require('../../old-ui/app/infura-conversion.json')
describe('currencyFormatting', function () {
it('be able to format any infura currency', function (done) {

View File

@ -1,87 +0,0 @@
const Component = require('react').Component
const h = require('react-hyperscript')
const PropTypes = require('prop-types')
const inherits = require('util').inherits
const connect = require('react-redux').connect
const actions = require('../../actions')
const { getCurrentViewContext } = require('../../selectors')
const classnames = require('classnames')
const NewAccountCreateForm = require('./create-form')
const NewAccountImportForm = require('../import')
function mapStateToProps (state) {
return {
displayedForm: getCurrentViewContext(state),
}
}
function mapDispatchToProps (dispatch) {
return {
displayForm: form => dispatch(actions.setNewAccountForm(form)),
showQrView: (selected, identity) => dispatch(actions.showQrView(selected, identity)),
showExportPrivateKeyModal: () => {
dispatch(actions.showModal({ name: 'EXPORT_PRIVATE_KEY' }))
},
hideModal: () => dispatch(actions.hideModal()),
setAccountLabel: (address, label) => dispatch(actions.setAccountLabel(address, label)),
}
}
inherits(AccountDetailsModal, Component)
function AccountDetailsModal (props) {
Component.call(this)
this.state = {
displayedForm: props.displayedForm,
}
}
AccountDetailsModal.contextTypes = {
t: PropTypes.func,
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(AccountDetailsModal)
AccountDetailsModal.prototype.render = function () {
const { displayedForm, displayForm } = this.props
return h('div.new-account', {}, [
h('div.new-account__header', [
h('div.new-account__title', this.context.t('newAccount')),
h('div.new-account__tabs', [
h('div.new-account__tabs__tab', {
className: classnames('new-account__tabs__tab', {
'new-account__tabs__selected': displayedForm === 'CREATE',
'new-account__tabs__unselected cursor-pointer': displayedForm !== 'CREATE',
}),
onClick: () => displayForm('CREATE'),
}, this.context.t('createDen')),
h('div.new-account__tabs__tab', {
className: classnames('new-account__tabs__tab', {
'new-account__tabs__selected': displayedForm === 'IMPORT',
'new-account__tabs__unselected cursor-pointer': displayedForm !== 'IMPORT',
}),
onClick: () => displayForm('IMPORT'),
}, this.context.t('import')),
]),
]),
h('div.new-account__form', [
displayedForm === 'CREATE'
? h(NewAccountCreateForm)
: h(NewAccountImportForm),
]),
])
}

View File

@ -1,365 +0,0 @@
const { Component } = require('react')
const PropTypes = require('prop-types')
const connect = require('react-redux').connect
const { Route, Switch, withRouter } = require('react-router-dom')
const { compose } = require('recompose')
const h = require('react-hyperscript')
const actions = require('./actions')
const classnames = require('classnames')
const log = require('loglevel')
const { getMetaMaskAccounts } = require('./selectors')
// init
const InitializeScreen = require('../../mascara/src/app/first-time').default
// accounts
const SendTransactionScreen = require('./components/send/send.container')
const ConfirmTransaction = require('./components/pages/confirm-transaction')
// slideout menu
const Sidebar = require('./components/sidebars').default
// other views
import Home from './components/pages/home'
import Settings from './components/pages/settings'
const Authenticated = require('./components/pages/authenticated')
const Initialized = require('./components/pages/initialized')
const RestoreVaultPage = require('./components/pages/keychains/restore-vault').default
const RevealSeedConfirmation = require('./components/pages/keychains/reveal-seed')
const AddTokenPage = require('./components/pages/add-token')
const ConfirmAddTokenPage = require('./components/pages/confirm-add-token')
const ConfirmAddSuggestedTokenPage = require('./components/pages/confirm-add-suggested-token')
const CreateAccountPage = require('./components/pages/create-account')
const NoticeScreen = require('./components/pages/notice')
const Loading = require('./components/loading-screen')
const NetworkDropdown = require('./components/dropdowns/network-dropdown')
const AccountMenu = require('./components/account-menu')
// Global Modals
const Modal = require('./components/modals/index').Modal
// Global Alert
const Alert = require('./components/alert')
import AppHeader from './components/app-header'
import UnlockPage from './components/pages/unlock-page'
// Routes
const {
DEFAULT_ROUTE,
UNLOCK_ROUTE,
SETTINGS_ROUTE,
REVEAL_SEED_ROUTE,
RESTORE_VAULT_ROUTE,
ADD_TOKEN_ROUTE,
CONFIRM_ADD_TOKEN_ROUTE,
CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE,
NEW_ACCOUNT_ROUTE,
SEND_ROUTE,
CONFIRM_TRANSACTION_ROUTE,
INITIALIZE_ROUTE,
NOTICE_ROUTE,
} = require('./routes')
class App extends Component {
componentWillMount () {
const { currentCurrency, setCurrentCurrencyToUSD } = this.props
if (!currentCurrency) {
setCurrentCurrencyToUSD()
}
}
renderRoutes () {
const exact = true
return (
h(Switch, [
h(Route, { path: INITIALIZE_ROUTE, component: InitializeScreen }),
h(Initialized, { path: UNLOCK_ROUTE, exact, component: UnlockPage }),
h(Initialized, { path: RESTORE_VAULT_ROUTE, exact, component: RestoreVaultPage }),
h(Authenticated, { path: REVEAL_SEED_ROUTE, exact, component: RevealSeedConfirmation }),
h(Authenticated, { path: SETTINGS_ROUTE, component: Settings }),
h(Authenticated, { path: NOTICE_ROUTE, exact, component: NoticeScreen }),
h(Authenticated, {
path: `${CONFIRM_TRANSACTION_ROUTE}/:id?`,
component: ConfirmTransaction,
}),
h(Authenticated, { path: SEND_ROUTE, exact, component: SendTransactionScreen }),
h(Authenticated, { path: ADD_TOKEN_ROUTE, exact, component: AddTokenPage }),
h(Authenticated, { path: CONFIRM_ADD_TOKEN_ROUTE, exact, component: ConfirmAddTokenPage }),
h(Authenticated, { path: CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE, exact, component: ConfirmAddSuggestedTokenPage }),
h(Authenticated, { path: NEW_ACCOUNT_ROUTE, component: CreateAccountPage }),
h(Authenticated, { path: DEFAULT_ROUTE, exact, component: Home }),
])
)
}
render () {
const {
isLoading,
alertMessage,
loadingMessage,
network,
isMouseUser,
provider,
frequentRpcList,
currentView,
setMouseUserState,
sidebar,
} = this.props
const isLoadingNetwork = network === 'loading' && currentView.name !== 'config'
const loadMessage = loadingMessage || isLoadingNetwork ?
this.getConnectingLabel(loadingMessage) : null
log.debug('Main ui render function')
return (
h('.flex-column.full-height', {
className: classnames({ 'mouse-user-styles': isMouseUser }),
style: {
overflowX: 'hidden',
position: 'relative',
alignItems: 'center',
},
tabIndex: '0',
onClick: () => setMouseUserState(true),
onKeyDown: (e) => {
if (e.keyCode === 9) {
setMouseUserState(false)
}
},
}, [
// global modal
h(Modal, {}, []),
// global alert
h(Alert, {visible: this.props.alertOpen, msg: alertMessage}),
h(AppHeader),
// sidebar
h(Sidebar, {
sidebarOpen: sidebar.isOpen,
hideSidebar: this.props.hideSidebar,
transitionName: sidebar.transitionName,
type: sidebar.type,
}),
// network dropdown
h(NetworkDropdown, {
provider,
frequentRpcList,
}, []),
h(AccountMenu),
h('div.main-container-wrapper', [
(isLoading || isLoadingNetwork) && h(Loading, {
loadingMessage: loadMessage,
}),
// content
this.renderRoutes(),
]),
])
)
}
toggleMetamaskActive () {
if (!this.props.isUnlocked) {
// currently inactive: redirect to password box
var passwordBox = document.querySelector('input[type=password]')
if (!passwordBox) return
passwordBox.focus()
} else {
// currently active: deactivate
this.props.dispatch(actions.lockMetamask(false))
}
}
getConnectingLabel = function (loadingMessage) {
if (loadingMessage) {
return loadingMessage
}
const { provider } = this.props
const providerName = provider.type
let name
if (providerName === 'mainnet') {
name = this.context.t('connectingToMainnet')
} else if (providerName === 'ropsten') {
name = this.context.t('connectingToRopsten')
} else if (providerName === 'kovan') {
name = this.context.t('connectingToKovan')
} else if (providerName === 'rinkeby') {
name = this.context.t('connectingToRinkeby')
} else if (providerName === 'poa') {
name = this.context.t('connectingToPOA')
} else {
name = this.context.t('connectingToUnknown')
}
return name
}
getNetworkName () {
const { provider } = this.props
const providerName = provider.type
let name
if (providerName === 'mainnet') {
name = this.context.t('mainnet')
} else if (providerName === 'ropsten') {
name = this.context.t('ropsten')
} else if (providerName === 'kovan') {
name = this.context.t('kovan')
} else if (providerName === 'rinkeby') {
name = this.context.t('rinkeby')
} else if (providerName === 'poa') {
name = this.context.t('poa')
} else {
name = this.context.t('unknownNetwork')
}
return name
}
}
App.propTypes = {
currentCurrency: PropTypes.string,
setCurrentCurrencyToUSD: PropTypes.func,
isLoading: PropTypes.bool,
loadingMessage: PropTypes.string,
alertMessage: PropTypes.string,
network: PropTypes.string,
provider: PropTypes.object,
frequentRpcList: PropTypes.array,
currentView: PropTypes.object,
sidebar: PropTypes.object,
alertOpen: PropTypes.bool,
hideSidebar: PropTypes.func,
isMascara: PropTypes.bool,
isOnboarding: PropTypes.bool,
isUnlocked: PropTypes.bool,
networkDropdownOpen: PropTypes.bool,
showNetworkDropdown: PropTypes.func,
hideNetworkDropdown: PropTypes.func,
history: PropTypes.object,
location: PropTypes.object,
dispatch: PropTypes.func,
toggleAccountMenu: PropTypes.func,
selectedAddress: PropTypes.string,
noActiveNotices: PropTypes.bool,
lostAccounts: PropTypes.array,
isInitialized: PropTypes.bool,
forgottenPassword: PropTypes.bool,
activeAddress: PropTypes.string,
unapprovedTxs: PropTypes.object,
seedWords: PropTypes.string,
unapprovedMsgCount: PropTypes.number,
unapprovedPersonalMsgCount: PropTypes.number,
unapprovedTypedMessagesCount: PropTypes.number,
welcomeScreenSeen: PropTypes.bool,
isPopup: PropTypes.bool,
betaUI: PropTypes.bool,
isMouseUser: PropTypes.bool,
setMouseUserState: PropTypes.func,
t: PropTypes.func,
}
function mapStateToProps (state) {
const { appState, metamask } = state
const {
networkDropdownOpen,
sidebar,
alertOpen,
alertMessage,
isLoading,
loadingMessage,
} = appState
const accounts = getMetaMaskAccounts(state)
const {
identities,
address,
keyrings,
isInitialized,
noActiveNotices,
seedWords,
unapprovedTxs,
nextUnreadNotice,
lostAccounts,
unapprovedMsgCount,
unapprovedPersonalMsgCount,
unapprovedTypedMessagesCount,
} = metamask
const selected = address || Object.keys(accounts)[0]
return {
// state from plugin
networkDropdownOpen,
sidebar,
alertOpen,
alertMessage,
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),
isPopup: state.metamask.isPopup,
seedWords: state.metamask.seedWords,
unapprovedTxs,
unapprovedMsgs: state.metamask.unapprovedMsgs,
unapprovedMsgCount,
unapprovedPersonalMsgCount,
unapprovedTypedMessagesCount,
menuOpen: state.appState.menuOpen,
network: state.metamask.network,
provider: state.metamask.provider,
forgottenPassword: state.appState.forgottenPassword,
nextUnreadNotice,
lostAccounts,
frequentRpcList: state.metamask.frequentRpcList || [],
currentCurrency: state.metamask.currentCurrency,
isMouseUser: state.appState.isMouseUser,
betaUI: state.metamask.featureFlags.betaUI,
isRevealingSeedWords: state.metamask.isRevealingSeedWords,
Qr: state.appState.Qr,
welcomeScreenSeen: state.metamask.welcomeScreenSeen,
// 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()),
setMouseUserState: (isMouseUser) => dispatch(actions.setMouseUserState(isMouseUser)),
}
}
App.contextTypes = {
t: PropTypes.func,
}
module.exports = compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(App)

View File

@ -1,324 +0,0 @@
const Component = require('react').Component
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const actions = require('../actions')
const ethNetProps = require('eth-net-props')
const connect = require('react-redux').connect
const Dropdown = require('./dropdown').Dropdown
const DropdownMenuItem = require('./dropdown').DropdownMenuItem
const Identicon = require('./identicon')
const copyToClipboard = require('copy-to-clipboard')
const { checksumAddress } = require('../util')
class AccountDropdowns extends Component {
constructor (props) {
super(props)
this.state = {
accountSelectorActive: false,
optionsMenuActive: false,
}
this.accountSelectorToggleClassName = 'accounts-selector'
this.optionsMenuToggleClassName = 'fa-ellipsis-h'
}
renderAccounts () {
const { identities, selected, keyrings } = this.props
return Object.keys(identities).map((key, index) => {
const identity = identities[key]
const isSelected = identity.address === selected
const simpleAddress = identity.address.substring(2).toLowerCase()
const keyring = keyrings.find((kr) => {
return kr.accounts.includes(simpleAddress) ||
kr.accounts.includes(identity.address)
})
return h(
DropdownMenuItem,
{
closeMenu: () => {},
onClick: () => {
this.props.actions.showAccountDetail(identity.address)
},
style: {
marginTop: index === 0 ? '5px' : '',
fontSize: '24px',
},
},
[
h(
Identicon,
{
address: identity.address,
diameter: 32,
style: {
marginLeft: '10px',
},
},
),
this.indicateIfLoose(keyring),
h('span', {
style: {
marginLeft: '20px',
fontSize: '24px',
maxWidth: '145px',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
},
}, identity.name || ''),
h('span', { style: { marginLeft: '20px', fontSize: '24px' } }, isSelected ? h('.check', '✓') : null),
]
)
})
}
indicateIfLoose (keyring) {
try { // Sometimes keyrings aren't loaded yet:
const type = keyring.type
const isLoose = type !== 'HD Key Tree'
return isLoose ? h('.keyring-label.allcaps', this.context.t('loose')) : null
} catch (e) { return }
}
renderAccountSelector () {
const { actions } = this.props
const { accountSelectorActive } = this.state
return h(
Dropdown,
{
useCssTransition: true, // Hardcoded because account selector is temporarily in app-header
style: {
marginLeft: '-238px',
marginTop: '38px',
minWidth: '180px',
overflowY: 'auto',
maxHeight: '300px',
width: '300px',
},
innerStyle: {
padding: '8px 25px',
},
isOpen: accountSelectorActive,
onClickOutside: (event) => {
const { classList } = event.target
const isNotToggleElement = !classList.contains(this.accountSelectorToggleClassName)
if (accountSelectorActive && isNotToggleElement) {
this.setState({ accountSelectorActive: false })
}
},
},
[
...this.renderAccounts(),
h(
DropdownMenuItem,
{
closeMenu: () => {},
onClick: () => actions.addNewAccount(),
},
[
h(
Identicon,
{
style: {
marginLeft: '10px',
},
diameter: 32,
},
),
h('span', { style: { marginLeft: '20px', fontSize: '24px' } }, this.context.t('createAccount')),
],
),
h(
DropdownMenuItem,
{
closeMenu: () => {},
onClick: () => actions.showImportPage(),
},
[
h(
Identicon,
{
style: {
marginLeft: '10px',
},
diameter: 32,
},
),
h('span', {
style: {
marginLeft: '20px',
fontSize: '24px',
marginBottom: '5px',
},
}, this.context.t('importAccount')),
]
),
]
)
}
renderAccountOptions () {
const { actions } = this.props
const { optionsMenuActive } = this.state
return h(
Dropdown,
{
style: {
marginLeft: '-215px',
minWidth: '180px',
},
isOpen: optionsMenuActive,
onClickOutside: (event) => {
const { classList } = event.target
const isNotToggleElement = !classList.contains(this.optionsMenuToggleClassName)
if (optionsMenuActive && isNotToggleElement) {
this.setState({ optionsMenuActive: false })
}
},
},
[
h(
DropdownMenuItem,
{
closeMenu: () => {},
onClick: () => {
const { selected, network } = this.props
const url = ethNetProps.explorerLinks.getExplorerAccountLinkFor(selected, network)
global.platform.openWindow({ url })
},
},
this.context.t('etherscanView'),
),
h(
DropdownMenuItem,
{
closeMenu: () => {},
onClick: () => {
const { selected, identities } = this.props
var identity = identities[selected]
actions.showQrView(selected, identity ? identity.name : '')
},
},
this.context.t('showQRCode'),
),
h(
DropdownMenuItem,
{
closeMenu: () => {},
onClick: () => {
const { selected } = this.props
copyToClipboard(checksumAddress(selected))
},
},
this.context.t('copyAddress'),
),
h(
DropdownMenuItem,
{
closeMenu: () => {},
onClick: () => {
actions.requestAccountExport()
},
},
this.context.t('exportPrivateKey'),
),
]
)
}
render () {
const { style, enableAccountsSelector, enableAccountOptions } = this.props
const { optionsMenuActive, accountSelectorActive } = this.state
return h(
'span',
{
style: style,
},
[
enableAccountsSelector && h(
// 'i.fa.fa-angle-down',
'div.cursor-pointer.color-orange.accounts-selector',
{
style: {
// fontSize: '1.8em',
background: 'url(images/switch_acc.svg) white center center no-repeat',
height: '25px',
width: '25px',
transform: 'scale(0.75)',
marginRight: '3px',
},
onClick: (event) => {
event.stopPropagation()
this.setState({
accountSelectorActive: !accountSelectorActive,
optionsMenuActive: false,
})
},
},
this.renderAccountSelector(),
),
enableAccountOptions && h(
'i.fa.fa-ellipsis-h',
{
style: {
marginRight: '0.5em',
fontSize: '1.8em',
},
onClick: (event) => {
event.stopPropagation()
this.setState({
accountSelectorActive: false,
optionsMenuActive: !optionsMenuActive,
})
},
},
this.renderAccountOptions()
),
]
)
}
}
AccountDropdowns.defaultProps = {
enableAccountsSelector: false,
enableAccountOptions: false,
}
AccountDropdowns.propTypes = {
identities: PropTypes.objectOf(PropTypes.object),
selected: PropTypes.string,
keyrings: PropTypes.array,
actions: PropTypes.objectOf(PropTypes.func),
network: PropTypes.string,
style: PropTypes.object,
enableAccountOptions: PropTypes.bool,
enableAccountsSelector: PropTypes.bool,
t: PropTypes.func,
}
const mapDispatchToProps = (dispatch) => {
return {
actions: {
showConfigPage: () => dispatch(actions.showConfigPage()),
requestAccountExport: () => dispatch(actions.requestExportAccount()),
showAccountDetail: (address) => dispatch(actions.showAccountDetail(address)),
addNewAccount: () => dispatch(actions.addNewAccount()),
showImportPage: () => dispatch(actions.showImportPage()),
showQrView: (selected, identity) => dispatch(actions.showQrView(selected, identity)),
},
}
}
AccountDropdowns.contextTypes = {
t: PropTypes.func,
}
module.exports = {
AccountDropdowns: connect(null, mapDispatchToProps)(AccountDropdowns),
}

View File

@ -1,138 +0,0 @@
const Component = require('react').Component
const h = require('react-hyperscript')
const PropTypes = require('prop-types')
const inherits = require('util').inherits
const exportAsFile = require('../util').exportAsFile
const copyToClipboard = require('copy-to-clipboard')
const actions = require('../actions')
const ethUtil = require('ethereumjs-util')
const connect = require('react-redux').connect
ExportAccountView.contextTypes = {
t: PropTypes.func,
}
module.exports = connect(mapStateToProps)(ExportAccountView)
inherits(ExportAccountView, Component)
function ExportAccountView () {
Component.call(this)
}
function mapStateToProps (state) {
return {
warning: state.appState.warning,
}
}
ExportAccountView.prototype.render = function () {
const state = this.props
const accountDetail = state.accountDetail
const nickname = state.identities[state.address].name
if (!accountDetail) return h('div')
const accountExport = accountDetail.accountExport
const notExporting = accountExport === 'none'
const exportRequested = accountExport === 'requested'
const accountExported = accountExport === 'completed'
if (notExporting) return h('div')
if (exportRequested) {
const warning = this.context.t('exportPrivateKeyWarning')
return (
h('div', {
style: {
display: 'inline-block',
textAlign: 'center',
},
},
[
h('div', {
key: 'exporting',
style: {
margin: '0 20px',
},
}, [
h('p.error', warning),
h('input#exportAccount.sizing-input', {
type: 'password',
placeholder: this.context.t('confirmPassword').toLowerCase(),
onKeyPress: this.onExportKeyPress.bind(this),
style: {
position: 'relative',
top: '1.5px',
marginBottom: '7px',
},
}),
]),
h('div', {
key: 'buttons',
style: {
margin: '0 20px',
},
},
[
h('button', {
onClick: () => this.onExportKeyPress({ key: 'Enter', preventDefault: () => {} }),
style: {
marginRight: '10px',
},
}, this.context.t('submit')),
h('button', {
onClick: () => this.props.dispatch(actions.backToAccountDetail(this.props.address)),
}, this.context.t('cancel')),
]),
(this.props.warning) && (
h('span.error', {
style: {
margin: '20px',
},
}, this.props.warning.split('-'))
),
])
)
}
if (accountExported) {
const plainKey = ethUtil.stripHexPrefix(accountDetail.privateKey)
return h('div.privateKey', {
style: {
margin: '0 20px',
},
}, [
h('label', this.context.t('copyPrivateKey') + ':'),
h('p.error.cursor-pointer', {
style: {
textOverflow: 'ellipsis',
overflow: 'hidden',
webkitUserSelect: 'text',
maxWidth: '275px',
},
onClick: function (event) {
copyToClipboard(ethUtil.stripHexPrefix(accountDetail.privateKey))
},
}, plainKey),
h('button', {
onClick: () => this.props.dispatch(actions.backToAccountDetail(this.props.address)),
}, this.context.t('done')),
h('button', {
style: {
marginLeft: '10px',
},
onClick: () => exportAsFile(`Nifty Wallet ${nickname} Private Key`, plainKey),
}, this.context.t('saveAsFile')),
])
}
}
ExportAccountView.prototype.onExportKeyPress = function (event) {
if (event.key !== 'Enter') return
event.preventDefault()
const input = document.getElementById('exportAccount').value
this.props.dispatch(actions.exportAccount(input, this.props.address))
}

View File

@ -1,249 +0,0 @@
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 PropTypes = require('prop-types')
const h = require('react-hyperscript')
const actions = require('../../actions')
const { Menu, Item, Divider, CloseArea } = require('../dropdowns/components/menu')
const Identicon = require('../identicon')
const { ENVIRONMENT_TYPE_POPUP } = require('../../../../app/scripts/lib/enums')
const { getEnvironmentType } = require('../../../../app/scripts/lib/util')
const Tooltip = require('../tooltip')
import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display'
import { PRIMARY } from '../../constants/common'
import { getMetaMaskAccounts } from '../../selectors'
const {
SETTINGS_ROUTE,
INFO_ROUTE,
NEW_ACCOUNT_ROUTE,
IMPORT_ACCOUNT_ROUTE,
CONNECT_HARDWARE_ROUTE,
DEFAULT_ROUTE,
} = require('../../routes')
module.exports = compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(AccountMenu)
AccountMenu.contextTypes = {
t: PropTypes.func,
}
inherits(AccountMenu, Component)
function AccountMenu () { Component.call(this) }
function mapStateToProps (state) {
return {
selectedAddress: state.metamask.selectedAddress,
isAccountMenuOpen: state.metamask.isAccountMenuOpen,
keyrings: state.metamask.keyrings,
identities: state.metamask.identities,
accounts: getMetaMaskAccounts(state),
}
}
function mapDispatchToProps (dispatch) {
return {
toggleAccountMenu: () => dispatch(actions.toggleAccountMenu()),
showAccountDetail: address => {
dispatch(actions.showAccountDetail(address))
dispatch(actions.hideSidebar())
dispatch(actions.toggleAccountMenu())
},
lockMetamask: () => {
dispatch(actions.lockMetamask())
dispatch(actions.hideWarning())
dispatch(actions.hideSidebar())
dispatch(actions.toggleAccountMenu())
},
showConfigPage: () => {
dispatch(actions.showConfigPage())
dispatch(actions.hideSidebar())
dispatch(actions.toggleAccountMenu())
},
showInfoPage: () => {
dispatch(actions.showInfoPage())
dispatch(actions.hideSidebar())
dispatch(actions.toggleAccountMenu())
},
showRemoveAccountConfirmationModal: (identity) => {
return dispatch(actions.showModal({ name: 'CONFIRM_REMOVE_ACCOUNT', identity }))
},
}
}
AccountMenu.prototype.render = function () {
const {
isAccountMenuOpen,
toggleAccountMenu,
lockMetamask,
history,
} = this.props
return h(Menu, { className: 'account-menu', isShowing: isAccountMenuOpen }, [
h(CloseArea, { onClick: toggleAccountMenu }),
h(Item, {
className: 'account-menu__header',
}, [
this.context.t('myAccounts'),
h('button.account-menu__logout-button', {
onClick: () => {
lockMetamask()
history.push(DEFAULT_ROUTE)
},
}, this.context.t('logout')),
]),
h(Divider),
h('div.account-menu__accounts', this.renderAccounts()),
h(Divider),
h(Item, {
onClick: () => {
toggleAccountMenu()
history.push(NEW_ACCOUNT_ROUTE)
},
icon: h('img.account-menu__item-icon', { src: 'images/plus-btn-white.svg' }),
text: this.context.t('createAccount'),
}),
h(Item, {
onClick: () => {
toggleAccountMenu()
history.push(IMPORT_ACCOUNT_ROUTE)
},
icon: h('img.account-menu__item-icon', { src: 'images/import-account.svg' }),
text: this.context.t('importAccount'),
}),
h(Item, {
onClick: () => {
toggleAccountMenu()
if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP) {
global.platform.openExtensionInBrowser(CONNECT_HARDWARE_ROUTE)
} else {
history.push(CONNECT_HARDWARE_ROUTE)
}
},
icon: h('img.account-menu__item-icon', { src: 'images/connect-icon.svg' }),
text: this.context.t('connectHardwareWallet'),
}),
h(Divider),
h(Item, {
onClick: () => {
toggleAccountMenu()
history.push(INFO_ROUTE)
},
icon: h('img', { src: 'images/mm-info-icon.svg' }),
text: this.context.t('infoHelp'),
}),
h(Item, {
onClick: () => {
toggleAccountMenu()
history.push(SETTINGS_ROUTE)
},
icon: h('img.account-menu__item-icon', { src: 'images/settings.svg' }),
text: this.context.t('settings'),
}),
])
}
AccountMenu.prototype.renderAccounts = function () {
const {
identities,
accounts,
selectedAddress,
keyrings,
showAccountDetail,
} = this.props
const accountOrder = keyrings.reduce((list, keyring) => list.concat(keyring.accounts), [])
return accountOrder.filter(address => !!identities[address]).map((address) => {
const identity = identities[address]
const isSelected = identity.address === selectedAddress
const balanceValue = accounts[address] ? accounts[address].balance : ''
const simpleAddress = identity.address.substring(2).toLowerCase()
const keyring = keyrings.find((kr) => {
return kr.accounts.includes(simpleAddress) ||
kr.accounts.includes(identity.address)
})
return h(
'div.account-menu__account.menu__item--clickable',
{ onClick: () => showAccountDetail(identity.address) },
[
h('div.account-menu__check-mark', [
isSelected ? h('div.account-menu__check-mark-icon') : null,
]),
h(
Identicon,
{
address: identity.address,
diameter: 24,
},
),
h('div.account-menu__account-info', [
h('div.account-menu__name', identity.name || ''),
h(UserPreferencedCurrencyDisplay, {
className: 'account-menu__balance',
value: balanceValue,
type: PRIMARY,
}),
]),
this.renderKeyringType(keyring),
this.renderRemoveAccount(keyring, identity),
],
)
})
}
AccountMenu.prototype.renderRemoveAccount = function (keyring, identity) {
// Any account that's not from the HD wallet Keyring can be removed
const type = keyring.type
const isRemovable = type !== 'HD Key Tree'
if (isRemovable) {
return h(Tooltip, {
title: this.context.t('removeAccount'),
position: 'bottom',
}, [
h('a.remove-account-icon', {
onClick: (e) => this.removeAccount(e, identity),
}, ''),
])
}
return null
}
AccountMenu.prototype.removeAccount = function (e, identity) {
e.preventDefault()
e.stopPropagation()
const { showRemoveAccountConfirmationModal } = this.props
showRemoveAccountConfirmationModal(identity)
}
AccountMenu.prototype.renderKeyringType = function (keyring) {
try { // Sometimes keyrings aren't loaded yet:
const type = keyring.type
let label
switch (type) {
case 'Trezor Hardware':
case 'Ledger Hardware':
label = this.context.t('hardware')
break
case 'Simple Key Pair':
label = this.context.t('imported')
break
default:
label = ''
}
return label !== '' ? h('.keyring-label.allcaps', label) : null
} catch (e) { return }
}

View File

@ -1,86 +0,0 @@
const inherits = require('util').inherits
const Component = require('react').Component
const h = require('react-hyperscript')
const Identicon = require('./identicon')
const formatBalance = require('../util').formatBalance
const addressSummary = require('../util').addressSummary
module.exports = AccountPanel
inherits(AccountPanel, Component)
function AccountPanel () {
Component.call(this)
}
AccountPanel.prototype.render = function () {
var state = this.props
var identity = state.identity || {}
var account = state.account || {}
var isFauceting = state.isFauceting
var panelState = {
key: `accountPanel${identity.address}`,
identiconKey: identity.address,
identiconLabel: identity.name || '',
attributes: [
{
key: 'ADDRESS',
value: addressSummary(identity.address),
},
balanceOrFaucetingIndication(account, isFauceting),
],
}
return (
h('.identity-panel.flex-row.flex-space-between', {
style: {
flex: '1 0 auto',
cursor: panelState.onClick ? 'pointer' : undefined,
},
onClick: panelState.onClick,
}, [
// account identicon
h('.identicon-wrapper.flex-column.select-none', [
h(Identicon, {
address: panelState.identiconKey,
imageify: state.imageifyIdenticons,
}),
h('span.font-small', panelState.identiconLabel.substring(0, 7) + '...'),
]),
// account address, balance
h('.identity-data.flex-column.flex-justify-center.flex-grow.select-none', [
panelState.attributes.map((attr) => {
return h('.flex-row.flex-space-between', {
key: '' + Math.round(Math.random() * 1000000),
}, [
h('label.font-small.no-select', attr.key),
h('span.font-small', attr.value),
])
}),
]),
])
)
}
function balanceOrFaucetingIndication (account, isFauceting) {
// Temporarily deactivating isFauceting indication
// because it shows fauceting for empty restored accounts.
if (/* isFauceting*/ false) {
return {
key: 'Account is auto-funding.',
value: 'Please wait.',
}
} else {
return {
key: 'BALANCE',
value: formatBalance(account.balance),
}
}
}

View File

@ -1,34 +0,0 @@
import PropTypes from 'prop-types'
import React, {PureComponent} from 'react'
export default class AddTokenButton extends PureComponent {
static contextTypes = {
t: PropTypes.func.isRequired,
}
static defaultProps = {
onClick: () => {},
}
static propTypes = {
onClick: PropTypes.func,
}
render () {
const { t } = this.context
const { onClick } = this.props
return (
<div className="add-token-button">
<h1 className="add-token-button__help-header">{t('missingYourTokens')}</h1>
<p className="add-token-button__help-desc">{t('clickToAdd', [t('addToken')])}</p>
<div
className="add-token-button__button"
onClick={onClick}
>
{t('addToken')}
</div>
</div>
)
}
}

View File

@ -1 +0,0 @@
export { default } from './add-token-button.component'

View File

@ -1,26 +0,0 @@
.add-token-button {
display: flex;
flex-direction: column;
color: lighten($scorpion, 25%);
width: 185px;
margin: 36px auto;
text-align: center;
&__help-header {
font-weight: bold;
font-size: 1rem;
}
&__help-desc {
font-size: 0.75rem;
margin-top: 1rem;
}
&__button {
font-size: 0.75rem;
margin: 1rem;
text-transform: uppercase;
color: $curious-blue;
cursor: pointer;
}
}

View File

@ -1,62 +0,0 @@
const { Component } = require('react')
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
class Alert extends Component {
constructor (props) {
super(props)
this.state = {
visble: false,
msg: false,
className: '',
}
}
componentWillReceiveProps (nextProps) {
if (!this.props.visible && nextProps.visible) {
this.animateIn(nextProps)
} else if (this.props.visible && !nextProps.visible) {
this.animateOut(nextProps)
}
}
animateIn (props) {
this.setState({
msg: props.msg,
visible: true,
className: '.visible',
})
}
animateOut (props) {
this.setState({
msg: null,
className: '.hidden',
})
setTimeout(_ => {
this.setState({visible: false})
}, 500)
}
render () {
if (this.state.visible) {
return (
h(`div.global-alert${this.state.className}`, {},
h('a.msg', {}, this.state.msg)
)
)
}
return null
}
}
Alert.propTypes = {
visible: PropTypes.bool.isRequired,
msg: PropTypes.string,
}
module.exports = Alert

View File

@ -1,136 +0,0 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import { matchPath } from 'react-router-dom'
const {
ENVIRONMENT_TYPE_NOTIFICATION,
ENVIRONMENT_TYPE_POPUP,
} = require('../../../../app/scripts/lib/enums')
const { DEFAULT_ROUTE, INITIALIZE_ROUTE, CONFIRM_TRANSACTION_ROUTE } = require('../../routes')
const Identicon = require('../identicon')
const NetworkIndicator = require('../network')
export default class AppHeader extends PureComponent {
static propTypes = {
history: PropTypes.object,
location: PropTypes.object,
network: PropTypes.string,
provider: PropTypes.object,
networkDropdownOpen: PropTypes.bool,
showNetworkDropdown: PropTypes.func,
hideNetworkDropdown: PropTypes.func,
toggleAccountMenu: PropTypes.func,
selectedAddress: PropTypes.string,
isUnlocked: PropTypes.bool,
}
static contextTypes = {
t: PropTypes.func,
}
handleNetworkIndicatorClick (event) {
event.preventDefault()
event.stopPropagation()
const { networkDropdownOpen, showNetworkDropdown, hideNetworkDropdown } = this.props
return networkDropdownOpen === false
? showNetworkDropdown()
: hideNetworkDropdown()
}
isConfirming () {
const { location } = this.props
return Boolean(matchPath(location.pathname, {
path: CONFIRM_TRANSACTION_ROUTE, exact: false,
}))
}
renderAccountMenu () {
const { isUnlocked, toggleAccountMenu, selectedAddress } = this.props
return isUnlocked && (
<div
className={classnames('account-menu__icon', {
'account-menu__icon--disabled': this.isConfirming(),
})}
onClick={() => this.isConfirming() || toggleAccountMenu()}
>
<Identicon
address={selectedAddress}
diameter={32}
/>
</div>
)
}
hideAppHeader () {
const { location } = this.props
const isInitializing = Boolean(matchPath(location.pathname, {
path: INITIALIZE_ROUTE, exact: false,
}))
if (isInitializing) {
return true
}
if (window.METAMASK_UI_TYPE === ENVIRONMENT_TYPE_NOTIFICATION) {
return true
}
if (window.METAMASK_UI_TYPE === ENVIRONMENT_TYPE_POPUP && this.isConfirming()) {
return true
}
}
render () {
const {
network,
provider,
history,
isUnlocked,
} = this.props
if (this.hideAppHeader()) {
return null
}
return (
<div
className={classnames('app-header', { 'app-header--back-drop': isUnlocked })}>
<div className="app-header__contents">
<div
className="app-header__logo-container"
onClick={() => history.push(DEFAULT_ROUTE)}
>
<img
className="app-header__metafox-logo app-header__metafox-logo--horizontal"
src="/images/logo/metamask-logo-horizontal-beta.svg"
height={30}
/>
<img
className="app-header__metafox-logo app-header__metafox-logo--icon"
src="/images/logo/metamask-fox.svg"
height={42}
width={42}
/>
</div>
<div className="app-header__account-menu-container">
<div className="app-header__network-component-wrapper">
<NetworkIndicator
network={network}
provider={provider}
onClick={event => this.handleNetworkIndicatorClick(event)}
disabled={this.isConfirming()}
/>
</div>
{ this.renderAccountMenu() }
</div>
</div>
</div>
)
}
}

View File

@ -1,38 +0,0 @@
import { connect } from 'react-redux'
import { withRouter } from 'react-router-dom'
import { compose } from 'recompose'
import AppHeader from './app-header.component'
const actions = require('../../actions')
const mapStateToProps = state => {
const { appState, metamask } = state
const { networkDropdownOpen } = appState
const {
network,
provider,
selectedAddress,
isUnlocked,
} = metamask
return {
networkDropdownOpen,
network,
provider,
selectedAddress,
isUnlocked,
}
}
const mapDispatchToProps = dispatch => {
return {
showNetworkDropdown: () => dispatch(actions.showNetworkDropdown()),
hideNetworkDropdown: () => dispatch(actions.hideNetworkDropdown()),
toggleAccountMenu: () => dispatch(actions.toggleAccountMenu()),
}
}
export default compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(AppHeader)

View File

@ -1 +0,0 @@
export { default } from './app-header.container'

View File

@ -1,90 +0,0 @@
.app-header {
align-items: center;
background: $gallery;
position: relative;
z-index: $header-z-index;
display: flex;
flex-flow: column nowrap;
width: 100%;
flex: 0 0 auto;
@media screen and (max-width: 575px) {
padding: 12px;
box-shadow: 0 0 0 1px rgba(0, 0, 0, .08);
z-index: $mobile-header-z-index;
}
@media screen and (min-width: 576px) {
height: 75px;
justify-content: center;
&--back-drop {
&::after {
content: '';
position: absolute;
width: 100%;
height: 32px;
background: $gallery;
bottom: -32px;
}
}
}
&__metafox-logo {
cursor: pointer;
&--icon {
@media screen and (min-width: $break-large) {
display: none;
}
}
&--horizontal {
@media screen and (max-width: $break-small) {
display: none;
}
}
}
&__contents {
display: flex;
justify-content: space-between;
flex-flow: row nowrap;
width: 100%;
@media screen and (max-width: 575px) {
height: 100%;
}
@media screen and (min-width: 576px) {
width: 85vw;
}
@media screen and (min-width: 769px) {
width: 80vw;
}
@media screen and (min-width: 1281px) {
width: 62vw;
}
}
&__logo-container {
display: flex;
flex-direction: row;
align-items: center;
cursor: pointer;
}
&__account-menu-container {
display: flex;
flex-flow: row nowrap;
align-items: center;
}
&__network-component-wrapper {
display: flex;
flex-direction: row;
align-items: center;
}
}

View File

@ -1,109 +0,0 @@
const Component = require('react').Component
const connect = require('react-redux').connect
const h = require('react-hyperscript')
const inherits = require('util').inherits
const TokenBalance = require('./token-balance')
const Identicon = require('./identicon')
import UserPreferencedCurrencyDisplay from './user-preferenced-currency-display'
import { PRIMARY, SECONDARY } from '../constants/common'
const { getAssetImages, conversionRateSelector, getCurrentCurrency, getMetaMaskAccounts } = require('../selectors')
const { formatBalance } = require('../util')
module.exports = connect(mapStateToProps)(BalanceComponent)
function mapStateToProps (state) {
const accounts = getMetaMaskAccounts(state)
const network = state.metamask.network
const selectedAddress = state.metamask.selectedAddress || Object.keys(accounts)[0]
const account = accounts[selectedAddress]
return {
account,
network,
conversionRate: conversionRateSelector(state),
currentCurrency: getCurrentCurrency(state),
assetImages: getAssetImages(state),
}
}
inherits(BalanceComponent, Component)
function BalanceComponent () {
Component.call(this)
}
BalanceComponent.prototype.render = function () {
const props = this.props
const { token, network, assetImages } = props
const address = token && token.address
const image = assetImages && address ? assetImages[token.address] : undefined
return h('div.balance-container', {}, [
// TODO: balance icon needs to be passed in
// h('img.balance-icon', {
// src: '../images/eth_logo.svg',
// style: {},
// }),
h(Identicon, {
diameter: 50,
address,
network,
image,
}),
token ? this.renderTokenBalance() : this.renderBalance(),
])
}
BalanceComponent.prototype.renderTokenBalance = function () {
const { token } = this.props
return h('div.flex-column.balance-display', [
h('div.token-amount', [ h(TokenBalance, { token }) ]),
])
}
BalanceComponent.prototype.renderBalance = function () {
const props = this.props
const { account } = props
const balanceValue = account && account.balance
const needsParse = 'needsParse' in props ? props.needsParse : true
const formattedBalance = balanceValue ? formatBalance(balanceValue, 6, needsParse) : '...'
const showFiat = 'showFiat' in props ? props.showFiat : true
if (formattedBalance === 'None' || formattedBalance === '...') {
return h('div.flex-column.balance-display', {}, [
h('div.token-amount', {
style: {},
}, formattedBalance),
])
}
return h('div.flex-column.balance-display', {}, [
h('div.token-amount', {}, h(UserPreferencedCurrencyDisplay, {
value: balanceValue,
type: PRIMARY,
ethNumberOfDecimals: 3,
})),
showFiat && h(UserPreferencedCurrencyDisplay, {
value: balanceValue,
type: SECONDARY,
ethNumberOfDecimals: 3,
}),
])
}
BalanceComponent.prototype.getFiatDisplayNumber = function (formattedBalance, conversionRate) {
if (formattedBalance === 'None') return formattedBalance
if (conversionRate === 0) return 'N/A'
const splitBalance = formattedBalance.split(' ')
const convertedNumber = (Number(splitBalance[0]) * conversionRate)
const wholePart = Math.floor(convertedNumber)
const decimalPart = convertedNumber - wholePart
return wholePart + Number(decimalPart.toPrecision(2))
}

View File

@ -1,188 +0,0 @@
const Component = require('react').Component
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const inherits = require('util').inherits
const ethUtil = require('ethereumjs-util')
const BN = ethUtil.BN
const extend = require('xtend')
const connect = require('react-redux').connect
BnAsDecimalInput.contextTypes = {
t: PropTypes.func,
}
module.exports = connect()(BnAsDecimalInput)
inherits(BnAsDecimalInput, Component)
function BnAsDecimalInput () {
this.state = { invalid: null }
Component.call(this)
}
/* Bn as Decimal Input
*
* A component for allowing easy, decimal editing
* of a passed in bn string value.
*
* On change, calls back its `onChange` function parameter
* and passes it an updated bn string.
*/
BnAsDecimalInput.prototype.render = function () {
const props = this.props
const state = this.state
const { value, scale, precision, onChange, min, max } = props
const suffix = props.suffix
const style = props.style
const valueString = value.toString(10)
const newMin = min && this.downsize(min.toString(10), scale)
const newMax = max && this.downsize(max.toString(10), scale)
const newValue = this.downsize(valueString, scale)
return (
h('.flex-column', [
h('.flex-row', {
style: {
alignItems: 'flex-end',
lineHeight: '13px',
fontFamily: 'Montserrat Light',
textRendering: 'geometricPrecision',
},
}, [
h('input.hex-input', {
type: 'number',
step: 'any',
required: true,
min: newMin,
max: newMax,
style: extend({
display: 'block',
textAlign: 'right',
backgroundColor: 'transparent',
border: '1px solid #bdbdbd',
}, style),
value: newValue,
onBlur: (event) => {
this.updateValidity(event)
},
onChange: (event) => {
this.updateValidity(event)
const value = (event.target.value === '') ? '' : event.target.value
const scaledNumber = this.upsize(value, scale, precision)
const precisionBN = new BN(scaledNumber, 10)
onChange(precisionBN, event.target.checkValidity())
},
onInvalid: (event) => {
const msg = this.constructWarning()
if (msg === state.invalid) {
return
}
this.setState({ invalid: msg })
event.preventDefault()
return false
},
}),
h('div', {
style: {
color: ' #AEAEAE',
fontSize: '12px',
marginLeft: '5px',
marginRight: '6px',
width: '20px',
},
}, suffix),
]),
state.invalid ? h('span.error', {
style: {
position: 'absolute',
right: '0px',
textAlign: 'right',
transform: 'translateY(26px)',
padding: '3px',
background: 'rgba(255,255,255,0.85)',
zIndex: '1',
textTransform: 'capitalize',
border: '2px solid #E20202',
},
}, state.invalid) : null,
])
)
}
BnAsDecimalInput.prototype.setValid = function (message) {
this.setState({ invalid: null })
}
BnAsDecimalInput.prototype.updateValidity = function (event) {
const target = event.target
const value = this.props.value
const newValue = target.value
if (value === newValue) {
return
}
const valid = target.checkValidity()
if (valid) {
this.setState({ invalid: null })
}
}
BnAsDecimalInput.prototype.constructWarning = function () {
const { name, min, max, scale, suffix } = this.props
const newMin = min && this.downsize(min.toString(10), scale)
const newMax = max && this.downsize(max.toString(10), scale)
let message = name ? name + ' ' : ''
if (min && max) {
message += this.context.t('betweenMinAndMax', [`${newMin} ${suffix}`, `${newMax} ${suffix}`])
} else if (min) {
message += this.context.t('greaterThanMin', [`${newMin} ${suffix}`])
} else if (max) {
message += this.context.t('lessThanMax', [`${newMax} ${suffix}`])
} else {
message += this.context.t('invalidInput')
}
return message
}
BnAsDecimalInput.prototype.downsize = function (number, scale) {
// if there is no scaling, simply return the number
if (scale === 0) {
return Number(number)
} else {
// if the scale is the same as the precision, account for this edge case.
var adjustedNumber = number
while (adjustedNumber.length < scale) {
adjustedNumber = '0' + adjustedNumber
}
return Number(adjustedNumber.slice(0, -scale) + '.' + adjustedNumber.slice(-scale))
}
}
BnAsDecimalInput.prototype.upsize = function (number, scale, precision) {
var stringArray = number.toString().split('.')
var decimalLength = stringArray[1] ? stringArray[1].length : 0
var newString = stringArray[0]
// If there is scaling and decimal parts exist, integrate them in.
if ((scale !== 0) && (decimalLength !== 0)) {
newString += stringArray[1].slice(0, precision)
}
// Add 0s to account for the upscaling.
for (var i = decimalLength; i < scale; i++) {
newString += '0'
}
return newString
}

View File

@ -1,61 +0,0 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
export default class ButtonGroup extends PureComponent {
static propTypes = {
defaultActiveButtonIndex: PropTypes.number,
disabled: PropTypes.bool,
children: PropTypes.array,
className: PropTypes.string,
style: PropTypes.object,
}
static defaultProps = {
className: 'button-group',
}
state = {
activeButtonIndex: this.props.defaultActiveButtonIndex || 0,
}
handleButtonClick (activeButtonIndex) {
this.setState({ activeButtonIndex })
}
renderButtons () {
const { children, disabled } = this.props
return React.Children.map(children, (child, index) => {
return child && (
<button
className={classnames(
'button-group__button',
{ 'button-group__button--active': index === this.state.activeButtonIndex },
)}
onClick={() => {
this.handleButtonClick(index)
child.props.onClick && child.props.onClick()
}}
disabled={disabled || child.props.disabled}
key={index}
>
{ child.props.children }
</button>
)
})
}
render () {
const { className, style } = this.props
return (
<div
className={className}
style={style}
>
{ this.renderButtons() }
</div>
)
}
}

View File

@ -1,49 +0,0 @@
import React from 'react'
import { storiesOf } from '@storybook/react'
import { action } from '@storybook/addon-actions'
import ButtonGroup from './'
import Button from '../button'
import { text, boolean } from '@storybook/addon-knobs/react'
storiesOf('ButtonGroup', module)
.add('with Buttons', () =>
<ButtonGroup
style={{ width: '300px' }}
disabled={boolean('Disabled', false)}
defaultActiveButtonIndex={1}
>
<Button
onClick={action('cheap')}
>
{text('Button1', 'Cheap')}
</Button>
<Button
onClick={action('average')}
>
{text('Button2', 'Average')}
</Button>
<Button
onClick={action('fast')}
>
{text('Button3', 'Fast')}
</Button>
</ButtonGroup>
)
.add('with a disabled Button', () =>
<ButtonGroup
style={{ width: '300px' }}
disabled={boolean('Disabled', false)}
>
<Button
onClick={action('enabled')}
>
{text('Button1', 'Enabled')}
</Button>
<Button
onClick={action('disabled')}
disabled
>
{text('Button2', 'Disabled')}
</Button>
</ButtonGroup>
)

View File

@ -1 +0,0 @@
export { default } from './button-group.component'

View File

@ -1,38 +0,0 @@
.button-group {
display: flex;
justify-content: center;
align-items: center;
&__button {
font-family: Roboto;
font-size: 1rem;
color: $tundora;
border-style: solid;
border-color: $alto;
border-width: 1px 1px 1px;
border-left: 0;
flex: 1;
padding: 12px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
&:first-child {
border-left: 1px solid $alto;
border-radius: 4px 0 0 4px;
}
&:last-child {
border-radius: 0 4px 4px 0;
}
&--active {
background-color: $dodger-blue;
color: $white;
}
&:disabled {
opacity: .5;
}
}
}

View File

@ -1,97 +0,0 @@
import React from 'react'
import assert from 'assert'
import { shallow } from 'enzyme'
import sinon from 'sinon'
import ButtonGroup from '../button-group.component.js'
const childButtonSpies = {
onClick: sinon.spy(),
}
sinon.spy(ButtonGroup.prototype, 'handleButtonClick')
sinon.spy(ButtonGroup.prototype, 'renderButtons')
const mockButtons = [
<button onClick={childButtonSpies.onClick} key={'a'}><div className="mockClass" /></button>,
<button onClick={childButtonSpies.onClick} key={'b'}></button>,
<button onClick={childButtonSpies.onClick} key={'c'}></button>,
]
describe('ButtonGroup Component', function () {
let wrapper
beforeEach(() => {
wrapper = shallow(<ButtonGroup
defaultActiveButtonIndex={1}
disabled={false}
className="someClassName"
style={ { color: 'red' } }
>{mockButtons}</ButtonGroup>)
})
afterEach(() => {
childButtonSpies.onClick.resetHistory()
ButtonGroup.prototype.handleButtonClick.resetHistory()
ButtonGroup.prototype.renderButtons.resetHistory()
})
describe('handleButtonClick', () => {
it('should set the activeButtonIndex', () => {
assert.equal(wrapper.state('activeButtonIndex'), 1)
wrapper.instance().handleButtonClick(2)
assert.equal(wrapper.state('activeButtonIndex'), 2)
})
})
describe('renderButtons', () => {
it('should render a button for each child', () => {
const childButtons = wrapper.find('.button-group__button')
assert.equal(childButtons.length, 3)
})
it('should render the correct button with an active state', () => {
const childButtons = wrapper.find('.button-group__button')
const activeChildButton = wrapper.find('.button-group__button--active')
assert.deepEqual(childButtons.get(1), activeChildButton.get(0))
})
it('should call handleButtonClick and the respective button\'s onClick method when a button is clicked', () => {
assert.equal(ButtonGroup.prototype.handleButtonClick.callCount, 0)
assert.equal(childButtonSpies.onClick.callCount, 0)
const childButtons = wrapper.find('.button-group__button')
childButtons.at(0).props().onClick()
childButtons.at(1).props().onClick()
childButtons.at(2).props().onClick()
assert.equal(ButtonGroup.prototype.handleButtonClick.callCount, 3)
assert.equal(childButtonSpies.onClick.callCount, 3)
})
it('should render all child buttons as disabled if props.disabled is true', () => {
const childButtons = wrapper.find('.button-group__button')
childButtons.forEach(button => {
assert.equal(button.props().disabled, undefined)
})
wrapper.setProps({ disabled: true })
const disabledChildButtons = wrapper.find('[disabled=true]')
assert.equal(disabledChildButtons.length, 3)
})
it('should render the children of the button', () => {
const mockClass = wrapper.find('.mockClass')
assert.equal(mockClass.length, 1)
})
})
describe('render', () => {
it('should render a div with the expected class and style', () => {
assert.equal(wrapper.find('div').at(0).props().className, 'someClassName')
assert.deepEqual(wrapper.find('div').at(0).props().style, { color: 'red' })
})
it('should call renderButtons when rendering', () => {
assert.equal(ButtonGroup.prototype.renderButtons.callCount, 1)
wrapper.instance().render()
assert.equal(ButtonGroup.prototype.renderButtons.callCount, 2)
})
})
})

View File

@ -1,25 +0,0 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
export default class Card extends PureComponent {
static propTypes = {
className: PropTypes.string,
overrideClassName: PropTypes.bool,
title: PropTypes.string,
children: PropTypes.node,
}
render () {
const { className, overrideClassName, title } = this.props
return (
<div className={classnames({ 'card': !overrideClassName }, className)}>
<div className="card__title">
{ title }
</div>
{ this.props.children }
</div>
)
}
}

View File

@ -1 +0,0 @@
export { default } from './card.component'

View File

@ -1,11 +0,0 @@
.card {
border-radius: 4px;
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.08);
padding: 8px;
&__title {
border-bottom: 1px solid #d8d8d8;
padding-bottom: 4px;
text-transform: capitalize;
}
}

View File

@ -1,25 +0,0 @@
import React from 'react'
import assert from 'assert'
import { shallow } from 'enzyme'
import Card from '../card.component'
describe('Card Component', () => {
it('should render a card with a title and child element', () => {
const wrapper = shallow(
<Card
title="Test"
className="card-test-class"
>
<div className="child-test-class">Child</div>
</Card>
)
assert.ok(wrapper.hasClass('card-test-class'))
const title = wrapper.find('.card__title')
assert.ok(title)
assert.equal(title.text(), 'Test')
const child = wrapper.find('.child-test-class')
assert.ok(child)
assert.equal(child.text(), 'Child')
})
})

View File

@ -1,69 +0,0 @@
const Component = require('react').Component
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const inherits = require('util').inherits
const connect = require('react-redux').connect
const actions = require('../actions')
CoinbaseForm.contextTypes = {
t: PropTypes.func,
}
module.exports = connect(mapStateToProps)(CoinbaseForm)
function mapStateToProps (state) {
return {
warning: state.appState.warning,
}
}
inherits(CoinbaseForm, Component)
function CoinbaseForm () {
Component.call(this)
}
CoinbaseForm.prototype.render = function () {
var props = this.props
return h('.flex-column', {
style: {
marginTop: '35px',
padding: '25px',
width: '100%',
},
}, [
h('.flex-row', {
style: {
justifyContent: 'space-around',
margin: '33px',
marginTop: '0px',
},
}, [
h('button.btn-green', {
onClick: this.toCoinbase.bind(this),
}, this.context.t('continueToCoinbase')),
h('button.btn-red', {
onClick: () => props.dispatch(actions.goHome()),
}, this.context.t('cancel')),
]),
])
}
CoinbaseForm.prototype.toCoinbase = function () {
const props = this.props
const address = props.buyView.buyAddress
props.dispatch(actions.buyEth({ network: '1', address, amount: 0 }))
}
CoinbaseForm.prototype.renderLoading = function () {
return h('img', {
style: {
width: '27px',
marginRight: '-27px',
},
src: 'images/loading.svg',
})
}

View File

@ -1,84 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display'
import { PRIMARY, SECONDARY } from '../../../constants/common'
const ConfirmDetailRow = props => {
const {
label,
primaryText,
secondaryText,
onHeaderClick,
primaryValueTextColor,
headerText,
headerTextClassName,
value,
} = props
return (
<div className="confirm-detail-row">
<div className="confirm-detail-row__label">
{ label }
</div>
<div className="confirm-detail-row__details">
<div
className={classnames('confirm-detail-row__header-text', headerTextClassName)}
onClick={() => onHeaderClick && onHeaderClick()}
>
{ headerText }
</div>
{
primaryText
? (
<div
className="confirm-detail-row__primary"
style={{ color: primaryValueTextColor }}
>
{ primaryText }
</div>
) : (
<UserPreferencedCurrencyDisplay
className="confirm-detail-row__primary"
type={PRIMARY}
value={value}
showEthLogo
ethLogoHeight="18"
style={{ color: primaryValueTextColor }}
hideLabel
/>
)
}
{
secondaryText
? (
<div className="confirm-detail-row__secondary">
{ secondaryText }
</div>
) : (
<UserPreferencedCurrencyDisplay
className="confirm-detail-row__secondary"
type={SECONDARY}
value={value}
showEthLogo
hideLabel
/>
)
}
</div>
</div>
)
}
ConfirmDetailRow.propTypes = {
headerText: PropTypes.string,
headerTextClassName: PropTypes.string,
label: PropTypes.string,
onHeaderClick: PropTypes.func,
primaryValueTextColor: PropTypes.string,
primaryText: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
secondaryText: PropTypes.string,
value: PropTypes.string,
}
export default ConfirmDetailRow

View File

@ -1 +0,0 @@
export { default } from './confirm-detail-row.component'

View File

@ -1,46 +0,0 @@
.confirm-detail-row {
padding: 14px 0;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
&__label {
font-size: .75rem;
font-weight: 500;
color: $scorpion;
text-transform: uppercase;
}
&__details {
flex: 1;
text-align: end;
min-width: 0;
}
&__primary {
font-size: 1.5rem;
justify-content: flex-end;
}
&__secondary {
color: $oslo-gray;
justify-content: flex-end;
}
&__header-text {
font-size: .75rem;
text-transform: uppercase;
margin-bottom: 6px;
color: $scorpion;
&--edit {
color: $curious-blue;
cursor: pointer;
}
&--total {
font-size: .625rem;
}
}
}

View File

@ -1,64 +0,0 @@
import React from 'react'
import assert from 'assert'
import { shallow } from 'enzyme'
import ConfirmDetailRow from '../confirm-detail-row.component.js'
import sinon from 'sinon'
const propsMethodSpies = {
onHeaderClick: sinon.spy(),
}
describe('Confirm Detail Row Component', function () {
let wrapper
beforeEach(() => {
wrapper = shallow(
<ConfirmDetailRow
errorType={'mockErrorType'}
label={'mockLabel'}
showError={false}
primaryText = {'mockFiatText'}
secondaryText = {'mockEthText'}
primaryValueTextColor= {'mockColor'}
onHeaderClick= {propsMethodSpies.onHeaderClick}
headerText = {'mockHeaderText'}
headerTextClassName = {'mockHeaderClass'}
/>
)
})
describe('render', () => {
it('should render a div with a confirm-detail-row class', () => {
assert.equal(wrapper.find('div.confirm-detail-row').length, 1)
})
it('should render the label as a child of the confirm-detail-row__label', () => {
assert.equal(wrapper.find('.confirm-detail-row > .confirm-detail-row__label').childAt(0).text(), 'mockLabel')
})
it('should render the headerText as a child of the confirm-detail-row__header-text', () => {
assert.equal(wrapper.find('.confirm-detail-row__details > .confirm-detail-row__header-text').childAt(0).text(), 'mockHeaderText')
})
it('should render the primaryText as a child of the confirm-detail-row__primary', () => {
assert.equal(wrapper.find('.confirm-detail-row__details > .confirm-detail-row__primary').childAt(0).text(), 'mockFiatText')
})
it('should render the ethText as a child of the confirm-detail-row__secondary', () => {
assert.equal(wrapper.find('.confirm-detail-row__details > .confirm-detail-row__secondary').childAt(0).text(), 'mockEthText')
})
it('should set the fiatTextColor on confirm-detail-row__primary', () => {
assert.equal(wrapper.find('.confirm-detail-row__primary').props().style.color, 'mockColor')
})
it('should assure the confirm-detail-row__header-text classname is correct', () => {
assert.equal(wrapper.find('.confirm-detail-row__header-text').props().className, 'confirm-detail-row__header-text mockHeaderClass')
})
it('should call onHeaderClick when headerText div gets clicked', () => {
wrapper.find('.confirm-detail-row__header-text').props().onClick()
assert.equal(assert.equal(propsMethodSpies.onHeaderClick.callCount, 1))
})
})
})

View File

@ -1,110 +0,0 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import { Tabs, Tab } from '../../tabs'
import { ConfirmPageContainerSummary, ConfirmPageContainerWarning } from './'
import ErrorMessage from '../../error-message'
export default class ConfirmPageContainerContent extends Component {
static propTypes = {
action: PropTypes.string,
dataComponent: PropTypes.node,
detailsComponent: PropTypes.node,
errorKey: PropTypes.string,
errorMessage: PropTypes.string,
hideSubtitle: PropTypes.bool,
identiconAddress: PropTypes.string,
nonce: PropTypes.string,
assetImage: PropTypes.string,
subtitle: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
subtitleComponent: PropTypes.node,
summaryComponent: PropTypes.node,
title: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
titleComponent: PropTypes.node,
warning: PropTypes.string,
}
renderContent () {
const { detailsComponent, dataComponent } = this.props
if (detailsComponent && dataComponent) {
return this.renderTabs()
} else {
return detailsComponent || dataComponent
}
}
renderTabs () {
const { detailsComponent, dataComponent } = this.props
return (
<Tabs>
<Tab name="Details">
{ detailsComponent }
</Tab>
<Tab name="Data">
{ dataComponent }
</Tab>
</Tabs>
)
}
render () {
const {
action,
errorKey,
errorMessage,
title,
titleComponent,
subtitle,
subtitleComponent,
hideSubtitle,
identiconAddress,
nonce,
assetImage,
summaryComponent,
detailsComponent,
dataComponent,
warning,
} = this.props
return (
<div className="confirm-page-container-content">
{
warning && (
<ConfirmPageContainerWarning warning={warning} />
)
}
{
summaryComponent || (
<ConfirmPageContainerSummary
className={classnames({
'confirm-page-container-summary--border': !detailsComponent || !dataComponent,
})}
action={action}
title={title}
titleComponent={titleComponent}
subtitle={subtitle}
subtitleComponent={subtitleComponent}
hideSubtitle={hideSubtitle}
identiconAddress={identiconAddress}
nonce={nonce}
assetImage={assetImage}
/>
)
}
{ this.renderContent() }
{
(errorKey || errorMessage) && (
<div className="confirm-page-container-content__error-container">
<ErrorMessage
errorMessage={errorMessage}
errorKey={errorKey}
/>
</div>
)
}
</div>
)
}
}

View File

@ -1,71 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import Identicon from '../../../identicon'
const ConfirmPageContainerSummary = props => {
const {
action,
title,
titleComponent,
subtitle,
subtitleComponent,
hideSubtitle,
className,
identiconAddress,
nonce,
assetImage,
} = props
return (
<div className={classnames('confirm-page-container-summary', className)}>
<div className="confirm-page-container-summary__action-row">
<div className="confirm-page-container-summary__action">
{ action }
</div>
{
nonce && (
<div className="confirm-page-container-summary__nonce">
{ `#${nonce}` }
</div>
)
}
</div>
<div className="confirm-page-container-summary__title">
{
identiconAddress && (
<Identicon
className="confirm-page-container-summary__identicon"
diameter={36}
address={identiconAddress}
image={assetImage}
/>
)
}
<div className="confirm-page-container-summary__title-text">
{ titleComponent || title }
</div>
</div>
{
hideSubtitle || <div className="confirm-page-container-summary__subtitle">
{ subtitleComponent || subtitle }
</div>
}
</div>
)
}
ConfirmPageContainerSummary.propTypes = {
action: PropTypes.string,
title: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
titleComponent: PropTypes.node,
subtitle: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
subtitleComponent: PropTypes.node,
hideSubtitle: PropTypes.bool,
className: PropTypes.string,
identiconAddress: PropTypes.string,
nonce: PropTypes.string,
assetImage: PropTypes.string,
}
export default ConfirmPageContainerSummary

View File

@ -1 +0,0 @@
export { default } from './confirm-page-container-summary.component'

View File

@ -1,54 +0,0 @@
.confirm-page-container-summary {
padding: 16px 24px 0;
background-color: #f9fafa;
height: 133px;
box-sizing: border-box;
&__action-row {
display: flex;
justify-content: space-between;
}
&__action {
text-transform: uppercase;
color: $oslo-gray;
font-size: .75rem;
padding: 3px 8px;
border: 1px solid $oslo-gray;
border-radius: 4px;
display: inline-block;
}
&__nonce {
color: $oslo-gray;
}
&__title {
padding: 4px 0;
display: flex;
align-items: center;
}
&__identicon {
flex: 0 0 auto;
margin-right: 8px;
}
&__title-text {
font-size: 2.25rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
&__subtitle {
color: $oslo-gray;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
&--border {
border-bottom: 1px solid $geyser;
}
}

View File

@ -1,22 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
const ConfirmPageContainerWarning = props => {
return (
<div className="confirm-page-container-warning">
<img
className="confirm-page-container-warning__icon"
src="/images/alert.svg"
/>
<div className="confirm-page-container-warning__warning">
{ props.warning }
</div>
</div>
)
}
ConfirmPageContainerWarning.propTypes = {
warning: PropTypes.string,
}
export default ConfirmPageContainerWarning

View File

@ -1 +0,0 @@
export { default } from './confirm-page-container-warning.component'

View File

@ -1,18 +0,0 @@
.confirm-page-container-warning {
background-color: #fffcdb;
display: flex;
justify-content: center;
align-items: center;
border-bottom: 1px solid $geyser;
padding: 12px 24px;
&__icon {
flex: 0 0 auto;
margin-right: 16px;
}
&__warning {
font-size: .75rem;
color: #5f5922;
}
}

View File

@ -1,3 +0,0 @@
export { default } from './confirm-page-container-content.component'
export { default as ConfirmPageContainerSummary } from './confirm-page-container-summary'
export { default as ConfirmPageContainerWarning } from './confirm-page-container-warning'

View File

@ -1,64 +0,0 @@
@import './confirm-page-container-warning/index';
@import './confirm-page-container-summary/index';
.confirm-page-container-content {
overflow-y: auto;
flex: 1;
&__error-container {
padding: 0 16px 16px 16px;
}
&__details {
box-sizing: border-box;
padding: 0 24px;
}
&__data {
padding: 16px;
color: $oslo-gray;
}
&__data-box {
background-color: #f9fafa;
padding: 12px;
font-size: .75rem;
margin-bottom: 16px;
word-wrap: break-word;
max-height: 200px;
overflow-y: auto;
&-label {
text-transform: uppercase;
padding: 8px 0 12px;
font-size: 12px;
}
}
&__data-field {
display: flex;
flex-direction: row;
&-label {
font-weight: 500;
padding-right: 16px;
}
&:not(:last-child) {
margin-bottom: 5px;
}
}
&__gas-fee {
border-bottom: 1px solid $geyser;
}
&__function-type {
font-size: .875rem;
font-weight: 500;
text-transform: capitalize;
color: $black;
padding-left: 5px;
}
}

View File

@ -1,63 +0,0 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import {
ENVIRONMENT_TYPE_POPUP,
ENVIRONMENT_TYPE_NOTIFICATION,
} from '../../../../../app/scripts/lib/enums'
import NetworkDisplay from '../../network-display'
export default class ConfirmPageContainer extends Component {
static contextTypes = {
t: PropTypes.func,
}
static propTypes = {
showEdit: PropTypes.bool,
onEdit: PropTypes.func,
children: PropTypes.node,
}
renderTop () {
const { onEdit, showEdit } = this.props
const windowType = window.METAMASK_UI_TYPE
const isFullScreen = windowType !== ENVIRONMENT_TYPE_NOTIFICATION &&
windowType !== ENVIRONMENT_TYPE_POPUP
if (!showEdit && isFullScreen) {
return null
}
return (
<div className="confirm-page-container-header__row">
<div
className="confirm-page-container-header__back-button-container"
style={{
visibility: showEdit ? 'initial' : 'hidden',
}}
>
<img
src="/images/caret-left.svg"
/>
<span
className="confirm-page-container-header__back-button"
onClick={() => onEdit()}
>
{ this.context.t('edit') }
</span>
</div>
{ !isFullScreen && <NetworkDisplay /> }
</div>
)
}
render () {
const { children } = this.props
return (
<div className="confirm-page-container-header">
{ this.renderTop() }
{ children }
</div>
)
}
}

View File

@ -1 +0,0 @@
export { default } from './confirm-page-container-header.component'

View File

@ -1,27 +0,0 @@
.confirm-page-container-header {
display: flex;
flex-direction: column;
flex: 0 0 auto;
&__row {
display: flex;
justify-content: space-between;
border-bottom: 1px solid $geyser;
padding: 13px 13px 13px 24px;
flex: 0 0 auto;
}
&__back-button-container {
display: flex;
justify-content: center;
align-items: center;
}
&__back-button {
color: #2f9ae0;
font-size: 1rem;
cursor: pointer;
font-weight: 400;
padding-left: 5px;
}
}

View File

@ -1,137 +0,0 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import SenderToRecipient from '../sender-to-recipient'
import { PageContainerFooter } from '../page-container'
import { ConfirmPageContainerHeader, ConfirmPageContainerContent } from './'
export default class ConfirmPageContainer extends Component {
static contextTypes = {
t: PropTypes.func,
}
static propTypes = {
// Header
action: PropTypes.string,
hideSubtitle: PropTypes.bool,
onEdit: PropTypes.func,
showEdit: PropTypes.bool,
subtitle: PropTypes.string,
subtitleComponent: PropTypes.node,
title: PropTypes.string,
titleComponent: PropTypes.node,
// Sender to Recipient
fromAddress: PropTypes.string,
fromName: PropTypes.string,
toAddress: PropTypes.string,
toName: PropTypes.string,
// Content
contentComponent: PropTypes.node,
errorKey: PropTypes.string,
errorMessage: PropTypes.string,
fiatTransactionAmount: PropTypes.string,
fiatTransactionFee: PropTypes.string,
fiatTransactionTotal: PropTypes.string,
ethTransactionAmount: PropTypes.string,
ethTransactionFee: PropTypes.string,
ethTransactionTotal: PropTypes.string,
onEditGas: PropTypes.func,
dataComponent: PropTypes.node,
detailsComponent: PropTypes.node,
identiconAddress: PropTypes.string,
nonce: PropTypes.string,
assetImage: PropTypes.string,
summaryComponent: PropTypes.node,
warning: PropTypes.string,
unapprovedTxCount: PropTypes.number,
// Footer
onCancelAll: PropTypes.func,
onCancel: PropTypes.func,
onSubmit: PropTypes.func,
disabled: PropTypes.bool,
}
render () {
const {
showEdit,
onEdit,
fromName,
fromAddress,
toName,
toAddress,
disabled,
errorKey,
errorMessage,
contentComponent,
action,
title,
titleComponent,
subtitle,
subtitleComponent,
hideSubtitle,
summaryComponent,
detailsComponent,
dataComponent,
onCancelAll,
onCancel,
onSubmit,
identiconAddress,
nonce,
unapprovedTxCount,
assetImage,
warning,
} = this.props
const renderAssetImage = contentComponent || (!contentComponent && !identiconAddress)
return (
<div className="page-container">
<ConfirmPageContainerHeader
showEdit={showEdit}
onEdit={() => onEdit()}
>
<SenderToRecipient
senderName={fromName}
senderAddress={fromAddress}
recipientName={toName}
recipientAddress={toAddress}
assetImage={renderAssetImage ? assetImage : undefined}
/>
</ConfirmPageContainerHeader>
{
contentComponent || (
<ConfirmPageContainerContent
action={action}
title={title}
titleComponent={titleComponent}
subtitle={subtitle}
subtitleComponent={subtitleComponent}
hideSubtitle={hideSubtitle}
summaryComponent={summaryComponent}
detailsComponent={detailsComponent}
dataComponent={dataComponent}
errorMessage={errorMessage}
errorKey={errorKey}
identiconAddress={identiconAddress}
nonce={nonce}
assetImage={assetImage}
warning={warning}
/>
)
}
<PageContainerFooter
onCancel={() => onCancel()}
cancelText={this.context.t('reject')}
onSubmit={() => onSubmit()}
submitText={this.context.t('confirm')}
submitButtonType="confirm"
disabled={disabled}
>
{unapprovedTxCount > 1 && (
<a onClick={() => onCancelAll()}>
{this.context.t('rejectTxsN', [unapprovedTxCount])}
</a>
)}
</PageContainerFooter>
</div>
)
}
}

View File

@ -1,8 +0,0 @@
export { default } from './confirm-page-container.component'
export { default as ConfirmPageContainerHeader } from './confirm-page-container-header'
export { default as ConfirmDetailRow } from './confirm-detail-row'
export {
default as ConfirmPageContainerContent,
ConfirmPageContainerSummary,
ConfirmPageContainerError,
} from './confirm-page-container-content'

View File

@ -1,5 +0,0 @@
@import './confirm-page-container-content/index';
@import './confirm-page-container-header/index';
@import './confirm-detail-row/index';

View File

@ -1,66 +0,0 @@
const Component = require('react').Component
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const inherits = require('util').inherits
const copyToClipboard = require('copy-to-clipboard')
const connect = require('react-redux').connect
const Tooltip = require('./tooltip')
CopyButton.contextTypes = {
t: PropTypes.func,
}
module.exports = connect()(CopyButton)
inherits(CopyButton, Component)
function CopyButton () {
Component.call(this)
}
// As parameters, accepts:
// "value", which is the value to copy (mandatory)
// "title", which is the text to show on hover (optional, defaults to 'Copy')
CopyButton.prototype.render = function () {
const props = this.props
const state = this.state || {}
const value = props.value
const copied = state.copied
const message = copied ? this.context.t('copiedButton') : props.title || this.context.t('copyButton')
return h('.copy-button', {
style: {
display: 'flex',
alignItems: 'center',
},
}, [
h(Tooltip, {
title: message,
}, [
h('i.fa.fa-clipboard.cursor-pointer.color-orange', {
style: {
margin: '5px',
},
onClick: (event) => {
event.preventDefault()
event.stopPropagation()
copyToClipboard(value)
this.debounceRestore()
},
}),
]),
])
}
CopyButton.prototype.debounceRestore = function () {
this.setState({ copied: true })
clearTimeout(this.timeout)
this.timeout = setTimeout(() => {
this.setState({ copied: false })
}, 850)
}

View File

@ -1,53 +0,0 @@
const Component = require('react').Component
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const inherits = require('util').inherits
const Tooltip = require('./tooltip')
const copyToClipboard = require('copy-to-clipboard')
const connect = require('react-redux').connect
Copyable.contextTypes = {
t: PropTypes.func,
}
module.exports = connect()(Copyable)
inherits(Copyable, Component)
function Copyable () {
Component.call(this)
this.state = {
copied: false,
}
}
Copyable.prototype.render = function () {
const props = this.props
const state = this.state
const { value, children } = props
const { copied } = state
return h(Tooltip, {
title: copied ? this.context.t('copiedExclamation') : this.context.t('copy'),
position: 'bottom',
}, h('span', {
style: {
cursor: 'pointer',
},
onClick: (event) => {
event.preventDefault()
event.stopPropagation()
copyToClipboard(value)
this.debounceRestore()
},
}, children))
}
Copyable.prototype.debounceRestore = function () {
this.setState({ copied: true })
clearTimeout(this.timeout)
this.timeout = setTimeout(() => {
this.setState({ copied: false })
}, 850)
}

View File

@ -1,36 +0,0 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import { ETH, GWEI } from '../../constants/common'
export default class CurrencyDisplay extends PureComponent {
static propTypes = {
className: PropTypes.string,
displayValue: PropTypes.string,
prefix: PropTypes.string,
prefixComponent: PropTypes.node,
style: PropTypes.object,
// Used in container
currency: PropTypes.oneOf([ETH]),
denomination: PropTypes.oneOf([GWEI]),
value: PropTypes.string,
numberOfDecimals: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
hideLabel: PropTypes.bool,
}
render () {
const { className, displayValue, prefix, prefixComponent, style } = this.props
const text = `${prefix || ''}${displayValue}`
return (
<div
className={classnames('currency-display-component', className)}
style={style}
title={text}
>
{ prefixComponent}
<span className="currency-display-component__text">{ text }</span>
</div>
)
}
}

View File

@ -1,40 +0,0 @@
import { connect } from 'react-redux'
import CurrencyDisplay from './currency-display.component'
import { getValueFromWeiHex, formatCurrency } from '../../helpers/confirm-transaction/util'
const mapStateToProps = state => {
const { metamask: { currentCurrency, conversionRate } } = state
return {
currentCurrency,
conversionRate,
}
}
const mergeProps = (stateProps, dispatchProps, ownProps) => {
const { currentCurrency, conversionRate, ...restStateProps } = stateProps
const {
value,
numberOfDecimals = 2,
currency,
denomination,
hideLabel,
...restOwnProps
} = ownProps
const toCurrency = currency || currentCurrency
const convertedValue = getValueFromWeiHex({
value, toCurrency, conversionRate, numberOfDecimals, toDenomination: denomination,
})
const formattedValue = formatCurrency(convertedValue, toCurrency)
const displayValue = hideLabel ? formattedValue : `${formattedValue} ${toCurrency.toUpperCase()}`
return {
...restStateProps,
...dispatchProps,
...restOwnProps,
displayValue,
}
}
export default connect(mapStateToProps, null, mergeProps)(CurrencyDisplay)

View File

@ -1 +0,0 @@
export { default } from './currency-display.container'

View File

@ -1,10 +0,0 @@
.currency-display-component {
display: flex;
align-items: center;
&__text {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}

View File

@ -1,27 +0,0 @@
import React from 'react'
import assert from 'assert'
import { shallow } from 'enzyme'
import CurrencyDisplay from '../currency-display.component'
describe('CurrencyDisplay Component', () => {
it('should render text with a className', () => {
const wrapper = shallow(<CurrencyDisplay
displayValue="$123.45"
className="currency-display"
/>)
assert.ok(wrapper.hasClass('currency-display'))
assert.equal(wrapper.text(), '$123.45')
})
it('should render text with a prefix', () => {
const wrapper = shallow(<CurrencyDisplay
displayValue="$123.45"
className="currency-display"
prefix="-"
/>)
assert.ok(wrapper.hasClass('currency-display'))
assert.equal(wrapper.text(), '-$123.45')
})
})

View File

@ -1,120 +0,0 @@
import assert from 'assert'
import proxyquire from 'proxyquire'
let mapStateToProps, mergeProps
proxyquire('../currency-display.container.js', {
'react-redux': {
connect: (ms, md, mp) => {
mapStateToProps = ms
mergeProps = mp
return () => ({})
},
},
})
describe('CurrencyDisplay container', () => {
describe('mapStateToProps()', () => {
it('should return the correct props', () => {
const mockState = {
metamask: {
conversionRate: 280.45,
currentCurrency: 'usd',
},
}
assert.deepEqual(mapStateToProps(mockState), {
conversionRate: 280.45,
currentCurrency: 'usd',
})
})
})
describe('mergeProps()', () => {
it('should return the correct props', () => {
const mockStateProps = {
conversionRate: 280.45,
currentCurrency: 'usd',
}
const tests = [
{
props: {
value: '0x2386f26fc10000',
numberOfDecimals: 2,
currency: 'usd',
},
result: {
displayValue: '$2.80 USD',
},
},
{
props: {
value: '0x2386f26fc10000',
},
result: {
displayValue: '$2.80 USD',
},
},
{
props: {
value: '0x1193461d01595930',
currency: 'ETH',
numberOfDecimals: 3,
},
result: {
displayValue: '1.266 ETH',
},
},
{
props: {
value: '0x1193461d01595930',
currency: 'ETH',
numberOfDecimals: 3,
hideLabel: true,
},
result: {
displayValue: '1.266',
},
},
{
props: {
value: '0x3b9aca00',
currency: 'ETH',
denomination: 'GWEI',
hideLabel: true,
},
result: {
displayValue: '1',
},
},
{
props: {
value: '0x3b9aca00',
currency: 'ETH',
denomination: 'WEI',
hideLabel: true,
},
result: {
displayValue: '1000000000',
},
},
{
props: {
value: '0x3b9aca00',
currency: 'ETH',
numberOfDecimals: 100,
hideLabel: true,
},
result: {
displayValue: '1e-9',
},
},
]
tests.forEach(({ props, result }) => {
assert.deepEqual(mergeProps(mockStateProps, {}, { ...props }), result)
})
})
})
})

View File

@ -1,120 +0,0 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import UnitInput from '../unit-input'
import CurrencyDisplay from '../currency-display'
import { getValueFromWeiHex, getWeiHexFromDecimalValue } from '../../helpers/conversions.util'
import { ETH } from '../../constants/common'
/**
* Component that allows user to enter currency values as a number, and props receive a converted
* hex value in WEI. props.value, used as a default or forced value, should be a hex value, which
* gets converted into a decimal value depending on the currency (ETH or Fiat).
*/
export default class CurrencyInput extends PureComponent {
static propTypes = {
conversionRate: PropTypes.number,
currentCurrency: PropTypes.string,
onChange: PropTypes.func,
onBlur: PropTypes.func,
suffix: PropTypes.string,
useFiat: PropTypes.bool,
value: PropTypes.string,
}
constructor (props) {
super(props)
const { value: hexValue } = props
const decimalValue = hexValue ? this.getDecimalValue(props) : 0
this.state = {
decimalValue,
hexValue,
}
}
componentDidUpdate (prevProps) {
const { value: prevPropsHexValue } = prevProps
const { value: propsHexValue } = this.props
const { hexValue: stateHexValue } = this.state
if (prevPropsHexValue !== propsHexValue && propsHexValue !== stateHexValue) {
const decimalValue = this.getDecimalValue(this.props)
this.setState({ hexValue: propsHexValue, decimalValue })
}
}
getDecimalValue (props) {
const { value: hexValue, useFiat, currentCurrency, conversionRate } = props
const decimalValueString = useFiat
? getValueFromWeiHex({
value: hexValue, toCurrency: currentCurrency, conversionRate, numberOfDecimals: 2,
})
: getValueFromWeiHex({
value: hexValue, toCurrency: ETH, numberOfDecimals: 6,
})
return Number(decimalValueString) || 0
}
handleChange = decimalValue => {
const { useFiat, currentCurrency: fromCurrency, conversionRate, onChange } = this.props
const hexValue = useFiat
? getWeiHexFromDecimalValue({
value: decimalValue, fromCurrency, conversionRate, invertConversionRate: true,
})
: getWeiHexFromDecimalValue({
value: decimalValue, fromCurrency: ETH, fromDenomination: ETH, conversionRate,
})
this.setState({ hexValue, decimalValue })
onChange(hexValue)
}
handleBlur = () => {
this.props.onBlur(this.state.hexValue)
}
renderConversionComponent () {
const { useFiat, currentCurrency } = this.props
const { hexValue } = this.state
let currency, numberOfDecimals
if (useFiat) {
// Display ETH
currency = ETH
numberOfDecimals = 6
} else {
// Display Fiat
currency = currentCurrency
numberOfDecimals = 2
}
return (
<CurrencyDisplay
className="currency-input__conversion-component"
currency={currency}
value={hexValue}
numberOfDecimals={numberOfDecimals}
/>
)
}
render () {
const { suffix, ...restProps } = this.props
const { decimalValue } = this.state
return (
<UnitInput
{...restProps}
suffix={suffix}
onChange={this.handleChange}
onBlur={this.handleBlur}
value={decimalValue}
>
{ this.renderConversionComponent() }
</UnitInput>
)
}
}

View File

@ -1,27 +0,0 @@
import { connect } from 'react-redux'
import CurrencyInput from './currency-input.component'
import { ETH } from '../../constants/common'
const mapStateToProps = state => {
const { metamask: { currentCurrency, conversionRate } } = state
return {
currentCurrency,
conversionRate,
}
}
const mergeProps = (stateProps, dispatchProps, ownProps) => {
const { currentCurrency } = stateProps
const { useFiat } = ownProps
const suffix = useFiat ? currentCurrency.toUpperCase() : ETH
return {
...stateProps,
...dispatchProps,
...ownProps,
suffix,
}
}
export default connect(mapStateToProps, null, mergeProps)(CurrencyInput)

View File

@ -1 +0,0 @@
export { default } from './currency-input.container'

View File

@ -1,7 +0,0 @@
.currency-input {
&__conversion-component {
font-size: 12px;
line-height: 12px;
padding-left: 1px;
}
}

View File

@ -1,239 +0,0 @@
import React from 'react'
import assert from 'assert'
import { shallow, mount } from 'enzyme'
import sinon from 'sinon'
import { Provider } from 'react-redux'
import configureMockStore from 'redux-mock-store'
import CurrencyInput from '../currency-input.component'
import UnitInput from '../../unit-input'
import CurrencyDisplay from '../../currency-display'
describe('CurrencyInput Component', () => {
describe('rendering', () => {
it('should render properly without a suffix', () => {
const wrapper = shallow(
<CurrencyInput />
)
assert.ok(wrapper)
assert.equal(wrapper.find(UnitInput).length, 1)
})
it('should render properly with a suffix', () => {
const mockStore = {
metamask: {
currentCurrency: 'usd',
conversionRate: 231.06,
},
}
const store = configureMockStore()(mockStore)
const wrapper = mount(
<Provider store={store}>
<CurrencyInput
suffix="ETH"
/>
</Provider>
)
assert.ok(wrapper)
assert.equal(wrapper.find('.unit-input__suffix').length, 1)
assert.equal(wrapper.find('.unit-input__suffix').text(), 'ETH')
assert.equal(wrapper.find(CurrencyDisplay).length, 1)
})
it('should render properly with an ETH value', () => {
const mockStore = {
metamask: {
currentCurrency: 'usd',
conversionRate: 231.06,
},
}
const store = configureMockStore()(mockStore)
const wrapper = mount(
<Provider store={store}>
<CurrencyInput
value="de0b6b3a7640000"
suffix="ETH"
currentCurrency="usd"
conversionRate={231.06}
/>
</Provider>
)
assert.ok(wrapper)
const currencyInputInstance = wrapper.find(CurrencyInput).at(0).instance()
assert.equal(currencyInputInstance.state.decimalValue, 1)
assert.equal(currencyInputInstance.state.hexValue, 'de0b6b3a7640000')
assert.equal(wrapper.find('.unit-input__suffix').length, 1)
assert.equal(wrapper.find('.unit-input__suffix').text(), 'ETH')
assert.equal(wrapper.find('.unit-input__input').props().value, '1')
assert.equal(wrapper.find('.currency-display-component').text(), '$231.06 USD')
})
it('should render properly with a fiat value', () => {
const mockStore = {
metamask: {
currentCurrency: 'usd',
conversionRate: 231.06,
},
}
const store = configureMockStore()(mockStore)
const wrapper = mount(
<Provider store={store}>
<CurrencyInput
value="f602f2234d0ea"
suffix="USD"
useFiat
currentCurrency="usd"
conversionRate={231.06}
/>
</Provider>
)
assert.ok(wrapper)
const currencyInputInstance = wrapper.find(CurrencyInput).at(0).instance()
assert.equal(currencyInputInstance.state.decimalValue, 1)
assert.equal(currencyInputInstance.state.hexValue, 'f602f2234d0ea')
assert.equal(wrapper.find('.unit-input__suffix').length, 1)
assert.equal(wrapper.find('.unit-input__suffix').text(), 'USD')
assert.equal(wrapper.find('.unit-input__input').props().value, '1')
assert.equal(wrapper.find('.currency-display-component').text(), '0.004328 ETH')
})
})
describe('handling actions', () => {
const handleChangeSpy = sinon.spy()
const handleBlurSpy = sinon.spy()
afterEach(() => {
handleChangeSpy.resetHistory()
handleBlurSpy.resetHistory()
})
it('should call onChange and onBlur on input changes with the hex value for ETH', () => {
const mockStore = {
metamask: {
currentCurrency: 'usd',
conversionRate: 231.06,
},
}
const store = configureMockStore()(mockStore)
const wrapper = mount(
<Provider store={store}>
<CurrencyInput
onChange={handleChangeSpy}
onBlur={handleBlurSpy}
suffix="ETH"
currentCurrency="usd"
conversionRate={231.06}
/>
</Provider>
)
assert.ok(wrapper)
assert.equal(handleChangeSpy.callCount, 0)
assert.equal(handleBlurSpy.callCount, 0)
const currencyInputInstance = wrapper.find(CurrencyInput).at(0).instance()
assert.equal(currencyInputInstance.state.decimalValue, 0)
assert.equal(currencyInputInstance.state.hexValue, undefined)
assert.equal(wrapper.find('.currency-display-component').text(), '$0.00 USD')
const input = wrapper.find('input')
assert.equal(input.props().value, 0)
input.simulate('change', { target: { value: 1 } })
assert.equal(handleChangeSpy.callCount, 1)
assert.ok(handleChangeSpy.calledWith('de0b6b3a7640000'))
assert.equal(wrapper.find('.currency-display-component').text(), '$231.06 USD')
assert.equal(currencyInputInstance.state.decimalValue, 1)
assert.equal(currencyInputInstance.state.hexValue, 'de0b6b3a7640000')
assert.equal(handleBlurSpy.callCount, 0)
input.simulate('blur')
assert.equal(handleBlurSpy.callCount, 1)
assert.ok(handleBlurSpy.calledWith('de0b6b3a7640000'))
})
it('should call onChange and onBlur on input changes with the hex value for fiat', () => {
const mockStore = {
metamask: {
currentCurrency: 'usd',
conversionRate: 231.06,
},
}
const store = configureMockStore()(mockStore)
const wrapper = mount(
<Provider store={store}>
<CurrencyInput
onChange={handleChangeSpy}
onBlur={handleBlurSpy}
suffix="USD"
currentCurrency="usd"
conversionRate={231.06}
useFiat
/>
</Provider>
)
assert.ok(wrapper)
assert.equal(handleChangeSpy.callCount, 0)
assert.equal(handleBlurSpy.callCount, 0)
const currencyInputInstance = wrapper.find(CurrencyInput).at(0).instance()
assert.equal(currencyInputInstance.state.decimalValue, 0)
assert.equal(currencyInputInstance.state.hexValue, undefined)
assert.equal(wrapper.find('.currency-display-component').text(), '0 ETH')
const input = wrapper.find('input')
assert.equal(input.props().value, 0)
input.simulate('change', { target: { value: 1 } })
assert.equal(handleChangeSpy.callCount, 1)
assert.ok(handleChangeSpy.calledWith('f602f2234d0ea'))
assert.equal(wrapper.find('.currency-display-component').text(), '0.004328 ETH')
assert.equal(currencyInputInstance.state.decimalValue, 1)
assert.equal(currencyInputInstance.state.hexValue, 'f602f2234d0ea')
assert.equal(handleBlurSpy.callCount, 0)
input.simulate('blur')
assert.equal(handleBlurSpy.callCount, 1)
assert.ok(handleBlurSpy.calledWith('f602f2234d0ea'))
})
it('should change the state and pass in a new decimalValue when props.value changes', () => {
const mockStore = {
metamask: {
currentCurrency: 'usd',
conversionRate: 231.06,
},
}
const store = configureMockStore()(mockStore)
const wrapper = shallow(
<Provider store={store}>
<CurrencyInput
onChange={handleChangeSpy}
onBlur={handleBlurSpy}
suffix="USD"
currentCurrency="usd"
conversionRate={231.06}
useFiat
/>
</Provider>
)
assert.ok(wrapper)
const currencyInputInstance = wrapper.find(CurrencyInput).dive()
assert.equal(currencyInputInstance.state('decimalValue'), 0)
assert.equal(currencyInputInstance.state('hexValue'), undefined)
assert.equal(currencyInputInstance.find(UnitInput).props().value, 0)
currencyInputInstance.setProps({ value: '1ec05e43e72400' })
currencyInputInstance.update()
assert.equal(currencyInputInstance.state('decimalValue'), 2)
assert.equal(currencyInputInstance.state('hexValue'), '1ec05e43e72400')
assert.equal(currencyInputInstance.find(UnitInput).props().value, 2)
})
})
})

View File

@ -1,55 +0,0 @@
import assert from 'assert'
import proxyquire from 'proxyquire'
let mapStateToProps, mergeProps
proxyquire('../currency-input.container.js', {
'react-redux': {
connect: (ms, md, mp) => {
mapStateToProps = ms
mergeProps = mp
return () => ({})
},
},
})
describe('CurrencyInput container', () => {
describe('mapStateToProps()', () => {
it('should return the correct props', () => {
const mockState = {
metamask: {
conversionRate: 280.45,
currentCurrency: 'usd',
},
}
assert.deepEqual(mapStateToProps(mockState), {
conversionRate: 280.45,
currentCurrency: 'usd',
})
})
})
describe('mergeProps()', () => {
it('should return the correct props', () => {
const mockStateProps = {
conversionRate: 280.45,
currentCurrency: 'usd',
}
const mockDispatchProps = {}
assert.deepEqual(mergeProps(mockStateProps, mockDispatchProps, { useFiat: true }), {
conversionRate: 280.45,
currentCurrency: 'usd',
useFiat: true,
suffix: 'USD',
})
assert.deepEqual(mergeProps(mockStateProps, mockDispatchProps, {}), {
conversionRate: 280.45,
currentCurrency: 'usd',
suffix: 'ETH',
})
})
})
})

View File

@ -1,54 +0,0 @@
const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
const InputNumber = require('../input-number.js')
// const GasSlider = require('./gas-slider.js')
module.exports = GasModalCard
inherits(GasModalCard, Component)
function GasModalCard () {
Component.call(this)
}
GasModalCard.prototype.render = function () {
const {
// memo,
onChange,
unitLabel,
value,
min,
// max,
step,
title,
copy,
} = this.props
return h('div.send-v2__gas-modal-card', [
h('div.send-v2__gas-modal-card__title', {}, title),
h('div.send-v2__gas-modal-card__copy', {}, copy),
h(InputNumber, {
unitLabel,
step,
// max,
min,
placeholder: '0',
value,
onChange,
}),
// h(GasSlider, {
// value,
// step,
// max,
// min,
// onChange,
// }),
])
}

View File

@ -1,50 +0,0 @@
// const Component = require('react').Component
// const h = require('react-hyperscript')
// const inherits = require('util').inherits
// module.exports = GasSlider
// inherits(GasSlider, Component)
// function GasSlider () {
// Component.call(this)
// }
// GasSlider.prototype.render = function () {
// const {
// memo,
// identities,
// onChange,
// unitLabel,
// value,
// id,
// step,
// max,
// min,
// } = this.props
// return h('div.gas-slider', [
// h('input.gas-slider__input', {
// type: 'range',
// step,
// max,
// min,
// value,
// id: 'gasSlider',
// onChange: event => onChange(event.target.value),
// }, []),
// h('div.gas-slider__bar', [
// h('div.gas-slider__low'),
// h('div.gas-slider__mid'),
// h('div.gas-slider__high'),
// ]),
// ])
// }

View File

@ -1,374 +0,0 @@
const Component = require('react').Component
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const inherits = require('util').inherits
const connect = require('react-redux').connect
const actions = require('../../actions')
const GasModalCard = require('./gas-modal-card')
import Button from '../button'
const ethUtil = require('ethereumjs-util')
import {
updateSendErrors,
} from '../../ducks/send.duck'
const {
MIN_GAS_PRICE_DEC,
MIN_GAS_LIMIT_DEC,
MIN_GAS_PRICE_GWEI,
} = require('../send/send.constants')
const {
isBalanceSufficient,
} = require('../send/send.utils')
const {
conversionUtil,
multiplyCurrencies,
conversionGreaterThan,
conversionMax,
subtractCurrencies,
} = require('../../conversion-util')
const {
getGasIsLoading,
getForceGasMin,
conversionRateSelector,
getSendAmount,
getSelectedToken,
getSendFrom,
getCurrentAccountWithSendEtherInfo,
getSelectedTokenToFiatRate,
getSendMaxModeState,
} = require('../../selectors')
const {
getGasPrice,
getGasLimit,
} = require('../send/send.selectors')
function mapStateToProps (state) {
const selectedToken = getSelectedToken(state)
const currentAccount = getSendFrom(state) || getCurrentAccountWithSendEtherInfo(state)
const conversionRate = conversionRateSelector(state)
return {
gasPrice: getGasPrice(state),
gasLimit: getGasLimit(state),
gasIsLoading: getGasIsLoading(state),
forceGasMin: getForceGasMin(state),
conversionRate,
amount: getSendAmount(state),
maxModeOn: getSendMaxModeState(state),
balance: currentAccount.balance,
primaryCurrency: selectedToken && selectedToken.symbol,
selectedToken,
amountConversionRate: selectedToken ? getSelectedTokenToFiatRate(state) : conversionRate,
}
}
function mapDispatchToProps (dispatch) {
return {
hideModal: () => dispatch(actions.hideModal()),
setGasPrice: newGasPrice => dispatch(actions.setGasPrice(newGasPrice)),
setGasLimit: newGasLimit => dispatch(actions.setGasLimit(newGasLimit)),
setGasTotal: newGasTotal => dispatch(actions.setGasTotal(newGasTotal)),
updateSendAmount: newAmount => dispatch(actions.updateSendAmount(newAmount)),
updateSendErrors: error => dispatch(updateSendErrors(error)),
}
}
function getFreshState (props) {
const gasPrice = props.gasPrice || MIN_GAS_PRICE_DEC
const gasLimit = props.gasLimit || MIN_GAS_LIMIT_DEC
const gasTotal = multiplyCurrencies(gasLimit, gasPrice, {
toNumericBase: 'hex',
multiplicandBase: 16,
multiplierBase: 16,
})
return {
gasPrice,
gasLimit,
gasTotal,
error: null,
priceSigZeros: '',
priceSigDec: '',
}
}
inherits(CustomizeGasModal, Component)
function CustomizeGasModal (props) {
Component.call(this)
const originalState = getFreshState(props)
this.state = {
...originalState,
originalState,
}
}
CustomizeGasModal.contextTypes = {
t: PropTypes.func,
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(CustomizeGasModal)
CustomizeGasModal.prototype.componentWillReceiveProps = function (nextProps) {
const currentState = getFreshState(this.props)
const {
gasPrice: currentGasPrice,
gasLimit: currentGasLimit,
} = currentState
const newState = getFreshState(nextProps)
const {
gasPrice: newGasPrice,
gasLimit: newGasLimit,
gasTotal: newGasTotal,
} = newState
const gasPriceChanged = currentGasPrice !== newGasPrice
const gasLimitChanged = currentGasLimit !== newGasLimit
if (gasPriceChanged) {
this.setState({
gasPrice: newGasPrice,
gasTotal: newGasTotal,
priceSigZeros: '',
priceSigDec: '',
})
}
if (gasLimitChanged) {
this.setState({ gasLimit: newGasLimit, gasTotal: newGasTotal })
}
if (gasLimitChanged || gasPriceChanged) {
this.validate({ gasLimit: newGasLimit, gasTotal: newGasTotal })
}
}
CustomizeGasModal.prototype.save = function (gasPrice, gasLimit, gasTotal) {
const {
setGasPrice,
setGasLimit,
hideModal,
setGasTotal,
maxModeOn,
selectedToken,
balance,
updateSendAmount,
updateSendErrors,
} = this.props
if (maxModeOn && !selectedToken) {
const maxAmount = subtractCurrencies(
ethUtil.addHexPrefix(balance),
ethUtil.addHexPrefix(gasTotal),
{ toNumericBase: 'hex' }
)
updateSendAmount(maxAmount)
}
setGasPrice(ethUtil.addHexPrefix(gasPrice))
setGasLimit(ethUtil.addHexPrefix(gasLimit))
setGasTotal(ethUtil.addHexPrefix(gasTotal))
updateSendErrors({ insufficientFunds: false })
hideModal()
}
CustomizeGasModal.prototype.revert = function () {
this.setState(this.state.originalState)
}
CustomizeGasModal.prototype.validate = function ({ gasTotal, gasLimit }) {
const {
amount,
balance,
selectedToken,
amountConversionRate,
conversionRate,
maxModeOn,
} = this.props
let error = null
const balanceIsSufficient = isBalanceSufficient({
amount: selectedToken || maxModeOn ? '0' : amount,
gasTotal,
balance,
selectedToken,
amountConversionRate,
conversionRate,
})
if (!balanceIsSufficient) {
error = this.context.t('balanceIsInsufficientGas')
}
const gasLimitTooLow = gasLimit && conversionGreaterThan(
{
value: MIN_GAS_LIMIT_DEC,
fromNumericBase: 'dec',
conversionRate,
},
{
value: gasLimit,
fromNumericBase: 'hex',
},
)
if (gasLimitTooLow) {
error = this.context.t('gasLimitTooLow')
}
this.setState({ error })
return error
}
CustomizeGasModal.prototype.convertAndSetGasLimit = function (newGasLimit) {
const { gasPrice } = this.state
const gasLimit = conversionUtil(newGasLimit, {
fromNumericBase: 'dec',
toNumericBase: 'hex',
})
const gasTotal = multiplyCurrencies(gasLimit, gasPrice, {
toNumericBase: 'hex',
multiplicandBase: 16,
multiplierBase: 16,
})
this.validate({ gasTotal, gasLimit })
this.setState({ gasTotal, gasLimit })
}
CustomizeGasModal.prototype.convertAndSetGasPrice = function (newGasPrice) {
const { gasLimit } = this.state
const sigZeros = String(newGasPrice).match(/^\d+[.]\d*?(0+)$/)
const sigDec = String(newGasPrice).match(/^\d+([.])0*$/)
this.setState({
priceSigZeros: sigZeros && sigZeros[1] || '',
priceSigDec: sigDec && sigDec[1] || '',
})
const gasPrice = conversionUtil(newGasPrice, {
fromNumericBase: 'dec',
toNumericBase: 'hex',
fromDenomination: 'GWEI',
toDenomination: 'WEI',
})
const gasTotal = multiplyCurrencies(gasLimit, gasPrice, {
toNumericBase: 'hex',
multiplicandBase: 16,
multiplierBase: 16,
})
this.validate({ gasTotal })
this.setState({ gasTotal, gasPrice })
}
CustomizeGasModal.prototype.render = function () {
const { hideModal, forceGasMin, gasIsLoading } = this.props
const { gasPrice, gasLimit, gasTotal, error, priceSigZeros, priceSigDec } = this.state
let convertedGasPrice = conversionUtil(gasPrice, {
fromNumericBase: 'hex',
toNumericBase: 'dec',
fromDenomination: 'WEI',
toDenomination: 'GWEI',
})
convertedGasPrice += convertedGasPrice.match(/[.]/) ? priceSigZeros : `${priceSigDec}${priceSigZeros}`
let newGasPrice = gasPrice
if (forceGasMin) {
const convertedMinPrice = conversionUtil(forceGasMin, {
fromNumericBase: 'hex',
toNumericBase: 'dec',
})
convertedGasPrice = conversionMax(
{ value: convertedMinPrice, fromNumericBase: 'dec' },
{ value: convertedGasPrice, fromNumericBase: 'dec' }
)
newGasPrice = conversionMax(
{ value: gasPrice, fromNumericBase: 'hex' },
{ value: forceGasMin, fromNumericBase: 'hex' }
)
}
const convertedGasLimit = conversionUtil(gasLimit, {
fromNumericBase: 'hex',
toNumericBase: 'dec',
})
return !gasIsLoading && h('div.send-v2__customize-gas', {}, [
h('div.send-v2__customize-gas__content', {
}, [
h('div.send-v2__customize-gas__header', {}, [
h('div.send-v2__customize-gas__title', this.context.t('customGas')),
h('div.send-v2__customize-gas__close', {
onClick: hideModal,
}),
]),
h('div.send-v2__customize-gas__body', {}, [
h(GasModalCard, {
value: convertedGasPrice,
min: forceGasMin || MIN_GAS_PRICE_GWEI,
step: 1,
onChange: value => this.convertAndSetGasPrice(value),
title: this.context.t('gasPrice'),
copy: this.context.t('gasPriceCalculation'),
gasIsLoading,
}),
h(GasModalCard, {
value: convertedGasLimit,
min: 1,
step: 1,
onChange: value => this.convertAndSetGasLimit(value),
title: this.context.t('gasLimit'),
copy: this.context.t('gasLimitCalculation'),
gasIsLoading,
}),
]),
h('div.send-v2__customize-gas__footer', {}, [
error && h('div.send-v2__customize-gas__error-message', [
error,
]),
h('div.send-v2__customize-gas__revert', {
onClick: () => this.revert(),
}, [this.context.t('revert')]),
h('div.send-v2__customize-gas__buttons', [
h(Button, {
type: 'default',
className: 'send-v2__customize-gas__cancel',
onClick: this.props.hideModal,
}, [this.context.t('cancel')]),
h(Button, {
type: 'primary',
className: 'send-v2__customize-gas__save',
onClick: () => !error && this.save(newGasPrice, gasLimit, gasTotal),
disabled: error,
}, [this.context.t('save')]),
]),
]),
]),
])
}

View File

@ -1,88 +0,0 @@
const { Component } = require('react')
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const classnames = require('classnames')
class EditableLabel extends Component {
constructor (props) {
super(props)
this.state = {
isEditing: false,
value: props.defaultValue || '',
}
}
handleSubmit () {
const { value } = this.state
if (value === '') {
return
}
Promise.resolve(this.props.onSubmit(value))
.then(() => this.setState({ isEditing: false }))
}
saveIfEnter (event) {
if (event.key === 'Enter') {
this.handleSubmit()
}
}
renderEditing () {
const { value } = this.state
return ([
h('input.large-input.editable-label__input', {
type: 'text',
required: true,
value: this.state.value,
onKeyPress: (event) => {
if (event.key === 'Enter') {
this.handleSubmit()
}
},
onChange: event => this.setState({ value: event.target.value }),
className: classnames({ 'editable-label__input--error': value === '' }),
}),
h('div.editable-label__icon-wrapper', [
h('i.fa.fa-check.editable-label__icon', {
onClick: () => this.handleSubmit(),
}),
]),
])
}
renderReadonly () {
return ([
h('div.editable-label__value', this.state.value),
h('div.editable-label__icon-wrapper', [
h('i.fa.fa-pencil.editable-label__icon', {
onClick: () => this.setState({ isEditing: true }),
}),
]),
])
}
render () {
const { isEditing } = this.state
const { className } = this.props
return (
h('div.editable-label', { className: classnames(className) },
isEditing
? this.renderEditing()
: this.renderReadonly()
)
)
}
}
EditableLabel.propTypes = {
onSubmit: PropTypes.func.isRequired,
defaultValue: PropTypes.string,
className: PropTypes.string,
}
module.exports = EditableLabel

View File

@ -1,181 +0,0 @@
const Component = require('react').Component
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const inherits = require('util').inherits
const extend = require('xtend')
const debounce = require('debounce')
const copyToClipboard = require('copy-to-clipboard')
const ENS = require('ethjs-ens')
const networkMap = require('ethjs-ens/lib/network-map.json')
const ensRE = /.+\..+$/
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
const connect = require('react-redux').connect
const ToAutoComplete = require('./send/to-autocomplete').default
const log = require('loglevel')
const { isValidENSAddress } = require('../util')
EnsInput.contextTypes = {
t: PropTypes.func,
}
module.exports = connect()(EnsInput)
inherits(EnsInput, Component)
function EnsInput () {
Component.call(this)
}
EnsInput.prototype.onChange = function (recipient) {
const network = this.props.network
const networkHasEnsSupport = getNetworkEnsSupport(network)
this.props.onChange({ toAddress: recipient })
if (!networkHasEnsSupport) return
if (recipient.match(ensRE) === null) {
return this.setState({
loadingEns: false,
ensResolution: null,
ensFailure: null,
toError: null,
})
}
this.setState({
loadingEns: true,
})
this.checkName(recipient)
}
EnsInput.prototype.render = function () {
const props = this.props
const opts = extend(props, {
list: 'addresses',
onChange: this.onChange.bind(this),
qrScanner: true,
})
return h('div', {
style: { width: '100%', position: 'relative' },
}, [
h(ToAutoComplete, { ...opts }),
this.ensIcon(),
])
}
EnsInput.prototype.componentDidMount = function () {
const network = this.props.network
const networkHasEnsSupport = getNetworkEnsSupport(network)
this.setState({ ensResolution: ZERO_ADDRESS })
if (networkHasEnsSupport) {
const provider = global.ethereumProvider
this.ens = new ENS({ provider, network })
this.checkName = debounce(this.lookupEnsName.bind(this), 200)
}
}
EnsInput.prototype.lookupEnsName = function (recipient) {
const { ensResolution } = this.state
log.info(`ENS attempting to resolve name: ${recipient}`)
this.ens.lookup(recipient.trim())
.then((address) => {
if (address === ZERO_ADDRESS) throw new Error(this.context.t('noAddressForName'))
if (address !== ensResolution) {
this.setState({
loadingEns: false,
ensResolution: address,
nickname: recipient.trim(),
hoverText: address + '\n' + this.context.t('clickCopy'),
ensFailure: false,
toError: null,
})
}
})
.catch((reason) => {
const setStateObj = {
loadingEns: false,
ensResolution: recipient,
ensFailure: true,
toError: null,
}
if (isValidENSAddress(recipient) && reason.message === 'ENS name not defined.') {
setStateObj.hoverText = this.context.t('ensNameNotFound')
setStateObj.toError = 'ensNameNotFound'
setStateObj.ensFailure = false
} else {
log.error(reason)
setStateObj.hoverText = reason.message
}
return this.setState(setStateObj)
})
}
EnsInput.prototype.componentDidUpdate = function (prevProps, prevState) {
const state = this.state || {}
const ensResolution = state.ensResolution
// If an address is sent without a nickname, meaning not from ENS or from
// the user's own accounts, a default of a one-space string is used.
const nickname = state.nickname || ' '
if (prevProps.network !== this.props.network) {
const provider = global.ethereumProvider
this.ens = new ENS({ provider, network: this.props.network })
this.onChange(ensResolution)
}
if (prevState && ensResolution && this.props.onChange &&
ensResolution !== prevState.ensResolution) {
this.props.onChange({ toAddress: ensResolution, nickname, toError: state.toError })
}
}
EnsInput.prototype.ensIcon = function (recipient) {
const { hoverText } = this.state || {}
return h('span.#ensIcon', {
title: hoverText,
style: {
position: 'absolute',
top: '16px',
left: '-25px',
},
}, this.ensIconContents(recipient))
}
EnsInput.prototype.ensIconContents = function (recipient) {
const { loadingEns, ensFailure, ensResolution, toError } = this.state || { ensResolution: ZERO_ADDRESS }
if (toError) return
if (loadingEns) {
return h('img', {
src: 'images/loading.svg',
style: {
width: '30px',
height: '30px',
transform: 'translateY(-6px)',
},
})
}
if (ensFailure) {
return h('i.fa.fa-warning.fa-lg.warning')
}
if (ensResolution && (ensResolution !== ZERO_ADDRESS)) {
return h('i.fa.fa-check-circle.fa-lg.cursor-pointer', {
style: { color: 'green' },
onClick: (event) => {
event.preventDefault()
event.stopPropagation()
copyToClipboard(ensResolution)
},
})
}
}
function getNetworkEnsSupport (network) {
return Boolean(networkMap[network])
}

View File

@ -1,30 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
const ErrorMessage = (props, context) => {
const { errorMessage, errorKey } = props
const error = errorKey ? context.t(errorKey) : errorMessage
return (
<div className="error-message">
<img
src="/images/alert-red.svg"
className="error-message__icon"
/>
<div className="error-message__text">
{ `ALERT: ${error}` }
</div>
</div>
)
}
ErrorMessage.propTypes = {
errorMessage: PropTypes.string,
errorKey: PropTypes.string,
}
ErrorMessage.contextTypes = {
t: PropTypes.func,
}
export default ErrorMessage

View File

@ -1 +0,0 @@
export { default } from './error-message.component'

View File

@ -1,21 +0,0 @@
.error-message {
min-height: 32px;
border: 1px solid $monzo;
color: $monzo;
background: lighten($monzo, 56%);
border-radius: 4px;
font-size: .75rem;
display: flex;
justify-content: flex-start;
align-items: center;
padding: 8px 16px;
&__icon {
margin-right: 8px;
flex: 0 0 auto;
}
&__text {
overflow: auto;
}
}

View File

@ -1,36 +0,0 @@
import React from 'react'
import assert from 'assert'
import { shallow } from 'enzyme'
import ErrorMessage from '../error-message.component'
describe('ErrorMessage Component', () => {
const t = key => `translate ${key}`
it('should render a message from props.errorMessage', () => {
const wrapper = shallow(
<ErrorMessage
errorMessage="This is an error."
/>,
{ context: { t }}
)
assert.ok(wrapper)
assert.equal(wrapper.find('.error-message').length, 1)
assert.equal(wrapper.find('.error-message__icon').length, 1)
assert.equal(wrapper.find('.error-message__text').text(), 'ALERT: This is an error.')
})
it('should render a message translated from props.errorKey', () => {
const wrapper = shallow(
<ErrorMessage
errorKey="testKey"
/>,
{ context: { t }}
)
assert.ok(wrapper)
assert.equal(wrapper.find('.error-message').length, 1)
assert.equal(wrapper.find('.error-message__icon').length, 1)
assert.equal(wrapper.find('.error-message__text').text(), 'ALERT: translate testKey')
})
})

View File

@ -1,96 +0,0 @@
const { Component } = require('react')
const h = require('react-hyperscript')
const { inherits } = require('util')
const {
formatBalance,
generateBalanceObject,
} = require('../util')
const Tooltip = require('./tooltip.js')
const FiatValue = require('./fiat-value.js')
module.exports = EthBalanceComponent
inherits(EthBalanceComponent, Component)
function EthBalanceComponent () {
Component.call(this)
}
EthBalanceComponent.prototype.render = function () {
const props = this.props
const { value, style, width, needsParse = true } = props
const formattedValue = value ? formatBalance(value, 6, needsParse) : '...'
return (
h('.ether-balance.ether-balance-amount', {
style,
}, [
h('div', {
style: {
display: 'inline',
width,
},
}, this.renderBalance(formattedValue)),
])
)
}
EthBalanceComponent.prototype.renderBalance = function (value) {
if (value === 'None') return value
if (value === '...') return value
const {
conversionRate,
shorten,
incoming,
currentCurrency,
hideTooltip,
styleOveride = {},
showFiat = true,
} = this.props
const { fontSize, color, fontFamily, lineHeight } = styleOveride
const { shortBalance, balance, label } = generateBalanceObject(value, shorten ? 1 : 3)
const balanceToRender = shorten ? shortBalance : balance
const [ethNumber, ethSuffix] = value.split(' ')
const containerProps = hideTooltip ? {} : {
position: 'bottom',
title: `${ethNumber} ${ethSuffix}`,
}
return (
h(hideTooltip ? 'div' : Tooltip,
containerProps,
h('div.flex-column', [
h('.flex-row', {
style: {
alignItems: 'flex-end',
lineHeight: lineHeight || '13px',
fontFamily: fontFamily || 'Montserrat Light',
textRendering: 'geometricPrecision',
},
}, [
h('div', {
style: {
width: '100%',
textAlign: 'right',
fontSize: fontSize || 'inherit',
color: color || 'inherit',
},
}, incoming ? `+${balanceToRender}` : balanceToRender),
h('div', {
style: {
color: color || '#AEAEAE',
fontSize: fontSize || '12px',
marginLeft: '5px',
},
}, label),
]),
showFiat ? h(FiatValue, { value: this.props.value, conversionRate, currentCurrency }) : null,
])
)
)
}

View File

@ -1,45 +0,0 @@
const { Component } = require('react')
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const copyToClipboard = require('copy-to-clipboard')
const { exportAsFile } = require('../../util')
class ExportTextContainer extends Component {
render () {
const { text = '', filename = '' } = this.props
const { t } = this.context
return (
h('.export-text-container', [
h('.export-text-container__text-container', [
h('.export-text-container__text', text),
]),
h('.export-text-container__buttons-container', [
h('.export-text-container__button.export-text-container__button--copy', {
onClick: () => copyToClipboard(text),
}, [
h('img', { src: 'images/copy-to-clipboard.svg' }),
h('.export-text-container__button-text', t('copyToClipboard')),
]),
h('.export-text-container__button', {
onClick: () => exportAsFile(filename, text),
}, [
h('img', { src: 'images/download.svg' }),
h('.export-text-container__button-text', t('saveAsCsvFile')),
]),
]),
])
)
}
}
ExportTextContainer.propTypes = {
text: PropTypes.string,
filename: PropTypes.string,
}
ExportTextContainer.contextTypes = {
t: PropTypes.func,
}
module.exports = ExportTextContainer

View File

@ -1,2 +0,0 @@
const ExportTextContainer = require('./export-text-container.component')
module.exports = ExportTextContainer

View File

@ -1,52 +0,0 @@
.export-text-container {
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
border: 1px solid $alto;
border-radius: 4px;
font-weight: 400;
&__text-container {
width: 100%;
display: flex;
justify-content: center;
padding: 20px;
border-radius: 4px;
background: $alabaster;
}
&__text {
resize: none;
border: none;
background: $alabaster;
font-size: 20px;
text-align: center;
}
&__buttons-container {
display: flex;
flex-direction: row;
border-top: 1px solid $alto;
width: 100%;
}
&__button {
padding: 10px;
flex: 1;
display: flex;
justify-content: center;
align-items: center;
font-size: 12px;
cursor: pointer;
color: $curious-blue;
&--copy {
border-right: 1px solid $alto;
}
}
&__button-text {
padding-left: 10px;
}
}

View File

@ -1,66 +0,0 @@
const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
const formatBalance = require('../util').formatBalance
module.exports = FiatValue
inherits(FiatValue, Component)
function FiatValue () {
Component.call(this)
}
FiatValue.prototype.render = function () {
const props = this.props
const { conversionRate, currentCurrency, style } = props
const renderedCurrency = currentCurrency || ''
const value = formatBalance(props.value, 6)
if (value === 'None') return value
var fiatDisplayNumber, fiatTooltipNumber
var splitBalance = value.split(' ')
if (conversionRate !== 0) {
fiatTooltipNumber = Number(splitBalance[0]) * conversionRate
fiatDisplayNumber = fiatTooltipNumber.toFixed(2)
} else {
fiatDisplayNumber = 'N/A'
fiatTooltipNumber = 'Unknown'
}
return fiatDisplay(fiatDisplayNumber, renderedCurrency.toUpperCase(), style)
}
function fiatDisplay (fiatDisplayNumber, fiatSuffix, styleOveride = {}) {
const { fontSize, color, fontFamily, lineHeight } = styleOveride
if (fiatDisplayNumber !== 'N/A') {
return h('.flex-row', {
style: {
alignItems: 'flex-end',
lineHeight: lineHeight || '13px',
fontFamily: fontFamily || 'Montserrat Light',
textRendering: 'geometricPrecision',
},
}, [
h('div', {
style: {
width: '100%',
textAlign: 'right',
fontSize: fontSize || '12px',
color: color || '#333333',
},
}, fiatDisplayNumber),
h('div', {
style: {
color: color || '#AEAEAE',
marginLeft: '5px',
fontSize: fontSize || '12px',
},
}, fiatSuffix),
])
} else {
return h('div')
}
}

View File

@ -1,21 +0,0 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import { hexToDecimal } from '../../helpers/conversions.util'
export default class HexToDecimal extends PureComponent {
static propTypes = {
className: PropTypes.string,
value: PropTypes.string,
}
render () {
const { className, value } = this.props
const decimalValue = hexToDecimal(value)
return (
<div className={className}>
{ decimalValue }
</div>
)
}
}

View File

@ -1 +0,0 @@
export { default } from './hex-to-decimal.component'

View File

@ -1,26 +0,0 @@
import React from 'react'
import assert from 'assert'
import { shallow } from 'enzyme'
import HexToDecimal from '../hex-to-decimal.component'
describe('HexToDecimal Component', () => {
it('should render a prefixed hex as a decimal with a className', () => {
const wrapper = shallow(<HexToDecimal
value="0x3039"
className="hex-to-decimal"
/>)
assert.ok(wrapper.hasClass('hex-to-decimal'))
assert.equal(wrapper.text(), '12345')
})
it('should render an unprefixed hex as a decimal with a className', () => {
const wrapper = shallow(<HexToDecimal
value="1A85"
className="hex-to-decimal"
/>)
assert.ok(wrapper.hasClass('hex-to-decimal'))
assert.equal(wrapper.text(), '6789')
})
})

View File

@ -1,59 +0,0 @@
@import './app-header/index';
@import './add-token-button/index';
@import './button-group/index';
@import './card/index';
@import './confirm-page-container/index';
@import './currency-input/index';
@import './currency-display/index';
@import './error-message/index';
@import './export-text-container/index';
@import './info-box/index';
@import './menu-bar/index';
@import './modal/index';
@import './modals/index';
@import './network-display/index';
@import './page-container/index';
@import './pages/index';
@import './selected-account/index';
@import './sender-to-recipient/index';
@import './tabs/index';
@import './transaction-activity-log/index';
@import './transaction-breakdown/index';
@import './transaction-view/index';
@import './transaction-view-balance/index';
@import './transaction-list/index';
@import './transaction-list-item/index';
@import './transaction-list-item-details/index';
@import './transaction-status/index';
@import './app-header/index';
@import './sidebars/index';
@import './unit-input/index';

View File

@ -1,2 +0,0 @@
import InfoBox from './info-box.component'
module.exports = InfoBox

Some files were not shown because too many files have changed in this diff Show More