diff options
97 files changed, 3779 insertions, 1501 deletions
diff --git a/.gitignore b/.gitignore index 1806b1932..08a544449 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,9 @@ test/background.js test/bundle.js test/test-bundle.js +#ignore css output and sourcemaps +ui/app/css/output/ + notes.txt .coveralls.yml diff --git a/.stylelintignore b/.stylelintignore new file mode 100644 index 000000000..854829a54 --- /dev/null +++ b/.stylelintignore @@ -0,0 +1,10 @@ +app/ +development/ +dist/ +docs/ +fonts/ +images/ +mascara/ +node_modules/ +notices/ +test/ diff --git a/.stylelintrc b/.stylelintrc new file mode 100644 index 000000000..d080d68d9 --- /dev/null +++ b/.stylelintrc @@ -0,0 +1,50 @@ +{ + "extends": "stylelint-config-standard", + "rules": { + "color-named": "never", + "font-family-name-quotes": "always-where-recommended", + "font-weight-notation": "numeric", + "function-url-quotes": "always", + "number-leading-zero": "never", + "value-no-vendor-prefix": true, + "value-list-comma-newline-before": "never-multi-line", + "custom-property-empty-line-before": "never", + "property-no-unknown": [ + true, + { + "ignoreProperties": [ + "composes", + "all", + "-webkit-appearance" + ] + } + ], + "declaration-block-semicolon-newline-after": "always", + "block-opening-brace-newline-after": "always", + "selector-attribute-quotes": "always", + "selector-max-specificity": "0,5,2", + "selector-pseudo-class-no-unknown": [ + true, + { + "ignorePseudoClasses": ["local", "global"] + } + ], + "at-rule-empty-line-before": [ + "always", + { + "ignore": [ + "after-comment", + ] + } + ], + "indentation": [ + 2, + { + "indentInsideParens": "once-at-root-twice-in-block" + } + ], + "max-nesting-depth": 3, + "no-duplicate-selectors": true, + "no-unknown-animations": true + } +} diff --git a/app/fonts/DIN Next/DIN Next W01 Bold.otf b/app/fonts/DIN Next/DIN Next W01 Bold.otf Binary files differnew file mode 100644 index 000000000..2b78d1ff4 --- /dev/null +++ b/app/fonts/DIN Next/DIN Next W01 Bold.otf diff --git a/app/fonts/DIN Next/DIN Next W01 Regular.otf b/app/fonts/DIN Next/DIN Next W01 Regular.otf Binary files differnew file mode 100644 index 000000000..09f6ee297 --- /dev/null +++ b/app/fonts/DIN Next/DIN Next W01 Regular.otf diff --git a/app/fonts/DIN Next/DIN Next W10 Black.otf b/app/fonts/DIN Next/DIN Next W10 Black.otf Binary files differnew file mode 100644 index 000000000..08eb73373 --- /dev/null +++ b/app/fonts/DIN Next/DIN Next W10 Black.otf diff --git a/app/fonts/DIN Next/DIN Next W10 Italic.otf b/app/fonts/DIN Next/DIN Next W10 Italic.otf Binary files differnew file mode 100644 index 000000000..73f2b9e8c --- /dev/null +++ b/app/fonts/DIN Next/DIN Next W10 Italic.otf diff --git a/app/fonts/DIN Next/DIN Next W10 Light.otf b/app/fonts/DIN Next/DIN Next W10 Light.otf Binary files differnew file mode 100644 index 000000000..700450e49 --- /dev/null +++ b/app/fonts/DIN Next/DIN Next W10 Light.otf diff --git a/app/fonts/DIN Next/DIN Next W10 Medium.otf b/app/fonts/DIN Next/DIN Next W10 Medium.otf Binary files differnew file mode 100644 index 000000000..b73f2e43f --- /dev/null +++ b/app/fonts/DIN Next/DIN Next W10 Medium.otf diff --git a/app/fonts/DIN_OT/DINOT-2.otf b/app/fonts/DIN_OT/DINOT-2.otf Binary files differnew file mode 100644 index 000000000..4a5e13127 --- /dev/null +++ b/app/fonts/DIN_OT/DINOT-2.otf diff --git a/app/fonts/DIN_OT/DINOT-Bold 2.otf b/app/fonts/DIN_OT/DINOT-Bold 2.otf Binary files differnew file mode 100644 index 000000000..6ed5b6c3d --- /dev/null +++ b/app/fonts/DIN_OT/DINOT-Bold 2.otf diff --git a/app/fonts/DIN_OT/DINOT-BoldItalic.otf b/app/fonts/DIN_OT/DINOT-BoldItalic.otf Binary files differnew file mode 100644 index 000000000..148c90588 --- /dev/null +++ b/app/fonts/DIN_OT/DINOT-BoldItalic.otf diff --git a/app/fonts/DIN_OT/DINOT-Italic 2.otf b/app/fonts/DIN_OT/DINOT-Italic 2.otf Binary files differnew file mode 100644 index 000000000..e365e77ab --- /dev/null +++ b/app/fonts/DIN_OT/DINOT-Italic 2.otf diff --git a/app/fonts/DIN_OT/DINOT-Medium 2.otf b/app/fonts/DIN_OT/DINOT-Medium 2.otf Binary files differnew file mode 100644 index 000000000..a87a2df37 --- /dev/null +++ b/app/fonts/DIN_OT/DINOT-Medium 2.otf diff --git a/app/fonts/DIN_OT/DINOT-MediumItalic 2.otf b/app/fonts/DIN_OT/DINOT-MediumItalic 2.otf Binary files differnew file mode 100644 index 000000000..14eddfc76 --- /dev/null +++ b/app/fonts/DIN_OT/DINOT-MediumItalic 2.otf diff --git a/app/fonts/Roboto/Roboto-Black.ttf b/app/fonts/Roboto/Roboto-Black.ttf Binary files differnew file mode 100644 index 000000000..71f01ac2b --- /dev/null +++ b/app/fonts/Roboto/Roboto-Black.ttf diff --git a/app/fonts/Roboto/Roboto-BlackItalic.ttf b/app/fonts/Roboto/Roboto-BlackItalic.ttf Binary files differnew file mode 100644 index 000000000..ec309c785 --- /dev/null +++ b/app/fonts/Roboto/Roboto-BlackItalic.ttf diff --git a/app/fonts/Roboto/Roboto-Bold.ttf b/app/fonts/Roboto/Roboto-Bold.ttf Binary files differnew file mode 100644 index 000000000..aaf374d2c --- /dev/null +++ b/app/fonts/Roboto/Roboto-Bold.ttf diff --git a/app/fonts/Roboto/Roboto-BoldItalic.ttf b/app/fonts/Roboto/Roboto-BoldItalic.ttf Binary files differnew file mode 100644 index 000000000..dcd0f8007 --- /dev/null +++ b/app/fonts/Roboto/Roboto-BoldItalic.ttf diff --git a/app/fonts/Roboto/Roboto-Italic.ttf b/app/fonts/Roboto/Roboto-Italic.ttf Binary files differnew file mode 100644 index 000000000..f382c6874 --- /dev/null +++ b/app/fonts/Roboto/Roboto-Italic.ttf diff --git a/app/fonts/Roboto/Roboto-Light.ttf b/app/fonts/Roboto/Roboto-Light.ttf Binary files differnew file mode 100644 index 000000000..664e1b2f9 --- /dev/null +++ b/app/fonts/Roboto/Roboto-Light.ttf diff --git a/app/fonts/Roboto/Roboto-LightItalic.ttf b/app/fonts/Roboto/Roboto-LightItalic.ttf Binary files differnew file mode 100644 index 000000000..b8f529637 --- /dev/null +++ b/app/fonts/Roboto/Roboto-LightItalic.ttf diff --git a/app/fonts/Roboto/Roboto-Medium.ttf b/app/fonts/Roboto/Roboto-Medium.ttf Binary files differnew file mode 100644 index 000000000..aa00de0ef --- /dev/null +++ b/app/fonts/Roboto/Roboto-Medium.ttf diff --git a/app/fonts/Roboto/Roboto-MediumItalic.ttf b/app/fonts/Roboto/Roboto-MediumItalic.ttf Binary files differnew file mode 100644 index 000000000..67e25f019 --- /dev/null +++ b/app/fonts/Roboto/Roboto-MediumItalic.ttf diff --git a/app/fonts/Roboto/Roboto-Regular.ttf b/app/fonts/Roboto/Roboto-Regular.ttf Binary files differnew file mode 100644 index 000000000..3e6e2e761 --- /dev/null +++ b/app/fonts/Roboto/Roboto-Regular.ttf diff --git a/app/fonts/Roboto/Roboto-Thin.ttf b/app/fonts/Roboto/Roboto-Thin.ttf Binary files differnew file mode 100644 index 000000000..d262d1446 --- /dev/null +++ b/app/fonts/Roboto/Roboto-Thin.ttf diff --git a/app/fonts/Roboto/Roboto-ThinItalic.ttf b/app/fonts/Roboto/Roboto-ThinItalic.ttf Binary files differnew file mode 100644 index 000000000..63e9f9718 --- /dev/null +++ b/app/fonts/Roboto/Roboto-ThinItalic.ttf diff --git a/app/fonts/Roboto/RobotoCondensed-Bold.ttf b/app/fonts/Roboto/RobotoCondensed-Bold.ttf Binary files differnew file mode 100644 index 000000000..48dd63534 --- /dev/null +++ b/app/fonts/Roboto/RobotoCondensed-Bold.ttf diff --git a/app/fonts/Roboto/RobotoCondensed-BoldItalic.ttf b/app/fonts/Roboto/RobotoCondensed-BoldItalic.ttf Binary files differnew file mode 100644 index 000000000..ad728646a --- /dev/null +++ b/app/fonts/Roboto/RobotoCondensed-BoldItalic.ttf diff --git a/app/fonts/Roboto/RobotoCondensed-Italic.ttf b/app/fonts/Roboto/RobotoCondensed-Italic.ttf Binary files differnew file mode 100644 index 000000000..a232513d5 --- /dev/null +++ b/app/fonts/Roboto/RobotoCondensed-Italic.ttf diff --git a/app/fonts/Roboto/RobotoCondensed-Light.ttf b/app/fonts/Roboto/RobotoCondensed-Light.ttf Binary files differnew file mode 100644 index 000000000..a6e368d40 --- /dev/null +++ b/app/fonts/Roboto/RobotoCondensed-Light.ttf diff --git a/app/fonts/Roboto/RobotoCondensed-LightItalic.ttf b/app/fonts/Roboto/RobotoCondensed-LightItalic.ttf Binary files differnew file mode 100644 index 000000000..5b2b6ae08 --- /dev/null +++ b/app/fonts/Roboto/RobotoCondensed-LightItalic.ttf diff --git a/app/fonts/Roboto/RobotoCondensed-Regular.ttf b/app/fonts/Roboto/RobotoCondensed-Regular.ttf Binary files differnew file mode 100644 index 000000000..65bf32a19 --- /dev/null +++ b/app/fonts/Roboto/RobotoCondensed-Regular.ttf diff --git a/app/images/eth_logo.svg b/app/images/eth_logo.svg new file mode 100644 index 000000000..894bd70dd --- /dev/null +++ b/app/images/eth_logo.svg @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg width="256px" height="417px" viewBox="0 0 256 417" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid"> + <g> + <polygon fill="#343434" points="127.9611 0 125.1661 9.5 125.1661 285.168 127.9611 287.958 255.9231 212.32"/> + <polygon fill="#8C8C8C" points="127.962 0 0 212.32 127.962 287.959 127.962 154.158"/> + <polygon fill="#3C3C3B" points="127.9611 312.1866 126.3861 314.1066 126.3861 412.3056 127.9611 416.9066 255.9991 236.5866"/> + <polygon fill="#8C8C8C" points="127.962 416.9052 127.962 312.1852 0 236.5852"/> + <polygon fill="#141414" points="127.9611 287.9577 255.9211 212.3207 127.9611 154.1587"/> + <polygon fill="#393939" points="0.0009 212.3208 127.9609 287.9578 127.9609 154.1588"/> + </g> +</svg>
\ No newline at end of file diff --git a/app/notification.html b/app/notification.html index cc485da7f..be38f4aa3 100644 --- a/app/notification.html +++ b/app/notification.html @@ -9,7 +9,7 @@ } </style> </head> - <body> + <body style="width:350px; height:500px;"> <div id="app-content"></div> <script src="./scripts/popup.js" type="text/javascript" charset="utf-8"></script> </body> diff --git a/app/popup.html b/app/popup.html index d09b09315..fddf01841 100644 --- a/app/popup.html +++ b/app/popup.html @@ -5,7 +5,7 @@ <meta name="viewport" content="width=device-width, initial-scale=1 user-scalable=no"> <title>MetaMask Plugin</title> </head> - <body style="width:357px; height:500px;"> + <body style="width:350px; height:500px;"> <div id="app-content"></div> <script src="./scripts/popup.js" type="text/javascript" charset="utf-8"></script> </body> diff --git a/app/scripts/lib/environment-type.js b/app/scripts/lib/environment-type.js new file mode 100644 index 000000000..7966926eb --- /dev/null +++ b/app/scripts/lib/environment-type.js @@ -0,0 +1,10 @@ +module.exports = function environmentType () { + const url = window.location.href + if (url.match(/popup.html$/)) { + return 'popup' + } else if (url.match(/home.html$/)) { + return 'responsive' + } else { + return 'notification' + } +} diff --git a/app/scripts/lib/is-popup-or-notification.js b/app/scripts/lib/is-popup-or-notification.js index 693fa8751..73a812d5f 100644 --- a/app/scripts/lib/is-popup-or-notification.js +++ b/app/scripts/lib/is-popup-or-notification.js @@ -1,6 +1,6 @@ module.exports = function isPopupOrNotification () { const url = window.location.href - if (url.match(/popup.html$/)) { + if (url.match(/popup.html$/) || url.match(/home.html$/)) { return 'popup' } else { return 'notification' diff --git a/gulpfile.js b/gulpfile.js index ac36cf983..f8ed456b0 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -19,10 +19,16 @@ var manifest = require('./app/manifest.json') var gulpif = require('gulp-if') var replace = require('gulp-replace') var mkdirp = require('mkdirp') +var sass = require('gulp-sass') +var autoprefixer = require('gulp-autoprefixer') +var gulpStylelint = require('gulp-stylelint') +var stylefmt = require('gulp-stylefmt') + var disableDebugTools = gutil.env.disableDebugTools var debug = gutil.env.debug + // browser reload gulp.task('dev:reload', function() { @@ -159,13 +165,7 @@ gulp.task('lint', function () { // To have the process exit with an error code (1) on // lint error, return the stream and pipe to failAfterError last. .pipe(eslint.failAfterError()) -}); - -/* -gulp.task('default', ['lint'], function () { - // This will only run if the lint task is successful... -}); -*/ +}) // build js @@ -176,6 +176,36 @@ const jsFiles = [ 'popup', ] +// scss compilation and autoprefixing tasks + +gulp.task('build:scss', function () { + return gulp.src('ui/app/css/index.scss') + .pipe(sourcemaps.init()) + .pipe(sass().on('error', sass.logError)) + .pipe(sourcemaps.write()) + .pipe(autoprefixer()) + .pipe(gulp.dest('ui/app/css/output')) +}) +gulp.task('watch:scss', function() { + gulp.watch(['ui/app/css/**/*.scss'], gulp.series(['build:scss'])) +}) + +gulp.task('lint-scss', function() { + return gulp + .src('ui/app/css/itcss/**/*.scss') + .pipe(gulpStylelint({ + reporters: [ + {formatter: 'string', console: true} + ] + })); +}); + +gulp.task('fmt-scss', function () { + return gulp.src('ui/app/css/itcss/**/*.scss') + .pipe(stylefmt()) + .pipe(gulp.dest('ui/app/css/itcss')); +}); + // bundle tasks var jsDevStrings = jsFiles.map(jsFile => `dev:js:${jsFile}`) @@ -214,9 +244,9 @@ gulp.task('zip', gulp.parallel('zip:chrome', 'zip:firefox', 'zip:edge', 'zip:ope // high level tasks -gulp.task('dev', gulp.series('dev:js', 'copy', gulp.parallel('copy:watch', 'dev:reload'))) +gulp.task('dev', gulp.series('build:scss', 'dev:js', 'copy', gulp.parallel('watch:scss', 'copy:watch', 'dev:reload'))) -gulp.task('build', gulp.series('clean', gulp.parallel('build:js', 'copy'))) +gulp.task('build', gulp.series('clean', 'build:scss', gulp.parallel('build:js', 'copy'))) gulp.task('dist', gulp.series('build', 'zip')) // task generators @@ -244,7 +274,7 @@ function zipTask(target) { return () => { return gulp.src(`dist/${target}/**`) .pipe(zip(`metamask-${target}-${manifest.version}.zip`)) - .pipe(gulp.dest('builds')); + .pipe(gulp.dest('builds')) } } diff --git a/package.json b/package.json index f3cf2882a..8092e03c5 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ ] } ], + "reactify", "envify", "brfs" ] @@ -54,6 +55,7 @@ "bip39": "^2.2.0", "bluebird": "^3.5.0", "bn.js": "^4.11.7", + "boron": "^0.2.3", "browser-passworder": "^2.0.3", "browserify-derequire": "^0.9.4", "client-sw-ready-event": "^3.3.0", @@ -84,7 +86,9 @@ "extensionizer": "^1.0.0", "fast-levenshtein": "^2.0.6", "gulp": "github:gulpjs/gulp#4.0", + "gulp-autoprefixer": "^4.0.0", "gulp-eslint": "^4.0.0", + "gulp-sass": "^3.1.0", "hat": "0.0.3", "idb-global": "^2.1.0", "identicon.js": "^2.3.1", @@ -118,6 +122,8 @@ "react-select": "^1.0.0-rc.2", "react-simple-file-input": "^1.0.0", "react-tooltip-component": "^0.3.0", + "react-transition-group": "^2.2.0", + "reactify": "^1.1.1", "readable-stream": "^2.1.2", "redux": "^3.0.5", "redux-logger": "^3.0.6", @@ -163,6 +169,8 @@ "gulp-livereload": "^3.8.1", "gulp-replace": "^0.6.1", "gulp-sourcemaps": "^2.6.0", + "gulp-stylefmt": "^1.1.0", + "gulp-stylelint": "^4.0.0", "gulp-util": "^3.0.7", "gulp-watch": "^4.3.5", "gulp-zip": "^4.0.0", diff --git a/test/unit/components/balance-component-test.js b/test/unit/components/balance-component-test.js new file mode 100644 index 000000000..c32a8ab2b --- /dev/null +++ b/test/unit/components/balance-component-test.js @@ -0,0 +1,32 @@ +var assert = require('assert') +var BalanceComponent = require('../../../ui/app/components/balance-component') + +describe('BalanceComponent', function () { + let balanceComponent + + beforeEach(function () { + balanceComponent = new BalanceComponent() + }) + + it('shows token balance and convert to fiat value based on conversion rate', function () { + const formattedBalance = '1.23 ETH' + + const tokenBalance = balanceComponent.getTokenBalance(formattedBalance, false) + const fiatDisplayNumber = balanceComponent.getFiatDisplayNumber(formattedBalance, 2) + + assert.equal('1.23 ETH', tokenBalance) + assert.equal(2.46, fiatDisplayNumber) + }) + + it('shows only the token balance when conversion rate is not available', function () { + const formattedBalance = '1.23 ETH' + + const tokenBalance = balanceComponent.getTokenBalance(formattedBalance, false) + const fiatDisplayNumber = balanceComponent.getFiatDisplayNumber(formattedBalance, 0) + + assert.equal('1.23 ETH', tokenBalance) + assert.equal('N/A', fiatDisplayNumber) + }) + +}) + diff --git a/ui/app/account-and-transaction-details.js b/ui/app/account-and-transaction-details.js new file mode 100644 index 000000000..03f2d9db5 --- /dev/null +++ b/ui/app/account-and-transaction-details.js @@ -0,0 +1,38 @@ +const Component = require('react').Component +const h = require('react-hyperscript') +const inherits = require('util').inherits +// Main Views +const TxView = require('./components/tx-view') +const WalletView = require('./components/wallet-view') + +module.exports = AccountAndTransactionDetails + +inherits(AccountAndTransactionDetails, Component) +function AccountAndTransactionDetails () { + Component.call(this) +} + +AccountAndTransactionDetails.prototype.render = function () { + return h('div', { + style: { + display: 'flex', + flex: '1 0 auto', + }, + }, [ + // wallet + h(WalletView, { + style: { + }, + responsiveDisplayClassname: '.lap-visible', + }, [ + ]), + + // transaction + h(TxView, { + style: { + } + }, [ + ]), + ]) +} + diff --git a/ui/app/account-detail.js b/ui/app/account-detail.js index f6041e856..2be5d5c3a 100644 --- a/ui/app/account-detail.js +++ b/ui/app/account-detail.js @@ -40,179 +40,11 @@ function AccountDetailScreen () { Component.call(this) } -AccountDetailScreen.prototype.render = function () { - var props = this.props - var selected = props.address || Object.keys(props.accounts)[0] - var checksumAddress = selected && ethUtil.toChecksumAddress(selected) - var identity = props.identities[selected] - var account = props.accounts[selected] - const { network, conversionRate, currentCurrency } = props - - return ( - - h('.account-detail-section.full-flex-height', [ - - // identicon, label, balance, etc - h('.account-data-subsection', { - style: { - margin: '0 20px', - flex: '1 0 auto', - }, - }, [ - - // header - identicon + nav - h('div', { - style: { - paddingTop: '20px', - display: 'flex', - justifyContent: 'flex-start', - alignItems: 'flex-start', - }, - }, [ - - // large identicon and addresses - h('.identicon-wrapper.select-none', [ - h(Identicon, { - diameter: 62, - address: selected, - }), - ]), - h('flex-column', { - style: { - lineHeight: '10px', - marginLeft: '15px', - width: '100%', - }, - }, [ - h(EditableLabel, { - textValue: identity ? identity.name : '', - state: { - isEditingLabel: false, - }, - saveText: (text) => { - props.dispatch(actions.saveAccountLabel(selected, text)) - }, - }, [ - - // What is shown when not editing + edit text: - h('label.editing-label', [h('.edit-text', 'edit')]), - h( - 'div', - { - style: { - display: 'flex', - justifyContent: 'flex-start', - alignItems: 'center', - }, - }, - [ - h( - 'div.font-medium.color-forest', - { - name: 'edit', - style: { - }, - }, - [ - h('h2', { - style: { - maxWidth: '180px', - overflowX: 'hidden', - textOverflow: 'ellipsis', - padding: '5px 0px', - }, - }, [ - identity && identity.name, - ]), - ] - ), - h( - AccountDropdowns, - { - style: { - marginRight: '8px', - marginLeft: 'auto', - cursor: 'pointer', - }, - selected, - network, - identities: props.identities, - enableAccountOptions: true, - }, - ), - ] - ), - ]), - h('.flex-row', { - style: { - width: '15em', - justifyContent: 'space-between', - alignItems: 'baseline', - }, - }, [ - - // address - - h('div', { - style: { - overflow: 'hidden', - textOverflow: 'ellipsis', - paddingTop: '3px', - width: '5em', - fontSize: '13px', - fontFamily: 'Montserrat Light', - textRendering: 'geometricPrecision', - marginBottom: '15px', - color: '#AEAEAE', - }, - }, checksumAddress), - ]), - - // account ballence - - ]), - ]), - h('.flex-row', { - style: { - justifyContent: 'space-between', - alignItems: 'flex-start', - }, - }, [ - - h(EthBalance, { - value: account && account.balance, - conversionRate, - currentCurrency, - style: { - lineHeight: '7px', - marginTop: '10px', - }, - }), - - h('.flex-grow'), - - h('button', { - onClick: () => props.dispatch(actions.buyEthView(selected)), - style: { marginRight: '10px' }, - }, 'BUY'), - - h('button', { - onClick: () => props.dispatch(actions.showSendPage()), - style: { - marginBottom: '20px', - marginRight: '8px', - }, - }, 'SEND'), - - ]), - ]), - - // subview (tx history, pk export confirm, buy eth warning) - this.subview(), - - ]) - ) -} +// Note: This component is no longer used. Leaving the file for reference: +// - structuring routing for add token +// - state required for TxList +// Delete file when those features are complete +AccountDetailScreen.prototype.render = function () {} AccountDetailScreen.prototype.subview = function () { var subview diff --git a/ui/app/actions.js b/ui/app/actions.js index eafd04b4c..ae6d92733 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -5,6 +5,21 @@ var actions = { GO_HOME: 'GO_HOME', goHome: goHome, + // modal state + MODAL_OPEN: 'UI_MODAL_OPEN', + MODAL_CLOSE: 'UI_MODAL_CLOSE', + showModal: showModal, + hideModal: hideModal, + // sidebar state + SIDEBAR_OPEN: 'UI_SIDEBAR_OPEN', + SIDEBAR_CLOSE: 'UI_SIDEBAR_CLOSE', + showSidebar: showSidebar, + hideSidebar: hideSidebar, + // network dropdown open + NETWORK_DROPDOWN_OPEN: 'UI_NETWORK_DROPDOWN_OPEN', + NETWORK_DROPDOWN_CLOSE: 'UI_NETWORK_DROPDOWN_CLOSE', + showNetworkDropdown: showNetworkDropdown, + hideNetworkDropdown: hideNetworkDropdown, // menu state getNetworkStatus: 'getNetworkStatus', // transition state @@ -763,6 +778,44 @@ function useEtherscanProvider () { } } +function showNetworkDropdown () { + return { + type: actions.NETWORK_DROPDOWN_OPEN, + } +} + +function hideNetworkDropdown () { + return { + type: actions.NETWORK_DROPDOWN_CLOSE, + } +} + + +function showModal () { + return { + type: actions.MODAL_OPEN, + } +} + +function hideModal () { + return { + type: actions.MODAL_CLOSE, + } +} + +function showSidebar () { + return { + type: actions.SIDEBAR_OPEN, + } +} + +function hideSidebar () { + return { + type: actions.SIDEBAR_CLOSE, + } +} + + function showLoadingIndication (message) { return { type: actions.SHOW_LOADING, diff --git a/ui/app/add-token.js b/ui/app/add-token.js index 15ef7a852..5c6dea4a0 100644 --- a/ui/app/add-token.js +++ b/ui/app/add-token.js @@ -212,7 +212,6 @@ AddTokenScreen.prototype.attemptToAutoFillTokenParams = async function (address) const [ symbol, decimals ] = results if (symbol && decimals) { - console.log('SETTING SYMBOL AND DECIMALS', { symbol, decimals }) this.setState({ symbol: symbol[0], decimals: decimals[0].toString() }) } } diff --git a/ui/app/app.js b/ui/app/app.js index 1f3d5b0f8..c6777ca16 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -9,12 +9,16 @@ const NewKeyChainScreen = require('./new-keychain') // unlock const UnlockScreen = require('./unlock') // accounts -const AccountDetailScreen = require('./account-detail') +const MainContainer = require('./main-container') const SendTransactionScreen = require('./send') const ConfirmTxScreen = require('./conf-tx') // notice const NoticeScreen = require('./components/notice') const generateLostAccountsNotice = require('../lib/lost-accounts-notice') + +// slideout menu +const WalletView = require('./components/wallet-view') + // other views const ConfigScreen = require('./config') const AddTokenScreen = require('./add-token') @@ -22,17 +26,19 @@ const Import = require('./accounts/import') const InfoScreen = require('./info') const Loading = require('./components/loading') const SandwichExpando = require('sandwich-expando') -const Dropdown = require('./components/dropdown').Dropdown -const DropdownMenuItem = require('./components/dropdown').DropdownMenuItem const NetworkIndicator = require('./components/network') const BuyView = require('./components/buy-button-subview') const QrView = require('./components/qr-code') const HDCreateVaultComplete = require('./keychains/hd/create-vault-complete') const HDRestoreVaultScreen = require('./keychains/hd/restore-vault') const RevealSeedConfirmation = require('./keychains/hd/recover-seed/confirmation') -const AccountDropdowns = require('./components/account-dropdowns').AccountDropdowns +const ReactCSSTransitionGroup = require('react-addons-css-transition-group') +const NetworkDropdown = require('./components/dropdowns/network-dropdown') -module.exports = connect(mapStateToProps)(App) +// Global Modals +const BuyModal = require('./components/modals/index').BuyModal + +module.exports = connect(mapStateToProps, mapDispatchToProps)(App) inherits(App, Component) function App () { Component.call(this) } @@ -47,6 +53,8 @@ function mapStateToProps (state) { return { // state from plugin + networkDropdownOpen: state.appState.networkDropdownOpen, + sidebarOpen: state.appState.sidebarOpen, isLoading: state.appState.isLoading, loadingMessage: state.appState.loadingMessage, noActiveNotices: state.metamask.noActiveNotices, @@ -72,9 +80,17 @@ function mapStateToProps (state) { } } +function mapDispatchToProps (dispatch, ownProps) { + return { + hideSidebar: () => {dispatch(actions.hideSidebar())}, + showNetworkDropdown: () => {dispatch(actions.showNetworkDropdown())}, + hideNetworkDropdown: () => {dispatch(actions.hideNetworkDropdown())}, + } +} + App.prototype.render = function () { var props = this.props - const { isLoading, loadingMessage, transForward, network } = props + const { isLoading, loadingMessage, transForward, network, sidebarOpen } = props const isLoadingNetwork = network === 'loading' && props.currentView.name !== 'config' const loadMessage = loadingMessage || isLoadingNetwork ? `Connecting to ${this.getNetworkName()}` : null @@ -84,35 +100,93 @@ App.prototype.render = function () { h('.flex-column.full-height', { style: { - // Windows was showing a vertical scroll bar: - overflow: 'hidden', + overflowX: 'hidden', position: 'relative', alignItems: 'center', }, }, [ + // global modal + h(BuyModal, {}, []), + // app bar this.renderAppBar(), - this.renderNetworkDropdown(), - this.renderDropdown(), + + // sidebar + this.renderSidebar(), + + // network dropdown + h(NetworkDropdown, { + provider: this.props.provider, + frequentRpcList: this.props.frequentRpcList, + }, []), h(Loading, { isLoading: isLoading || isLoadingNetwork, loadingMessage: loadMessage, }), - // panel content - h('.app-primary' + (transForward ? '.from-right' : '.from-left'), { - style: { - width: '100%', - }, - }, [ - this.renderPrimary(), - ]), + // content + this.renderPrimary(), ]) ) } +App.prototype.renderGlobalModal = function() { + return h(Modal, { + ref: "modalRef", + }, [ + h(BuyOptions, {}, []), + ]) +} + +App.prototype.renderSidebar = function() { + + return h('div', { + }, [ + h('style', ` + .sidebar-enter { + transition: transform 300ms ease-in-out; + transform: translateX(-100%); + } + .sidebar-enter.sidebar-enter-active { + transition: transform 300ms ease-in-out; + transform: translateX(0%); + } + .sidebar-leave { + transition: transform 200ms ease-out; + transform: translateX(0%); + } + .sidebar-leave.sidebar-leave-active { + transition: transform 200ms ease-out; + transform: translateX(-100%); + } + `), + + h(ReactCSSTransitionGroup, { + transitionName: 'sidebar', + transitionEnterTimeout: 300, + transitionLeaveTimeout: 200, + }, [ + // A second instance of Walletview is used for non-mobile viewports + this.props.sidebarOpen ? h(WalletView, { + responsiveDisplayClassname: '.sidebar', + style: {}, + }) : undefined, + + ]), + + // overlay + // TODO: add onClick for overlay to close sidebar + this.props.sidebarOpen ? h('div.sidebar-overlay', { + style: {}, + onClick: () => { + this.props.hideSidebar() + }, + }, []) : undefined, + ]) +} + App.prototype.renderAppBar = function () { if (window.METAMASK_UI_TYPE === 'notification') { return null @@ -125,28 +199,16 @@ App.prototype.renderAppBar = function () { return ( h('.full-width', { - height: '38px', + style: {} }, [ h('.app-header.flex-row.flex-space-between', { - style: { - alignItems: 'center', - visibility: props.isUnlocked ? 'visible' : 'none', - background: props.isUnlocked ? 'white' : 'none', - height: '38px', - position: 'relative', - zIndex: 12, - }, + style: {}, }, [ - h('div.left-menu-section', { - style: { - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - }, + h('div.left-menu-wrapper', { + style: {}, }, [ - // mini logo h('img', { height: 24, @@ -154,245 +216,41 @@ App.prototype.renderAppBar = function () { src: '/images/icon-128.png', }), + // metamask name + h('h1', { + style: { + position: 'relative', + left: '9px', + }, + }, 'MetaMask'), + + ]), + + h('div.network-component-wrapper', { + style: {}, + }, [ + // Network Indicator h(NetworkIndicator, { network: this.props.network, provider: this.props.provider, onClick: (event) => { event.preventDefault() event.stopPropagation() - this.setState({ isNetworkMenuOpen: !isNetworkMenuOpen }) + if (this.props.networkDropdownOpen === false) { + this.props.showNetworkDropdown() + } else { + this.props.hideNetworkDropdown() + } }, }), - ]), - - // metamask name - props.isUnlocked && h('h1', { - style: { - position: 'relative', - left: '9px', - }, - }, 'MetaMask'), - props.isUnlocked && h('div', { - style: { - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - }, - }, [ - - props.isUnlocked && h(AccountDropdowns, { - style: {}, - enableAccountsSelector: true, - identities: this.props.identities, - selected: this.props.currentView.context, - network: this.props.network, - }, []), - - // hamburger - props.isUnlocked && h(SandwichExpando, { - className: 'sandwich-expando', - width: 16, - barHeight: 2, - padding: 0, - isOpen: state.isMainMenuOpen, - color: 'rgb(247,146,30)', - onClick: () => { - this.setState({ - isMainMenuOpen: !state.isMainMenuOpen, - }) - }, - }), ]), ]), + ]) ) } -App.prototype.renderNetworkDropdown = function () { - const props = this.props - const { provider: { type: providerType, rpcTarget: activeNetwork } } = props - const rpcList = props.frequentRpcList - const state = this.state || {} - const isOpen = state.isNetworkMenuOpen - - return h(Dropdown, { - useCssTransition: true, - isOpen, - onClickOutside: (event) => { - const { classList } = event.target - const isNotToggleElement = [ - classList.contains('menu-icon'), - classList.contains('network-name'), - classList.contains('network-indicator'), - ].filter(bool => bool).length === 0 - // classes from three constituent nodes of the toggle element - - if (isNotToggleElement) { - this.setState({ isNetworkMenuOpen: false }) - } - }, - zIndex: 11, - style: { - position: 'absolute', - left: '2px', - top: '36px', - }, - innerStyle: { - padding: '2px 16px 2px 0px', - }, - }, [ - - h( - DropdownMenuItem, - { - key: 'main', - closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }), - onClick: () => props.dispatch(actions.setProviderType('mainnet')), - style: { - fontSize: '18px', - }, - }, - [ - h('.menu-icon.diamond'), - 'Main Ethereum Network', - providerType === 'mainnet' ? h('.check', '✓') : null, - ] - ), - - h( - DropdownMenuItem, - { - key: 'ropsten', - closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }), - onClick: () => props.dispatch(actions.setProviderType('ropsten')), - style: { - fontSize: '18px', - }, - }, - [ - h('.menu-icon.red-dot'), - 'Ropsten Test Network', - providerType === 'ropsten' ? h('.check', '✓') : null, - ] - ), - - h( - DropdownMenuItem, - { - key: 'kovan', - closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }), - onClick: () => props.dispatch(actions.setProviderType('kovan')), - style: { - fontSize: '18px', - }, - }, - [ - h('.menu-icon.hollow-diamond'), - 'Kovan Test Network', - providerType === 'kovan' ? h('.check', '✓') : null, - ] - ), - - h( - DropdownMenuItem, - { - key: 'rinkeby', - closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }), - onClick: () => props.dispatch(actions.setProviderType('rinkeby')), - style: { - fontSize: '18px', - }, - }, - [ - h('.menu-icon.golden-square'), - 'Rinkeby Test Network', - providerType === 'rinkeby' ? h('.check', '✓') : null, - ] - ), - - h( - DropdownMenuItem, - { - key: 'default', - closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }), - onClick: () => props.dispatch(actions.setDefaultRpcTarget()), - style: { - fontSize: '18px', - }, - }, - [ - h('i.fa.fa-question-circle.fa-lg.menu-icon'), - 'Localhost 8545', - activeNetwork === 'http://localhost:8545' ? h('.check', '✓') : null, - ] - ), - - this.renderCustomOption(props.provider), - this.renderCommonRpc(rpcList, props.provider), - - h( - DropdownMenuItem, - { - closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }), - onClick: () => this.props.dispatch(actions.showConfigPage()), - style: { - fontSize: '18px', - }, - }, - [ - h('i.fa.fa-question-circle.fa-lg.menu-icon'), - 'Custom RPC', - activeNetwork === 'custom' ? h('.check', '✓') : null, - ] - ), - - ]) -} - -App.prototype.renderDropdown = function () { - const state = this.state || {} - const isOpen = state.isMainMenuOpen - - return h(Dropdown, { - useCssTransition: true, - isOpen: isOpen, - zIndex: 11, - onClickOutside: (event) => { - const classList = event.target.classList - const parentClassList = event.target.parentElement.classList - - const isToggleElement = classList.contains('sandwich-expando') || - parentClassList.contains('sandwich-expando') - - if (isOpen && !isToggleElement) { - this.setState({ isMainMenuOpen: false }) - } - }, - style: { - position: 'absolute', - right: '2px', - top: '38px', - }, - innerStyle: {}, - }, [ - h(DropdownMenuItem, { - closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }), - onClick: () => { this.props.dispatch(actions.showConfigPage()) }, - }, 'Settings'), - - h(DropdownMenuItem, { - closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }), - onClick: () => { this.props.dispatch(actions.lockMetamask()) }, - }, 'Lock'), - - h(DropdownMenuItem, { - closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }), - onClick: () => { this.props.dispatch(actions.showInfoPage()) }, - }, 'Info/Help'), - ]) -} - App.prototype.renderBackButton = function (style, justArrow = false) { var props = this.props return ( @@ -456,28 +314,18 @@ App.prototype.renderPrimary = function () { // show unlock screen if (!props.isUnlocked) { - switch (props.currentView.name) { - - case 'restoreVault': - log.debug('rendering restore vault screen') - return h(HDRestoreVaultScreen, {key: 'HDRestoreVaultScreen'}) - - case 'config': - log.debug('rendering config screen from unlock screen.') - return h(ConfigScreen, {key: 'config'}) - - default: - log.debug('rendering locked screen') - return h(UnlockScreen, {key: 'locked'}) - } + return h(MainContainer, { + currentViewName: props.currentView.name, + isUnlocked: props.isUnlocked, + }) } // show current view switch (props.currentView.name) { case 'accountDetail': - log.debug('rendering account detail screen') - return h(AccountDetailScreen, {key: 'account-detail'}) + log.debug('rendering main container') + return h(MainContainer, {key: 'account-detail'}) case 'sendTransaction': log.debug('rendering send tx screen') @@ -545,7 +393,7 @@ App.prototype.renderPrimary = function () { default: log.debug('rendering default, account detail screen') - return h(AccountDetailScreen, {key: 'account-detail'}) + return h(MainContainer, {key: 'account-detail'}) } } @@ -561,40 +409,6 @@ App.prototype.toggleMetamaskActive = function () { } } -App.prototype.renderCustomOption = function (provider) { - const { rpcTarget, type } = provider - const props = this.props - - if (type !== 'rpc') return null - - // Concatenate long URLs - let label = rpcTarget - if (rpcTarget.length > 31) { - label = label.substr(0, 34) + '...' - } - - switch (rpcTarget) { - - case 'http://localhost:8545': - return null - - default: - return h( - DropdownMenuItem, - { - key: rpcTarget, - onClick: () => props.dispatch(actions.setRpcTarget(rpcTarget)), - closeMenu: () => this.setState({ isNetworkMenuOpen: false }), - }, - [ - h('i.fa.fa-question-circle.fa-lg.menu-icon'), - label, - h('.check', '✓'), - ] - ) - } -} - App.prototype.getNetworkName = function () { const { provider } = this.props const providerName = provider.type @@ -615,28 +429,3 @@ App.prototype.getNetworkName = function () { return name } - -App.prototype.renderCommonRpc = function (rpcList, provider) { - const props = this.props - const rpcTarget = provider.rpcTarget - - return rpcList.map((rpc) => { - if ((rpc === 'http://localhost:8545') || (rpc === rpcTarget)) { - return null - } else { - return h( - DropdownMenuItem, - { - key: `common${rpc}`, - closeMenu: () => this.setState({ isNetworkMenuOpen: false }), - onClick: () => props.dispatch(actions.setRpcTarget(rpc)), - }, - [ - h('i.fa.fa-question-circle.fa-lg.menu-icon'), - rpc, - rpcTarget === rpc ? h('.check', '✓') : null, - ] - ) - } - }) -} diff --git a/ui/app/components/balance-component.js b/ui/app/components/balance-component.js new file mode 100644 index 000000000..48efc7b6a --- /dev/null +++ b/ui/app/components/balance-component.js @@ -0,0 +1,95 @@ +const Component = require('react').Component +const connect = require('react-redux').connect +const h = require('react-hyperscript') +const inherits = require('util').inherits + +const { formatBalance, generateBalanceObject } = require('../util') + +module.exports = connect(mapStateToProps)(BalanceComponent) + +function mapStateToProps (state) { + return { + conversionRate: state.metamask.conversionRate, + currentCurrency: state.metamask.currentCurrency, + } +} + +inherits(BalanceComponent, Component) +function BalanceComponent () { + Component.call(this) +} + +BalanceComponent.prototype.render = function () { + const props = this.props + const { balanceValue } = props + const needsParse = 'needsParse' in props ? props.needsParse : true + const formattedBalance = balanceValue ? formatBalance(balanceValue, 6, needsParse) : '...' + + return h('div.balance-container', {}, [ + + // TODO: balance icon needs to be passed in + h('img.balance-icon', { + src: '../images/eth_logo.svg', + style: {}, + }), + + this.renderBalance(formattedBalance), + ]) +} + +BalanceComponent.prototype.renderBalance = function (formattedBalance) { + const props = this.props + const { shorten } = props + 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', { + style: {}, + }, this.getTokenBalance(formattedBalance, shorten)), + + showFiat ? this.renderFiatValue(formattedBalance) : null, + ]) +} + +BalanceComponent.prototype.renderFiatValue = function (formattedBalance) { + + const { conversionRate, currentCurrency } = this.props + + const fiatDisplayNumber = this.getFiatDisplayNumber(formattedBalance, conversionRate) + + return this.renderFiatAmount(fiatDisplayNumber, currentCurrency) +} + +BalanceComponent.prototype.renderFiatAmount = function (fiatDisplayNumber, fiatSuffix) { + if (fiatDisplayNumber === 'N/A') return null + + return h('div.fiat-amount', { + style: {}, + }, `${fiatDisplayNumber} ${fiatSuffix}`) +} + +BalanceComponent.prototype.getTokenBalance = function (formattedBalance, shorten) { + const balanceObj = generateBalanceObject(formattedBalance, shorten ? 1 : 3) + + const balanceValue = shorten ? balanceObj.shortBalance : balanceObj.balance + const label = balanceObj.label + + return `${balanceValue} ${label}` +} + +BalanceComponent.prototype.getFiatDisplayNumber = function (formattedBalance, conversionRate) { + if (formattedBalance === 'None') return formattedBalance + if (conversionRate === 0) return 'N/A' + + const splitBalance = formattedBalance.split(' ') + + return (Number(splitBalance[0]) * conversionRate).toFixed(2) +} diff --git a/ui/app/components/buy-options.js b/ui/app/components/buy-options.js new file mode 100644 index 000000000..7d640c007 --- /dev/null +++ b/ui/app/components/buy-options.js @@ -0,0 +1,80 @@ +const Component = require('react').Component +const h = require('react-hyperscript') +const inherits = require('util').inherits +const connect = require('react-redux').connect +const actions = require('../actions') + +function mapStateToProps (state) { + return { + network: state.metamask.network, + address: state.metamask.selectedAddress, + } +} + +function mapDispatchToProps (dispatch) { + return { + toCoinbase: (address) => { + dispatch(actions.buyEth({ network: '1', address, amount: 0 })) + }, + hideModal: () => { + dispatch(actions.hideModal()) + } + } +} + +inherits(BuyOptions, Component) +function BuyOptions () { + Component.call(this) +} + +module.exports = connect(mapStateToProps, mapDispatchToProps)(BuyOptions) + +// BuyOptions is currently meant to be rendered inside <Modal /> +// It is the only component in this codebase that does so +// It utilizes modal styles +BuyOptions.prototype.render = function () { + return h('div', {}, [ + h('div.modal-content.transfers-subview', { + }, [ + h('div.modal-content-title-wrapper.flex-column.flex-center', { + style: {}, + }, [ + h('div.modal-content-title', { + style: {}, + }, 'Transfers'), + h('div', {}, 'How would you like to buy Ether?'), + ]), + + h('div.modal-content-options.flex-column.flex-center', {}, [ + + h('div.modal-content-option', { + onClick: () => { + const { toCoinbase, address } = this.props + toCoinbase(address) + }, + }, [ + h('div.modal-content-option-title', {}, 'Coinbase'), + h('div.modal-content-option-subtitle', {}, 'Buy with Fiat'), + ]), + + h('div.modal-content-option', {}, [ + h('div.modal-content-option-title', {}, 'Shapeshift'), + h('div.modal-content-option-subtitle', {}, 'Trade any digital asset for any other'), + ]), + + h('div.modal-content-option', {}, [ + h('div.modal-content-option-title', {}, 'Direct Deposit'), + h('div.modal-content-option-subtitle', {}, 'Deposit from another account'), + ]), + + ]), + + h('button', { + style: { + background: 'white', + }, + onClick: () => { this.props.hideModal() }, + }, h('div.modal-content-footer#modal-content-footer-text',{}, 'Cancel')), + ]) + ]) +} diff --git a/ui/app/components/dropdowns/account-options-dropdown.js b/ui/app/components/dropdowns/account-options-dropdown.js new file mode 100644 index 000000000..7d7188ec4 --- /dev/null +++ b/ui/app/components/dropdowns/account-options-dropdown.js @@ -0,0 +1,28 @@ +const Component = require('react').Component +const h = require('react-hyperscript') +const inherits = require('util').inherits +const AccountDropdowns = require('./components/account-dropdowns') + +inherits(AccountOptionsDropdown, Component) +function AccountOptionsDropdown () { + Component.call(this) +} + +module.exports = AccountOptionsDropdown + +// TODO: specify default props and proptypes +// TODO: hook up to state, connect to redux to clean up API +AccountOptionsDropdown.prototype.render = function () { + const { selected, network, identities, style, dropdownWrapperStyle, menuItemStyles } = this.props + + return h(AccountDropdowns, { + enableAccountOptions: true, + enableAccountsSelector: false, + selected: selectedAddress, + network, + identities, + style: !!style ? style : {}, + dropdownWrapperStyle: !!dropdownWrapperStyle ? dropdownWrapperStyle : {}, + menuItemStyles: !!menuItemStyles ? menuItemStyles : {}, + }, []) +} diff --git a/ui/app/components/dropdowns/account-selection-dropdown.js b/ui/app/components/dropdowns/account-selection-dropdown.js new file mode 100644 index 000000000..ccb73bde7 --- /dev/null +++ b/ui/app/components/dropdowns/account-selection-dropdown.js @@ -0,0 +1,28 @@ +const Component = require('react').Component +const h = require('react-hyperscript') +const inherits = require('util').inherits +const AccountDropdowns = require('./components/account-dropdowns') + +inherits(AccountSelectionDropdown, Component) +function AccountSelectionDropdown () { + Component.call(this) +} + +module.exports = AccountSelectionDropdown + +// TODO: specify default props and proptypes +// TODO: hook up to state, connect to redux to clean up API +AccountSelectionDropdown.prototype.render = function () { + const { selected, network, identities, style, dropdownWrapperStyle, menuItemStyles } = this.props + + return h(AccountDropdowns, { + enableAccountOptions: false, + enableAccountsSelector: true, + selected: selectedAddress, + network, + identities, + style: !!style ? style : {}, + dropdownWrapperStyle: !!dropdownWrapperStyle ? dropdownWrapperStyle : {}, + menuItemStyles: !!menuItemStyles ? menuItemStyles : {}, + }, []) +} diff --git a/ui/app/components/account-dropdowns.js b/ui/app/components/dropdowns/components/account-dropdowns.js index 7c24e70bd..b59f9bbf4 100644 --- a/ui/app/components/account-dropdowns.js +++ b/ui/app/components/dropdowns/components/account-dropdowns.js @@ -1,12 +1,12 @@ const Component = require('react').Component const PropTypes = require('react').PropTypes const h = require('react-hyperscript') -const actions = require('../actions') -const genAccountLink = require('../../lib/account-link.js') +const actions = require('../../../actions') +const genAccountLink = require('../../../../lib/account-link.js') const connect = require('react-redux').connect const Dropdown = require('./dropdown').Dropdown const DropdownMenuItem = require('./dropdown').DropdownMenuItem -const Identicon = require('./identicon') +const Identicon = require('../../identicon') const ethUtil = require('ethereumjs-util') const copyToClipboard = require('copy-to-clipboard') @@ -17,12 +17,14 @@ class AccountDropdowns extends Component { accountSelectorActive: false, optionsMenuActive: false, } - this.accountSelectorToggleClassName = 'accounts-selector' + // Used for orangeaccount selector icon + // this.accountSelectorToggleClassName = 'accounts-selector' + this.accountSelectorToggleClassName = 'fa-angle-down' this.optionsMenuToggleClassName = 'fa-ellipsis-h' } renderAccounts () { - const { identities, selected } = this.props + const { identities, selected, menuItemStyles, dropdownWrapperStyle } = this.props return Object.keys(identities).map((key, index) => { const identity = identities[key] @@ -35,10 +37,13 @@ class AccountDropdowns extends Component { onClick: () => { this.props.actions.showAccountDetail(identity.address) }, - style: { - marginTop: index === 0 ? '5px' : '', - fontSize: '24px', - }, + style: Object.assign( + { + marginTop: index === 0 ? '5px' : '', + fontSize: '24px', + }, + menuItemStyles, + ), }, [ h( @@ -68,16 +73,16 @@ class AccountDropdowns extends Component { } renderAccountSelector () { - const { actions } = this.props - const { accountSelectorActive } = this.state + const { actions, dropdownWrapperStyle, useCssTransition } = this.props + const { accountSelectorActive, menuItemStyles } = this.state return h( Dropdown, { - useCssTransition: true, // Hardcoded because account selector is temporarily in app-header + useCssTransition, style: { - marginLeft: '-238px', - marginTop: '38px', + marginLeft: '-185px', + marginTop: '50px', minWidth: '180px', overflowY: 'auto', maxHeight: '300px', @@ -102,6 +107,10 @@ class AccountDropdowns extends Component { { closeMenu: () => {}, onClick: () => actions.addNewAccount(), + style: Object.assign( + {}, + menuItemStyles, + ), }, [ h( @@ -121,6 +130,10 @@ class AccountDropdowns extends Component { { closeMenu: () => {}, onClick: () => actions.showImportPage(), + style: Object.assign( + {}, + menuItemStyles, + ), }, [ h( @@ -146,16 +159,21 @@ class AccountDropdowns extends Component { } renderAccountOptions () { - const { actions } = this.props - const { optionsMenuActive } = this.state + const { actions, dropdownWrapperStyle, useCssTransition } = this.props + const { optionsMenuActive, menuItemStyles } = this.state return h( Dropdown, { - style: { - marginLeft: '-215px', - minWidth: '180px', - }, + useCssTransition, + style: Object.assign( + { + marginLeft: '-10px', + position: 'absolute', + width: '29vh', // affects both mobile and laptop views + }, + dropdownWrapperStyle, + ), isOpen: optionsMenuActive, onClickOutside: () => { const { classList } = event.target @@ -175,6 +193,10 @@ class AccountDropdowns extends Component { const url = genAccountLink(selected, network) global.platform.openWindow({ url }) }, + style: Object.assign( + {}, + menuItemStyles, + ), }, 'View account on Etherscan', ), @@ -187,6 +209,10 @@ class AccountDropdowns extends Component { var identity = identities[selected] actions.showQrView(selected, identity ? identity.name : '') }, + style: Object.assign( + {}, + menuItemStyles, + ), }, 'Show QR Code', ), @@ -199,6 +225,10 @@ class AccountDropdowns extends Component { const checkSumAddress = selected && ethUtil.toChecksumAddress(selected) copyToClipboard(checkSumAddress) }, + style: Object.assign( + {}, + menuItemStyles, + ), }, 'Copy Address to clipboard', ), @@ -209,6 +239,10 @@ class AccountDropdowns extends Component { onClick: () => { actions.requestAccountExport() }, + style: Object.assign( + {}, + menuItemStyles, + ), }, 'Export Private Key', ), @@ -217,7 +251,7 @@ class AccountDropdowns extends Component { } render () { - const { style, enableAccountsSelector, enableAccountOptions } = this.props + const { style, enableAccountsSelector, enableAccountOptions, dropdownWrapperStyle } = this.props const { optionsMenuActive, accountSelectorActive } = this.state return h( @@ -227,16 +261,16 @@ class AccountDropdowns extends Component { }, [ enableAccountsSelector && h( - // 'i.fa.fa-angle-down', - 'div.cursor-pointer.color-orange.accounts-selector', + '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', + // fontSize: '135%', + // 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() @@ -252,8 +286,7 @@ class AccountDropdowns extends Component { 'i.fa.fa-ellipsis-h', { style: { - marginRight: '0.5em', - fontSize: '1.8em', + fontSize: '135%', }, onClick: (event) => { event.stopPropagation() @@ -277,7 +310,7 @@ AccountDropdowns.defaultProps = { AccountDropdowns.propTypes = { identities: PropTypes.objectOf(PropTypes.object), - selected: PropTypes.string, + selected: PropTypes.string, // TODO: refactor to be more explicit: selectedAddress } const mapDispatchToProps = (dispatch) => { @@ -293,6 +326,5 @@ const mapDispatchToProps = (dispatch) => { } } -module.exports = { - AccountDropdowns: connect(null, mapDispatchToProps)(AccountDropdowns), -} +module.exports = connect(null, mapDispatchToProps)(AccountDropdowns) + diff --git a/ui/app/components/dropdown.js b/ui/app/components/dropdowns/components/dropdown.js index 34c7149ee..1f35f0c70 100644 --- a/ui/app/components/dropdown.js +++ b/ui/app/components/dropdowns/components/dropdown.js @@ -1,7 +1,7 @@ const Component = require('react').Component const PropTypes = require('react').PropTypes const h = require('react-hyperscript') -const MenuDroppo = require('./menu-droppo') +const MenuDroppo = require('../../menu-droppo') const extend = require('xtend') const noop = () => {} @@ -22,7 +22,7 @@ class Dropdown extends Component { { useCssTransition, isOpen, - zIndex: 11, + zIndex: 30, onClickOutside, style, innerStyle: innerStyleDefaults, @@ -67,7 +67,7 @@ class DropdownMenuItem extends Component { }, style: Object.assign({ listStyle: 'none', - padding: '8px 0px 8px 0px', + padding: '8px 0px', fontSize: '18px', fontStyle: 'normal', fontFamily: 'Montserrat Regular', @@ -75,6 +75,7 @@ class DropdownMenuItem extends Component { display: 'flex', justifyContent: 'flex-start', alignItems: 'center', + color: 'white', }, style), }, children diff --git a/ui/app/components/dropdowns/index.js b/ui/app/components/dropdowns/index.js new file mode 100644 index 000000000..d21c795f5 --- /dev/null +++ b/ui/app/components/dropdowns/index.js @@ -0,0 +1,18 @@ +// Reusable Dropdown Components +//TODO: Refactor into separate components +const Dropdown = require('./components/dropdown').Dropdown +const DropdownMenuItem = require('./components/dropdown').DropdownMenuItem +const AccountDropdowns = require('./components/account-dropdowns') + +// App-Specific Instances +const AccountSelectionDropdown = require('./account-selection-dropdown') +const AccountOptionsDropdown = require('./account-options-dropdown') +const NetworkDropdown = require('./network-dropdown').default + +module.exports = { + AccountSelectionDropdown, + AccountOptionsDropdown, + NetworkDropdown, + Dropdown, + AccountDropdowns, +}
\ No newline at end of file diff --git a/ui/app/components/dropdowns/network-dropdown.js b/ui/app/components/dropdowns/network-dropdown.js new file mode 100644 index 000000000..6228513c9 --- /dev/null +++ b/ui/app/components/dropdowns/network-dropdown.js @@ -0,0 +1,268 @@ +const Component = require('react').Component +const h = require('react-hyperscript') +const inherits = require('util').inherits +const connect = require('react-redux').connect +const actions = require('../../actions') +const Dropdown = require('./components/dropdown').Dropdown +const DropdownMenuItem = require('./components/dropdown').DropdownMenuItem + +function mapStateToProps (state) { + return { + provider: state.metamask.provider, + frequentRpcList: state.metamask.frequentRpcList || [], + networkDropdownOpen: state.appState.networkDropdownOpen, + } +} + +function mapDispatchToProps (dispatch) { + return { + hideModal: () => { + dispatch(actions.hideModal()) + }, + setProviderType: (type) => { + dispatch(actions.setProviderType(type)) + }, + setDefaultRpcTarget: () => { + dispatch(actions.setDefaultRpcTarget(type)) + }, + setRpcTarget: (target) => { + dispatch(actions.setRpcTarget(target)) + }, + showConfigPage: () => { + dispatch(actions.showConfigPage()) + }, + showNetworkDropdown: () => {dispatch(actions.showNetworkDropdown())}, + hideNetworkDropdown: () => {dispatch(actions.hideNetworkDropdown())}, + } +} + + +inherits(NetworkDropdown, Component) +function NetworkDropdown () { + Component.call(this) +} + +module.exports = connect(mapStateToProps, mapDispatchToProps)(NetworkDropdown) + +// TODO: specify default props and proptypes +NetworkDropdown.prototype.render = function () { + const props = this.props + const { provider: { type: providerType, rpcTarget: activeNetwork } } = props + const rpcList = props.frequentRpcList + const state = this.state || {} + const isOpen = this.props.networkDropdownOpen + + return h(Dropdown, { + useCssTransition: true, + isOpen, + onClickOutside: (event) => { + const { classList } = event.target + const isNotToggleElement = [ + classList.contains('menu-icon'), + classList.contains('network-name'), + classList.contains('network-indicator'), + ].filter(bool => bool).length === 0 + // classes from three constituent nodes of the toggle element + + if (isNotToggleElement) { + this.props.hideNetworkDropdown() + } + }, + zIndex: 11, + style: { + position: 'absolute', + right: '2px', + top: '38px', + }, + innerStyle: { + padding: '2px 16px 2px 0px', + }, + }, [ + + h( + DropdownMenuItem, + { + key: 'main', + closeMenu: () => this.props.hideNetworkDropdown(), + onClick: () => props.setProviderType('mainnet'), + style: { + fontSize: '18px', + }, + }, + [ + h('.menu-icon.diamond'), + 'Main Ethereum Network', + providerType === 'mainnet' ? h('.check', '✓') : null, + ] + ), + + h( + DropdownMenuItem, + { + key: 'ropsten', + closeMenu: () => this.props.hideNetworkDropdown(), + onClick: () => props.setProviderType('ropsten'), + style: { + fontSize: '18px', + }, + }, + [ + h('.menu-icon.red-dot'), + 'Ropsten Test Network', + providerType === 'ropsten' ? h('.check', '✓') : null, + ] + ), + + h( + DropdownMenuItem, + { + key: 'kovan', + closeMenu: () => this.props.hideNetworkDropdown(), + onClick: () => props.setProviderType('kovan'), + style: { + fontSize: '18px', + }, + }, + [ + h('.menu-icon.hollow-diamond'), + 'Kovan Test Network', + providerType === 'kovan' ? h('.check', '✓') : null, + ] + ), + + h( + DropdownMenuItem, + { + key: 'rinkeby', + closeMenu: () => this.props.hideNetworkDropdown(), + onClick: () => propssetProviderType('rinkeby'), + style: { + fontSize: '18px', + }, + }, + [ + h('.menu-icon.golden-square'), + 'Rinkeby Test Network', + providerType === 'rinkeby' ? h('.check', '✓') : null, + ] + ), + + h( + DropdownMenuItem, + { + key: 'default', + closeMenu: () => this.props.hideNetworkDropdown(), + onClick: () => props.setDefaultRpcTarget(), + style: { + fontSize: '18px', + }, + }, + [ + h('i.fa.fa-question-circle.fa-lg.menu-icon'), + 'Localhost 8545', + activeNetwork === 'http://localhost:8545' ? h('.check', '✓') : null, + ] + ), + + this.renderCustomOption(props.provider), + this.renderCommonRpc(rpcList, props.provider), + + h( + DropdownMenuItem, + { + closeMenu: () => this.props.hideNetworkDropdown(), + onClick: () => this.props.showConfigPage(), + style: { + fontSize: '18px', + }, + }, + [ + h('i.fa.fa-question-circle.fa-lg.menu-icon'), + 'Custom RPC', + activeNetwork === 'custom' ? h('.check', '✓') : null, + ] + ), + + ]) +} + + +NetworkDropdown.prototype.getNetworkName = function () { + const { provider } = this.props + const providerName = provider.type + + let name + + if (providerName === 'mainnet') { + name = 'Main Ethereum Network' + } else if (providerName === 'ropsten') { + name = 'Ropsten Test Network' + } else if (providerName === 'kovan') { + name = 'Kovan Test Network' + } else if (providerName === 'rinkeby') { + name = 'Rinkeby Test Network' + } else { + name = 'Unknown Private Network' + } + + return name +} + +NetworkDropdown.prototype.renderCommonRpc = function (rpcList, provider) { + const props = this.props + const rpcTarget = provider.rpcTarget + + return rpcList.map((rpc) => { + if ((rpc === 'http://localhost:8545') || (rpc === rpcTarget)) { + return null + } else { + return h( + DropdownMenuItem, + { + key: `common${rpc}`, + closeMenu: () => this.props.hideNetworkDropdown(), + onClick: () => props.setRpcTarget(rpc), + }, + [ + h('i.fa.fa-question-circle.fa-lg.menu-icon'), + rpc, + rpcTarget === rpc ? h('.check', '✓') : null, + ] + ) + } + }) +} + +NetworkDropdown.prototype.renderCustomOption = function (provider) { + const { rpcTarget, type } = provider + const props = this.props + + if (type !== 'rpc') return null + + // Concatenate long URLs + let label = rpcTarget + if (rpcTarget.length > 31) { + label = label.substr(0, 34) + '...' + } + + switch (rpcTarget) { + + case 'http://localhost:8545': + return null + + default: + return h( + DropdownMenuItem, + { + key: rpcTarget, + onClick: () => props.setRpcTarget(rpcTarget), + closeMenu: () => this.props.hideNetworkDropdown(), + }, + [ + h('i.fa.fa-question-circle.fa-lg.menu-icon'), + label, + h('.check', '✓'), + ] + ) + } +} diff --git a/ui/app/components/ens-input.js b/ui/app/components/ens-input.js index 3a33ebf74..93c07599d 100644 --- a/ui/app/components/ens-input.js +++ b/ui/app/components/ens-input.js @@ -44,6 +44,13 @@ EnsInput.prototype.render = function () { return h('div', { style: { width: '100%' }, }, [ + h('span', { + style: { + textAlign: 'left', + } + }, [ + 'To:' + ]), h('input.large-input', opts), // The address book functionality. h('datalist#addresses', diff --git a/ui/app/components/modals/buy-modal.js b/ui/app/components/modals/buy-modal.js new file mode 100644 index 000000000..9a3e4dff9 --- /dev/null +++ b/ui/app/components/modals/buy-modal.js @@ -0,0 +1,23 @@ +const Component = require('react').Component +const h = require('react-hyperscript') +const inherits = require('util').inherits +const BuyOptions = require('../buy-options') +const Modal = require('./modal.js') + +inherits(BuyModal, Component) +function BuyModal () { + Component.call(this) +} + +module.exports = BuyModal + +BuyModal.prototype.render = function () { + return h(Modal, { + ref: "modalRef", + }, [ + h(BuyOptions, {}, []), + ]) + +} + +// TODO: specify default props and proptypes diff --git a/ui/app/components/modals/index.js b/ui/app/components/modals/index.js new file mode 100644 index 000000000..23b432b7c --- /dev/null +++ b/ui/app/components/modals/index.js @@ -0,0 +1,9 @@ +const Modal = require('./modal') +const BuyModal = require('./buy-modal') +// const h = require('account-options') +// const h = require('account-details') + +module.exports = { + Modal, + BuyModal, +}
\ No newline at end of file diff --git a/ui/app/components/modals/modal.js b/ui/app/components/modals/modal.js new file mode 100644 index 000000000..006e009b3 --- /dev/null +++ b/ui/app/components/modals/modal.js @@ -0,0 +1,100 @@ +const Component = require('react').Component +const h = require('react-hyperscript') +const inherits = require('util').inherits +const connect = require('react-redux').connect +const FadeModal = require('boron').FadeModal +const actions = require('../../actions') +const isMobileView = require('../../../lib/is-mobile-view') +const isPopupOrNotification = require('../../../../app/scripts/lib/is-popup-or-notification') + +function mapStateToProps (state) { + return { + active: state.appState.modalOpen + } +} + +function mapDispatchToProps (dispatch) { + return { + hideModal: () => { + dispatch(actions.hideModal()) + }, + } +} + +// Global Modal Component +inherits(Modal, Component) +function Modal () { + Component.call(this) +} + +module.exports = connect(mapStateToProps, mapDispatchToProps)(Modal) + +const mobileModalStyles = { + width: '95%', + // Used to create matching t/l/r/b space in mobile view. + top: isPopupOrNotification() === 'popup' ? '48vh' : '36.5vh', + boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px', +} + +const laptopModalStyles = { + width: '66%', + top: 'calc(30% + 10px)', + boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px', +} + +const backdropStyles = { + backgroundColor: 'rgba(245, 245, 245, 0.85)', +} + +Modal.prototype.render = function () { + + return h(FadeModal, + { + className: 'modal', + keyboard: false, + onHide: () => {this.onHide()}, + ref: (ref) => { + this.modalRef = ref + }, + modalStyle: isMobileView() ? mobileModalStyles : laptopModalStyles, + backdropStyle: backdropStyles, + }, + this.props.children, + ) +} + +Modal.prototype.componentWillReceiveProps = function(nextProps) { + if (nextProps.active) { + this.show() + } else if (this.props.active) { + this.hide() + } +} + +Modal.prototype.onHide = function() { + if (this.props.onHideCallback) { + this.props.onHideCallback() + } + this.props.hideModal() +} + +Modal.prototype.hide = function() { + this.modalRef.hide() +} + +Modal.prototype.show = function() { + this.modalRef.show() +} + +// TODO: specify default props and proptypes +// Modal.defaultProps = {} + +// const elementType = require('react-prop-types/lib/elementType') +// const PropTypes from 'prop-types' + +// Modal.propTypes = { +// active: PropTypes.bool, +// hideModal: PropTypes.func.isRequired, +// component: elementType, +// onHideCallback: PropTypes.func, +// } diff --git a/ui/app/components/network.js b/ui/app/components/network.js index 698a0bbb9..ba1d0ea11 100644 --- a/ui/app/components/network.js +++ b/ui/app/components/network.js @@ -60,7 +60,7 @@ Network.prototype.render = function () { } return ( - h('#network_component.pointer', { + h('.network-component.pointer', { title: hoverText, onClick: (event) => this.props.onClick(event), }, [ diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 5324ccd64..1c47440f2 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -20,6 +20,34 @@ const GWEI_FACTOR = new BN(1e9) const MIN_GAS_PRICE_BN = MIN_GAS_PRICE_GWEI_BN.mul(GWEI_FACTOR) const MIN_GAS_LIMIT_BN = new BN(21000) + +// Faked, for Icon +const Identicon = require('./identicon') +const ARAGON = '960b236A07cf122663c4303350609A66A7B288C0' + +// Next: create separate react components +// roughly 5 components: +// heroIcon +// numericDisplay (contains symbol + currency) +// divider +// contentBox +// actionButtons +const sectionDivider = h('div', { + style: { + height:'1px', + background:'#E7E7E7', + }, +}) + +const contentDivider = h('div', { + style: { + marginLeft: '16px', + marginRight: '16px', + height:'1px', + background:'#E7E7E7', + }, +}) + module.exports = PendingTx inherits(PendingTx, Component) function PendingTx () { @@ -70,344 +98,342 @@ PendingTx.prototype.render = function () { this.inputs = [] return ( - - h('div', { - key: txMeta.id, + h('div.flex-column.flex-grow', { + style: { + // overflow: 'scroll', + minWidth: '355px', // TODO: maxWidth TBD, use home.html + }, }, [ - h('form#pending-tx-form', { - onSubmit: this.onSubmit.bind(this), - + // Main Send token Card + h('div.send-screen.flex-column.flex-grow', { + style: { + marginLeft: '3.5%', + marginRight: '3.5%', + background: '#FFFFFF', // $background-white + boxShadow: '0 2px 4px 0 rgba(0,0,0,0.08)', + } }, [ + h('section.flex-center.flex-row', { + style: { + zIndex: 15, // $token-icon-z-index + marginTop: '-35px', + } + }, [ + h(Identicon, { + address: ARAGON, + diameter: 76, + }), + ]), - // tx info - h('div', [ + // + // Required Fields + // - h('.flex-row.flex-center', { - style: { - maxWidth: '100%', - }, - }, [ + h('h3.flex-center', { + style: { + marginTop: '-18px', + fontSize: '16px', + }, + }, [ + 'Confirm Transaction', + ]), - h(MiniAccountPanel, { - imageSeed: address, - picOrder: 'right', - }, [ - h('span.font-small', { - style: { - fontFamily: 'Montserrat Bold, Montserrat, sans-serif', - }, - }, identity.name), - - h(Copyable, { - value: ethUtil.toChecksumAddress(address), - }, [ - h('span.font-small', { - style: { - fontFamily: 'Montserrat Light, Montserrat, sans-serif', - }, - }, addressSummary(address, 6, 4, false)), - ]), - - h('span.font-small', { - style: { - fontFamily: 'Montserrat Light, Montserrat, sans-serif', - }, - }, [ - h(EthBalance, { - value: balance, - conversionRate, - currentCurrency, - inline: true, - labelColor: '#F7861C', - }), - ]), - ]), + h('h3.flex-center', { + style: { + textAlign: 'center', + fontSize: '12px', + }, + }, [ + 'You\'re sending to Recipient ...5924', + ]), - forwardCarrat(), + h('h3.flex-center', { + style: { + textAlign: 'center', + fontSize: '36px', + marginTop: '8px', + }, + }, [ + '0.24', + ]), - this.miniAccountPanelForRecipient(), - ]), + h('h3.flex-center', { + style: { + textAlign: 'center', + fontSize: '12px', + marginTop: '4px', + }, + }, [ + 'ANT', + ]), - h('style', ` - .table-box { - margin: 7px 0px 0px 0px; - width: 100%; - } - .table-box .row { - margin: 0px; - background: rgb(236,236,236); - display: flex; - justify-content: space-between; - font-family: Montserrat Light, sans-serif; - font-size: 13px; - padding: 5px 25px; - } - .table-box .row .value { - font-family: Montserrat Regular; - } - `), + // error message + props.error && h('span.error.flex-center', props.error), - h('.table-box', [ + sectionDivider, - // Ether Value - // Currently not customizable, but easily modified - // in the way that gas and gasLimit currently are. - h('.row', [ - h('.cell.label', 'Amount'), - h(EthBalance, { value: txParams.value, currentCurrency, conversionRate }), - ]), + h('section.flex-row.flex-center', { + }, [ + h('div', { + style: { + width: '50%', + } + }, [ + h('span', { + style: { + textAlign: 'left', + fontSize: '12px', + } + }, [ + 'From' + ]) + ]), - // Gas Limit (customizable) - h('.cell.row', [ - h('.cell.label', 'Gas Limit'), - h('.cell.value', { - }, [ - h(BNInput, { - name: 'Gas Limit', - value: gasBn, - precision: 0, - scale: 0, - // The hard lower limit for gas. - min: MIN_GAS_LIMIT_BN.toString(10), - max: safeGasLimit, - suffix: 'UNITS', - style: { - position: 'relative', - top: '5px', - }, - onChange: this.gasLimitChanged.bind(this), - - ref: (hexInput) => { this.inputs.push(hexInput) }, - }), - ]), - ]), + h('div', { + style: { + width: '50%', + } + },[ + h('div', { + style: { + textAlign: 'left', + fontSize: '10px', + marginBottom: '-10px', + }, + }, 'Aragon Token'), - // Gas Price (customizable) - h('.cell.row', [ - h('.cell.label', 'Gas Price'), - h('.cell.value', { - }, [ - h(BNInput, { - name: 'Gas Price', - value: gasPriceBn, - precision: 9, - scale: 9, - suffix: 'GWEI', - min: MIN_GAS_PRICE_GWEI_BN.toString(10), - style: { - position: 'relative', - top: '5px', - }, - onChange: this.gasPriceChanged.bind(this), - ref: (hexInput) => { this.inputs.push(hexInput) }, - }), - ]), - ]), + h('div', { + style: { + textAlign: 'left', + fontSize: '8px', + }, + }, 'Your Balance 2.34 ANT') + ]) + ]), - // Max Transaction Fee (calculated) - h('.cell.row', [ - h('.cell.label', 'Max Transaction Fee'), - h(EthBalance, { value: txFeeBn.toString(16), currentCurrency, conversionRate }), - ]), + contentDivider, - h('.cell.row', { + h('section.flex-row.flex-center', { + }, [ + h('div', { + style: { + width: '50%', + } + }, [ + h('span', { style: { - fontFamily: 'Montserrat Regular', - background: 'white', - padding: '10px 25px', - }, + textAlign: 'left', + fontSize: '12px', + } }, [ - h('.cell.label', 'Max Total'), - h('.cell.value', { - style: { - display: 'flex', - alignItems: 'center', - }, - }, [ - h(EthBalance, { - value: maxCost.toString(16), - currentCurrency, - conversionRate, - inline: true, - labelColor: 'black', - fontSize: '16px', - }), - ]), - ]), + 'To' + ]) + ]), - // Data size row: - h('.cell.row', { + h('div', { + style: { + width: '50%', + } + },[ + h('div', { style: { - background: '#f7f7f7', - paddingBottom: '0px', + textAlign: 'left', + fontSize: '10px', + marginBottom: '-10px', }, - }, [ - h('.cell.label'), - h('.cell.value', { - style: { - fontFamily: 'Montserrat Light', - fontSize: '11px', - }, - }, `Data included: ${dataLength} bytes`), - ]), - ]), // End of Table + }, 'Ethereum Address'), + h('div', { + style: { + textAlign: 'left', + fontSize: '8px', + }, + }, '...5924') + ]) ]), - h('style', ` - .conf-buttons button { - margin-left: 10px; - text-transform: uppercase; - } - `), + contentDivider, - txMeta.simulationFails ? - h('.error', { - style: { - marginLeft: 50, - fontSize: '0.9em', - }, - }, 'Transaction Error. Exception thrown in contract code.') - : null, - - !isValidAddress ? - h('.error', { + h('section.flex-row.flex-center', { + }, [ + h('div', { style: { - marginLeft: 50, - fontSize: '0.9em', - }, - }, 'Recipient address is invalid. Sending this transaction will result in a loss of ETH.') - : null, - - insufficientBalance ? - h('span.error', { + width: '50%', + } + }, [ + h('span', { + style: { + textAlign: 'left', + fontSize: '12px', + } + }, [ + 'Gas Fee' + ]) + ]), + + h('div', { style: { - marginLeft: 50, - fontSize: '0.9em', - }, - }, 'Insufficient balance for transaction') - : null, - - // send + cancel - h('.flex-row.flex-space-around.conf-buttons', { + width: '50%', + } + },[ + h('div', { + style: { + textAlign: 'left', + fontSize: '10px', + marginBottom: '-10px', + }, + }, '$0.04 USD'), + + h('div', { + style: { + textAlign: 'left', + fontSize: '8px', + }, + }, '0.001575 ETH') + ]) + ]), + + contentDivider, + + h('section.flex-row.flex-center', { style: { - display: 'flex', - justifyContent: 'flex-end', - margin: '14px 25px', - }, + backgroundColor: '#F6F6F6', // $wild-sand + borderRadius: '8px', + marginLeft: '10px', + marginRight: '10px', + paddingLeft: '6px', + paddingRight: '6px', + marginBottom: '10px', + } }, [ + h('div', { + style: { + width: '50%', + } + }, [ + h('div', { + style: { + textAlign: 'left', + fontSize: '12px', + marginBottom: '-10px', + } + }, [ + 'Total Tokens' + ]), + h('div', { + style: { + textAlign: 'left', + fontSize: '8px', + } + }, [ + 'Total Gas' + ]) - insufficientBalance ? - h('button.btn-green', { - onClick: props.buyEth, - }, 'Buy Ether') - : null, - - h('button', { - onClick: (event) => { - this.resetGasFields() - event.preventDefault() - }, - }, 'Reset'), - - // Accept Button - h('input.confirm.btn-green', { - type: 'submit', - value: 'SUBMIT', - style: { marginLeft: '10px' }, - disabled: insufficientBalance || !this.state.valid || !isValidAddress || this.state.submitting, - }), + ]), - h('button.cancel.btn-red', { - onClick: props.cancelTransaction, - }, 'Reject'), - ]), - ]), - ]) - ) -} + h('div', { + style: { + width: '50%', + } + },[ + h('div', { + style: { + textAlign: 'left', + fontSize: '10px', + marginBottom: '-10px', + }, + }, '0.24 ANT (127.00 USD)'), -PendingTx.prototype.miniAccountPanelForRecipient = function () { - const props = this.props - const txData = props.txData - const txParams = txData.txParams || {} - const isContractDeploy = !('to' in txParams) - - // If it's not a contract deploy, send to the account - if (!isContractDeploy) { - return h(MiniAccountPanel, { - imageSeed: txParams.to, - picOrder: 'left', - }, [ + h('div', { + style: { + textAlign: 'left', + fontSize: '8px', + }, + }, '0.249 ETH') + ]) + ]), - h('span.font-small', { - style: { - fontFamily: 'Montserrat Bold, Montserrat, sans-serif', - }, - }, nameForAddress(txParams.to, props.identities)), + ]), // end of container - h(Copyable, { - value: ethUtil.toChecksumAddress(txParams.to), + h('form#pending-tx-form.flex-column.flex-center', { + onSubmit: this.onSubmit.bind(this), }, [ - h('span.font-small', { + // Reset Button + // h('button', { + // onClick: (event) => { + // this.resetGasFields() + // event.preventDefault() + // }, + // }, 'Reset'), + + // Accept Button + h('input.confirm.btn-green', { + type: 'submit', + value: 'CONFIRM', style: { - fontFamily: 'Montserrat Light, Montserrat, sans-serif', + marginTop: '8px', + width: '8em', + color: '#FFFFFF', + borderRadius: '2px', + fontSize: '12px', + lineHeight: '20px', + textAlign: 'center', + borderStyle: 'none', }, - }, addressSummary(txParams.to, 6, 4, false)), - ]), - - ]) - } else { - return h(MiniAccountPanel, { - picOrder: 'left', - }, [ + disabled: insufficientBalance || !this.state.valid || !isValidAddress || this.state.submitting, + }), - h('span.font-small', { - style: { - fontFamily: 'Montserrat Bold, Montserrat, sans-serif', - }, - }, 'New Contract'), - - ]) - } -} - -PendingTx.prototype.gasPriceChanged = function (newBN, valid) { - log.info(`Gas price changed to: ${newBN.toString(10)}`) - const txMeta = this.gatherTxMeta() - txMeta.txParams.gasPrice = '0x' + newBN.toString('hex') - this.setState({ - txData: clone(txMeta), - valid, - }) -} - -PendingTx.prototype.gasLimitChanged = function (newBN, valid) { - log.info(`Gas limit changed to ${newBN.toString(10)}`) - const txMeta = this.gatherTxMeta() - txMeta.txParams.gas = '0x' + newBN.toString('hex') - this.setState({ - txData: clone(txMeta), - valid, - }) + // Cancel Button + h('button.cancel.btn-light', { + style: { + background: '#F7F7F7', // $alabaster + border: 'none', + opacity: 1, + width: '8em', + }, + onClick: props.cancelTransaction, + }, 'CANCEL'), + ]), + ]) // end of minwidth wrapper + ) } -PendingTx.prototype.resetGasFields = function () { - log.debug(`pending-tx resetGasFields`) - - this.inputs.forEach((hexInput) => { - if (hexInput) { - hexInput.setValid() - } - }) - - this.setState({ - txData: null, - valid: true, - }) -} +// PendingTx.prototype.gasPriceChanged = function (newBN, valid) { +// log.info(`Gas price changed to: ${newBN.toString(10)}`) +// const txMeta = this.gatherTxMeta() +// txMeta.txParams.gasPrice = '0x' + newBN.toString('hex') +// this.setState({ +// txData: clone(txMeta), +// valid, +// }) +// } + +// PendingTx.prototype.gasLimitChanged = function (newBN, valid) { +// log.info(`Gas limit changed to ${newBN.toString(10)}`) +// const txMeta = this.gatherTxMeta() +// txMeta.txParams.gas = '0x' + newBN.toString('hex') +// this.setState({ +// txData: clone(txMeta), +// valid, +// }) +// } + +// PendingTx.prototype.resetGasFields = function () { +// log.debug(`pending-tx resetGasFields`) + +// this.inputs.forEach((hexInput) => { +// if (hexInput) { +// hexInput.setValid() +// } +// }) + +// this.setState({ +// txData: null, +// valid: true, +// }) +// } PendingTx.prototype.onSubmit = function (event) { event.preventDefault() @@ -466,15 +492,3 @@ PendingTx.prototype.bnMultiplyByFraction = function (targetBN, numerator, denomi const denomBN = new BN(denominator) return targetBN.mul(numBN).div(denomBN) } - -function forwardCarrat () { - return ( - h('img', { - src: 'images/forward-carrat.svg', - style: { - padding: '5px 6px 0px 10px', - height: '37px', - }, - }) - ) -} diff --git a/ui/app/components/tx-list.js b/ui/app/components/tx-list.js new file mode 100644 index 000000000..74d46728c --- /dev/null +++ b/ui/app/components/tx-list.js @@ -0,0 +1,166 @@ +const Component = require('react').Component +const connect = require('react-redux').connect +const h = require('react-hyperscript') +const inherits = require('util').inherits +const selectors = require('../selectors') +const Identicon = require('./identicon') + +const valuesFor = require('../util').valuesFor + +module.exports = connect(mapStateToProps)(TxList) + +function mapStateToProps (state) { + return { + txsToRender: selectors.transactionsSelector(state), + conversionRate: selectors.conversionRateSelector(state), + } +} + +inherits(TxList, Component) +function TxList () { + Component.call(this) +} + +const contentDivider = h('div.tx-list-content-divider', { + style: {}, +}) + +TxList.prototype.render = function () { + + const { txsToRender, conversionRate } = this.props + + console.log('transactions to render', txsToRender) + + return h('div.flex-column.tx-list-container', {}, [ + + h('div.flex-row.tx-list-header-wrapper', { + style: {}, + }, [ + + h('div.flex-row.tx-list-header', { + }, [ + + h('div', { + style: {} + }, 'TRANSACTIONS'), + + ]), + + ]), + + contentDivider, + + this.renderTransactionListItem(), + + contentDivider, + + this.renderTransactionListItem(), + + contentDivider, + + this.renderTransactionListItem(), + + contentDivider, + + this.renderTransactionListItem(), + + contentDivider, + + this.renderTransactionListItem(), + + contentDivider, + + this.renderTransactionListItem(), + + contentDivider, + + this.renderTransactionListItem(), + + contentDivider, + + this.renderTransactionListItem(), + + contentDivider, + + this.renderTransactionListItem(), + + contentDivider, + + this.renderTransactionListItem(), + + contentDivider, + + ]) +} + +TxList.prototype.renderTransactionListItem = function () { + // fake data + const props = { + dateString: 'Jul 01, 2017', + address: '0x82df11beb942beeed58d466fcb0f0791365c7684', + transactionStatus: 'Confirmed', + transactionAmount: '+ 3 ETH' + } + + const { address, transactionStatus, transactionAmount, dateString } = props + + return h('div.flex-column.tx-list-item-wrapper', { + style: {} + }, [ + + h('div.tx-list-date-wrapper', { + style: {} + }, [ + h('span.tx-list-date', {}, [ + dateString, + ]) + ]), + + h('div.flex-row.tx-list-content-wrapper', { + style: {} + }, [ + + h('div.tx-list-identicon-wrapper', { + style: {} + }, [ + h(Identicon, { + address, + diameter: 24, + }) + ]), + + h('div.tx-list-account-and-status-wrapper', {}, [ + h('div.tx-list-account-wrapper', { + style: {} + }, [ + h('span.tx-list-account', {}, [ + '0x82df11be...7684', //address + ]), + ]), + + h('div.tx-list-status-wrapper', { + style: {} + }, [ + h('span.tx-list-status', {}, [ + transactionStatus, + ]), + ]), + ]), + + h('div.flex-column.tx-list-details-wrapper', { + style: {} + }, [ + + h('span.tx-list-value', {}, [ + transactionAmount, + ]), + + h('span.tx-list-fiat-value', {}, [ + '+ $300 USD', + ]), + + ]), + ]) + ]) +} + diff --git a/ui/app/components/tx-view.js b/ui/app/components/tx-view.js new file mode 100644 index 000000000..0d1f3fc31 --- /dev/null +++ b/ui/app/components/tx-view.js @@ -0,0 +1,124 @@ +const Component = require('react').Component +const connect = require('react-redux').connect +const h = require('react-hyperscript') +const ethUtil = require('ethereumjs-util') +const inherits = require('util').inherits +const actions = require('../actions') + +const WalletView = require('./wallet-view') +const BalanceComponent = require('./balance-component') +const TxList = require('./tx-list') +const Identicon = require('./identicon') + +module.exports = connect(mapStateToProps, mapDispatchToProps)(TxView) + +function mapStateToProps (state) { + const sidebarOpen = state.appState.sidebarOpen + + const identities = state.metamask.identities + const accounts = state.metamask.accounts + const selectedAddress = state.metamask.selectedAddress || Object.keys(accounts)[0] + const checksumAddress = selectedAddress && ethUtil.toChecksumAddress(selectedAddress) + const identity = identities[selectedAddress] + const account = accounts[selectedAddress] + + return { + sidebarOpen, + selectedAddress, + checksumAddress, + identity, + account, + } +} + +function mapDispatchToProps (dispatch) { + return { + showSidebar: () => { dispatch(actions.showSidebar()) }, + hideSidebar: () => { dispatch(actions.hideSidebar()) }, + showModal: () => { dispatch(actions.showModal()) }, + } +} + +inherits(TxView, Component) +function TxView () { + Component.call(this) +} + +TxView.prototype.render = function () { + + const { selectedAddress, identity, account } = this.props + + return h('div.tx-view.flex-column', { + style: {}, + }, [ + + h('div.flex-row.phone-visible', { + style: { + margin: '1em 0.9em', + alignItems: 'center', + }, + onClick: () => { + this.props.sidebarOpen ? this.props.hideSidebar() : this.props.showSidebar() + }, + }, [ + + h('div.fa.fa-bars', { + style: { + fontSize: '1.3em', + }, + }, []), + + h('.identicon-wrapper.select-none', { + style: { + marginLeft: '0.9em', + }, + }, [ + h(Identicon, { + diameter: 24, + address: selectedAddress, + }), + ]), + + h('span.account-name', { + style: {}, + }, [ + identity.name, + ]), + + ]), + + h('div.hero-balance', { + style: {}, + }, [ + + h(BalanceComponent, { + balanceValue: account && account.balance, + style: {}, + }), + + h('div.flex-row.flex-center.hero-balance-buttons', { + style: {}, + }, [ + h('button.btn-clear', { + style: { + textAlign: 'center', + }, + onClick: () => { + this.props.showModal() + }, + }, 'BUY'), + + h('button.btn-clear', { + style: { + textAlign: 'center', + marginLeft: '1.4em', + }, + }, 'SEND'), + + ]), + ]), + + h(TxList, {}), + + ]) +} diff --git a/ui/app/components/wallet-content-display.js b/ui/app/components/wallet-content-display.js new file mode 100644 index 000000000..3baffad69 --- /dev/null +++ b/ui/app/components/wallet-content-display.js @@ -0,0 +1,56 @@ +const Component = require('react').Component +const h = require('react-hyperscript') +const inherits = require('util').inherits + +module.exports = WalletContentDisplay + +inherits(WalletContentDisplay, Component) +function WalletContentDisplay () { + Component.call(this) +} + +WalletContentDisplay.prototype.render = function () { + const { title, amount, fiatValue, active, style } = this.props + + // TODO: Separate component: wallet-content-account + return h('div.flex-column', { + style: { + marginLeft: '1.3em', + alignItems: 'flex-start', + ...style, + } + }, [ + + h('span', { + style: { + fontSize: '1.1em', + }, + }, title), + + h('span', { + style: { + fontSize: '1.8em', + margin: '0.4em 0em', + }, + }, amount), + + h('span', { + style: { + fontSize: '1.3em', + }, + }, fiatValue), + + active && h('div', { + style: { + position: 'absolute', + marginLeft: '-1.3em', + height: '6em', + width: '0.3em', + background: '#D8D8D8', // TODO: add to resuable colors + } + }, [ + ]) + ]) + +} + diff --git a/ui/app/components/wallet-view.js b/ui/app/components/wallet-view.js new file mode 100644 index 000000000..6bde2d9f4 --- /dev/null +++ b/ui/app/components/wallet-view.js @@ -0,0 +1,172 @@ +const Component = require('react').Component +const connect = require('react-redux').connect +const h = require('react-hyperscript') +const inherits = require('util').inherits +const Identicon = require('./identicon') +const AccountDropdowns = require('./dropdowns/index.js').AccountDropdowns +const Content = require('./wallet-content-display') +const actions = require('../actions') +const BalanceComponent = require('./balance-component') +const selectors = require('../selectors') + +module.exports = connect(mapStateToProps, mapDispatchToProps)(WalletView) + +function mapStateToProps (state) { + + return { + network: state.metamask.network, + sidebarOpen: state.appState.sidebarOpen, + identities: state.metamask.identities, + accounts: state.metamask.accounts, + selectedAddress: selectors.getSelectedAddress(state), + selectedIdentity: selectors.getSelectedIdentity(state), + selectedAccount: selectors.getSelectedAccount(state), + } +} + +function mapDispatchToProps (dispatch) { + return { + showSendPage: () => {dispatch(actions.showSendPage())}, + hideSidebar: () => {dispatch(actions.hideSidebar())}, + } +} + +inherits(WalletView, Component) +function WalletView () { + Component.call(this) +} + +const noop = () => {} + +WalletView.prototype.render = function () { + const { network, responsiveDisplayClassname, style, identities, selectedAddress, selectedAccount } = this.props + + // temporary logs + fake extra wallets + console.log(selectedAccount) + + const extraWallet = h('div.flex-column.wallet-balance-wrapper', {}, [ + h('div.wallet-balance', {}, [ + h(BalanceComponent, { + balanceValue: selectedAccount.balance, + style: {}, + }), + ]), + ]) + + return h('div.wallet-view.flex-column' + (responsiveDisplayClassname || ''), { + style: {}, + }, [ + + // TODO: Separate component: wallet account details + h('div.flex-column.wallet-view-account-details', { + style: {} + }, [ + + h('div.flex-row.account-options-menu', { + style: { + position: 'relative', + }, + }, [ + + h(AccountDropdowns, { + selected: selectedAddress, + network, + identities, + useCssTransition: true, + enableAccountOptions: true, + dropdownWrapperStyle: { + padding: '1px 15px', + marginLeft: '-25px', + position: 'absolute', + width: '122%', //TODO, refactor all of this component out into media queries + }, + menuItemStyles: { + padding: '0px 0px', + margin: '22px 0px', + } + }, []), + + ]), + + h('div.flex-column.flex-center', { + }, [ + h('div', { + style: { + position: 'relative', + } + }, [ + h(AccountDropdowns, { + style: { + position: 'absolute', + left: 'calc(50% + 28px + 5.5px)', + top: '14px', + }, + useCssTransition: true, + selected: selectedAddress, + network, + identities, + enableAccountsSelector: true, + }, []), + ]), + + h(Identicon, { + diameter: 54, + address: selectedAddress, + }), + + h('span.account-name', { + style: {} + }, [ + 'Account 1' + ]), + + ]), + ]), + + //'Wallet' - Title + // Not visible on mobile + h('div.flex-column.wallet-view-title-wrapper', {}, [ + h('span.wallet-view-title', {}, [ + 'Wallet', + ]) + ]), + + //Wallet Balances + h('div.flex-column.wallet-balance-wrapper.wallet-balance-wrapper-active', {}, [ + + h('div.wallet-balance', {}, [ + + h(BalanceComponent, { + balanceValue: selectedAccount.balance, + style: {}, + }), + + ]), + + ]), + + h('div.flex-column.wallet-balance-wrapper', {}, [ + + h('div.wallet-balance', {}, [ + + h(BalanceComponent, { + balanceValue: selectedAccount.balance, + style: {}, + }), + + ]), + + ]), + + extraWallet, + extraWallet, + extraWallet, + extraWallet, + extraWallet, + extraWallet, + extraWallet, + extraWallet, + extraWallet, + extraWallet, + ]) +} diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index 34727ff78..4a8c616e2 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -52,66 +52,25 @@ ConfirmTxScreen.prototype.render = function () { log.info(`rendering a combined ${unconfTxList.length} unconf msg & txs`) if (unconfTxList.length === 0) return h(Loading, { isLoading: true }) - return ( - - h('.flex-column.flex-grow', [ - - // subtitle and nav - h('.section-title.flex-row.flex-center', [ - !isNotification ? h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', { - onClick: this.goHome.bind(this), - }) : null, - h('h2.page-subtitle', 'Confirm Transaction'), - isNotification ? h(NetworkIndicator, { - network: network, - provider: provider, - }) : null, - ]), - - h('h3', { - style: { - alignSelf: 'center', - display: unconfTxList.length > 1 ? 'block' : 'none', - }, - }, [ - h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', { - style: { - display: props.index === 0 ? 'none' : 'inline-block', - }, - onClick: () => props.dispatch(actions.previousTx()), - }), - ` ${props.index + 1} of ${unconfTxList.length} `, - h('i.fa.fa-arrow-right.fa-lg.cursor-pointer', { - style: { - display: props.index + 1 === unconfTxList.length ? 'none' : 'inline-block', - }, - onClick: () => props.dispatch(actions.nextTx()), - }), - ]), - - warningIfExists(props.warning), - - currentTxView({ - // Properties - txData: txData, - key: txData.id, - selectedAddress: props.selectedAddress, - accounts: props.accounts, - identities: props.identities, - conversionRate, - currentCurrency, - blockGasLimit, - // Actions - buyEth: this.buyEth.bind(this, txParams.from || props.selectedAddress), - sendTransaction: this.sendTransaction.bind(this), - cancelTransaction: this.cancelTransaction.bind(this, txData), - signMessage: this.signMessage.bind(this, txData), - signPersonalMessage: this.signPersonalMessage.bind(this, txData), - cancelMessage: this.cancelMessage.bind(this, txData), - cancelPersonalMessage: this.cancelPersonalMessage.bind(this, txData), - }), - ]) - ) + return currentTxView({ + // Properties + txData: txData, + key: txData.id, + selectedAddress: props.selectedAddress, + accounts: props.accounts, + identities: props.identities, + conversionRate, + currentCurrency, + blockGasLimit, + // Actions + buyEth: this.buyEth.bind(this, txParams.from || props.selectedAddress), + sendTransaction: this.sendTransaction.bind(this), + cancelTransaction: this.cancelTransaction.bind(this, txData), + signMessage: this.signMessage.bind(this, txData), + signPersonalMessage: this.signPersonalMessage.bind(this, txData), + cancelMessage: this.cancelMessage.bind(this, txData), + cancelPersonalMessage: this.cancelPersonalMessage.bind(this, txData), + }) } function currentTxView (opts) { diff --git a/ui/app/css/debug.css b/ui/app/css/debug.css deleted file mode 100644 index 3e125bcd4..000000000 --- a/ui/app/css/debug.css +++ /dev/null @@ -1,21 +0,0 @@ -/* -debug / dev -*/ - -#app-content { - border: 2px solid green; -} - -#design-container { - position: absolute; - left: 360px; - top: -42px; - width: calc(100vw - 360px); - height: 100vh; - overflow: scroll; -} - -#design-container img { - width: 2000px; - margin-right: 600px; -}
\ No newline at end of file diff --git a/ui/app/css/fonts.css b/ui/app/css/fonts.css deleted file mode 100644 index 3b9f581b9..000000000 --- a/ui/app/css/fonts.css +++ /dev/null @@ -1,36 +0,0 @@ -@import url(https://fonts.googleapis.com/css?family=Roboto:300,500); -@import url(https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css); - -@font-face { - font-family: 'Montserrat Regular'; - src: url('/fonts/Montserrat/Montserrat-Regular.woff') format('woff'); - src: url('/fonts/Montserrat/Montserrat-Regular.ttf') format('truetype'); - font-weight: normal; - font-style: normal; - font-size: 'small'; - -} - -@font-face { - font-family: 'Montserrat Bold'; - src: url('/fonts/Montserrat/Montserrat-Bold.woff') format('woff'); - src: url('/fonts/Montserrat/Montserrat-Bold.ttf') format('truetype'); - font-weight: normal; - font-style: normal; -} - -@font-face { - font-family: 'Montserrat Light'; - src: url('/fonts/Montserrat/Montserrat-Light.woff') format('woff'); - src: url('/fonts/Montserrat/Montserrat-Light.ttf') format('truetype'); - font-weight: normal; - font-style: normal; -} - -@font-face { - font-family: 'Montserrat UltraLight'; - src: url('/fonts/Montserrat/Montserrat-UltraLight.woff') format('woff'); - src: url('/fonts/Montserrat/Montserrat-UltraLight.ttf') format('truetype'); - font-weight: normal; - font-style: normal; -} diff --git a/ui/app/css/index.scss b/ui/app/css/index.scss new file mode 100644 index 000000000..01899ccad --- /dev/null +++ b/ui/app/css/index.scss @@ -0,0 +1,13 @@ +/* + ITCSS + + http://www.creativebloq.com/web-design/manage-large-css-projects-itcss-101517528 + https://www.xfive.co/blog/itcss-scalable-maintainable-css-architecture/ + */ +@import './itcss/settings/index.scss'; +@import './itcss/tools/index.scss'; +@import './itcss/generic/index.scss'; +@import './itcss/base/index.scss'; +@import './itcss/objects/index.scss'; +@import './itcss/components/index.scss'; +@import './itcss/trumps/index.scss'; diff --git a/ui/app/css/itcss/base/index.scss b/ui/app/css/itcss/base/index.scss new file mode 100644 index 000000000..baa6ea037 --- /dev/null +++ b/ui/app/css/itcss/base/index.scss @@ -0,0 +1 @@ +// Base diff --git a/ui/app/css/itcss/components/buttons.scss b/ui/app/css/itcss/components/buttons.scss new file mode 100644 index 000000000..9916db488 --- /dev/null +++ b/ui/app/css/itcss/components/buttons.scss @@ -0,0 +1,83 @@ +/* + Buttons + */ + +.btn-green { + background-color: #02c9b1; // TODO: reusable color in colors.css +} + +button.btn-clear { + background: $white; + border: 1px solid; +} + +// No longer used in flat design, remove when modal buttons done +// div.wallet-btn { +// border: 1px solid rgb(91, 93, 103); +// border-radius: 2px; +// height: 30px; +// width: 75px; +// font-size: 0.8em; +// text-align: center; +// line-height: 25px; +// } + +// .btn-red { +// background: rgba(254, 35, 17, 1); +// box-shadow: 0px 3px 6px rgba(254, 35, 17, 0.36); +// } + +// button[disabled], input[type="submit"][disabled] { +// cursor: not-allowed; +// background: rgba(197, 197, 197, 1); +// box-shadow: 0px 3px 6px rgba(197, 197, 197, 0.36); +// } + +// button.spaced { +// margin: 2px; +// } + +// button:not([disabled]):hover, input[type="submit"]:not([disabled]):hover { +// transform: scale(1.1); +// } +// button:not([disabled]):active, input[type="submit"]:not([disabled]):active { +// transform: scale(0.95); +// } + +button.primary { + padding: 8px 12px; + background: #f7861c; + box-shadow: 0 3px 6px rgba(247, 134, 28, .36); + color: $white; + font-size: 1.1em; + font-family: 'Montserrat Regular'; + text-transform: uppercase; +} + +.btn-light { + padding: 8px 12px; + // background: #FFFFFF; // $bg-white + box-shadow: 0 3px 6px rgba(247, 134, 28, .36); + color: #585d67; // TODO: make reusable light button color + font-size: 1.1em; + font-family: 'Montserrat Regular'; + text-transform: uppercase; + text-align: center; + line-height: 20px; + border-radius: 2px; + border: 1px solid #979797; // #TODO: make reusable light border color + opacity: .5; +} + +// TODO: cleanup: not used anywhere +button.btn-thin { + border: 1px solid; + border-color: #4d4d4d; + color: #4d4d4d; + background: rgb(255, 174, 41); + border-radius: 4px; + min-width: 200px; + margin: 12px 0; + padding: 6px; + font-size: 13px; +} diff --git a/ui/app/css/itcss/components/footer.scss b/ui/app/css/itcss/components/footer.scss new file mode 100644 index 000000000..000a53eed --- /dev/null +++ b/ui/app/css/itcss/components/footer.scss @@ -0,0 +1,4 @@ +.app-footer { + padding-bottom: 10px; + align-items: center; +} diff --git a/ui/app/css/itcss/components/header.scss b/ui/app/css/itcss/components/header.scss new file mode 100644 index 000000000..9434d0386 --- /dev/null +++ b/ui/app/css/itcss/components/header.scss @@ -0,0 +1,52 @@ +.app-header { + align-items: center; + visibility: visible; + background: $gallery; + padding: 6px 8px; + height: 12vh; + max-height: 60px; + position: relative; + z-index: 12; + + @media screen and (max-width: 575px) { + position: fixed; + height: 34px; + width: 100%; + box-shadow: 0 2px 2px 1px rgba(0, 0, 0, .08); + z-index: 30; + } +} + +.app-header h1 { + font-family: 'Montserrat Regular'; + text-transform: uppercase; + color: #22232c; // $shark +} + +h2.page-subtitle { + font-family: 'Montserrat Regular'; + text-transform: uppercase; + color: #aeaeae; + font-size: 1em; + margin: 12px; +} + +.network-component-wrapper { + display: flex; + flex-direction: row; + align-items: center; + + @media screen and (min-width: 576px) { + margin-bottom: 1.8em; + } +} + +.left-menu-wrapper { + display: flex; + flex-direction: row; + align-items: center; + + @media screen and (min-width: 576px) { + margin-bottom: 1.8em; + } +} diff --git a/ui/app/css/itcss/components/hero-balance.scss b/ui/app/css/itcss/components/hero-balance.scss new file mode 100644 index 000000000..ad7f5952f --- /dev/null +++ b/ui/app/css/itcss/components/hero-balance.scss @@ -0,0 +1,107 @@ +.hero-balance { + + @media screen and (max-width: $break-small) { + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; + margin: .3em .9em 0; + height: 80vh; + max-height: 225px; + } + + @media screen and (min-width: $break-large) { + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: center; + margin: 2.8em .9em .8em; + } + + .balance-container { + display: flex; + margin: 0; + justify-content: flex-start; + align-items: center; + + @media screen and (max-width: $break-small) { + flex-direction: column; + } + + @media screen and (min-width: $break-large) { + flex-direction: row; + flex-grow: 3; + } + } + + .balance-display { + + @media screen and (max-width: $break-small) { + text-align: center; + + .token-amount { + font-size: 175%; + margin-top: 12.5%; + } + + .fiat-amount { + font-size: 115%; + margin-top: 8.5%; + color: #a0a0a0; + } + } + + @media screen and (min-width: $break-large) { + margin-left: 3%; + justify-content: flex-start; + align-items: flex-start; + + .token-amount { + font-size: 135%; + } + + .fiat-amount { + margin-top: .25%; + font-size: 105%; + } + } + } + + .balance-icon { + border-radius: 25px; + width: 45px; + height: 45px; + border: 1px solid $alto; + } + + .hero-balance-buttons { + + @media screen and (max-width: $break-small) { + width: 100%; + height: 100px; // needed a round number to set the heights of the buttons inside + } + + @media screen and (min-width: $break-large) { + flex-grow: 2; + } + + button.btn-clear { + font-size: 75%; + background: $white; + border: 1px solid; + + @media screen and (max-width: $break-small) { + width: 28%; + height: 55%; + } + + @media screen and (min-width: $break-large) { + width: 5%; + flex-grow: 2; + height: 4.2vh; + min-height: 28px; + font-size: .7em; + } + } + } +} diff --git a/ui/app/css/itcss/components/index.scss b/ui/app/css/itcss/components/index.scss new file mode 100644 index 000000000..291e09007 --- /dev/null +++ b/ui/app/css/itcss/components/index.scss @@ -0,0 +1,21 @@ +@import './buttons.scss'; + +@import './header.scss'; + +@import './footer.scss'; + +@import './network.scss'; + +@import './modal.scss'; + +@import './newui-sections.scss'; + +// Balances +@import './hero-balance.scss'; + +@import './wallet-balance.scss'; + +// Tx List and Sections +@import './transaction-list.scss'; + +@import './sections.scss'; diff --git a/ui/app/css/itcss/components/modal.scss b/ui/app/css/itcss/components/modal.scss new file mode 100644 index 000000000..8bf7cd44f --- /dev/null +++ b/ui/app/css/itcss/components/modal.scss @@ -0,0 +1,128 @@ +.modal-content { + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; + font-family: 'DIN OT'; +} + +@media screen and (max-width: 575px) { + .modal-content-title-wrapper { + justify-content: space-around; + width: 100%; + height: 100px; + } + + .modal-content-title { + font-size: 26px; + margin-top: 15px; + } + + .modal-content-options { + flex-direction: column; + padding: 5% 33%; + } + + .modal-content-footer { + text-transform: uppercase; + width: 100%; + height: 50px; + } + + div.modal-content-option { + display: flex; + flex-direction: column; + width: 80vw; + height: 15vh; + margin: 10px; + text-align: center; + border-radius: 6px; + border: 1px solid $black; + padding: 0% 7%; + justify-content: space-around; + + div.modal-content-option-title { + font-size: 20px; + } + + div.modal-content-option-subtitle { + font-size: 16px; + } + } +} + +@media screen and (min-width: 576px) { + .modal-content-title-wrapper { + justify-content: space-around; + width: 100%; + height: 110px; + } + + .modal-content-title { + font-size: 26px; + margin-top: 15px; + } + + .modal-content-footer { + text-transform: uppercase; + width: 100%; + height: 50px; + } + + .modal-content-options { + flex-direction: row; + margin: 20px 0; + } + + div.modal-content-option { + display: flex; + flex-direction: column; + width: 20vw; + height: 18vw; + text-align: center; + border-radius: 6px; + border: 1px solid $black; + margin: 0 .5vw; + justify-content: space-around; + + div.modal-content-option-title { + font-size: 20px; + + @media screen and (max-width: 679px) { + font-size: 14px; + } + + @media screen and (min-width: 1281px) { + font-size: 26px; + } + } + + div.modal-content-option-subtitle { + font-size: 16px; + padding: 0 10px; + height: 25%; + + @media screen and (max-width: 679px) { + font-size: 10px; + padding: 0 10px; + margin-bottom: 5px; + line-height: 15px; + } + + @media screen and (min-width: 680px) { + font-size: 14px; + padding: 0 4px; + margin-bottom: 2px; + } + + @media screen and (min-width: 1281px) { + font-size: 20px; + padding: 0; + } + } + + div.modal-content-footer { + margin-top: 8vh; + } + } +} diff --git a/ui/app/css/itcss/components/network.scss b/ui/app/css/itcss/components/network.scss new file mode 100644 index 000000000..aea5063f0 --- /dev/null +++ b/ui/app/css/itcss/components/network.scss @@ -0,0 +1,11 @@ +.network-indicator { + display: flex; + align-items: center; + font-size: .6em; +} + +.network-name { + width: 5.2em; + line-height: 9px; + text-rendering: geometricPrecision; +} diff --git a/ui/app/css/itcss/components/newui-sections.scss b/ui/app/css/itcss/components/newui-sections.scss new file mode 100644 index 000000000..7daf72bf2 --- /dev/null +++ b/ui/app/css/itcss/components/newui-sections.scss @@ -0,0 +1,166 @@ +/* + NewUI Container Elements + */ + +// Component Colors +$tx-view-bg: $white; +$wallet-view-bg: $wild-sand; + +// Main container +.main-container { + position: absolute; + z-index: 18; + font-family: "DIN OT"; + display: flex; + flex-wrap: wrap; + align-items: stretch; + overflow-y: scroll; +} + +// tx view + +.tx-view { + flex: 63.5 0 66.5%; + background: $tx-view-bg; +} + +// wallet view and sidebar + +.wallet-view { + display: flex; + flex-direction: column; + flex: 33.5 0 33.5%; + background: $wallet-view-bg; + + @media screen and (min-width: 576px) { + overflow-y: scroll; + } + + .wallet-view-account-details { + flex: 0 0 150px; + } +} + +.wallet-view-title-wrapper { + flex: 0 0 25px; +} + +.wallet-view-title { + margin-left: 15px; + font-size: 16px; + + // No title on mobile + @media screen and (max-width: 575px) { + display: none; + } +} + +.wallet-view.sidebar { + flex: 1 0 230px; + background: rgb(250, 250, 250); + z-index: 26; + position: fixed; + top: 35px; + left: 0; + right: 0; + bottom: 0; + opacity: 1; + visibility: visible; + will-change: transform; + overflow-y: auto; + box-shadow: rgba(0, 0, 0, .15) 2px 2px 4px; + width: 85%; + height: 100%; +} + +.sidebar-overlay { + z-index: 25; + position: fixed; + top: 35px; + left: 0; + right: 0; + bottom: 0; + opacity: 1; + visibility: visible; + background-color: rgba(0, 0, 0, .3); +} + +// main-container media queries + +@media screen and (min-width: 576px) { + .lap-visible { + display: flex; + } + + .phone-visible { + display: none; + } + + .main-container { + margin-top: 35px; + width: 85%; + height: 90vh; + box-shadow: 0 0 7px 0 rgba(0, 0, 0, .08); + } +} + +@media screen and (min-width: 769px) { + .main-container { + margin-top: 35px; + width: 80%; + height: 82vh; + box-shadow: 0 0 7px 0 rgba(0, 0, 0, .08); + } +} + +@media screen and (min-width: 1281px) { + .main-container { + margin-top: 35px; + width: 65%; + height: 82vh; + box-shadow: 0 0 7px 0 rgba(0, 0, 0, .08); + } +} + +@media screen and (max-width: 575px) { + .lap-visible { + display: none; + } + + .phone-visible { + display: flex; + } + + .main-container { + margin-top: 35px; + width: 100%; + } + + button.btn-clear { + width: 93px; + height: 50px; + font-size: .7em; + background: $white; + border: 1px solid; + } +} + +// wallet view +.account-name { + + @media screen and (max-width: 575px) { + font-size: 102%; + margin-left: 3%; + } + + @media screen and (max-width: 575px) { + text-align: center; + } +} + +// account options dropdown +.account-options-menu { + align-items: center; + justify-content: flex-start; + margin: 5% 7%; +} diff --git a/ui/app/css/index.css b/ui/app/css/itcss/components/sections.scss index 49b432a1f..5f0a034d8 100644 --- a/ui/app/css/index.css +++ b/ui/app/css/itcss/components/sections.scss @@ -1,222 +1,26 @@ -/* -faint orange (textfield shades) #FAF6F0 -light orange (button shades): #F5C26D -dark orange (text): #F5A623 -borders/font/any gray: #4A4A4A -*/ - -/* -application specific styles -*/ - -* { - box-sizing: border-box; -} - -html, body { - font-family: 'Montserrat Regular', Arial; - color: #4D4D4D; - font-weight: 300; - line-height: 1.4em; - background: #F7F7F7; - margin: 0; - padding: 0; -} - -html { - min-height: 500px; -} - -.app-root { - overflow: hidden; - position: relative -} - -.app-primary { - display: flex; -} +// Old scss, do not lint - clean up later +/* stylelint-disable */ -input:focus, textarea:focus { - outline: none; -} - -.full-size { - height: 100%; - width: 100%; -} - -.full-width { - width: 100%; -} - -.full-height { - height: 100%; -} - -.full-flex-height { - display: flex; - flex: 1 1 auto; - flex-direction: column; -} - -#app-content { - overflow-x: hidden; - min-width: 357px; - height: 100%; - display: flex; - flex-direction: column; -} - -button, input[type="submit"] { - font-family: 'Montserrat Bold'; - outline: none; - cursor: pointer; - padding: 8px 12px; - border: none; - color: white; - transform-origin: center center; - transition: transform 50ms ease-in; - /* default orange */ - background: rgba(247, 134, 28, 1); - box-shadow: 0px 3px 6px rgba(247, 134, 28, 0.36); -} - -.btn-green, input[type="submit"].btn-green { - background: rgba(106, 195, 96, 1); - box-shadow: 0px 3px 6px rgba(106, 195, 96, 0.36); -} - -.btn-red { - background: rgba(254, 35, 17, 1); - box-shadow: 0px 3px 6px rgba(254, 35, 17, 0.36); -} - -button[disabled], input[type="submit"][disabled] { - cursor: not-allowed; - background: rgba(197, 197, 197, 1); - box-shadow: 0px 3px 6px rgba(197, 197, 197, 0.36); -} - -button.spaced { - margin: 2px; -} - -button:not([disabled]):hover, input[type="submit"]:not([disabled]):hover { - transform: scale(1.1); -} -button:not([disabled]):active, input[type="submit"]:not([disabled]):active { - transform: scale(0.95); -} - -a { - text-decoration: none; - color: inherit; -} - -a:hover{ - color: #df6b0e; -} /* -app +App Sections + TODO: Move into separate files. */ -.active { - color: #909090; -} - -button.primary { - padding: 8px 12px; - background: #F7861C; - box-shadow: 0px 3px 6px rgba(247, 134, 28, 0.36); - color: white; - font-size: 1.1em; - font-family: 'Montserrat Regular'; - text-transform: uppercase; -} - -button.btn-thin { - border: 1px solid; - border-color: #4D4D4D; - color: #4D4D4D; - background: rgb(255, 174, 41); - border-radius: 4px; - min-width: 200px; - margin: 12px 0; - padding: 6px; - font-size: 13px; -} - -.app-header { - padding: 6px 8px; -} - -.app-header h1 { - font-family: 'Montserrat Regular'; - text-transform: uppercase; - color: #AEAEAE; -} - -h2.page-subtitle { - font-family: 'Montserrat Regular'; - text-transform: uppercase; - color: #AEAEAE; - font-size: 1em; - margin: 12px; -} - -.app-footer { - padding-bottom: 10px; - align-items: center; -} - -.identicon { - height: 46px; - width: 46px; - background-size: cover; - border-radius: 100%; - border: 3px solid gray; -} - +/* initialize */ textarea.twelve-word-phrase { padding: 12px; width: 300px; height: 140px; font-size: 16px; - background: white; + background: $white; resize: none; } -.network-indicator { - display: flex; - align-items: center; - font-size: 0.6em; - -} - -.network-name { - width: 5.2em; - line-height: 9px; - text-rendering: geometricPrecision; -} - -.check { - margin-left: 12px; - color: #F7861C; - flex: 1 0 auto; - display: flex; - justify-content: flex-end; -} -/* -app sections -*/ - -/* initialize */ - .initialize-screen hr { width: 60px; margin: 12px; - border-color: #F7861C; + border-color: #f7861c; border-style: solid; } @@ -235,11 +39,11 @@ app sections /* unlock */ .error { - color: #E20202; + color: #e20202; } .warning { - color: #FFAE00; + color: #ffae00; } .lock { @@ -249,9 +53,10 @@ app sections .lock.locked { transform: scale(1.5); - opacity: 0.0; + opacity: 0; transition: opacity 400ms ease-in, transform 400ms ease-in; } + .lock.unlocked { transform: scale(1); opacity: 1; @@ -262,15 +67,18 @@ app sections transform: scaleX(1) translateX(0); transition: transform 250ms ease-in; } + .lock.unlocked .lock-top { transform: scaleX(-1) translateX(-12px); transition: transform 250ms ease-in; } + .lock.unlocked:hover { border-radius: 4px; background: #e5e5e5; border: 1px solid #b1b1b1; } + .lock.unlocked:active { background: #c3c3c3; } @@ -290,55 +98,46 @@ app sections .unlock-screen input[type=password] { width: 260px; - /*height: 36px; - margin-bottom: 24px; - padding: 8px;*/ } -.sizing-input{ +.sizing-input { font-size: 14px; height: 30px; padding-left: 5px; } -.editable-label{ + +.editable-label { display: flex; } + /* Webkit */ + .unlock-screen input::-webkit-input-placeholder { text-align: center; font-size: 1.2em; } + /* Firefox 18- */ + .unlock-screen input:-moz-placeholder { text-align: center; font-size: 1.2em; } + /* Firefox 19+ */ + .unlock-screen input::-moz-placeholder { text-align: center; font-size: 1.2em; } + /* IE */ + .unlock-screen input:-ms-input-placeholder { text-align: center; font-size: 1.2em; } -input.large-input, textarea.large-input { - /*margin-bottom: 24px;*/ - padding: 8px; -} - -input.large-input { - height: 36px; -} - -.letter-spacey { - letter-spacing: 0.1em; -} - - - /* accounts */ .accounts-section { @@ -346,7 +145,7 @@ input.large-input { } .accounts-section .horizontal-line { - margin: 0px 18px; + margin: 0 18px; } .accounts-list-option { @@ -363,7 +162,7 @@ input.large-input { } .unconftx-link .fa-arrow-right { - margin: 0px -8px 0px 8px; + margin: 0 -8px 0px 8px; } /* identity panel */ @@ -390,7 +189,7 @@ input.large-input { .identity-panel i { margin-top: 32px; margin-right: 6px; - color: #B9B9B9; + color: #b9b9b9; } .identity-panel .arrow-right { @@ -401,34 +200,33 @@ input.large-input { } .identity-copy.flex-column { - flex: 0.25 0 auto; + flex: .25 0 auto; justify-content: center; } /* accounts screen */ .identity-section { - } .identity-section .identity-panel { - background: #E9E9E9; - border-bottom: 1px solid #B1B1B1; + background: #e9e9e9; + border-bottom: 1px solid #b1b1b1; cursor: pointer; } .identity-section .identity-panel.selected { - background: white; - color: #F3C83E; + background: $white; + color: #f3c83e; } .identity-section .identity-panel.selected .identicon { - border-color: orange; + border-color: $orange; } .identity-section .accounts-list-option:hover, .identity-section .accounts-list-option.selected { - background:white; + background: $white; } /* account detail screen */ @@ -444,15 +242,14 @@ input.large-input { flex-grow: 10; } -.name-label{ - +.name-label { } .unapproved-tx-icon { height: 16px; width: 16px; background: rgb(47, 174, 244); - border-color: #AEAEAE; + border-color: $silver-chalice; border-radius: 13px; } @@ -460,6 +257,7 @@ input.large-input { height: 100%; visibility: hidden; } + .editing-label { display: flex; justify-content: flex-start; @@ -467,8 +265,9 @@ input.large-input { margin-bottom: 2px; font-size: 11px; text-rendering: geometricPrecision; - color: #F7861C; + color: #f7861c; } + .name-label:hover .edit-text { visibility: visible; } @@ -480,18 +279,14 @@ input.large-input { margin: 12px; margin-bottom: 24px; border-radius: 4px; - border: 2px solid #F3C83E; - background: #FAF6F0; + border: 2px solid #f3c83e; + background: #faf6f0; } /* Send Screen */ -.send-screen { - -} - .send-screen section { - margin: 8px 16px; + margin: 4px 16px; } .send-screen input { @@ -502,25 +297,25 @@ input.large-input { /* Ether Balance Widget */ .ether-balance-amount { - color: #F7861C; + color: #f7861c; } .ether-balance-label { - color: #ABA9AA; + color: #aba9aa; } /* Info screen */ -.info-gray{ +.info-gray { font-family: 'Montserrat Regular'; text-transform: uppercase; - color: #AEAEAE; + color: $silver-chalice; } -.icon-size{ +.icon-size { width: 20px; } -.info{ +.info { font-family: 'Montserrat Regular', Arial; padding-bottom: 10px; display: inline-block; @@ -533,7 +328,6 @@ input.large-input { align-items: center; } - .custom-radio-selected { width: 17px; height: 17px; @@ -542,7 +336,7 @@ input.large-input { border-radius: 15px; border-width: 5px; background: rgba(247, 134, 28, 1); - border-color: #F7F7F7; + border-color: #f7f7f7; } .custom-radio-inactive { @@ -551,38 +345,26 @@ input.large-input { border: solid; border-width: 1px; border-radius: 24px; - border-color: #AEAEAE; + border-color: $silver-chalice; } .radio-titles { color: rgba(247, 134, 28, 1); } -.radio-titles-subtext { - -} - -.selected-exchange { - -} - -.buy-radio { - -} - -.eth-warning{ +.eth-warning { transition: opacity 400ms ease-in, transform 400ms ease-in; } -.buy-subview{ +.buy-subview { transition: opacity 400ms ease-in, transform 400ms ease-in; } -.input-container:hover .edit-text{ +.input-container:hover .edit-text { visibility: visible; } -.buy-inputs{ +.buy-inputs { font-family: 'Montserrat Light'; font-size: 13px; height: 20px; @@ -590,33 +372,32 @@ input.large-input { box-sizing: border-box; border: solid; border-color: transparent; - border-width: 0.5px; + border-width: .5px; border-radius: 2px; - } -.input-container:hover .buy-inputs{ + +.input-container:hover .buy-inputs { box-sizing: inherit; border: solid; - border-color: #F7861C; - border-width: 0.5px; + border-color: #f7861c; + border-width: .5px; border-radius: 2px; } -.buy-inputs:focus{ +.buy-inputs:focus { border: solid; - border-color: #F7861C; - border-width: 0.5px; + border-color: #f7861c; + border-width: .5px; border-radius: 2px; } .activeForm { - background: #F7F7F7; + background: #f7f7f7; border: none; border-radius: 8px 8px 0px 0px; width: 50%; text-align: center; padding-bottom: 4px; - } .inactiveForm { @@ -635,12 +416,12 @@ input.large-input { width: 118px; height: 42px; padding: 1px; - color: #4D4D4D; + color: #4d4d4d; } -.marketinfo{ +.marketinfo { font-family: 'Montserrat light'; - color: #AEAEAE; + color: $silver-chalice; font-size: 15px; line-height: 17px; } @@ -655,25 +436,25 @@ input.large-input { overflow: scroll; } -.icon-control .fa-refresh{ +.icon-control .fa-refresh { visibility: hidden; } -.icon-control:hover .fa-refresh{ +.icon-control:hover .fa-refresh { visibility: visible; } -.icon-control:hover .fa-chevron-right{ +.icon-control:hover .fa-chevron-right { visibility: hidden; } .inactive { - color: #AEAEAE; + color: $silver-chalice; } -.inactive button{ - background: #AEAEAE; - color: white; +.inactive button { + background: $silver-chalice; + color: $white; } .ellip-address { @@ -686,21 +467,23 @@ input.large-input { } .qr-header { - font-size: 25px; - margin-top: 40px; + font-size: 25px; + margin-top: 40px; } .qr-message { font-size: 12px; - color: #F7861C; + color: #f7861c; } div.message-container > div:first-child { margin-top: 18px; font-size: 15px; - color: #4D4D4D; + color: #4d4d4d; } .pop-hover:hover { - transform: scale(1.1); + transform: scale(1.1); } + +/* stylelint-enable */ diff --git a/ui/app/css/itcss/components/transaction-list.scss b/ui/app/css/itcss/components/transaction-list.scss new file mode 100644 index 000000000..a6e68df42 --- /dev/null +++ b/ui/app/css/itcss/components/transaction-list.scss @@ -0,0 +1,141 @@ +.tx-list-container { + height: 87.5%; + + @media screen and (min-width: $break-large) { + overflow-y: scroll; + } +} + +@media screen and (max-width: $break-small) { + .tx-list-header-wrapper { + margin-top: .2em; + margin-bottom: .6em; + // TODO: Resolve Layout Conflicst in Wallet View + // - This fixes txlist "transactions" title dispay + // margin-top: 0.2em; + // margin-bottom: 0.6em; + justify-content: center; + } + + .tx-list-header { + align-self: center; + font-size: 12px; + color: $dusty-gray; + } +} + +@media screen and (min-width: $break-large) { + .tx-list-header-wrapper { + flex: 0 0 55px; + } + + .tx-list-header { + font-size: 16px; + margin: 1.8em 1.3em; + } +} + +.tx-list-content-divider { + height: 1px; + background: rgb(231, 231, 231); + flex: 0 0 1px; + + @media screen and (max-width: $break-small) { + margin: .1em 0; + } + + @media screen and (min-width: $break-large) { + margin: .1em 1.3em; + } +} + +.tx-list-item-wrapper { + flex: 0 0 70px; + align-items: stretch; + justify-content: flex-start; + + @media screen and (max-width: $break-small) { + margin: 0 1.3em .95em; + } + + @media screen and (min-width: $break-large) { + margin: 0 1.3em; + } +} + +.tx-list-date-wrapper { + flex: 1 1 auto; + + @media screen and (max-width: $break-small) { + margin-top: 6px; + margin-bottom: 20px; + } + + @media screen and (min-width: $break-large) { + margin-top: 6px; + } +} + +.tx-list-content-wrapper { + align-items: stretch; + margin-bottom: 8px; +} + +.tx-list-date { + color: $dusty-gray; + font-size: 14px; +} + +.tx-list-identicon-wrapper { + align-self: center; + flex: 1 1 auto; + margin-left: 3px; +} + +.tx-list-account-and-status-wrapper { + display: flex; + flex: 8 8 auto; + + @media screen and (max-width: $break-small) { + flex-direction: column; + justify-content: flex-start; + align-items: flex-start; + align-self: center; + } + + @media screen and (min-width: $break-large) { + flex-direction: row; + justify-content: flex-start; + align-items: center; + + .tx-list-account-wrapper { + flex: 2 2 auto; + } + + .tx-list-status-wrapper { + flex: 6 6 auto; + } + } + + .tx-list-account { + font-size: 16px; + } + + .tx-list-status { + color: $dusty-gray; + font-size: 16px; + } +} + +.tx-list-details-wrapper { + align-self: center; + flex: 2 2 auto; + + .tx-list-value { + font-size: 16px; + } + + .tx-list-fiat-value { + font-size: 12px; + } +} diff --git a/ui/app/css/itcss/components/wallet-balance.scss b/ui/app/css/itcss/components/wallet-balance.scss new file mode 100644 index 000000000..36f0e1025 --- /dev/null +++ b/ui/app/css/itcss/components/wallet-balance.scss @@ -0,0 +1,66 @@ +$wallet-balance-bg: $gallery; +$wallet-balance-breakpoint: 890px; +$wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and (max-width: #{$wallet-balance-breakpoint})"; + +.wallet-balance-wrapper { + flex: 0 0 80px; +} + +.wallet-balance { + background: inherit; + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: center; + + .balance-container { + display: flex; + justify-content: flex-start; + align-items: center; + margin: 20px 24px; + flex-direction: row; + flex-grow: 3; + + @media #{$wallet-balance-breakpoint-range} { + margin: 10% 4%; + } + } + + .balance-display { + margin-left: 15px; + justify-content: flex-start; + align-items: flex-start; + + .token-amount { + font-size: 135%; + } + + .fiat-amount { + margin-top: .25%; + font-size: 105%; + } + + @media #{$wallet-balance-breakpoint-range} { + margin-left: 4%; + + .token-amount { + font-size: 105%; + } + + .fiat-amount { + font-size: 95%; + } + } + } + + .balance-icon { + border-radius: 25px; + width: 45px; + height: 45px; + border: 1px solid $alto; + } +} + +.wallet-balance-wrapper-active { + background: $wallet-balance-bg; +} diff --git a/ui/app/css/itcss/generic/index.scss b/ui/app/css/itcss/generic/index.scss new file mode 100644 index 000000000..a1ffa98a3 --- /dev/null +++ b/ui/app/css/itcss/generic/index.scss @@ -0,0 +1,68 @@ +/* + Generic + */ + +@import './reset.scss'; + +* { + box-sizing: border-box; +} + +html, +body { + font-family: 'Montserrat Regular', Arial; + color: #4d4d4d; + font-weight: 300; + line-height: 1.4em; + background: #f7f7f7; + width: 100%; + height: 100%; + margin: 0; + padding: 0; +} + +html { + min-height: 500px; +} + +.app-root { + overflow: hidden; + position: relative; +} + +.app-primary { + display: flex; +} + +input:focus, +textarea:focus { + outline: none; +} + +/* stylelint-disable */ +#app-content { + overflow-x: hidden; + min-width: 357px; + height: 100%; + display: flex; + flex-direction: column; +} +/* stylelint-enable */ + +a { + text-decoration: none; + color: inherit; +} + +a:hover { + color: #df6b0e; +} + +input.large-input, +textarea.large-input { + padding: 8px; +} + +input.large-input { + height: 36px; +} diff --git a/ui/app/css/itcss/generic/reset.scss b/ui/app/css/itcss/generic/reset.scss new file mode 100644 index 000000000..2c70ee70a --- /dev/null +++ b/ui/app/css/itcss/generic/reset.scss @@ -0,0 +1,146 @@ +/* http://meyerweb.com/eric/tools/css/reset/ + v2.0 | 20110126 + License: none (public domain) +*/ + +html, +body, +div, +span, +applet, +object, +iframe, +h1, +h2, +h3, +h4, +h5, +h6, +p, +blockquote, +pre, +a, +abbr, +acronym, +address, +big, +cite, +code, +del, +dfn, +em, +img, +ins, +kbd, +q, +s, +samp, +small, +strike, +strong, +sub, +sup, +tt, +var, +b, +u, +i, +center, +dl, +dt, +dd, +ol, +ul, +li, +fieldset, +form, +label, +legend, +table, +caption, +tbody, +tfoot, +thead, +tr, +th, +td, +article, +aside, +canvas, +details, +embed, +figure, +figcaption, +footer, +header, +hgroup, +menu, +nav, +output, +ruby, +section, +summary, +time, +mark, +audio, +video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + /* stylelint-disable */ + font: inherit; + /* stylelint-enable */ + vertical-align: baseline; +} + +/* HTML5 display-role reset for older browsers */ + +/* stylelint-disable */ + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +menu, +nav, +section { + display: block; +} + +body { + line-height: 1; +} + +ol, +ul { + list-style: none; +} + +blockquote, +q { + quotes: none; +} + +blockquote:before, +blockquote:after, +q:before, +q:after { + content: ''; + content: none; +} + +table { + border-collapse: collapse; + border-spacing: 0; +} + +button { + border-style: none; +} + +/* stylelint-enable */ diff --git a/ui/app/css/itcss/objects/index.scss b/ui/app/css/itcss/objects/index.scss new file mode 100644 index 000000000..220775682 --- /dev/null +++ b/ui/app/css/itcss/objects/index.scss @@ -0,0 +1 @@ +// Objects diff --git a/ui/app/css/itcss/settings/index.scss b/ui/app/css/itcss/settings/index.scss new file mode 100644 index 000000000..58a7ca7b7 --- /dev/null +++ b/ui/app/css/itcss/settings/index.scss @@ -0,0 +1,3 @@ +@import './variables.scss'; + +@import './typography.scss'; diff --git a/ui/app/css/itcss/settings/typography.scss b/ui/app/css/itcss/settings/typography.scss new file mode 100644 index 000000000..e18a1979d --- /dev/null +++ b/ui/app/css/itcss/settings/typography.scss @@ -0,0 +1,43 @@ +@import url('https://fonts.googleapis.com/css?family=Roboto:300,500'); + +@import url('https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css'); + +@font-face { + font-family: 'Montserrat Regular'; + src: url('/fonts/Montserrat/Montserrat-Regular.woff') format('woff'); + src: url('/fonts/Montserrat/Montserrat-Regular.ttf') format('truetype'); + font-weight: 400; + font-style: normal; + font-size: 'small'; +} + +@font-face { + font-family: 'Montserrat Bold'; + src: url('/fonts/Montserrat/Montserrat-Bold.woff') format('woff'); + src: url('/fonts/Montserrat/Montserrat-Bold.ttf') format('truetype'); + font-weight: 400; + font-style: normal; +} + +@font-face { + font-family: 'Montserrat Light'; + src: url('/fonts/Montserrat/Montserrat-Light.woff') format('woff'); + src: url('/fonts/Montserrat/Montserrat-Light.ttf') format('truetype'); + font-weight: 400; + font-style: normal; +} + +@font-face { + font-family: 'Montserrat UltraLight'; + src: url('/fonts/Montserrat/Montserrat-UltraLight.woff') format('woff'); + src: url('/fonts/Montserrat/Montserrat-UltraLight.ttf') format('truetype'); + font-weight: 400; + font-style: normal; +} + +@font-face { + font-family: 'DIN OT'; + src: url('/fonts/DIN_OT/DINOT-2.otf') format('opentype'); + font-weight: 400; + font-style: normal; +} diff --git a/ui/app/css/itcss/settings/variables.scss b/ui/app/css/itcss/settings/variables.scss new file mode 100644 index 000000000..829c3d591 --- /dev/null +++ b/ui/app/css/itcss/settings/variables.scss @@ -0,0 +1,51 @@ +/* + Variables + */ + +// Base Colors +$white: #fff; +$black: #000; +$orange: #ffa500; +$red: #f00; + +/* + Colors + */ +$white-linen: #faf6f0; // formerly 'faint orange (textfield shades)' +$rajah: #f5c26d; // formerly 'light orange (button shades)' +$buttercup: #f5a623; // formerly 'dark orange (text)' +$tundora: #4a4a4a; // formerly 'borders/font/any gray' +$gallery: #efefef; +$alabaster: #f7f7f7; +$shark: #22232c; +$wild-sand: #f6f6f6; +$white: #fff; +$dusty-gray: #9b9b9b; +$alto: #dedede; +$alabaster: #fafafa; +$silver-chalice: #aeaeae; + +/* + Z-Indicies + */ + +// Planned +$dropdown-z: 30; +$container-z: 15; +$header-z: 12; + +/* + Z Indicies - Current + app - 11 + hex/bn as decimal input - 1 - remove? + dropdown - 11 + loading - 10 - higher? + mascot - 0 - remove? + */ + +/* + Responsive Breakpoints + */ +$break-small: 575px; +$break-midpoint: 780px; +$break-large: 576px; diff --git a/ui/app/css/itcss/tools/index.scss b/ui/app/css/itcss/tools/index.scss new file mode 100644 index 000000000..2236729e8 --- /dev/null +++ b/ui/app/css/itcss/tools/index.scss @@ -0,0 +1 @@ +@import './utilities.scss'; diff --git a/ui/app/css/lib.css b/ui/app/css/itcss/tools/utilities.scss index 81647d1c1..b9c99219b 100644 --- a/ui/app/css/lib.css +++ b/ui/app/css/itcss/tools/utilities.scss @@ -1,19 +1,34 @@ +/* + Utility Classes + */ + /* color */ .color-orange { - color: #F7861C; + color: #f7861c; // TODO: move to settings/variables } .color-forest { - color: #0A5448; + color: #0a5448; // TODO: move to settings/variables } /* lib */ +.full-size { + height: 100%; + width: 100%; +} + .full-width { width: 100%; } +.full-flex-height { + display: flex; + flex: 1 1 auto; + flex-direction: column; +} + .full-height { height: 100%; } @@ -118,16 +133,19 @@ .pointer { cursor: pointer; } + .cursor-pointer { cursor: pointer; transform-origin: center center; transition: transform 50ms ease-in-out; } + .cursor-pointer:hover { transform: scale(1.1); } + .cursor-pointer:active { - transform: scale(0.95); + transform: scale(.95); } .cursor-disabled { @@ -147,7 +165,7 @@ } .bold { - font-weight: bold; + font-weight: 700; } .text-transform-uppercase { @@ -172,12 +190,12 @@ hr.horizontal-line { } .hover-white:hover { - background: white; + background: $white; } .red-dot { - background: #E91550; - color: white; + background: #e91550; + color: $white; border-radius: 10px; } @@ -192,14 +210,14 @@ hr.horizontal-line { } .golden-square { - background: #EBB33F; + background: #ebb33f; } .pending-dot { - background: red; + background: $red; left: 14px; top: 14px; - color: white; + color: $white; border-radius: 10px; height: 20px; min-width: 20px; @@ -214,9 +232,9 @@ hr.horizontal-line { .keyring-label { z-index: 1; font-size: 11px; - background: rgba(255,0,0,0.8); + background: rgba(255, 0, 0, .8); bottom: -47px; - color: white; + color: $white; border-radius: 10px; height: 20px; min-width: 20px; @@ -243,16 +261,13 @@ hr.horizontal-line { margin: 13px; } -i.fa.fa-question-circle.fa-lg.menu-icon { - font-size: 18px; -} - .ether-icon { background: rgb(0, 163, 68); border-radius: 20px; } + .testnet-icon { - background: #2465E1; + background: #2465e1; } .drop-menu-item { @@ -273,33 +288,26 @@ i.fa.fa-question-circle.fa-lg.menu-icon { .critical-error { text-align: center; margin-top: 20px; - color: red; + color: $red; } /* - Hacky breakpoint fix for account + tab sections - Resolves issue from @frankiebee in - https://github.com/MetaMask/metamask-extension/pull/1835 - Please remove this when integrating new designs + Misc */ -@media screen and (min-width: 575px) and (max-width: 800px) { - .account-data-subsection { - flex: 0 0 auto !important; // reset flex - margin-left: 10px !important; // create additional horizontal space - margin-right: 10px !important; - width: 40%; - } - - .tabSection { - flex: 0 0 auto !important; - margin-left: 10px !important; - margin-right: 10px !important; - min-width: 285px; - width: 49%; - } - - .name-label { - width: 80%; - } +// TODO: move into component-level contextual 'active' state +.letter-spacey { + letter-spacing: .1em; +} + +.active { + color: #909090; +} + +.check { + margin-left: 7px; + color: #f7861c; + flex: 1 0 auto; + display: flex; + justify-content: flex-end; } diff --git a/ui/app/css/transitions.css b/ui/app/css/itcss/trumps/index.scss index 393a944f9..e09642aa8 100644 --- a/ui/app/css/transitions.css +++ b/ui/app/css/itcss/trumps/index.scss @@ -1,3 +1,9 @@ +/* + Trumps + */ + +// Transitions + /* universal */ .app-primary .main-enter { position: absolute; @@ -8,7 +14,7 @@ .app-primary.from-right .main-enter-active, .app-primary.from-left .main-enter-active { overflow-x: hidden; - transform: translateX(0px); + transform: translateX(0); transition: transform 300ms ease-in; } @@ -17,18 +23,27 @@ transform: translateX(360px); transition: transform 300ms ease-in; } + .app-primary.from-right .main-leave-active { transform: translateX(-360px); transition: transform 300ms ease-in; } +.sidebar.from-left { + transform: translateX(-320px); + transition: transform 300ms ease-in; +} + /* loader transitions */ -.loader-enter, .loader-leave-active { - opacity: 0.0; +.loader-enter, +.loader-leave-active { + opacity: 0; transition: opacity 150 ease-in; } -.loader-enter-active, .loader-leave { - opacity: 1.0; + +.loader-enter-active, +.loader-leave { + opacity: 1; transition: opacity 150 ease-in; } @@ -36,7 +51,22 @@ .app-primary.from-right .main-enter:not(.main-enter-active) { transform: translateX(360px); } + .app-primary.from-left .main-enter:not(.main-enter-active) { transform: translateX(-360px); } +i.fa.fa-question-circle.fa-lg.menu-icon { + font-size: 18px; +} + +// This text is contained inside a div. +// ID needed to override user agent stylesheet. +// See components/modal.scss + +/* stylelint-disable */ +#modal-content-footer-text { + font-family: 'DIN OT'; + font-size: 16px; +} +/* stylelint-enable */ diff --git a/ui/app/css/reset.css b/ui/app/css/reset.css deleted file mode 100644 index 9ce89e8bc..000000000 --- a/ui/app/css/reset.css +++ /dev/null @@ -1,48 +0,0 @@ -/* http://meyerweb.com/eric/tools/css/reset/ - v2.0 | 20110126 - License: none (public domain) -*/ - -html, body, div, span, applet, object, iframe, -h1, h2, h3, h4, h5, h6, p, blockquote, pre, -a, abbr, acronym, address, big, cite, code, -del, dfn, em, img, ins, kbd, q, s, samp, -small, strike, strong, sub, sup, tt, var, -b, u, i, center, -dl, dt, dd, ol, ul, li, -fieldset, form, label, legend, -table, caption, tbody, tfoot, thead, tr, th, td, -article, aside, canvas, details, embed, -figure, figcaption, footer, header, hgroup, -menu, nav, output, ruby, section, summary, -time, mark, audio, video { - margin: 0; - padding: 0; - border: 0; - font-size: 100%; - font: inherit; - vertical-align: baseline; -} -/* HTML5 display-role reset for older browsers */ -article, aside, details, figcaption, figure, -footer, header, hgroup, menu, nav, section { - display: block; -} -body { - line-height: 1; -} -ol, ul { - list-style: none; -} -blockquote, q { - quotes: none; -} -blockquote:before, blockquote:after, -q:before, q:after { - content: ''; - content: none; -} -table { - border-collapse: collapse; - border-spacing: 0; -}
\ No newline at end of file diff --git a/ui/app/main-container.js b/ui/app/main-container.js new file mode 100644 index 000000000..31583f3e5 --- /dev/null +++ b/ui/app/main-container.js @@ -0,0 +1,71 @@ +const Component = require('react').Component +const h = require('react-hyperscript') +const inherits = require('util').inherits +const TxView = require('./components/tx-view') +const WalletView = require('./components/wallet-view') +const AccountAndTransactionDetails = require('./account-and-transaction-details') +const HDRestoreVaultScreen = require('./keychains/hd/restore-vault') +const ConfigScreen = require('./config') +const UnlockScreen = require('./unlock') + +module.exports = MainContainer + +inherits(MainContainer, Component) +function MainContainer () { + Component.call(this) +} + +MainContainer.prototype.render = function () { + // 3. summarize: + // switch statement goes inside MainContainer, + // or a method in renderPrimary + // - pass resulting h() to MainContainer + // - error checking in separate func + // - router in separate func + let contents = { + component: AccountAndTransactionDetails, + key: 'account-detail', + style: {}, + } + + if (this.props.isUnlocked === false) { + switch (this.props.currentViewName) { + case 'restoreVault': + log.debug('rendering restore vault screen') + contents = { + component: HDRestoreVaultScreen, + key: 'HDRestoreVaultScreen', + } + case 'config': + log.debug('rendering config screen from unlock screen.') + contents = { + component: ConfigScreen, + key: 'config', + } + default: + log.debug('rendering locked screen') + contents = { + component: UnlockScreen, + style: { + boxShadow: 'none', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + background: '#F7F7F7', + // must force 100%, because lock screen is full-width + width: '100%', + }, + key: 'locked', + } + } + } + + return h('div.main-container', { + style: contents.style, + }, [ + h(contents.component, { + key: contents.key, + }, []) + ]) +} + diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index 3a98d53a9..3e74fb732 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -36,6 +36,9 @@ function reduceApp (state, action) { var appState = extend({ shouldClose: false, menuOpen: false, + modalOpen: false, + sidebarOpen: false, + networkDropdownOpen: false, currentView: seedWords ? seedConfView : defaultView, accountDetail: { subview: 'transactions', @@ -49,9 +52,40 @@ function reduceApp (state, action) { }, state.appState) switch (action.type) { + // dropdown methods + case actions.NETWORK_DROPDOWN_OPEN: + return extend(appState, { + networkDropdownOpen: true, + }) - // transition methods + case actions.NETWORK_DROPDOWN_CLOSE: + return extend(appState, { + networkDropdownOpen: false, + }) + + // sidebar methods + case actions.SIDEBAR_OPEN: + return extend(appState, { + sidebarOpen: true, + }) + + case actions.SIDEBAR_CLOSE: + return extend(appState, { + sidebarOpen: false, + }) + + // modal methods: + case actions.MODAL_OPEN: + return extend(appState, { + modalOpen: true, + }) + case actions.MODAL_CLOSE: + return extend(appState, { + modalOpen: false, + }) + + // transition methods case actions.TRANSITION_FORWARD: return extend(appState, { transForward: true, diff --git a/ui/app/selectors.js b/ui/app/selectors.js new file mode 100644 index 000000000..fd203dbb4 --- /dev/null +++ b/ui/app/selectors.js @@ -0,0 +1,45 @@ +const valuesFor = require('./util').valuesFor + +const selectors = { + getSelectedAddress, + getSelectedIdentity, + getSelectedAccount, + conversionRateSelector, + transactionsSelector, +} + +module.exports = selectors + +function getSelectedAddress(state) { + const selectedAddress = state.metamask.selectedAddress || Object.keys(accounts)[0] + + return selectedAddress +} + +function getSelectedIdentity(state) { + const selectedAddress = getSelectedAddress(state) + const identities = state.metamask.identities + + return identities[selectedAddress] +} + +function getSelectedAccount(state) { + const accounts = state.metamask.accounts + const selectedAddress = getSelectedAddress(state) + + return accounts[selectedAddress] +} + +function conversionRateSelector(state) { + return state.metamask.conversionRate +} + +function transactionsSelector(state) { + const { network } = state.metamask + const unapprovedMsgs = valuesFor(state.metamask.unapprovedMsgs) + const shapeShiftTxList = (network === '1') ? state.metamask.shapeShiftTxList : undefined + const transactions = state.metamask.selectedAddressTxList || [] + const txsToRender = !shapeShiftTxList ? transactions.concat(unapprovedMsgs) : transactions.concat(unapprovedMsgs, shapeShiftTxList) + + return txsToRender.sort((a, b) => b.time - a.time) +}
\ No newline at end of file diff --git a/ui/app/send.js b/ui/app/send.js index a21a219eb..ab527019f 100644 --- a/ui/app/send.js +++ b/ui/app/send.js @@ -11,6 +11,9 @@ const isHex = require('./util').isHex const EthBalance = require('./components/eth-balance') const EnsInput = require('./components/ens-input') const ethUtil = require('ethereumjs-util') + +const ARAGON = '960b236A07cf122663c4303350609A66A7B288C0' + module.exports = connect(mapStateToProps)(SendTransactionScreen) function mapStateToProps (state) { @@ -56,172 +59,425 @@ SendTransactionScreen.prototype.render = function () { return ( - h('.send-screen.flex-column.flex-grow', [ - - // - // Sender Profile - // + h('div.flex-column.flex-grow', { + style: { + minWidth: '355px', // TODO: maxWidth TBD, use home.html + }, + }, [ - h('.account-data-subsection.flex-row.flex-grow', { + // Main Send token Card + h('div.send-screen.flex-column.flex-grow', { style: { - margin: '0 20px', - }, + marginLeft: '3.5%', + marginRight: '3.5%', + background: '#FFFFFF', // $background-white + boxShadow: '0 2px 4px 0 rgba(0,0,0,0.08)', + } }, [ + h('section.flex-center.flex-row', { + style: { + zIndex: 15, // $token-icon-z-index + marginTop: '-35px', + } + }, [ + h(Identicon, { + address: ARAGON, + diameter: 76, + }), + ]), - // header - identicon + nav - h('.flex-row.flex-space-between', { + h('h3.flex-center', { style: { - marginTop: '15px', + marginTop: '-18px', + fontSize: '16px', }, }, [ - // back button - h('i.fa.fa-arrow-left.fa-lg.cursor-pointer.color-orange', { - onClick: this.back.bind(this), + 'Send Tokens', + ]), + + h('h3.flex-center', { + style: { + textAlign: 'center', + fontSize: '12px', + }, + }, [ + 'Send Tokens to anyone with an Ethereum account', + ]), + + h('h3.flex-center', { + style: { + textAlign: 'center', + marginTop: '2px', + fontSize: '12px', + }, + }, [ + 'Your Aragon Token Balance is:', + ]), + + h('h3.flex-center', { + style: { + textAlign: 'center', + fontSize: '36px', + marginTop: '8px', + }, + }, [ + '2.34', + ]), + + h('h3.flex-center', { + style: { + textAlign: 'center', + fontSize: '12px', + marginTop: '4px', + }, + }, [ + 'ANT', + ]), + + // error message + props.error && h('span.error.flex-center', props.error), + + // 'to' field + h('section.flex-row.flex-center', { + style: { + fontSize: '12px', + }, + }, [ + h(EnsInput, { + name: 'address', + placeholder: 'Recipient Address', + onChange: this.recipientDidChange.bind(this), + network, + identities, + addressBook, }), + ]), - // large identicon - h('.identicon-wrapper.flex-column.flex-center.select-none', [ - h(Identicon, { - diameter: 62, - address: address, - }), + // 'amount' and send button + h('section.flex-column.flex-center', [ + h('div.flex-row.flex-center', { + style: { + fontSize: '12px', + width: '100%', + justifyContent: 'space-between', + } + },[ + h('span', { style: {} }, ['Amount']), + h('span', { style: {} }, ['Token <> USD']), ]), - // invisible place holder - h('i.fa.fa-users.fa-lg.invisible', { + h('input.large-input', { + name: 'amount', + placeholder: '0', + type: 'number', style: { - marginTop: '28px', + marginRight: '6px', + fontSize: '12px', + }, + dataset: { + persistentFormId: 'tx-amount', }, }), ]), - // account label + h('section.flex-column.flex-center', [ + h('div.flex-row.flex-center', { + style: { + fontSize: '12px', + width: '100%', + justifyContent: 'space-between', + } + },[ + h('span', { style: {} }, ['Gas Fee:']), + h('span', { style: { fontSize: '8px' } }, ['What\'s this?']), + ]), + + h('input.large-input', { + name: 'Gas Fee', + placeholder: '0', + type: 'number', + style: { + fontSize: '12px', + marginRight: '6px', + }, + // dataset: { + // persistentFormId: 'tx-amount', + // }, + }), + + ]), - h('.flex-column', { + h('section.flex-column.flex-center', { style: { - marginTop: '10px', - alignItems: 'flex-start', + marginBottom: '10px', }, }, [ - h('h2.font-medium.color-forest.flex-center', { + h('div.flex-row.flex-center', { style: { - paddingTop: '8px', - marginBottom: '8px', - }, - }, identity && identity.name), + fontSize: '12px', + width: '100%', + justifyContent: 'flex-start', + } + },[ + h('span', { style: {} }, ['Transaction Memo (optional)']), + ]), - // address and getter actions - h('.flex-row.flex-center', { + h('input.large-input', { + name: 'memo', + placeholder: '', + type: 'string', style: { - marginBottom: '8px', + marginRight: '6px', }, - }, [ + }), + ]), + ]), - h('div', { - style: { - lineHeight: '16px', - }, - }, addressSummary(address)), + // Buttons underneath card + h('section.flex-column.flex-center', [ - ]), + h('button.btn-light', { + onClick: this.onSubmit.bind(this), + style: { + marginTop: '8px', + width: '8em', + background: '#FFFFFF' + }, + }, 'Next'), - // balance - h('.flex-row.flex-center', [ + h('button.btn-light', { + onClick: this.back.bind(this), + style: { + background: '#F7F7F7', // $alabaster + border: 'none', + opacity: 1, + width: '8em', + }, + }, 'Cancel'), + ]), + ]) - h(EthBalance, { - value: account && account.balance, - conversionRate, - currentCurrency, - }), + ) +} - ]), - ]), - ]), +// WIP - hyperscript for renderSendToken - hook up later +SendTransactionScreen.prototype.renderSendToken = function () { + this.persistentFormParentId = 'send-tx-form' + + const props = this.props + const { + address, + account, + identity, + network, + identities, + addressBook, + conversionRate, + currentCurrency, + } = props + + return ( - // - // Required Fields - // + h('div.flex-column.flex-grow', { + style: { + minWidth: '355px', // TODO: maxWidth TBD, use home.html + }, + }, [ - h('h3.flex-center.text-transform-uppercase', { + // Main Send token Card + h('div.send-screen.flex-column.flex-grow', { style: { - background: '#EBEBEB', - color: '#AEAEAE', - marginTop: '15px', - marginBottom: '16px', - }, + marginLeft: '3.5%', + marginRight: '3.5%', + background: '#FFFFFF', // $background-white + boxShadow: '0 2px 4px 0 rgba(0,0,0,0.08)', + } }, [ - 'Send Transaction', - ]), + h('section.flex-center.flex-row', { + style: { + zIndex: 15, // $token-icon-z-index + marginTop: '-35px', + } + }, [ + h(Identicon, { + address: ARAGON, + diameter: 76, + }), + ]), - // error message - props.error && h('span.error.flex-center', props.error), - - // 'to' field - h('section.flex-row.flex-center', [ - h(EnsInput, { - name: 'address', - placeholder: 'Recipient Address', - onChange: this.recipientDidChange.bind(this), - network, - identities, - addressBook, - }), - ]), + h('h3.flex-center', { + style: { + marginTop: '-18px', + fontSize: '16px', + }, + }, [ + 'Send Tokens', + ]), - // 'amount' and send button - h('section.flex-row.flex-center', [ + h('h3.flex-center', { + style: { + textAlign: 'center', + fontSize: '12px', + }, + }, [ + 'Send Tokens to anyone with an Ethereum account', + ]), - h('input.large-input', { - name: 'amount', - placeholder: 'Amount', - type: 'number', + h('h3.flex-center', { style: { - marginRight: '6px', + textAlign: 'center', + marginTop: '2px', + fontSize: '12px', }, - dataset: { - persistentFormId: 'tx-amount', + }, [ + 'Your Aragon Token Balance is:', + ]), + + h('h3.flex-center', { + style: { + textAlign: 'center', + fontSize: '36px', + marginTop: '8px', }, - }), + }, [ + '2.34', + ]), - h('button.primary', { - onClick: this.onSubmit.bind(this), + h('h3.flex-center', { style: { - textTransform: 'uppercase', + textAlign: 'center', + fontSize: '12px', + marginTop: '4px', }, - }, 'Next'), + }, [ + 'ANT', + ]), - ]), + // error message + props.error && h('span.error.flex-center', props.error), - // - // Optional Fields - // - h('h3.flex-center.text-transform-uppercase', { - style: { - background: '#EBEBEB', - color: '#AEAEAE', - marginTop: '16px', - marginBottom: '16px', - }, - }, [ - 'Transaction Data (optional)', + // 'to' field + h('section.flex-row.flex-center', { + style: { + fontSize: '12px', + }, + }, [ + h(EnsInput, { + name: 'address', + placeholder: 'Recipient Address', + onChange: this.recipientDidChange.bind(this), + network, + identities, + addressBook, + }), + ]), + + // 'amount' and send button + h('section.flex-column.flex-center', [ + h('div.flex-row.flex-center', { + style: { + fontSize: '12px', + width: '100%', + justifyContent: 'space-between', + } + },[ + h('span', { style: {} }, ['Amount']), + h('span', { style: {} }, ['Token <> USD']), + ]), + + h('input.large-input', { + name: 'amount', + placeholder: '0', + type: 'number', + style: { + marginRight: '6px', + fontSize: '12px', + }, + dataset: { + persistentFormId: 'tx-amount', + }, + }), + + ]), + + h('section.flex-column.flex-center', [ + h('div.flex-row.flex-center', { + style: { + fontSize: '12px', + width: '100%', + justifyContent: 'space-between', + } + },[ + h('span', { style: {} }, ['Gas Fee:']), + h('span', { style: { fontSize: '8px' } }, ['What\'s this?']), + ]), + + h('input.large-input', { + name: 'Gas Fee', + placeholder: '0', + type: 'number', + style: { + fontSize: '12px', + marginRight: '6px', + }, + // dataset: { + // persistentFormId: 'tx-amount', + // }, + }), + + ]), + + h('section.flex-column.flex-center', { + style: { + marginBottom: '10px', + }, + }, [ + h('div.flex-row.flex-center', { + style: { + fontSize: '12px', + width: '100%', + justifyContent: 'flex-start', + } + },[ + h('span', { style: {} }, ['Transaction Memo (optional)']), + ]), + + h('input.large-input', { + name: 'memo', + placeholder: '', + type: 'string', + style: { + marginRight: '6px', + }, + }), + ]), ]), - // 'data' field + // Buttons underneath card h('section.flex-column.flex-center', [ - h('input.large-input', { - name: 'txData', - placeholder: '0x01234', + + h('button.btn-light', { + onClick: this.onSubmit.bind(this), style: { - width: '100%', - resize: 'none', + marginTop: '8px', + width: '8em', + background: '#FFFFFF' }, - dataset: { - persistentFormId: 'tx-data', + }, 'Next'), + + h('button.btn-light', { + onClick: this.back.bind(this), + style: { + background: '#F7F7F7', // $alabaster + border: 'none', + opacity: 1, + width: '8em', }, - }), + }, 'Cancel'), ]), ]) + ) } @@ -248,7 +504,11 @@ SendTransactionScreen.prototype.onSubmit = function () { const nickname = state.nickname || ' ' const input = document.querySelector('input[name="amount"]').value const value = util.normalizeEthStringToWei(input) - const txData = document.querySelector('input[name="txData"]').value + // TODO: check with team on whether txData is removed completely. + const txData = false; + // Must replace with memo data. + // const txData = document.querySelector('input[name="txData"]').value + const balance = this.props.balance let message @@ -267,7 +527,7 @@ SendTransactionScreen.prototype.onSubmit = function () { return this.props.dispatch(actions.displayWarning(message)) } - if (!isHex(ethUtil.stripHexPrefix(txData)) && txData) { + if (txData && !isHex(ethUtil.stripHexPrefix(txData))) { message = 'Transaction data must be hex string.' return this.props.dispatch(actions.displayWarning(message)) } diff --git a/ui/app/unlock.js b/ui/app/unlock.js index 9bacd5124..1918e2e6a 100644 --- a/ui/app/unlock.js +++ b/ui/app/unlock.js @@ -50,7 +50,7 @@ UnlockScreen.prototype.render = function () { id: 'password-box', placeholder: 'enter password', style: { - + background: 'white', }, onKeyPress: this.onKeyPress.bind(this), onInput: this.inputChanged.bind(this), @@ -4,11 +4,7 @@ const path = require('path') module.exports = bundleCss var cssFiles = { - 'fonts.css': fs.readFileSync(path.join(__dirname, '/app/css/fonts.css'), 'utf8'), - 'reset.css': fs.readFileSync(path.join(__dirname, '/app/css/reset.css'), 'utf8'), - 'lib.css': fs.readFileSync(path.join(__dirname, '/app/css/lib.css'), 'utf8'), - 'index.css': fs.readFileSync(path.join(__dirname, '/app/css/index.css'), 'utf8'), - 'transitions.css': fs.readFileSync(path.join(__dirname, '/app/css/transitions.css'), 'utf8'), + 'index.css': fs.readFileSync(path.join(__dirname, '/app/css/output/index.css'), 'utf8'), 'react-tooltip-component.css': fs.readFileSync(path.join(__dirname, '..', 'node_modules', 'react-tooltip-component', 'dist', 'react-tooltip-component.css'), 'utf8'), 'react-css': fs.readFileSync(path.join(__dirname, '..', 'node_modules', 'react-select', 'dist', 'react-select.css'), 'utf8'), } diff --git a/ui/lib/is-mobile-view.js b/ui/lib/is-mobile-view.js new file mode 100644 index 000000000..8a8591c1a --- /dev/null +++ b/ui/lib/is-mobile-view.js @@ -0,0 +1,5 @@ +// Checks if viewport at invoke time fits mobile dimensions +// isMobileView :: () => Bool +const isMobileView = () => window.matchMedia("screen and (max-width: 575px)").matches + +module.exports = isMobileView
\ No newline at end of file |