diff options
Diffstat (limited to 'packages/website')
201 files changed, 1578 insertions, 709 deletions
diff --git a/packages/website/md/docs/sol-compiler/introduction.md b/packages/website/md/docs/sol-compiler/introduction.md index aa1939006..fcd80f47b 100644 --- a/packages/website/md/docs/sol-compiler/introduction.md +++ b/packages/website/md/docs/sol-compiler/introduction.md @@ -1,13 +1,8 @@ -Welcome to the [sol-compiler](https://github.com/0xProject/0x-monorepo/tree/development/packages/sol-compiler) documentation! Sol-compiler is a tool for compiling Solidity smart contracts and generating artifacts with ease. - -It serves a similar purpose as parts of the [Truffle framework](http://truffleframework.com/), but with the UNIX philosophy in mind: Make each program do one thing well. This tool is for intermediate to advanced Solidity developers that require greater configurability and reliability. - -Sol-compiler has the following advantages over Truffle: - -* Compile each smart contract with a specific version of Solidity. -* Improved artifact files: - * Storage of constructor args, source maps and paths to all requisite source files. - * An easy to maintain codebase: TypeScript + Single repo. - * Supports Solidity version ranges - contract compiled with latest Solidity version that satisfies the range. - -Sol-compiler can be used as a command-line tool or as an imported module. +Welcome to the [sol-compiler](https://github.com/0xProject/0x-monorepo/tree/development/packages/sol-compiler) documentation! Sol-compiler is a wrapper around [solc-js](https://www.npmjs.com/package/solc) that adds: + +* Smart re-compilation: Only recompiles when smart contracts have changed +* Ability to compile an entire project instead of only individual `.sol` files +* Compilation using the Solidity version specified at the top of each individual `.sol` file +* Proper parsing of Solidity version ranges +* Support for the standard [input description](https://solidity.readthedocs.io/en/develop/using-the-compiler.html#input-description) for what information you'd like added to the resulting `artifacts` file (i.e 100% configurable artifacts content). +* Storage of constructor args, source maps and paths to all dependency source files. diff --git a/packages/website/md/docs/web3_wrapper/introduction.md b/packages/website/md/docs/web3_wrapper/introduction.md index ea2f4cf0d..17bd316f1 100644 --- a/packages/website/md/docs/web3_wrapper/introduction.md +++ b/packages/website/md/docs/web3_wrapper/introduction.md @@ -1 +1 @@ -Welcome to the [Web3Wrapper](https://github.com/0xProject/0x-monorepo/tree/development/packages/web3-wrapper) documentation! Web3Wrapper is a convenience library that wraps Web3 v0.x, providing promise-based endpoints and a consistent API. +Welcome to the [Web3Wrapper](https://github.com/0xProject/0x-monorepo/tree/development/packages/web3-wrapper) documentation! Web3-wrapper is a JSON-RPC client for Ethereum nodes. It is a type-safe alternative to [Web3.js](https://github.com/ethereum/web3.js/) written in TypeScript. diff --git a/packages/website/package.json b/packages/website/package.json index c7b689356..a5768a60b 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -38,6 +38,7 @@ "lodash": "^4.17.4", "material-ui": "^0.17.1", "moment": "2.21.0", + "numeral": "^2.0.6", "polished": "^1.9.2", "query-string": "^6.0.0", "react": "15.6.1", @@ -57,8 +58,7 @@ "styled-components": "^3.3.0", "thenby": "^1.2.3", "truffle-contract": "2.0.1", - "web3": "^0.20.0", - "web3-provider-engine": "^14.0.4", + "web3-provider-engine": "14.0.6", "whatwg-fetch": "^2.0.3", "xml-js": "^1.6.4" }, @@ -71,6 +71,7 @@ "@types/lodash": "4.14.104", "@types/material-ui": "0.18.0", "@types/node": "^8.0.53", + "@types/numeral": "^0.0.22", "@types/query-string": "^5.1.0", "@types/react": "16.3.13", "@types/react-copy-to-clipboard": "^4.2.0", diff --git a/packages/website/public/images/lock_icon.svg b/packages/website/public/images/lock_icon.svg new file mode 100644 index 000000000..83e8191a1 --- /dev/null +++ b/packages/website/public/images/lock_icon.svg @@ -0,0 +1,3 @@ +<svg width="26" height="32" viewBox="0 0 26 32" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M6.47619 0C3.79509 0 1.60489 2.21216 1.60489 4.92014V6.33135C0.717479 6.33135 -3.60127e-08 7.05602 -3.60127e-08 7.95232V14.379C-3.60127e-08 15.2753 0.717479 16 1.60489 16H11.3475C12.2349 16 12.9524 15.2753 12.9524 14.379V7.95232C12.9524 7.05602 12.2349 6.33135 11.3475 6.33135V4.92014C11.3475 2.21216 9.1573 0 6.47619 0ZM9.6482 6.33135H3.30418V4.92014C3.30418 3.16567 4.72026 1.71633 6.47619 1.71633C8.23213 1.71633 9.6482 3.16567 9.6482 4.92014V6.33135Z" transform="scale(2)" fill="black"/> +</svg> diff --git a/packages/website/public/images/setup_account_icon.svg b/packages/website/public/images/setup_account_icon.svg new file mode 100644 index 000000000..eaa5b2fd6 --- /dev/null +++ b/packages/website/public/images/setup_account_icon.svg @@ -0,0 +1,3 @@ +<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M16.5 9C16.5 13.1421 13.1421 16.5 9 16.5C4.85791 16.5 1.5 13.1421 1.5 9C1.5 4.85791 4.85791 1.5 9 1.5C13.1421 1.5 16.5 4.85791 16.5 9ZM18 9C18 13.9706 13.9707 18 9 18C4.0293 18 0 13.9706 0 9C0 4.02942 4.0293 0 9 0C13.9707 0 18 4.02942 18 9ZM9.21973 5.7196C9.5127 5.42664 9.9873 5.42664 10.2803 5.7196L13.0806 8.51953C13.373 8.8125 13.373 9.28735 13.0806 9.5802L10.2803 12.3802C9.9873 12.6731 9.5127 12.6731 9.21973 12.3802C8.92676 12.0873 8.92676 11.6124 9.21973 11.3196L10.7393 9.7998H4.75C4.33594 9.7998 4 9.46399 4 9.0498C4 8.63562 4.33594 8.2998 4.75 8.2998H10.7393L9.21973 6.78015C8.92676 6.4873 8.92676 6.01245 9.21973 5.7196Z" transform="scale(2)" fill="#3289F1"/> +</svg> diff --git a/packages/website/public/images/team/alexbrowne.png b/packages/website/public/images/team/alexbrowne.png Binary files differnew file mode 100644 index 000000000..76a61913e --- /dev/null +++ b/packages/website/public/images/team/alexbrowne.png diff --git a/packages/website/public/images/token_icons/firstblood.jpg b/packages/website/public/images/token_icons/1ST.png Binary files differindex 9cd23b10e..9cd23b10e 100644 --- a/packages/website/public/images/token_icons/firstblood.jpg +++ b/packages/website/public/images/token_icons/1ST.png diff --git a/packages/website/public/images/token_icons/ABYSS.png b/packages/website/public/images/token_icons/ABYSS.png Binary files differnew file mode 100644 index 000000000..6ed2efef8 --- /dev/null +++ b/packages/website/public/images/token_icons/ABYSS.png diff --git a/packages/website/public/images/token_icons/ADT.png b/packages/website/public/images/token_icons/ADT.png Binary files differnew file mode 100644 index 000000000..2e7f3f54f --- /dev/null +++ b/packages/website/public/images/token_icons/ADT.png diff --git a/packages/website/public/images/token_icons/AE.png b/packages/website/public/images/token_icons/AE.png Binary files differnew file mode 100644 index 000000000..01dc13dda --- /dev/null +++ b/packages/website/public/images/token_icons/AE.png diff --git a/packages/website/public/images/token_icons/AION.png b/packages/website/public/images/token_icons/AION.png Binary files differnew file mode 100644 index 000000000..a2bfb9253 --- /dev/null +++ b/packages/website/public/images/token_icons/AION.png diff --git a/packages/website/public/images/token_icons/AIR.png b/packages/website/public/images/token_icons/AIR.png Binary files differnew file mode 100644 index 000000000..ab3a13b6d --- /dev/null +++ b/packages/website/public/images/token_icons/AIR.png diff --git a/packages/website/public/images/token_icons/ANT.png b/packages/website/public/images/token_icons/ANT.png Binary files differnew file mode 100644 index 000000000..437a8f21a --- /dev/null +++ b/packages/website/public/images/token_icons/ANT.png diff --git a/packages/website/public/images/token_icons/APCC.png b/packages/website/public/images/token_icons/APCC.png Binary files differnew file mode 100644 index 000000000..4294618be --- /dev/null +++ b/packages/website/public/images/token_icons/APCC.png diff --git a/packages/website/public/images/token_icons/APPC.png b/packages/website/public/images/token_icons/APPC.png Binary files differnew file mode 100644 index 000000000..4294618be --- /dev/null +++ b/packages/website/public/images/token_icons/APPC.png diff --git a/packages/website/public/images/token_icons/ARN.png b/packages/website/public/images/token_icons/ARN.png Binary files differnew file mode 100644 index 000000000..0d17bb0dd --- /dev/null +++ b/packages/website/public/images/token_icons/ARN.png diff --git a/packages/website/public/images/token_icons/ART.png b/packages/website/public/images/token_icons/ART.png Binary files differnew file mode 100644 index 000000000..194f58fa0 --- /dev/null +++ b/packages/website/public/images/token_icons/ART.png diff --git a/packages/website/public/images/token_icons/AST.png b/packages/website/public/images/token_icons/AST.png Binary files differnew file mode 100644 index 000000000..25d7c00ee --- /dev/null +++ b/packages/website/public/images/token_icons/AST.png diff --git a/packages/website/public/images/token_icons/BAT.png b/packages/website/public/images/token_icons/BAT.png Binary files differnew file mode 100644 index 000000000..840ed0a16 --- /dev/null +++ b/packages/website/public/images/token_icons/BAT.png diff --git a/packages/website/public/images/token_icons/BCAP.png b/packages/website/public/images/token_icons/BCAP.png Binary files differnew file mode 100644 index 000000000..acf50e368 --- /dev/null +++ b/packages/website/public/images/token_icons/BCAP.png diff --git a/packages/website/public/images/token_icons/BCPT.png b/packages/website/public/images/token_icons/BCPT.png Binary files differnew file mode 100644 index 000000000..e1ecbeafe --- /dev/null +++ b/packages/website/public/images/token_icons/BCPT.png diff --git a/packages/website/public/images/token_icons/BNT.png b/packages/website/public/images/token_icons/BNT.png Binary files differnew file mode 100644 index 000000000..a3b91ec34 --- /dev/null +++ b/packages/website/public/images/token_icons/BNT.png diff --git a/packages/website/public/images/token_icons/BRM.png b/packages/website/public/images/token_icons/BRM.png Binary files differnew file mode 100644 index 000000000..109c21ef5 --- /dev/null +++ b/packages/website/public/images/token_icons/BRM.png diff --git a/packages/website/public/images/token_icons/CAG.png b/packages/website/public/images/token_icons/CAG.png Binary files differnew file mode 100644 index 000000000..2aa368381 --- /dev/null +++ b/packages/website/public/images/token_icons/CAG.png diff --git a/packages/website/public/images/token_icons/CAN.png b/packages/website/public/images/token_icons/CAN.png Binary files differnew file mode 100644 index 000000000..7fdf3a9b9 --- /dev/null +++ b/packages/website/public/images/token_icons/CAN.png diff --git a/packages/website/public/images/token_icons/CAT.png b/packages/website/public/images/token_icons/CAT.png Binary files differnew file mode 100644 index 000000000..c59b6b15d --- /dev/null +++ b/packages/website/public/images/token_icons/CAT.png diff --git a/packages/website/public/images/token_icons/CFI.png b/packages/website/public/images/token_icons/CFI.png Binary files differnew file mode 100644 index 000000000..b1f2e6db9 --- /dev/null +++ b/packages/website/public/images/token_icons/CFI.png diff --git a/packages/website/public/images/token_icons/civic.png b/packages/website/public/images/token_icons/CVC.png Binary files differindex 1daf28d00..1daf28d00 100644 --- a/packages/website/public/images/token_icons/civic.png +++ b/packages/website/public/images/token_icons/CVC.png diff --git a/packages/website/public/images/token_icons/DAI.png b/packages/website/public/images/token_icons/DAI.png Binary files differnew file mode 100644 index 000000000..bf7da4a01 --- /dev/null +++ b/packages/website/public/images/token_icons/DAI.png diff --git a/packages/website/public/images/token_icons/DATA.png b/packages/website/public/images/token_icons/DATA.png Binary files differnew file mode 100644 index 000000000..43f2e6dde --- /dev/null +++ b/packages/website/public/images/token_icons/DATA.png diff --git a/packages/website/public/images/token_icons/DEB.png b/packages/website/public/images/token_icons/DEB.png Binary files differnew file mode 100644 index 000000000..c729fd265 --- /dev/null +++ b/packages/website/public/images/token_icons/DEB.png diff --git a/packages/website/public/images/token_icons/DGD.png b/packages/website/public/images/token_icons/DGD.png Binary files differnew file mode 100644 index 000000000..cb81ecb45 --- /dev/null +++ b/packages/website/public/images/token_icons/DGD.png diff --git a/packages/website/public/images/token_icons/DIVX.png b/packages/website/public/images/token_icons/DIVX.png Binary files differnew file mode 100644 index 000000000..d8d50f1f8 --- /dev/null +++ b/packages/website/public/images/token_icons/DIVX.png diff --git a/packages/website/public/images/token_icons/DNT.png b/packages/website/public/images/token_icons/DNT.png Binary files differnew file mode 100644 index 000000000..b4ac550f6 --- /dev/null +++ b/packages/website/public/images/token_icons/DNT.png diff --git a/packages/website/public/images/token_icons/edgeless.png b/packages/website/public/images/token_icons/EDG.png Binary files differindex 606784154..606784154 100644 --- a/packages/website/public/images/token_icons/edgeless.png +++ b/packages/website/public/images/token_icons/EDG.png diff --git a/packages/website/public/images/token_icons/EDU.png b/packages/website/public/images/token_icons/EDU.png Binary files differnew file mode 100644 index 000000000..d74785e5a --- /dev/null +++ b/packages/website/public/images/token_icons/EDU.png diff --git a/packages/website/public/images/token_icons/ELEC.png b/packages/website/public/images/token_icons/ELEC.png Binary files differnew file mode 100644 index 000000000..cc1a3745a --- /dev/null +++ b/packages/website/public/images/token_icons/ELEC.png diff --git a/packages/website/public/images/token_icons/EMONT.png b/packages/website/public/images/token_icons/EMONT.png Binary files differnew file mode 100644 index 000000000..ba7fe4a3d --- /dev/null +++ b/packages/website/public/images/token_icons/EMONT.png diff --git a/packages/website/public/images/token_icons/ENG.png b/packages/website/public/images/token_icons/ENG.png Binary files differnew file mode 100644 index 000000000..6f83a35ea --- /dev/null +++ b/packages/website/public/images/token_icons/ENG.png diff --git a/packages/website/public/images/token_icons/ENTR.png b/packages/website/public/images/token_icons/ENTR.png Binary files differnew file mode 100644 index 000000000..2936c20e2 --- /dev/null +++ b/packages/website/public/images/token_icons/ENTR.png diff --git a/packages/website/public/images/token_icons/EVE.png b/packages/website/public/images/token_icons/EVE.png Binary files differnew file mode 100644 index 000000000..d78362134 --- /dev/null +++ b/packages/website/public/images/token_icons/EVE.png diff --git a/packages/website/public/images/token_icons/FUN.png b/packages/website/public/images/token_icons/FUN.png Binary files differnew file mode 100644 index 000000000..a473a1c72 --- /dev/null +++ b/packages/website/public/images/token_icons/FUN.png diff --git a/packages/website/public/images/token_icons/GEE.png b/packages/website/public/images/token_icons/GEE.png Binary files differnew file mode 100644 index 000000000..887cc8429 --- /dev/null +++ b/packages/website/public/images/token_icons/GEE.png diff --git a/packages/website/public/images/token_icons/GEN.png b/packages/website/public/images/token_icons/GEN.png Binary files differnew file mode 100644 index 000000000..b1fe28370 --- /dev/null +++ b/packages/website/public/images/token_icons/GEN.png diff --git a/packages/website/public/images/token_icons/GET.png b/packages/website/public/images/token_icons/GET.png Binary files differnew file mode 100644 index 000000000..6a5fbdf63 --- /dev/null +++ b/packages/website/public/images/token_icons/GET.png diff --git a/packages/website/public/images/token_icons/GNO.png b/packages/website/public/images/token_icons/GNO.png Binary files differnew file mode 100644 index 000000000..7c7d09433 --- /dev/null +++ b/packages/website/public/images/token_icons/GNO.png diff --git a/packages/website/public/images/token_icons/golem.png b/packages/website/public/images/token_icons/GNT.png Binary files differindex e61a4367d..e61a4367d 100644 --- a/packages/website/public/images/token_icons/golem.png +++ b/packages/website/public/images/token_icons/GNT.png diff --git a/packages/website/public/images/token_icons/HGT.png b/packages/website/public/images/token_icons/HGT.png Binary files differnew file mode 100644 index 000000000..b35c601a3 --- /dev/null +++ b/packages/website/public/images/token_icons/HGT.png diff --git a/packages/website/public/images/token_icons/HOT.png b/packages/website/public/images/token_icons/HOT.png Binary files differnew file mode 100644 index 000000000..0c7f61755 --- /dev/null +++ b/packages/website/public/images/token_icons/HOT.png diff --git a/packages/website/public/images/token_icons/ICN.png b/packages/website/public/images/token_icons/ICN.png Binary files differnew file mode 100644 index 000000000..e7eebad10 --- /dev/null +++ b/packages/website/public/images/token_icons/ICN.png diff --git a/packages/website/public/images/token_icons/IND.png b/packages/website/public/images/token_icons/IND.png Binary files differnew file mode 100644 index 000000000..edc3d217b --- /dev/null +++ b/packages/website/public/images/token_icons/IND.png diff --git a/packages/website/public/images/token_icons/J8T.png b/packages/website/public/images/token_icons/J8T.png Binary files differnew file mode 100644 index 000000000..74a2f4855 --- /dev/null +++ b/packages/website/public/images/token_icons/J8T.png diff --git a/packages/website/public/images/token_icons/JET.png b/packages/website/public/images/token_icons/JET.png Binary files differnew file mode 100644 index 000000000..f34a28481 --- /dev/null +++ b/packages/website/public/images/token_icons/JET.png diff --git a/packages/website/public/images/token_icons/KIN.png b/packages/website/public/images/token_icons/KIN.png Binary files differnew file mode 100644 index 000000000..a38d656e1 --- /dev/null +++ b/packages/website/public/images/token_icons/KIN.png diff --git a/packages/website/public/images/token_icons/KNC.png b/packages/website/public/images/token_icons/KNC.png Binary files differnew file mode 100644 index 000000000..7ebe359b7 --- /dev/null +++ b/packages/website/public/images/token_icons/KNC.png diff --git a/packages/website/public/images/token_icons/LINK.png b/packages/website/public/images/token_icons/LINK.png Binary files differnew file mode 100644 index 000000000..0873f72c9 --- /dev/null +++ b/packages/website/public/images/token_icons/LINK.png diff --git a/packages/website/public/images/token_icons/LOOM.png b/packages/website/public/images/token_icons/LOOM.png Binary files differnew file mode 100644 index 000000000..0da2c41c9 --- /dev/null +++ b/packages/website/public/images/token_icons/LOOM.png diff --git a/packages/website/public/images/token_icons/LUN.png b/packages/website/public/images/token_icons/LUN.png Binary files differnew file mode 100644 index 000000000..d661bdc9f --- /dev/null +++ b/packages/website/public/images/token_icons/LUN.png diff --git a/packages/website/public/images/token_icons/MANA.png b/packages/website/public/images/token_icons/MANA.png Binary files differnew file mode 100644 index 000000000..52cdffa69 --- /dev/null +++ b/packages/website/public/images/token_icons/MANA.png diff --git a/packages/website/public/images/token_icons/MCO.png b/packages/website/public/images/token_icons/MCO.png Binary files differnew file mode 100644 index 000000000..7c3c5bfa0 --- /dev/null +++ b/packages/website/public/images/token_icons/MCO.png diff --git a/packages/website/public/images/token_icons/MKR.png b/packages/website/public/images/token_icons/MKR.png Binary files differnew file mode 100644 index 000000000..6da588979 --- /dev/null +++ b/packages/website/public/images/token_icons/MKR.png diff --git a/packages/website/public/images/token_icons/melon.png b/packages/website/public/images/token_icons/MLN.png Binary files differindex 29f58e631..29f58e631 100644 --- a/packages/website/public/images/token_icons/melon.png +++ b/packages/website/public/images/token_icons/MLN.png diff --git a/packages/website/public/images/token_icons/MOD.png b/packages/website/public/images/token_icons/MOD.png Binary files differnew file mode 100644 index 000000000..4fbe66b83 --- /dev/null +++ b/packages/website/public/images/token_icons/MOD.png diff --git a/packages/website/public/images/token_icons/MORPH.png b/packages/website/public/images/token_icons/MORPH.png Binary files differnew file mode 100644 index 000000000..a9a8dd067 --- /dev/null +++ b/packages/website/public/images/token_icons/MORPH.png diff --git a/packages/website/public/images/token_icons/MOT.png b/packages/website/public/images/token_icons/MOT.png Binary files differnew file mode 100644 index 000000000..b5457f9f1 --- /dev/null +++ b/packages/website/public/images/token_icons/MOT.png diff --git a/packages/website/public/images/token_icons/MTL.png b/packages/website/public/images/token_icons/MTL.png Binary files differnew file mode 100644 index 000000000..3297462ce --- /dev/null +++ b/packages/website/public/images/token_icons/MTL.png diff --git a/packages/website/public/images/token_icons/NANJ.png b/packages/website/public/images/token_icons/NANJ.png Binary files differnew file mode 100644 index 000000000..0c54c5bde --- /dev/null +++ b/packages/website/public/images/token_icons/NANJ.png diff --git a/packages/website/public/images/token_icons/NAVI.png b/packages/website/public/images/token_icons/NAVI.png Binary files differnew file mode 100644 index 000000000..3dc359c47 --- /dev/null +++ b/packages/website/public/images/token_icons/NAVI.png diff --git a/packages/website/public/images/token_icons/NCT.png b/packages/website/public/images/token_icons/NCT.png Binary files differnew file mode 100644 index 000000000..879c8d085 --- /dev/null +++ b/packages/website/public/images/token_icons/NCT.png diff --git a/packages/website/public/images/token_icons/NDC.png b/packages/website/public/images/token_icons/NDC.png Binary files differnew file mode 100644 index 000000000..b16890ca3 --- /dev/null +++ b/packages/website/public/images/token_icons/NDC.png diff --git a/packages/website/public/images/token_icons/NEXO.png b/packages/website/public/images/token_icons/NEXO.png Binary files differnew file mode 100644 index 000000000..f6459a39f --- /dev/null +++ b/packages/website/public/images/token_icons/NEXO.png diff --git a/packages/website/public/images/token_icons/NMR.png b/packages/website/public/images/token_icons/NMR.png Binary files differnew file mode 100644 index 000000000..8767f019a --- /dev/null +++ b/packages/website/public/images/token_icons/NMR.png diff --git a/packages/website/public/images/token_icons/OAX.png b/packages/website/public/images/token_icons/OAX.png Binary files differnew file mode 100644 index 000000000..7a53e71af --- /dev/null +++ b/packages/website/public/images/token_icons/OAX.png diff --git a/packages/website/public/images/token_icons/OCC.png b/packages/website/public/images/token_icons/OCC.png Binary files differnew file mode 100644 index 000000000..049812208 --- /dev/null +++ b/packages/website/public/images/token_icons/OCC.png diff --git a/packages/website/public/images/token_icons/OMG.png b/packages/website/public/images/token_icons/OMG.png Binary files differnew file mode 100644 index 000000000..c1552abf2 --- /dev/null +++ b/packages/website/public/images/token_icons/OMG.png diff --git a/packages/website/public/images/token_icons/OMX.png b/packages/website/public/images/token_icons/OMX.png Binary files differnew file mode 100644 index 000000000..0c3485d79 --- /dev/null +++ b/packages/website/public/images/token_icons/OMX.png diff --git a/packages/website/public/images/token_icons/PAL.png b/packages/website/public/images/token_icons/PAL.png Binary files differnew file mode 100644 index 000000000..211e42ea5 --- /dev/null +++ b/packages/website/public/images/token_icons/PAL.png diff --git a/packages/website/public/images/token_icons/tenx.png b/packages/website/public/images/token_icons/PAY.png Binary files differindex d9ffca043..d9ffca043 100644 --- a/packages/website/public/images/token_icons/tenx.png +++ b/packages/website/public/images/token_icons/PAY.png diff --git a/packages/website/public/images/token_icons/PKT.png b/packages/website/public/images/token_icons/PKT.png Binary files differnew file mode 100644 index 000000000..169390929 --- /dev/null +++ b/packages/website/public/images/token_icons/PKT.png diff --git a/packages/website/public/images/token_icons/PLAY.png b/packages/website/public/images/token_icons/PLAY.png Binary files differnew file mode 100644 index 000000000..9b141a6ec --- /dev/null +++ b/packages/website/public/images/token_icons/PLAY.png diff --git a/packages/website/public/images/token_icons/PLU.png b/packages/website/public/images/token_icons/PLU.png Binary files differnew file mode 100644 index 000000000..6f9b0344e --- /dev/null +++ b/packages/website/public/images/token_icons/PLU.png diff --git a/packages/website/public/images/token_icons/POLY.png b/packages/website/public/images/token_icons/POLY.png Binary files differnew file mode 100644 index 000000000..03ded07dc --- /dev/null +++ b/packages/website/public/images/token_icons/POLY.png diff --git a/packages/website/public/images/token_icons/REN.png b/packages/website/public/images/token_icons/REN.png Binary files differnew file mode 100644 index 000000000..f70856e9f --- /dev/null +++ b/packages/website/public/images/token_icons/REN.png diff --git a/packages/website/public/images/token_icons/REP.png b/packages/website/public/images/token_icons/REP.png Binary files differnew file mode 100644 index 000000000..c767f4b6f --- /dev/null +++ b/packages/website/public/images/token_icons/REP.png diff --git a/packages/website/public/images/token_icons/REQ.png b/packages/website/public/images/token_icons/REQ.png Binary files differnew file mode 100644 index 000000000..3c0e8ed9a --- /dev/null +++ b/packages/website/public/images/token_icons/REQ.png diff --git a/packages/website/public/images/token_icons/RFR.png b/packages/website/public/images/token_icons/RFR.png Binary files differnew file mode 100644 index 000000000..05d71c4f3 --- /dev/null +++ b/packages/website/public/images/token_icons/RFR.png diff --git a/packages/website/public/images/token_icons/RLC.png b/packages/website/public/images/token_icons/RLC.png Binary files differnew file mode 100644 index 000000000..c21dee4c4 --- /dev/null +++ b/packages/website/public/images/token_icons/RLC.png diff --git a/packages/website/public/images/token_icons/ROL.png b/packages/website/public/images/token_icons/ROL.png Binary files differnew file mode 100644 index 000000000..430fa9af1 --- /dev/null +++ b/packages/website/public/images/token_icons/ROL.png diff --git a/packages/website/public/images/token_icons/RVT.png b/packages/website/public/images/token_icons/RVT.png Binary files differnew file mode 100644 index 000000000..4f32c0e87 --- /dev/null +++ b/packages/website/public/images/token_icons/RVT.png diff --git a/packages/website/public/images/token_icons/SALT.png b/packages/website/public/images/token_icons/SALT.png Binary files differnew file mode 100644 index 000000000..ce425eed5 --- /dev/null +++ b/packages/website/public/images/token_icons/SALT.png diff --git a/packages/website/public/images/token_icons/SAN.png b/packages/website/public/images/token_icons/SAN.png Binary files differnew file mode 100644 index 000000000..36aa6a554 --- /dev/null +++ b/packages/website/public/images/token_icons/SAN.png diff --git a/packages/website/public/images/token_icons/SIG.png b/packages/website/public/images/token_icons/SIG.png Binary files differnew file mode 100644 index 000000000..33af7f085 --- /dev/null +++ b/packages/website/public/images/token_icons/SIG.png diff --git a/packages/website/public/images/token_icons/SNGLS.png b/packages/website/public/images/token_icons/SNGLS.png Binary files differnew file mode 100644 index 000000000..16bf28819 --- /dev/null +++ b/packages/website/public/images/token_icons/SNGLS.png diff --git a/packages/website/public/images/token_icons/SNT.png b/packages/website/public/images/token_icons/SNT.png Binary files differnew file mode 100644 index 000000000..6f072cffb --- /dev/null +++ b/packages/website/public/images/token_icons/SNT.png diff --git a/packages/website/public/images/token_icons/SPANK.png b/packages/website/public/images/token_icons/SPANK.png Binary files differnew file mode 100644 index 000000000..aab84ef90 --- /dev/null +++ b/packages/website/public/images/token_icons/SPANK.png diff --git a/packages/website/public/images/token_icons/SPN.png b/packages/website/public/images/token_icons/SPN.png Binary files differnew file mode 100644 index 000000000..c569c4687 --- /dev/null +++ b/packages/website/public/images/token_icons/SPN.png diff --git a/packages/website/public/images/token_icons/SS.png b/packages/website/public/images/token_icons/SS.png Binary files differnew file mode 100644 index 000000000..127e42c45 --- /dev/null +++ b/packages/website/public/images/token_icons/SS.png diff --git a/packages/website/public/images/token_icons/STORJ.png b/packages/website/public/images/token_icons/STORJ.png Binary files differnew file mode 100644 index 000000000..4539afb4a --- /dev/null +++ b/packages/website/public/images/token_icons/STORJ.png diff --git a/packages/website/public/images/token_icons/SUB.png b/packages/website/public/images/token_icons/SUB.png Binary files differnew file mode 100644 index 000000000..633bcbbd1 --- /dev/null +++ b/packages/website/public/images/token_icons/SUB.png diff --git a/packages/website/public/images/token_icons/SWT.png b/packages/website/public/images/token_icons/SWT.png Binary files differnew file mode 100644 index 000000000..910d9fdbf --- /dev/null +++ b/packages/website/public/images/token_icons/SWT.png diff --git a/packages/website/public/images/token_icons/SXDT.png b/packages/website/public/images/token_icons/SXDT.png Binary files differnew file mode 100644 index 000000000..b37e92050 --- /dev/null +++ b/packages/website/public/images/token_icons/SXDT.png diff --git a/packages/website/public/images/token_icons/TIME.png b/packages/website/public/images/token_icons/TIME.png Binary files differnew file mode 100644 index 000000000..920cc7636 --- /dev/null +++ b/packages/website/public/images/token_icons/TIME.png diff --git a/packages/website/public/images/token_icons/TKN.png b/packages/website/public/images/token_icons/TKN.png Binary files differnew file mode 100644 index 000000000..e1b276416 --- /dev/null +++ b/packages/website/public/images/token_icons/TKN.png diff --git a/packages/website/public/images/token_icons/TRL.png b/packages/website/public/images/token_icons/TRL.png Binary files differnew file mode 100644 index 000000000..afd5815ba --- /dev/null +++ b/packages/website/public/images/token_icons/TRL.png diff --git a/packages/website/public/images/token_icons/TRST.png b/packages/website/public/images/token_icons/TRST.png Binary files differnew file mode 100644 index 000000000..0ba34778f --- /dev/null +++ b/packages/website/public/images/token_icons/TRST.png diff --git a/packages/website/public/images/token_icons/TRX.png b/packages/website/public/images/token_icons/TRX.png Binary files differnew file mode 100644 index 000000000..56338957c --- /dev/null +++ b/packages/website/public/images/token_icons/TRX.png diff --git a/packages/website/public/images/token_icons/UPP.png b/packages/website/public/images/token_icons/UPP.png Binary files differnew file mode 100644 index 000000000..bc90081a0 --- /dev/null +++ b/packages/website/public/images/token_icons/UPP.png diff --git a/packages/website/public/images/token_icons/VSL.png b/packages/website/public/images/token_icons/VSL.png Binary files differnew file mode 100644 index 000000000..b3d0950d2 --- /dev/null +++ b/packages/website/public/images/token_icons/VSL.png diff --git a/packages/website/public/images/token_icons/WAND.png b/packages/website/public/images/token_icons/WAND.png Binary files differnew file mode 100644 index 000000000..e22531e21 --- /dev/null +++ b/packages/website/public/images/token_icons/WAND.png diff --git a/packages/website/public/images/token_icons/ether_erc20.png b/packages/website/public/images/token_icons/WETH.png Binary files differindex bc8beae8b..bc8beae8b 100644 --- a/packages/website/public/images/token_icons/ether_erc20.png +++ b/packages/website/public/images/token_icons/WETH.png diff --git a/packages/website/public/images/token_icons/WTC.png b/packages/website/public/images/token_icons/WTC.png Binary files differnew file mode 100644 index 000000000..06aac0617 --- /dev/null +++ b/packages/website/public/images/token_icons/WTC.png diff --git a/packages/website/public/images/token_icons/WYV.png b/packages/website/public/images/token_icons/WYV.png Binary files differnew file mode 100644 index 000000000..d63aa857b --- /dev/null +++ b/packages/website/public/images/token_icons/WYV.png diff --git a/packages/website/public/images/token_icons/XAUR.png b/packages/website/public/images/token_icons/XAUR.png Binary files differnew file mode 100644 index 000000000..c356cc9dd --- /dev/null +++ b/packages/website/public/images/token_icons/XAUR.png diff --git a/packages/website/public/images/token_icons/XNK.png b/packages/website/public/images/token_icons/XNK.png Binary files differnew file mode 100644 index 000000000..04b2ebf80 --- /dev/null +++ b/packages/website/public/images/token_icons/XNK.png diff --git a/packages/website/public/images/token_icons/XSC.png b/packages/website/public/images/token_icons/XSC.png Binary files differnew file mode 100644 index 000000000..7f70686aa --- /dev/null +++ b/packages/website/public/images/token_icons/XSC.png diff --git a/packages/website/public/images/token_icons/XYO.png b/packages/website/public/images/token_icons/XYO.png Binary files differnew file mode 100644 index 000000000..81314d2ff --- /dev/null +++ b/packages/website/public/images/token_icons/XYO.png diff --git a/packages/website/public/images/token_icons/ZIL.png b/packages/website/public/images/token_icons/ZIL.png Binary files differnew file mode 100644 index 000000000..197227803 --- /dev/null +++ b/packages/website/public/images/token_icons/ZIL.png diff --git a/packages/website/public/images/token_icons/zero_ex.png b/packages/website/public/images/token_icons/ZRX.png Binary files differindex 8ed9a984b..8ed9a984b 100644 --- a/packages/website/public/images/token_icons/zero_ex.png +++ b/packages/website/public/images/token_icons/ZRX.png diff --git a/packages/website/public/images/token_icons/adtoken.png b/packages/website/public/images/token_icons/adtoken.png Binary files differdeleted file mode 100644 index 59290af6b..000000000 --- a/packages/website/public/images/token_icons/adtoken.png +++ /dev/null diff --git a/packages/website/public/images/token_icons/aragon.png b/packages/website/public/images/token_icons/aragon.png Binary files differdeleted file mode 100644 index d162aab24..000000000 --- a/packages/website/public/images/token_icons/aragon.png +++ /dev/null diff --git a/packages/website/public/images/token_icons/augur.png b/packages/website/public/images/token_icons/augur.png Binary files differdeleted file mode 100644 index b7d61100a..000000000 --- a/packages/website/public/images/token_icons/augur.png +++ /dev/null diff --git a/packages/website/public/images/token_icons/bancor.png b/packages/website/public/images/token_icons/bancor.png Binary files differdeleted file mode 100644 index d2b2fa472..000000000 --- a/packages/website/public/images/token_icons/bancor.png +++ /dev/null diff --git a/packages/website/public/images/token_icons/basicattentiontoken.png b/packages/website/public/images/token_icons/basicattentiontoken.png Binary files differdeleted file mode 100644 index 77e7dfb1f..000000000 --- a/packages/website/public/images/token_icons/basicattentiontoken.png +++ /dev/null diff --git a/packages/website/public/images/token_icons/bitquence.png b/packages/website/public/images/token_icons/bitquence.png Binary files differdeleted file mode 100644 index d8a2c6960..000000000 --- a/packages/website/public/images/token_icons/bitquence.png +++ /dev/null diff --git a/packages/website/public/images/token_icons/btc.png b/packages/website/public/images/token_icons/btc.png Binary files differdeleted file mode 100644 index 1d9fc8347..000000000 --- a/packages/website/public/images/token_icons/btc.png +++ /dev/null diff --git a/packages/website/public/images/token_icons/clams.png b/packages/website/public/images/token_icons/clams.png Binary files differdeleted file mode 100644 index 04c2ba7d3..000000000 --- a/packages/website/public/images/token_icons/clams.png +++ /dev/null diff --git a/packages/website/public/images/token_icons/cofound-it.png b/packages/website/public/images/token_icons/cofound-it.png Binary files differdeleted file mode 100644 index 7bccd6248..000000000 --- a/packages/website/public/images/token_icons/cofound-it.png +++ /dev/null diff --git a/packages/website/public/images/token_icons/default.png b/packages/website/public/images/token_icons/default.png Binary files differdeleted file mode 100644 index 5c9ea4b0f..000000000 --- a/packages/website/public/images/token_icons/default.png +++ /dev/null diff --git a/packages/website/public/images/token_icons/digixdao.png b/packages/website/public/images/token_icons/digixdao.png Binary files differdeleted file mode 100644 index f292db716..000000000 --- a/packages/website/public/images/token_icons/digixdao.png +++ /dev/null diff --git a/packages/website/public/images/token_icons/district0x.png b/packages/website/public/images/token_icons/district0x.png Binary files differdeleted file mode 100644 index 7427b1146..000000000 --- a/packages/website/public/images/token_icons/district0x.png +++ /dev/null diff --git a/packages/website/public/images/token_icons/eos.png b/packages/website/public/images/token_icons/eos.png Binary files differindex a08f3c042..622df61bc 100644 --- a/packages/website/public/images/token_icons/eos.png +++ b/packages/website/public/images/token_icons/eos.png diff --git a/packages/website/public/images/token_icons/etheroll.png b/packages/website/public/images/token_icons/etheroll.png Binary files differdeleted file mode 100644 index 89dd5e04b..000000000 --- a/packages/website/public/images/token_icons/etheroll.png +++ /dev/null diff --git a/packages/website/public/images/token_icons/funfair.png b/packages/website/public/images/token_icons/funfair.png Binary files differdeleted file mode 100644 index 1b7c67ec6..000000000 --- a/packages/website/public/images/token_icons/funfair.png +++ /dev/null diff --git a/packages/website/public/images/token_icons/gnosis.png b/packages/website/public/images/token_icons/gnosis.png Binary files differdeleted file mode 100644 index 0111846d0..000000000 --- a/packages/website/public/images/token_icons/gnosis.png +++ /dev/null diff --git a/packages/website/public/images/token_icons/iconomi.png b/packages/website/public/images/token_icons/iconomi.png Binary files differdeleted file mode 100644 index 3499e4765..000000000 --- a/packages/website/public/images/token_icons/iconomi.png +++ /dev/null diff --git a/packages/website/public/images/token_icons/iexec.png b/packages/website/public/images/token_icons/iexec.png Binary files differdeleted file mode 100644 index ef4860457..000000000 --- a/packages/website/public/images/token_icons/iexec.png +++ /dev/null diff --git a/packages/website/public/images/token_icons/lunyr.png b/packages/website/public/images/token_icons/lunyr.png Binary files differdeleted file mode 100644 index f77094ba5..000000000 --- a/packages/website/public/images/token_icons/lunyr.png +++ /dev/null diff --git a/packages/website/public/images/token_icons/makerdao.png b/packages/website/public/images/token_icons/makerdao.png Binary files differdeleted file mode 100644 index adbc9f38c..000000000 --- a/packages/website/public/images/token_icons/makerdao.png +++ /dev/null diff --git a/packages/website/public/images/token_icons/metal.png b/packages/website/public/images/token_icons/metal.png Binary files differdeleted file mode 100644 index d8a8c33ec..000000000 --- a/packages/website/public/images/token_icons/metal.png +++ /dev/null diff --git a/packages/website/public/images/token_icons/monaco.png b/packages/website/public/images/token_icons/monaco.png Binary files differdeleted file mode 100644 index 865341fd3..000000000 --- a/packages/website/public/images/token_icons/monaco.png +++ /dev/null diff --git a/packages/website/public/images/token_icons/numeraire.png b/packages/website/public/images/token_icons/numeraire.png Binary files differdeleted file mode 100644 index 698f7cfdd..000000000 --- a/packages/website/public/images/token_icons/numeraire.png +++ /dev/null diff --git a/packages/website/public/images/token_icons/omisego.png b/packages/website/public/images/token_icons/omisego.png Binary files differdeleted file mode 100644 index 40a86b9d7..000000000 --- a/packages/website/public/images/token_icons/omisego.png +++ /dev/null diff --git a/packages/website/public/images/token_icons/santiment.png b/packages/website/public/images/token_icons/santiment.png Binary files differdeleted file mode 100644 index 05ce98c1d..000000000 --- a/packages/website/public/images/token_icons/santiment.png +++ /dev/null diff --git a/packages/website/public/images/token_icons/singularity.png b/packages/website/public/images/token_icons/singularity.png Binary files differdeleted file mode 100644 index 9db788935..000000000 --- a/packages/website/public/images/token_icons/singularity.png +++ /dev/null diff --git a/packages/website/public/images/token_icons/status.png b/packages/website/public/images/token_icons/status.png Binary files differdeleted file mode 100644 index a73ba23ba..000000000 --- a/packages/website/public/images/token_icons/status.png +++ /dev/null diff --git a/packages/website/public/images/token_icons/storjcoinx.png b/packages/website/public/images/token_icons/storjcoinx.png Binary files differdeleted file mode 100644 index 87c4d4292..000000000 --- a/packages/website/public/images/token_icons/storjcoinx.png +++ /dev/null diff --git a/packages/website/public/images/token_icons/taas.png b/packages/website/public/images/token_icons/taas.png Binary files differindex 4cca722f7..266e3690b 100644 --- a/packages/website/public/images/token_icons/taas.png +++ b/packages/website/public/images/token_icons/taas.png diff --git a/packages/website/public/images/token_icons/tokencard.png b/packages/website/public/images/token_icons/tokencard.png Binary files differdeleted file mode 100644 index 490c1be69..000000000 --- a/packages/website/public/images/token_icons/tokencard.png +++ /dev/null diff --git a/packages/website/public/images/token_icons/trust.png b/packages/website/public/images/token_icons/trust.png Binary files differdeleted file mode 100644 index 62b412b41..000000000 --- a/packages/website/public/images/token_icons/trust.png +++ /dev/null diff --git a/packages/website/public/images/token_icons/wings.png b/packages/website/public/images/token_icons/wings.png Binary files differindex cd0eb4213..c0a9ce527 100644 --- a/packages/website/public/images/token_icons/wings.png +++ b/packages/website/public/images/token_icons/wings.png diff --git a/packages/website/public/images/toshi_logo.jpg b/packages/website/public/images/toshi_logo.jpg Binary files differnew file mode 100644 index 000000000..3cf451d24 --- /dev/null +++ b/packages/website/public/images/toshi_logo.jpg diff --git a/packages/website/public/images/unlock-mm.png b/packages/website/public/images/unlock-mm.png Binary files differnew file mode 100644 index 000000000..531c95dd2 --- /dev/null +++ b/packages/website/public/images/unlock-mm.png diff --git a/packages/website/public/index.html b/packages/website/public/index.html index 4c0985c71..060f2c3c2 100644 --- a/packages/website/public/index.html +++ b/packages/website/public/index.html @@ -70,7 +70,18 @@ })(document, 'script', 'twitter-wjs'); </script> <!-- End Twitter SDK --> - + <!-- Hotjar Tracking Code for https://0xproject.com/ --> + <script> + (function (h, o, t, j, a, r) { + h.hj = h.hj || function () { (h.hj.q = h.hj.q || []).push(arguments) }; + h._hjSettings = { hjid: 935597, hjsv: 6 }; + a = o.getElementsByTagName('head')[0]; + r = o.createElement('script'); r.async = 1; + r.src = t + h._hjSettings.hjid + j + h._hjSettings.hjsv; + a.appendChild(r); + })(window, document, 'https://static.hotjar.com/c/hotjar-', '.js?sv='); + </script> + <!-- End Hotjar Tracking Code --> <!-- Main --> <script type="text/javascript" crossorigin="anonymous" src="/bundle.js" charset="utf-8"></script> </body> diff --git a/packages/website/translations/english.json b/packages/website/translations/english.json index 04fb0507a..d94dbb29e 100644 --- a/packages/website/translations/english.json +++ b/packages/website/translations/english.json @@ -76,5 +76,6 @@ "WEBSITE": "website", "DEVELOPERS": "developers", "HOME": "home", - "ROCKETCHAT": "rocket.chat" + "ROCKETCHAT": "rocket.chat", + "TRADE_CALL_TO_ACTION": "trade on 0x" } diff --git a/packages/website/ts/artifacts/Exchange.json b/packages/website/ts/artifacts/Exchange.json new file mode 100644 index 000000000..af8db7360 --- /dev/null +++ b/packages/website/ts/artifacts/Exchange.json @@ -0,0 +1,610 @@ +{ + "contract_name": "Exchange", + "abi": [ + { + "constant": true, + "inputs": [ + { + "name": "numerator", + "type": "uint256" + }, + { + "name": "denominator", + "type": "uint256" + }, + { + "name": "target", + "type": "uint256" + } + ], + "name": "isRoundingError", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "name": "filled", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "name": "cancelled", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "orderAddresses", + "type": "address[5][]" + }, + { + "name": "orderValues", + "type": "uint256[6][]" + }, + { + "name": "fillTakerTokenAmount", + "type": "uint256" + }, + { + "name": "shouldThrowOnInsufficientBalanceOrAllowance", + "type": "bool" + }, + { + "name": "v", + "type": "uint8[]" + }, + { + "name": "r", + "type": "bytes32[]" + }, + { + "name": "s", + "type": "bytes32[]" + } + ], + "name": "fillOrdersUpTo", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "orderAddresses", + "type": "address[5]" + }, + { + "name": "orderValues", + "type": "uint256[6]" + }, + { + "name": "cancelTakerTokenAmount", + "type": "uint256" + } + ], + "name": "cancelOrder", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "ZRX_TOKEN_CONTRACT", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "orderAddresses", + "type": "address[5][]" + }, + { + "name": "orderValues", + "type": "uint256[6][]" + }, + { + "name": "fillTakerTokenAmounts", + "type": "uint256[]" + }, + { + "name": "v", + "type": "uint8[]" + }, + { + "name": "r", + "type": "bytes32[]" + }, + { + "name": "s", + "type": "bytes32[]" + } + ], + "name": "batchFillOrKillOrders", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "orderAddresses", + "type": "address[5]" + }, + { + "name": "orderValues", + "type": "uint256[6]" + }, + { + "name": "fillTakerTokenAmount", + "type": "uint256" + }, + { + "name": "v", + "type": "uint8" + }, + { + "name": "r", + "type": "bytes32" + }, + { + "name": "s", + "type": "bytes32" + } + ], + "name": "fillOrKillOrder", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "orderHash", + "type": "bytes32" + } + ], + "name": "getUnavailableTakerTokenAmount", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "signer", + "type": "address" + }, + { + "name": "hash", + "type": "bytes32" + }, + { + "name": "v", + "type": "uint8" + }, + { + "name": "r", + "type": "bytes32" + }, + { + "name": "s", + "type": "bytes32" + } + ], + "name": "isValidSignature", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "numerator", + "type": "uint256" + }, + { + "name": "denominator", + "type": "uint256" + }, + { + "name": "target", + "type": "uint256" + } + ], + "name": "getPartialAmount", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "TOKEN_TRANSFER_PROXY_CONTRACT", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "orderAddresses", + "type": "address[5][]" + }, + { + "name": "orderValues", + "type": "uint256[6][]" + }, + { + "name": "fillTakerTokenAmounts", + "type": "uint256[]" + }, + { + "name": "shouldThrowOnInsufficientBalanceOrAllowance", + "type": "bool" + }, + { + "name": "v", + "type": "uint8[]" + }, + { + "name": "r", + "type": "bytes32[]" + }, + { + "name": "s", + "type": "bytes32[]" + } + ], + "name": "batchFillOrders", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "orderAddresses", + "type": "address[5][]" + }, + { + "name": "orderValues", + "type": "uint256[6][]" + }, + { + "name": "cancelTakerTokenAmounts", + "type": "uint256[]" + } + ], + "name": "batchCancelOrders", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "orderAddresses", + "type": "address[5]" + }, + { + "name": "orderValues", + "type": "uint256[6]" + }, + { + "name": "fillTakerTokenAmount", + "type": "uint256" + }, + { + "name": "shouldThrowOnInsufficientBalanceOrAllowance", + "type": "bool" + }, + { + "name": "v", + "type": "uint8" + }, + { + "name": "r", + "type": "bytes32" + }, + { + "name": "s", + "type": "bytes32" + } + ], + "name": "fillOrder", + "outputs": [ + { + "name": "filledTakerTokenAmount", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "orderAddresses", + "type": "address[5]" + }, + { + "name": "orderValues", + "type": "uint256[6]" + } + ], + "name": "getOrderHash", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "EXTERNAL_QUERY_GAS_LIMIT", + "outputs": [ + { + "name": "", + "type": "uint16" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "VERSION", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "type": "function" + }, + { + "inputs": [ + { + "name": "_zrxToken", + "type": "address" + }, + { + "name": "_tokenTransferProxy", + "type": "address" + } + ], + "payable": false, + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "maker", + "type": "address" + }, + { + "indexed": false, + "name": "taker", + "type": "address" + }, + { + "indexed": true, + "name": "feeRecipient", + "type": "address" + }, + { + "indexed": false, + "name": "makerToken", + "type": "address" + }, + { + "indexed": false, + "name": "takerToken", + "type": "address" + }, + { + "indexed": false, + "name": "filledMakerTokenAmount", + "type": "uint256" + }, + { + "indexed": false, + "name": "filledTakerTokenAmount", + "type": "uint256" + }, + { + "indexed": false, + "name": "paidMakerFee", + "type": "uint256" + }, + { + "indexed": false, + "name": "paidTakerFee", + "type": "uint256" + }, + { + "indexed": true, + "name": "tokens", + "type": "bytes32" + }, + { + "indexed": false, + "name": "orderHash", + "type": "bytes32" + } + ], + "name": "LogFill", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "maker", + "type": "address" + }, + { + "indexed": true, + "name": "feeRecipient", + "type": "address" + }, + { + "indexed": false, + "name": "makerToken", + "type": "address" + }, + { + "indexed": false, + "name": "takerToken", + "type": "address" + }, + { + "indexed": false, + "name": "cancelledMakerTokenAmount", + "type": "uint256" + }, + { + "indexed": false, + "name": "cancelledTakerTokenAmount", + "type": "uint256" + }, + { + "indexed": true, + "name": "tokens", + "type": "bytes32" + }, + { + "indexed": false, + "name": "orderHash", + "type": "bytes32" + } + ], + "name": "LogCancel", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "errorId", + "type": "uint8" + }, + { + "indexed": true, + "name": "orderHash", + "type": "bytes32" + } + ], + "name": "LogError", + "type": "event" + } + ], + "networks": { + "1": { + "address": "0x12459c951127e0c374ff9105dda097662a027093" + }, + "3": { + "address": "0x479cc461fecd078f766ecc58533d6f69580cf3ac" + }, + "4": { + "address": "0x1d16ef40fac01cec8adac2ac49427b9384192c05" + }, + "42": { + "address": "0x90fe2af704b34e0224bf2299c838e04d4dcf1364" + }, + "50": { + "address": "0x48bacb9266a570d521063ef5dd96e61686dbe788" + } + } +} diff --git a/packages/website/ts/blockchain.ts b/packages/website/ts/blockchain.ts index d18c34c32..fde134b18 100644 --- a/packages/website/ts/blockchain.ts +++ b/packages/website/ts/blockchain.ts @@ -12,10 +12,10 @@ import { import { isValidOrderHash, signOrderHashAsync } from '@0xproject/order-utils'; import { EtherscanLinkSuffixes, utils as sharedUtils } from '@0xproject/react-shared'; import { - InjectedWeb3Subprovider, ledgerEthereumBrowserClientFactoryAsync, LedgerSubprovider, RedundantSubprovider, + SignerSubprovider, Subprovider, } from '@0xproject/subproviders'; import { @@ -46,6 +46,7 @@ import { Fill, InjectedProviderObservable, InjectedProviderUpdate, + InjectedWeb3, Order as PortalOrder, Providers, ProviderType, @@ -59,13 +60,15 @@ import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; import { errorReporter } from 'ts/utils/error_reporter'; import { utils } from 'ts/utils/utils'; -import Web3 = require('web3'); import ProviderEngine = require('web3-provider-engine'); import FilterSubprovider = require('web3-provider-engine/subproviders/filters'); import RpcSubprovider = require('web3-provider-engine/subproviders/rpc'); import * as MintableArtifacts from '../contracts/Mintable.json'; +// HACK: remove this hard-coded abi and use @0xproject/contract-wrappers +import * as Exchange from './artifacts/Exchange.json'; + const BLOCK_NUMBER_BACK_TRACK = 50; const GWEI_IN_WEI = 1000000000; @@ -73,6 +76,8 @@ const providerToName: { [provider: string]: string } = { [Providers.Metamask]: constants.PROVIDER_NAME_METAMASK, [Providers.Parity]: constants.PROVIDER_NAME_PARITY_SIGNER, [Providers.Mist]: constants.PROVIDER_NAME_MIST, + [Providers.Toshi]: constants.PROVIDER_NAME_TOSHI, + [Providers.Cipher]: constants.PROVIDER_NAME_CIPHER, }; export class Blockchain { @@ -87,6 +92,7 @@ export class Blockchain { private _userAddressIfExists: string; private _ledgerSubprovider: LedgerSubprovider; private _defaultGasPrice: BigNumber; + private _watchGasPriceIntervalId: NodeJS.Timer; private static _getNameGivenProvider(provider: Provider): string { const providerType = utils.getProviderType(provider); const providerNameIfExists = providerToName[providerType]; @@ -95,8 +101,19 @@ export class Blockchain { } return providerNameIfExists; } - private static _getInjectedWeb3(): any { - return (window as any).web3; + private static _getInjectedWeb3(): InjectedWeb3 { + const injectedWeb3IfExists = (window as any).web3; + // Our core assumptions about the injected web3 object is that it has the following + // properties and methods. + if ( + _.isUndefined(injectedWeb3IfExists) || + _.isUndefined(injectedWeb3IfExists.version) || + _.isUndefined(injectedWeb3IfExists.version.getNetwork) || + _.isUndefined(injectedWeb3IfExists.currentProvider) + ) { + return undefined; + } + return injectedWeb3IfExists; } private static async _getInjectedWeb3ProviderNetworkIdIfExistsAsync(): Promise<number | undefined> { // Hack: We need to know the networkId the injectedWeb3 is connected to (if it is defined) in @@ -117,7 +134,7 @@ export class Blockchain { return networkIdIfExists; } private static async _getProviderAsync( - injectedWeb3: Web3, + injectedWeb3: InjectedWeb3, networkIdIfExists: number, shouldUserLedgerProvider: boolean = false, ): Promise<[Provider, LedgerSubprovider | undefined]> { @@ -151,7 +168,7 @@ export class Blockchain { // We catch all requests involving a users account and send it to the injectedWeb3 // instance. All other requests go to the public hosted node. const provider = new ProviderEngine(); - provider.addProvider(new InjectedWeb3Subprovider(injectedWeb3.currentProvider)); + provider.addProvider(new SignerSubprovider(injectedWeb3.currentProvider)); provider.addProvider(new FilterSubprovider()); const rpcSubproviders = _.map(publicNodeUrlsIfExistsForNetworkId, publicNodeUrl => { return new RpcSubprovider({ @@ -183,13 +200,11 @@ export class Blockchain { } constructor(dispatcher: Dispatcher) { this._dispatcher = dispatcher; - const defaultGasPrice = GWEI_IN_WEI * 30; + const defaultGasPrice = GWEI_IN_WEI * 40; this._defaultGasPrice = new BigNumber(defaultGasPrice); // We need a unique reference to this function so we can use it to unsubcribe. this._injectedProviderUpdateHandler = this._handleInjectedProviderUpdateAsync.bind(this); // tslint:disable-next-line:no-floating-promises - this._updateDefaultGasPriceAsync(); - // tslint:disable-next-line:no-floating-promises this._onPageLoadInitFireAndForgetAsync(); } public async networkIdUpdatedFireAndForgetAsync(newNetworkId: number): Promise<void> { @@ -524,6 +539,7 @@ export class Blockchain { this._blockchainWatcher.destroy(); this._injectedProviderObservable.unsubscribe(this._injectedProviderUpdateHandler); this._stopWatchingExchangeLogFillEvents(); + this._stopWatchingGasPrice(); } public async fetchTokenInformationAsync(): Promise<void> { utils.assert( @@ -611,7 +627,9 @@ export class Blockchain { ); const provider = this._contractWrappers.getProvider(); const web3Wrapper = new Web3Wrapper(provider); - web3Wrapper.abiDecoder.addABI(this._contractWrappers.exchange.abi); + // HACK: remove this hard-coded abi and use @0xproject/contract-wrappers + const exchangeAbi = _.get(Exchange, 'abi', []); + web3Wrapper.abiDecoder.addABI(exchangeAbi); const receipt = await web3Wrapper.awaitTransactionSuccessAsync(txHash); return receipt; } @@ -756,7 +774,7 @@ export class Blockchain { _.each(tokenRegistryTokens, (t: ZeroExToken) => { // HACK: For now we have a hard-coded list of iconUrls for the dummyTokens // TODO: Refactor this out and pull the iconUrl directly from the TokenRegistry - const iconUrl = configs.ICON_URL_BY_SYMBOL[t.symbol]; + const iconUrl = utils.getTokenIconUrl(t.symbol); const token: Token = { iconUrl, address: t.address, @@ -785,8 +803,30 @@ export class Blockchain { this._updateProviderName(injectedWeb3IfExists); const shouldPollUserAddress = true; const shouldUseLedgerProvider = false; + this._startWatchingGasPrice(); await this._resetOrInitializeAsync(this.networkId, shouldPollUserAddress, shouldUseLedgerProvider); } + private _startWatchingGasPrice(): void { + if (!_.isUndefined(this._watchGasPriceIntervalId)) { + return; // we are already watching + } + const oneMinuteInMs = 60000; + // tslint:disable-next-line:no-floating-promises + this._updateDefaultGasPriceAsync(); + this._watchGasPriceIntervalId = intervalUtils.setAsyncExcludingInterval( + this._updateDefaultGasPriceAsync.bind(this), + oneMinuteInMs, + (err: Error) => { + logUtils.log(`Watching gas price failed: ${err.stack}`); + this._stopWatchingGasPrice(); + }, + ); + } + private _stopWatchingGasPrice(): void { + if (!_.isUndefined(this._watchGasPriceIntervalId)) { + intervalUtils.clearAsyncExcludingInterval(this._watchGasPriceIntervalId); + } + } private async _resetOrInitializeAsync( networkId: number, shouldPollUserAddress: boolean = false, @@ -832,10 +872,10 @@ export class Blockchain { this._dispatcher.updateNetworkId(networkId); await this._rehydrateStoreWithContractEventsAsync(); } - private _updateProviderName(injectedWeb3: Web3): void { - const doesInjectedWeb3Exist = !_.isUndefined(injectedWeb3); + private _updateProviderName(injectedWeb3IfExists: InjectedWeb3): void { + const doesInjectedWeb3Exist = !_.isUndefined(injectedWeb3IfExists); const providerName = doesInjectedWeb3Exist - ? Blockchain._getNameGivenProvider(injectedWeb3.currentProvider) + ? Blockchain._getNameGivenProvider(injectedWeb3IfExists.currentProvider) : constants.PROVIDER_NAME_PUBLIC; this._dispatcher.updateInjectedProviderName(providerName); } @@ -882,7 +922,7 @@ export class Blockchain { private async _updateDefaultGasPriceAsync(): Promise<void> { try { const gasInfo = await backendClient.getGasInfoAsync(); - const gasPriceInGwei = new BigNumber(gasInfo.average / 10); + const gasPriceInGwei = new BigNumber(gasInfo.fast / 10); const gasPriceInWei = gasPriceInGwei.mul(1000000000); this._defaultGasPrice = gasPriceInWei; } catch (err) { diff --git a/packages/website/ts/blockchain_watcher.ts b/packages/website/ts/blockchain_watcher.ts index df5f73fd1..4b23aa98a 100644 --- a/packages/website/ts/blockchain_watcher.ts +++ b/packages/website/ts/blockchain_watcher.ts @@ -10,6 +10,7 @@ export class BlockchainWatcher { private _watchBalanceIntervalId: NodeJS.Timer; private _prevUserEtherBalanceInWei?: BigNumber; private _prevUserAddressIfExists: string; + private _prevNodeVersionIfExists: string; constructor(dispatcher: Dispatcher, web3Wrapper: Web3Wrapper, shouldPollUserAddress: boolean) { this._dispatcher = dispatcher; this._shouldPollUserAddress = shouldPollUserAddress; @@ -43,11 +44,9 @@ export class BlockchainWatcher { ); } private async _updateBalanceAsync(): Promise<void> { - let prevNodeVersion: string; - // Check for node version changes const currentNodeVersion = await this._web3Wrapper.getNodeVersionAsync(); - if (currentNodeVersion !== prevNodeVersion) { - prevNodeVersion = currentNodeVersion; + if (this._prevNodeVersionIfExists !== currentNodeVersion) { + this._prevNodeVersionIfExists = currentNodeVersion; this._dispatcher.updateNodeVersion(currentNodeVersion); } diff --git a/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx b/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx index 9ac78e80e..7b09cc92c 100644 --- a/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx +++ b/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx @@ -103,7 +103,6 @@ export class EthWethConversionDialog extends React.Component< shouldCheckAllowance={false} onChange={this._onValueChange.bind(this)} amount={this.state.value} - onVisitBalancesPageClick={this.props.onCancelled} /> ) : ( <EthAmountInput @@ -112,7 +111,6 @@ export class EthWethConversionDialog extends React.Component< onChange={this._onValueChange.bind(this)} shouldCheckBalance={true} shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs} - onVisitBalancesPageClick={this.props.onCancelled} /> )} <div className="pt1" style={{ fontSize: 12 }}> diff --git a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx index 38e4732a4..d2f373d67 100644 --- a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx +++ b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx @@ -29,7 +29,7 @@ interface LedgerConfigDialogProps { toggleDialogFn: (isOpen: boolean) => void; dispatcher: Dispatcher; blockchain: Blockchain; - networkId: number; + networkId?: number; providerType: ProviderType; } @@ -44,6 +44,9 @@ interface LedgerConfigDialogState { } export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps, LedgerConfigDialogState> { + public static defaultProps = { + networkId: 1, + }; constructor(props: LedgerConfigDialogProps) { super(props); const derivationPathIfExists = props.blockchain.getLedgerDerivationPathIfExists(); diff --git a/packages/website/ts/components/dialogs/send_dialog.tsx b/packages/website/ts/components/dialogs/send_dialog.tsx index 421f18b4f..8a98fdf69 100644 --- a/packages/website/ts/components/dialogs/send_dialog.tsx +++ b/packages/website/ts/components/dialogs/send_dialog.tsx @@ -80,7 +80,6 @@ export class SendDialog extends React.Component<SendDialogProps, SendDialogState shouldCheckAllowance={false} onChange={this._onValueChange.bind(this)} amount={this.state.value} - onVisitBalancesPageClick={this.props.onCancelled} lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch} /> </div> diff --git a/packages/website/ts/components/eth_wrappers.tsx b/packages/website/ts/components/eth_wrappers.tsx index 20b446155..0b282b2a1 100644 --- a/packages/website/ts/components/eth_wrappers.tsx +++ b/packages/website/ts/components/eth_wrappers.tsx @@ -20,6 +20,7 @@ import { } from 'ts/types'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; +import { utils } from 'ts/utils/utils'; const DATE_FORMAT = 'D/M/YY'; const ICON_DIMENSION = 40; @@ -95,7 +96,11 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt this.props.networkId, EtherscanLinkSuffixes.Address, ); - const tokenLabel = this._renderToken('Wrapped Ether', etherToken.address, configs.ICON_URL_BY_SYMBOL.WETH); + const tokenLabel = this._renderToken( + 'Wrapped Ether', + etherToken.address, + utils.getTokenIconUrl(etherToken.symbol), + ); const userEtherBalanceInEth = !_.isUndefined(this.props.userEtherBalanceInWei) ? Web3Wrapper.toUnitAmount(this.props.userEtherBalanceInWei, constants.DECIMAL_PLACES_ETH) : undefined; diff --git a/packages/website/ts/components/generate_order/asset_picker.tsx b/packages/website/ts/components/generate_order/asset_picker.tsx index 3d53a9e7d..5eada37b6 100644 --- a/packages/website/ts/components/generate_order/asset_picker.tsx +++ b/packages/website/ts/components/generate_order/asset_picker.tsx @@ -3,6 +3,8 @@ import Dialog from 'material-ui/Dialog'; import FlatButton from 'material-ui/FlatButton'; import * as moment from 'moment'; import * as React from 'react'; +import firstBy = require('thenby'); + import { Blockchain } from 'ts/blockchain'; import { NewTokenForm } from 'ts/components/generate_order/new_token_form'; import { TrackTokenConfirmation } from 'ts/components/track_token_confirmation'; @@ -87,10 +89,10 @@ export class AssetPicker extends React.Component<AssetPickerProps, AssetPickerSt return ( <Dialog title={dialogConfigs.title} - titleStyle={{ fontWeight: 100 }} modal={dialogConfigs.isModal} open={this.props.isOpen} actions={dialogConfigs.actions} + autoScrollBodyContent={true} onRequestClose={this._onCloseDialog.bind(this)} > {this.state.assetView === AssetViews.ASSET_PICKER && this._renderAssetPicker()} @@ -121,9 +123,8 @@ export class AssetPicker extends React.Component<AssetPickerProps, AssetPickerSt <div className="flex flex-wrap" style={{ - overflowY: 'auto', - maxWidth: 720, - maxHeight: 356, + maxWidth: 1000, + maxHeight: 600, marginBottom: 10, }} > @@ -134,15 +135,28 @@ export class AssetPicker extends React.Component<AssetPickerProps, AssetPickerSt private _renderGridTiles(): React.ReactNode { let isHovered; let tileStyles; - const gridTiles = _.map(this.props.tokenByAddress, (token: Token, address: string) => { - if ( - (this.props.tokenVisibility === TokenVisibility.TRACKED && !utils.isTokenTracked(token)) || - (this.props.tokenVisibility === TokenVisibility.UNTRACKED && utils.isTokenTracked(token)) || - token.symbol === constants.ZRX_TOKEN_SYMBOL || - token.symbol === constants.ETHER_TOKEN_SYMBOL - ) { - return null; // Skip - } + const allTokens = _.values(this.props.tokenByAddress); + // filter tokens based on visibility specified in props, do not show ZRX or ETHER as tracked or untracked + const filteredTokens = + this.props.tokenVisibility === TokenVisibility.ALL + ? allTokens + : _.filter(allTokens, token => { + return ( + token.symbol !== constants.ZRX_TOKEN_SYMBOL && + token.symbol !== constants.ETHER_TOKEN_SYMBOL && + ((this.props.tokenVisibility === TokenVisibility.TRACKED && utils.isTokenTracked(token)) || + (this.props.tokenVisibility === TokenVisibility.UNTRACKED && + !utils.isTokenTracked(token))) + ); + }); + // if we are showing tracked tokens, sort by date added, otherwise sort by symbol + const sortKey = this.props.tokenVisibility === TokenVisibility.TRACKED ? 'trackedTimestamp' : 'symbol'; + const sortedTokens = filteredTokens.sort(firstBy(sortKey)); + if (_.isEmpty(sortedTokens)) { + return <div className="mx-auto p4 h2">No tokens to remove.</div>; + } + const gridTiles = _.map(sortedTokens, token => { + const address = token.address; isHovered = this.state.hoveredAddress === address; tileStyles = { cursor: 'pointer', diff --git a/packages/website/ts/components/inputs/balance_bounded_input.tsx b/packages/website/ts/components/inputs/balance_bounded_input.tsx index 968609030..f23beb436 100644 --- a/packages/website/ts/components/inputs/balance_bounded_input.tsx +++ b/packages/website/ts/components/inputs/balance_bounded_input.tsx @@ -3,9 +3,8 @@ import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import TextField from 'material-ui/TextField'; import * as React from 'react'; -import { Link } from 'react-router-dom'; import { RequiredLabel } from 'ts/components/ui/required_label'; -import { ValidatedBigNumberCallback, WebsitePaths } from 'ts/types'; +import { ValidatedBigNumberCallback } from 'ts/types'; import { utils } from 'ts/utils/utils'; interface BalanceBoundedInputProps { @@ -18,8 +17,6 @@ interface BalanceBoundedInputProps { shouldShowIncompleteErrs?: boolean; shouldCheckBalance: boolean; validate?: (amount: BigNumber) => React.ReactNode; - onVisitBalancesPageClick?: () => void; - shouldHideVisitBalancesLink?: boolean; isDisabled?: boolean; shouldShowErrs?: boolean; shouldShowUnderline?: boolean; @@ -35,7 +32,6 @@ interface BalanceBoundedInputState { export class BalanceBoundedInput extends React.Component<BalanceBoundedInputProps, BalanceBoundedInputState> { public static defaultProps: Partial<BalanceBoundedInputProps> = { shouldShowIncompleteErrs: false, - shouldHideVisitBalancesLink: false, isDisabled: false, shouldShowErrs: true, hintText: 'amount', @@ -124,38 +120,11 @@ export class BalanceBoundedInput extends React.Component<BalanceBoundedInputProp return 'Cannot be zero'; } if (this.props.shouldCheckBalance && amount.gt(balance)) { - return <span>Insufficient balance. {this._renderIncreaseBalanceLink()}</span>; + return <span>Insufficient balance.</span>; } const errMsg = _.isUndefined(this.props.validate) ? undefined : this.props.validate(amount); return errMsg; } - private _renderIncreaseBalanceLink(): React.ReactNode { - if (this.props.shouldHideVisitBalancesLink) { - return null; - } - - const increaseBalanceText = 'Increase balance'; - const linkStyle = { - cursor: 'pointer', - color: colors.darkestGrey, - textDecoration: 'underline', - display: 'inline', - }; - if (_.isUndefined(this.props.onVisitBalancesPageClick)) { - return ( - <Link to={`${WebsitePaths.Portal}/balances`} style={linkStyle}> - {increaseBalanceText} - </Link> - ); - } else { - return ( - <div onClick={this.props.onVisitBalancesPageClick} style={linkStyle}> - {increaseBalanceText} - </div> - ); - } - } - private _setAmountState(amount: string, balance: BigNumber, callback: () => void = _.noop): void { const errorMsg = this._validate(amount, balance); this.props.onErrorMsgChange(errorMsg); diff --git a/packages/website/ts/components/inputs/eth_amount_input.tsx b/packages/website/ts/components/inputs/eth_amount_input.tsx index 1f0f27410..552d4277a 100644 --- a/packages/website/ts/components/inputs/eth_amount_input.tsx +++ b/packages/website/ts/components/inputs/eth_amount_input.tsx @@ -14,9 +14,7 @@ interface EthAmountInputProps { onChange: ValidatedBigNumberCallback; onErrorMsgChange?: (errorMsg: React.ReactNode) => void; shouldShowIncompleteErrs: boolean; - onVisitBalancesPageClick?: () => void; shouldCheckBalance: boolean; - shouldHideVisitBalancesLink?: boolean; shouldShowErrs?: boolean; shouldShowUnderline?: boolean; style?: React.CSSProperties; @@ -46,8 +44,6 @@ export class EthAmountInput extends React.Component<EthAmountInputProps, EthAmou onErrorMsgChange={this.props.onErrorMsgChange} shouldCheckBalance={this.props.shouldCheckBalance} shouldShowIncompleteErrs={this.props.shouldShowIncompleteErrs} - onVisitBalancesPageClick={this.props.onVisitBalancesPageClick} - shouldHideVisitBalancesLink={this.props.shouldHideVisitBalancesLink} hintText={this.props.hintText} shouldShowErrs={this.props.shouldShowErrs} shouldShowUnderline={this.props.shouldShowUnderline} diff --git a/packages/website/ts/components/inputs/token_amount_input.tsx b/packages/website/ts/components/inputs/token_amount_input.tsx index a67120320..93ef516cf 100644 --- a/packages/website/ts/components/inputs/token_amount_input.tsx +++ b/packages/website/ts/components/inputs/token_amount_input.tsx @@ -21,7 +21,6 @@ interface TokenAmountInputProps { shouldCheckAllowance: boolean; onChange: ValidatedBigNumberCallback; onErrorMsgChange?: (errorMsg: React.ReactNode) => void; - onVisitBalancesPageClick?: () => void; lastForceTokenStateRefetch: number; shouldShowErrs?: boolean; shouldShowUnderline?: boolean; @@ -88,7 +87,6 @@ export class TokenAmountInput extends React.Component<TokenAmountInputProps, Tok validate={this._validate.bind(this)} shouldCheckBalance={this.props.shouldCheckBalance} shouldShowIncompleteErrs={this.props.shouldShowIncompleteErrs} - onVisitBalancesPageClick={this.props.onVisitBalancesPageClick} isDisabled={!this.state.isBalanceAndAllowanceLoaded} hintText={this.props.hintText} shouldShowErrs={this.props.shouldShowErrs} diff --git a/packages/website/ts/components/onboarding/add_eth_onboarding_step.tsx b/packages/website/ts/components/onboarding/add_eth_onboarding_step.tsx index bccdc0c18..ca71fcd50 100644 --- a/packages/website/ts/components/onboarding/add_eth_onboarding_step.tsx +++ b/packages/website/ts/components/onboarding/add_eth_onboarding_step.tsx @@ -1,10 +1,10 @@ import { BigNumber } from '@0xproject/utils'; import * as React from 'react'; +import { Balance } from 'ts/components/ui/balance'; import { Container } from 'ts/components/ui/container'; import { Image } from 'ts/components/ui/image'; import { Text } from 'ts/components/ui/text'; import { constants } from 'ts/utils/constants'; -import { utils } from 'ts/utils/utils'; export interface AddEthOnboardingStepProps { userEthBalanceInWei: BigNumber; @@ -15,13 +15,11 @@ export const AddEthOnboardingStep: React.StatelessComponent<AddEthOnboardingStep <div className="flex items-center flex-column"> <Text> Great! Looks like you already have{' '} - <b> - {utils.getFormattedAmount( - props.userEthBalanceInWei, - constants.DECIMAL_PLACES_ETH, - constants.ETHER_SYMBOL, - )}{' '} - </b> + <Balance + amount={props.userEthBalanceInWei} + decimals={constants.DECIMAL_PLACES_ETH} + symbol={constants.ETHER_SYMBOL} + />{' '} in your wallet. </Text> <Container marginTop="15px" marginBottom="15px"> diff --git a/packages/website/ts/components/onboarding/install_wallet_onboarding_step.tsx b/packages/website/ts/components/onboarding/install_wallet_onboarding_step.tsx index a95c464af..d618c8318 100644 --- a/packages/website/ts/components/onboarding/install_wallet_onboarding_step.tsx +++ b/packages/website/ts/components/onboarding/install_wallet_onboarding_step.tsx @@ -1,19 +1,42 @@ import { colors } from '@0xproject/react-shared'; -import ActionAccountBalanceWallet from 'material-ui/svg-icons/action/account-balance-wallet'; import * as React from 'react'; import { Container } from 'ts/components/ui/container'; +import { Image } from 'ts/components/ui/image'; import { Text } from 'ts/components/ui/text'; +import { utils } from 'ts/utils/utils'; export interface InstallWalletOnboardingStepProps {} -export const InstallWalletOnboardingStep: React.StatelessComponent<InstallWalletOnboardingStepProps> = () => ( - <div className="flex items-center flex-column"> - <Text> - Before you begin, you need to connect to a wallet. This will be used across all 0x relayers and dApps. - </Text> - <Container marginTop="15px" marginBottom="15px"> - <ActionAccountBalanceWallet style={{ width: '50px', height: '50px' }} color={colors.orange} /> - </Container> - <Text>Please refresh the page once you've done this to continue!</Text> - </div> -); +export const InstallWalletOnboardingStep: React.StatelessComponent<InstallWalletOnboardingStepProps> = () => { + const [downloadLink, isOnMobile] = utils.getBestWalletDownloadLinkAndIsMobile(); + const followupText = isOnMobile + ? `Please revisit this site in your mobile dApp browser to continue!` + : `Please refresh the page once you've done this to continue!`; + const downloadText = isOnMobile ? 'Get the Toshi Wallet' : 'Get the MetaMask extension'; + return ( + <div className="flex items-center flex-column"> + <Text>First, you need to connect to a wallet. This will be used across all 0x relayers and dApps.</Text> + <Container className="flex items-center" marginTop="15px" marginBottom="15px"> + <Image + height="50px" + width="50px" + borderRadius="22%" + src={isOnMobile ? '/images/toshi_logo.jpg' : '/images/metamask_icon.png'} + /> + <Container marginLeft="10px"> + <a href={downloadLink} target="_blank"> + <Text + fontWeight={700} + fontSize="18px" + fontColor={colors.mediumBlue} + textDecorationLine="underline" + > + {downloadText} + </Text> + </a> + </Container> + </Container> + <Text>{followupText}</Text> + </div> + ); +}; diff --git a/packages/website/ts/components/onboarding/onboarding_card.tsx b/packages/website/ts/components/onboarding/onboarding_card.tsx index 48e8ab022..e1b0f304b 100644 --- a/packages/website/ts/components/onboarding/onboarding_card.tsx +++ b/packages/website/ts/components/onboarding/onboarding_card.tsx @@ -12,6 +12,7 @@ export type ContinueButtonDisplay = 'enabled' | 'disabled'; export interface OnboardingCardProps { title?: string; + shouldCenterTitle?: boolean; content: React.ReactNode; isLastStep: boolean; onClose: () => void; @@ -23,10 +24,13 @@ export interface OnboardingCardProps { shouldHideNextButton?: boolean; continueButtonText?: string; borderRadius?: string; + // Used for super-custom content. + shouldRemoveExtraSpacing?: boolean; } export const OnboardingCard: React.StatelessComponent<OnboardingCardProps> = ({ title, + shouldCenterTitle, content, continueButtonDisplay, continueButtonText, @@ -37,55 +41,75 @@ export const OnboardingCard: React.StatelessComponent<OnboardingCardProps> = ({ shouldHideBackButton, shouldHideNextButton, borderRadius, -}) => ( - <Island borderRadius={borderRadius}> - <Container paddingRight="30px" paddingLeft="30px" maxWidth={350} paddingTop="15px" paddingBottom="15px"> - <div className="flex flex-column"> - <div className="flex justify-between"> - <Title>{title}</Title> - <Container position="relative" bottom="20px" left="15px"> - <IconButton color={colors.grey} iconName="zmdi-close" onClick={onClose}> - Close - </IconButton> + shouldRemoveExtraSpacing, +}) => { + const padding = shouldRemoveExtraSpacing + ? {} + : { + paddingRight: '30px', + paddingLeft: '30px', + paddingTop: '15px', + paddingBottom: '15px', + }; + const closeIconPositioning = shouldRemoveExtraSpacing + ? { right: '15px', bottom: '3px' } + : { bottom: '20px', left: '15px' }; + return ( + <Island borderRadius={borderRadius}> + <Container {...padding}> + <div className="flex flex-column"> + <Container className="flex justify-between"> + <Container width="100%"> + <Title center={shouldCenterTitle}>{title}</Title> + </Container> + <Container position="relative" {...closeIconPositioning}> + <IconButton color={colors.grey} iconName="zmdi-close" onClick={onClose}> + Close + </IconButton> + </Container> </Container> + <Container marginBottom={shouldRemoveExtraSpacing ? undefined : '15px'}> + <Text>{content}</Text> + </Container> + {continueButtonDisplay && ( + <Button + isDisabled={continueButtonDisplay === 'disabled'} + onClick={!_.isUndefined(onContinueButtonClick) ? onContinueButtonClick : onClickNext} + fontColor={colors.white} + fontSize="15px" + backgroundColor={colors.mediumBlue} + > + {continueButtonText} + </Button> + )} + {!(shouldHideBackButton && shouldHideNextButton) && ( + <Container className="clearfix" marginTop="15px"> + <div className="left"> + {!shouldHideBackButton && ( + <Text fontColor={colors.grey} onClick={onClickBack}> + Back + </Text> + )} + </div> + <div className="right"> + {!shouldHideNextButton && ( + <Text fontColor={colors.grey} onClick={onClickNext}> + Skip + </Text> + )} + </div> + </Container> + )} </div> - <Container marginBottom="15px"> - <Text>{content}</Text> - </Container> - {continueButtonDisplay && ( - <Button - isDisabled={continueButtonDisplay === 'disabled'} - onClick={!_.isUndefined(onContinueButtonClick) ? onContinueButtonClick : onClickNext} - fontColor={colors.white} - fontSize="15px" - backgroundColor={colors.mediumBlue} - > - {continueButtonText} - </Button> - )} - <Container className="clearfix" marginTop="15px"> - <div className="left"> - {!shouldHideBackButton && ( - <Text fontColor={colors.grey} onClick={onClickBack}> - Back - </Text> - )} - </div> - <div className="right"> - {!shouldHideNextButton && ( - <Text fontColor={colors.grey} onClick={onClickNext}> - Skip - </Text> - )} - </div> - </Container> - </div> - </Container> - </Island> -); + </Container> + </Island> + ); +}; OnboardingCard.defaultProps = { continueButtonText: 'Continue', + shouldCenterTitle: false, + shouldRemoveExtraSpacing: false, }; OnboardingCard.displayName = 'OnboardingCard'; diff --git a/packages/website/ts/components/onboarding/onboarding_flow.tsx b/packages/website/ts/components/onboarding/onboarding_flow.tsx index 1f4c6df82..91d5f2476 100644 --- a/packages/website/ts/components/onboarding/onboarding_flow.tsx +++ b/packages/website/ts/components/onboarding/onboarding_flow.tsx @@ -2,22 +2,44 @@ import * as React from 'react'; import { Placement, Popper, PopperChildrenProps } from 'react-popper'; import { OnboardingCard } from 'ts/components/onboarding/onboarding_card'; -import { ContinueButtonDisplay, OnboardingTooltip } from 'ts/components/onboarding/onboarding_tooltip'; +import { + ContinueButtonDisplay, + OnboardingTooltip, + TooltipPointerDisplay, +} from 'ts/components/onboarding/onboarding_tooltip'; import { Animation } from 'ts/components/ui/animation'; import { Container } from 'ts/components/ui/container'; import { Overlay } from 'ts/components/ui/overlay'; import { zIndex } from 'ts/style/z_index'; -export interface Step { +export interface FixedPositionSettings { + type: 'fixed'; + top?: string; + bottom?: string; + left?: string; + right?: string; + tooltipPointerDisplay?: TooltipPointerDisplay; +} + +export interface TargetPositionSettings { + type: 'target'; target: string; + placement: Placement; +} + +export interface Step { + // Provide either a CSS selector, or fixed position settings. Only applies to desktop. + position: TargetPositionSettings | FixedPositionSettings; title?: string; + shouldCenterTitle?: boolean; content: React.ReactNode; - placement?: Placement; shouldHideBackButton?: boolean; shouldHideNextButton?: boolean; continueButtonDisplay?: ContinueButtonDisplay; continueButtonText?: string; onContinueButtonClick?: () => void; + // Only used for very custom steps. + shouldRemoveExtraSpacing?: boolean; } export interface OnboardingFlowProps { @@ -28,44 +50,55 @@ export interface OnboardingFlowProps { updateOnboardingStep: (stepIndex: number) => void; disableOverlay?: boolean; isMobile: boolean; + disableCloseOnClickOutside?: boolean; } export class OnboardingFlow extends React.Component<OnboardingFlowProps> { public static defaultProps = { disableOverlay: false, isMobile: false, + disableCloseOnClickOutside: false, }; public render(): React.ReactNode { if (!this.props.isRunning) { return null; } let onboardingElement = null; + const currentStep = this._getCurrentStep(); if (this.props.isMobile) { - onboardingElement = <Animation type="easeUpFromBottom">{this._renderOnboardignCard()}</Animation>; - } else { + onboardingElement = <Animation type="easeUpFromBottom">{this._renderOnboardingCard()}</Animation>; + } else if (currentStep.position.type === 'target') { + const { placement, target } = currentStep.position; onboardingElement = ( - <Popper - referenceElement={this._getElementForStep()} - placement={this._getCurrentStep().placement} - positionFixed={true} - > + <Popper referenceElement={document.querySelector(target)} placement={placement} positionFixed={true}> {this._renderPopperChildren.bind(this)} </Popper> ); + } else if (currentStep.position.type === 'fixed') { + const { top, right, bottom, left, tooltipPointerDisplay } = currentStep.position; + onboardingElement = ( + <Container + position="fixed" + zIndex={zIndex.aboveOverlay} + top={top} + right={right} + bottom={bottom} + left={left} + > + {this._renderToolTip(tooltipPointerDisplay)} + </Container> + ); } if (this.props.disableOverlay) { return onboardingElement; } return ( <div> - <Overlay onClick={this.props.onClose} /> + <Overlay onClick={this.props.disableCloseOnClickOutside ? undefined : this.props.onClose} /> {onboardingElement} </div> ); } - private _getElementForStep(): Element { - return document.querySelector(this._getCurrentStep().target); - } private _renderPopperChildren(props: PopperChildrenProps): React.ReactNode { const customStyles = { zIndex: zIndex.aboveOverlay }; // On re-render, we want to re-center the popper. @@ -76,7 +109,7 @@ export class OnboardingFlow extends React.Component<OnboardingFlowProps> { </div> ); } - private _renderToolTip(): React.ReactNode { + private _renderToolTip(tooltipPointerDisplay?: TooltipPointerDisplay): React.ReactNode { const { steps, stepIndex } = this.props; const step = steps[stepIndex]; const isLastStep = steps.length - 1 === stepIndex; @@ -84,6 +117,7 @@ export class OnboardingFlow extends React.Component<OnboardingFlowProps> { <Container marginLeft="30px" width="400px"> <OnboardingTooltip title={step.title} + shouldCenterTitle={step.shouldCenterTitle} content={step.content} isLastStep={isLastStep} shouldHideBackButton={step.shouldHideBackButton} @@ -94,12 +128,14 @@ export class OnboardingFlow extends React.Component<OnboardingFlowProps> { continueButtonDisplay={step.continueButtonDisplay} continueButtonText={step.continueButtonText} onContinueButtonClick={step.onContinueButtonClick} + pointerDisplay={tooltipPointerDisplay} + shouldRemoveExtraSpacing={step.shouldRemoveExtraSpacing} /> </Container> ); } - private _renderOnboardignCard(): React.ReactNode { + private _renderOnboardingCard(): React.ReactNode { const { steps, stepIndex } = this.props; const step = steps[stepIndex]; const isLastStep = steps.length - 1 === stepIndex; @@ -107,6 +143,7 @@ export class OnboardingFlow extends React.Component<OnboardingFlowProps> { <Container position="relative" zIndex={1}> <OnboardingCard title={step.title} + shouldCenterTitle={step.shouldCenterTitle} content={step.content} isLastStep={isLastStep} shouldHideBackButton={step.shouldHideBackButton} @@ -118,6 +155,7 @@ export class OnboardingFlow extends React.Component<OnboardingFlowProps> { continueButtonText={step.continueButtonText} onContinueButtonClick={step.onContinueButtonClick} borderRadius="10px 10px 0px 0px" + shouldRemoveExtraSpacing={step.shouldRemoveExtraSpacing} /> </Container> ); diff --git a/packages/website/ts/components/onboarding/onboarding_tooltip.tsx b/packages/website/ts/components/onboarding/onboarding_tooltip.tsx index d8065625d..15d47908d 100644 --- a/packages/website/ts/components/onboarding/onboarding_tooltip.tsx +++ b/packages/website/ts/components/onboarding/onboarding_tooltip.tsx @@ -4,22 +4,27 @@ import { OnboardingCard, OnboardingCardProps } from 'ts/components/onboarding/on import { Pointer, PointerDirection } from 'ts/components/ui/pointer'; export type ContinueButtonDisplay = 'enabled' | 'disabled'; +export type TooltipPointerDisplay = PointerDirection | 'none'; export interface OnboardingTooltipProps extends OnboardingCardProps { className?: string; - pointerDirection?: PointerDirection; + pointerDisplay?: TooltipPointerDisplay; } export const OnboardingTooltip: React.StatelessComponent<OnboardingTooltipProps> = props => { - const { pointerDirection, className, ...cardProps } = props; + const { pointerDisplay, className, ...cardProps } = props; + const card = <OnboardingCard {...cardProps} />; + if (pointerDisplay === 'none') { + return card; + } return ( - <Pointer className={className} direction={pointerDirection}> + <Pointer className={className} direction={pointerDisplay}> <OnboardingCard {...cardProps} /> </Pointer> ); }; OnboardingTooltip.defaultProps = { - pointerDirection: 'left', + pointerDisplay: 'left', }; OnboardingTooltip.displayName = 'OnboardingTooltip'; diff --git a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx index 6bfa5c75f..20a8f0a32 100644 --- a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx +++ b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx @@ -9,7 +9,12 @@ import { AddEthOnboardingStep } from 'ts/components/onboarding/add_eth_onboardin import { CongratsOnboardingStep } from 'ts/components/onboarding/congrats_onboarding_step'; import { InstallWalletOnboardingStep } from 'ts/components/onboarding/install_wallet_onboarding_step'; import { IntroOnboardingStep } from 'ts/components/onboarding/intro_onboarding_step'; -import { OnboardingFlow, Step } from 'ts/components/onboarding/onboarding_flow'; +import { + FixedPositionSettings, + OnboardingFlow, + Step, + TargetPositionSettings, +} from 'ts/components/onboarding/onboarding_flow'; import { SetAllowancesOnboardingStep } from 'ts/components/onboarding/set_allowances_onboarding_step'; import { UnlockWalletOnboardingStep } from 'ts/components/onboarding/unlock_wallet_onboarding_step'; import { @@ -18,7 +23,7 @@ import { WrapEthOnboardingStep3, } from 'ts/components/onboarding/wrap_eth_onboarding_step'; import { AllowanceToggle } from 'ts/containers/inputs/allowance_toggle'; -import { ProviderType, ScreenWidths, Token, TokenByAddress, TokenStateByAddress } from 'ts/types'; +import { BrowserType, ProviderType, ScreenWidths, Token, TokenByAddress, TokenStateByAddress } from 'ts/types'; import { analytics } from 'ts/utils/analytics'; import { utils } from 'ts/utils/utils'; @@ -45,8 +50,6 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp private _unlisten: () => void; public componentDidMount(): void { this._adjustStepIfShould(); - // Wait until the step is adjusted to decide whether we should show onboarding. - setTimeout(this._autoStartOnboardingIfShould.bind(this), 1000); // If there is a route change, just close onboarding. this._unlisten = this.props.history.listen(() => this.props.updateIsRunning(false)); } @@ -54,15 +57,18 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp this._unlisten(); } public componentDidUpdate(prevProps: PortalOnboardingFlowProps): void { - this._adjustStepIfShould(); - if (!prevProps.isRunning && this.props.isRunning) { + // Any one of steps 0-3 could be the starting step, and we only want to reset the scroll on the starting step. + if (this.props.isRunning && utils.isMobileWidth(this.props.screenWidth) && this.props.stepIndex < 3) { // On mobile, make sure the wallet is completely visible. - if (this.props.screenWidth === ScreenWidths.Sm) { - document.querySelector('.wallet').scrollIntoView(); - } + document.querySelector('.wallet').scrollIntoView(); + } + this._adjustStepIfShould(); + if (!prevProps.blockchainIsLoaded && this.props.blockchainIsLoaded) { + this._autoStartOnboardingIfShould(); } } public render(): React.ReactNode { + const browserType = utils.getBrowserType(); return ( <OnboardingFlow steps={this._getSteps()} @@ -72,73 +78,75 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp updateOnboardingStep={this._updateOnboardingStep.bind(this)} disableOverlay={this.props.screenWidth === ScreenWidths.Sm} isMobile={this.props.screenWidth === ScreenWidths.Sm} + // This is necessary to ensure onboarding stays open once the user unlocks metamask and clicks away + disableCloseOnClickOutside={browserType === BrowserType.Firefox || browserType === BrowserType.Opera} /> ); } private _getSteps(): Step[] { + const nextToWalletPosition: TargetPositionSettings = { + type: 'target', + target: '.wallet', + placement: 'right', + }; + const underMetamaskExtension: FixedPositionSettings = { + type: 'fixed', + top: '10px', + right: '10px', + tooltipPointerDisplay: 'none', + }; const steps: Step[] = [ { - target: '.wallet', + position: nextToWalletPosition, title: '0x Ecosystem Setup', content: <InstallWalletOnboardingStep />, - placement: 'right', shouldHideBackButton: true, shouldHideNextButton: true, }, { - target: '.wallet', - title: '0x Ecosystem Setup', + position: underMetamaskExtension, + title: 'Please Unlock Metamask...', content: <UnlockWalletOnboardingStep />, - placement: 'right', shouldHideBackButton: true, shouldHideNextButton: true, + shouldCenterTitle: true, + shouldRemoveExtraSpacing: true, }, { - target: '.wallet', + position: nextToWalletPosition, title: '0x Ecosystem Account Setup', content: <IntroOnboardingStep />, - placement: 'right', shouldHideBackButton: true, continueButtonDisplay: 'enabled', }, { - target: '.wallet', + position: nextToWalletPosition, title: 'Step 1: Add ETH', content: ( <AddEthOnboardingStep userEthBalanceInWei={this.props.userEtherBalanceInWei || new BigNumber(0)} /> ), - placement: 'right', continueButtonDisplay: this._userHasVisibleEth() ? 'enabled' : 'disabled', }, { - target: '.wallet', + position: nextToWalletPosition, title: 'Step 2: Wrap ETH', content: <WrapEthOnboardingStep1 />, - placement: 'right', continueButtonDisplay: 'enabled', }, { - target: '.wallet', + position: nextToWalletPosition, title: 'Step 2: Wrap ETH', content: <WrapEthOnboardingStep2 />, - placement: 'right', continueButtonDisplay: this._userHasVisibleWeth() ? 'enabled' : 'disabled', }, { - target: '.wallet', + position: nextToWalletPosition, title: 'Step 2: Wrap ETH', - content: ( - <WrapEthOnboardingStep3 - formattedWethBalanceIfExists={ - this._userHasVisibleWeth() ? this._getFormattedWethBalance() : undefined - } - /> - ), - placement: 'right', + content: <WrapEthOnboardingStep3 wethAmount={this._getWethBalance()} />, continueButtonDisplay: this._userHasVisibleWeth() ? 'enabled' : 'disabled', }, { - target: '.wallet', + position: nextToWalletPosition, title: 'Step 3: Unlock Tokens', content: ( <SetAllowancesOnboardingStep @@ -147,14 +155,12 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp doesUserHaveAllowancesForWethAndZrx={this._doesUserHaveAllowancesForWethAndZrx()} /> ), - placement: 'right', continueButtonDisplay: this._doesUserHaveAllowancesForWethAndZrx() ? 'enabled' : 'disabled', }, { - target: '.wallet', + position: nextToWalletPosition, title: '🎉 The Ecosystem Awaits', content: <CongratsOnboardingStep />, - placement: 'right', continueButtonDisplay: 'enabled', shouldHideNextButton: true, continueButtonText: 'Enter the 0x Ecosystem', @@ -177,11 +183,6 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp const ethTokenState = this.props.trackedTokenStateByAddress[ethToken.address]; return ethTokenState.balance; } - private _getFormattedWethBalance(): string { - const ethToken = utils.getEthToken(this.props.tokenByAddress); - const ethTokenState = this.props.trackedTokenStateByAddress[ethToken.address]; - return utils.getFormattedAmountFromToken(ethToken, ethTokenState); - } private _userHasVisibleWeth(): boolean { return this._getWethBalance() > new BigNumber(0); } @@ -221,7 +222,7 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp } private _autoStartOnboardingIfShould(): void { if ( - (this.props.stepIndex === 0 && !this.props.isRunning) || + (this.props.stepIndex === 0 && !this.props.isRunning && this.props.blockchainIsLoaded) || (!this.props.isRunning && !this.props.hasBeenClosed && this.props.blockchainIsLoaded) ) { const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId]; @@ -267,7 +268,7 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp ); } private _handleFinalStepContinueClick(): void { - if (utils.isMobile(this.props.screenWidth)) { + if (utils.isMobileWidth(this.props.screenWidth)) { window.scrollTo(0, 0); this.props.history.push('/portal'); } diff --git a/packages/website/ts/components/onboarding/unlock_wallet_onboarding_step.tsx b/packages/website/ts/components/onboarding/unlock_wallet_onboarding_step.tsx index 0039aa545..358141520 100644 --- a/packages/website/ts/components/onboarding/unlock_wallet_onboarding_step.tsx +++ b/packages/website/ts/components/onboarding/unlock_wallet_onboarding_step.tsx @@ -1,16 +1,8 @@ import * as React from 'react'; -import { Container } from 'ts/components/ui/container'; -import { Text } from 'ts/components/ui/text'; +import { Image } from 'ts/components/ui/image'; export interface UnlockWalletOnboardingStepProps {} export const UnlockWalletOnboardingStep: React.StatelessComponent<UnlockWalletOnboardingStepProps> = () => ( - <div className="flex items-center flex-column"> - <div className="flex items-center flex-column"> - <Container marginTop="15px" marginBottom="15px"> - <img src="/images/metamask_icon.png" height="50px" width="50px" /> - </Container> - <Text center={true}>Unlock your metamask extension to get started.</Text> - </div> - </div> + <Image src="/images/unlock-mm.png" /> ); diff --git a/packages/website/ts/components/onboarding/wrap_eth_onboarding_step.tsx b/packages/website/ts/components/onboarding/wrap_eth_onboarding_step.tsx index 4d336c80f..e4332de75 100644 --- a/packages/website/ts/components/onboarding/wrap_eth_onboarding_step.tsx +++ b/packages/website/ts/components/onboarding/wrap_eth_onboarding_step.tsx @@ -1,8 +1,11 @@ import { colors } from '@0xproject/react-shared'; +import { BigNumber } from '@0xproject/utils'; import * as React from 'react'; +import { Balance } from 'ts/components/ui/balance'; import { Container } from 'ts/components/ui/container'; import { IconButton } from 'ts/components/ui/icon_button'; import { Text } from 'ts/components/ui/text'; +import { constants } from 'ts/utils/constants'; export interface WrapEthOnboardingStep1Props {} @@ -51,16 +54,20 @@ export const WrapEthOnboardingStep2: React.StatelessComponent<WrapEthOnboardingS ); export interface WrapEthOnboardingStep3Props { - formattedWethBalanceIfExists?: string; + wethAmount: BigNumber; } -export const WrapEthOnboardingStep3: React.StatelessComponent<WrapEthOnboardingStep3Props> = ({ - formattedWethBalanceIfExists, -}) => ( +export const WrapEthOnboardingStep3: React.StatelessComponent<WrapEthOnboardingStep3Props> = ({ wethAmount }) => ( <div className="flex items-center flex-column"> <Text> - You have <b>{formattedWethBalanceIfExists || '0 WETH'}</b> in your wallet. - {formattedWethBalanceIfExists && ' Great!'} + You have{' '} + <Balance + amount={wethAmount} + decimals={constants.DECIMAL_PLACES_ETH} + symbol={constants.ETHER_TOKEN_SYMBOL} + />{' '} + in your wallet. + {wethAmount.gt(0) && ' Great!'} </Text> <Container width="100%" marginTop="25px" marginBottom="15px" className="flex justify-center"> <div className="flex flex-column items-center"> diff --git a/packages/website/ts/components/portal/portal.tsx b/packages/website/ts/components/portal/portal.tsx index 9c0cb866d..f983241fa 100644 --- a/packages/website/ts/components/portal/portal.tsx +++ b/packages/website/ts/components/portal/portal.tsx @@ -1,7 +1,6 @@ import { colors, constants as sharedConstants } from '@0xproject/react-shared'; import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; -import Help from 'material-ui/svg-icons/action/help'; import * as React from 'react'; import * as DocumentTitle from 'react-document-title'; import { Link, Route, RouteComponentProps, Switch } from 'react-router-dom'; @@ -24,6 +23,7 @@ import { TopBar, TopBarDisplayType } from 'ts/components/top_bar/top_bar'; import { TradeHistory } from 'ts/components/trade_history/trade_history'; import { Container } from 'ts/components/ui/container'; import { FlashMessage } from 'ts/components/ui/flash_message'; +import { Image } from 'ts/components/ui/image'; import { Text } from 'ts/components/ui/text'; import { Wallet } from 'ts/components/wallet/wallet'; import { GenerateOrderForm } from 'ts/containers/generate_order_form'; @@ -91,7 +91,7 @@ interface PortalState { interface AccountManagementItem { pathName: string; - headerText: string; + headerText?: string; render: () => React.ReactNode; } @@ -106,7 +106,7 @@ const TOP_BAR_HEIGHT = TopBar.heightForDisplayType(TopBarDisplayType.Expanded); const LEFT_COLUMN_WIDTH = 346; const MENU_PADDING_LEFT = 185; const LARGE_LAYOUT_MAX_WIDTH = 1200; -const LARGE_LAYOUT_MARGIN = 30; +const SIDE_PADDING = 20; export class Portal extends React.Component<PortalProps, PortalState> { private _blockchain: Blockchain; @@ -225,7 +225,7 @@ export class Portal extends React.Component<PortalProps, PortalState> { : TokenVisibility.TRACKED; return ( <Container> - <DocumentTitle title="0x Portal DApp" /> + <DocumentTitle title="0x Portal" /> <TopBar userAddress={this.props.userAddress} networkId={this.props.networkId} @@ -318,13 +318,17 @@ export class Portal extends React.Component<PortalProps, PortalState> { ); } private _renderWallet(): React.ReactNode { - const isMobile = utils.isMobile(this.props.screenWidth); + const isMobile = utils.isMobileWidth(this.props.screenWidth); // We need room to scroll down for mobile onboarding - const marginBottom = isMobile ? '200px' : '15px'; + const marginBottom = isMobile ? '250px' : '15px'; return ( <div> <Container className="flex flex-column items-center"> - {isMobile && <Container marginBottom="20px">{this._renderStartOnboarding()}</Container>} + {isMobile && ( + <Container marginTop="20px" marginBottom="20px"> + {this._renderStartOnboarding()} + </Container> + )} <Container marginBottom={marginBottom} width="100%"> <Wallet style={ @@ -364,15 +368,15 @@ export class Portal extends React.Component<PortalProps, PortalState> { ); } private _renderStartOnboarding(): React.ReactNode { - const isMobile = utils.isMobile(this.props.screenWidth); + const isMobile = utils.isMobileWidth(this.props.screenWidth); const shouldStartOnboarding = !isMobile || this.props.location.pathname === `${WebsitePaths.Portal}/account`; const startOnboarding = ( <Container className="flex items-center center"> - <Help style={{ width: '20px', height: '20px' }} color={colors.mediumBlue} /> - <Container marginLeft="8px"> - <Text fontColor={colors.mediumBlue} fontSize="16px" onClick={this._startOnboarding.bind(this)}> - Learn how to set up your account - </Text> + <Text fontColor={colors.mediumBlue} fontSize="16px" onClick={this._startOnboarding.bind(this)}> + Set up your account to start trading + </Text> + <Container marginLeft="8px" paddingTop="3px"> + <Image src="/images/setup_account_icon.svg" height="20px" width="20x" /> </Container> </Container> ); @@ -402,7 +406,7 @@ export class Portal extends React.Component<PortalProps, PortalState> { }, { pathName: `${WebsitePaths.Portal}/account`, - headerText: 'Your Account', + headerText: this._isSmallScreen() ? undefined : 'Your Account', render: this._isSmallScreen() ? this._renderWallet.bind(this) : this._renderTokenBalances.bind(this), }, { @@ -445,7 +449,7 @@ export class Portal extends React.Component<PortalProps, PortalState> { private _renderAccountManagementItem(item: AccountManagementItem): React.ReactNode { return ( <Section - header={<TextHeader labelText={item.headerText} />} + header={!_.isUndefined(item.headerText) && <TextHeader labelText={item.headerText} />} body={<Loading isLoading={!this.props.blockchainIsLoaded} content={item.render()} />} /> ); @@ -527,15 +531,21 @@ export class Portal extends React.Component<PortalProps, PortalState> { ); } private _renderRelayerIndexSection(): React.ReactNode { - return <Section header={<TextHeader labelText="0x Relayers" />} body={this._renderRelayerIndex()} />; - } - private _renderRelayerIndex(): React.ReactNode { - const isMobile = utils.isMobile(this.props.screenWidth); + const isMobile = utils.isMobileWidth(this.props.screenWidth); return ( - <Container className="flex flex-column items-center"> - {isMobile && <Container marginBottom="20px">{this._renderStartOnboarding()}</Container>} - <RelayerIndex networkId={this.props.networkId} screenWidth={this.props.screenWidth} /> - </Container> + <Section + header={!isMobile && <TextHeader labelText="0x Relayers" />} + body={ + <Container className="flex flex-column items-center"> + {isMobile && ( + <Container marginTop="20px" marginBottom="20px"> + {this._renderStartOnboarding()} + </Container> + )} + <RelayerIndex networkId={this.props.networkId} screenWidth={this.props.screenWidth} /> + </Container> + } + /> ); } private _renderNotFoundMessage(): React.ReactNode { @@ -685,19 +695,19 @@ interface LargeLayoutProps { } const LargeLayout = (props: LargeLayoutProps) => { return ( - <Container className="mx-auto flex flex-center" maxWidth={LARGE_LAYOUT_MAX_WIDTH}> + <Container + className="mx-auto flex flex-center" + maxWidth={LARGE_LAYOUT_MAX_WIDTH} + paddingLeft={SIDE_PADDING} + paddingRight={SIDE_PADDING} + > <div className="flex-last"> - <Container - width={LEFT_COLUMN_WIDTH} - position="fixed" - zIndex={zIndex.aboveTopBar} - marginLeft={LARGE_LAYOUT_MARGIN} - > + <Container width={LEFT_COLUMN_WIDTH} position="fixed" zIndex={zIndex.aboveTopBar}> {props.left} </Container> </div> - <Container className="flex-auto" marginLeft={LEFT_COLUMN_WIDTH + LARGE_LAYOUT_MARGIN}> - <Container className="flex-auto" marginLeft={LARGE_LAYOUT_MARGIN} marginRight={LARGE_LAYOUT_MARGIN}> + <Container className="flex-auto" marginLeft={LEFT_COLUMN_WIDTH}> + <Container className="flex-auto" marginLeft={SIDE_PADDING}> {props.right} </Container> </Container> @@ -711,7 +721,9 @@ interface SmallLayoutProps { const SmallLayout = (props: SmallLayoutProps) => { return ( <div className="flex flex-center"> - <div className="flex-auto px3">{props.content}</div> + <Container className="flex-auto" paddingLeft={SIDE_PADDING} paddingRight={SIDE_PADDING}> + {props.content} + </Container> </div> ); }; // tslint:disable:max-file-line-count diff --git a/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx b/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx index b26bf512b..431cf145b 100644 --- a/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx +++ b/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx @@ -9,6 +9,7 @@ import { Container } from 'ts/components/ui/container'; import { Image } from 'ts/components/ui/image'; import { Island } from 'ts/components/ui/island'; import { colors } from 'ts/style/colors'; +import { media } from 'ts/style/media'; import { styled } from 'ts/style/theme'; import { WebsiteBackendRelayerInfo } from 'ts/types'; import { utils } from 'ts/utils/utils'; @@ -55,7 +56,7 @@ const styles: Styles = { }; const FALLBACK_IMG_SRC = '/images/relayer_fallback.png'; -const FALLBACK_PRIMARY_COLOR = colors.grey200; +const FALLBACK_PRIMARY_COLOR = colors.grey300; const NO_CONTENT_MESSAGE = '--'; const RELAYER_ICON_HEIGHT = '110px'; @@ -107,10 +108,14 @@ export const RelayerGridTile: React.StatelessComponent<RelayerGridTileProps> = ( const GridTile = styled(PlainGridTile)` cursor: pointer; - transition: transform 0.2s ease; &:hover { + transition: transform 0.2s ease; transform: translate(0px, -3px); } + ${media.small` + transform: none !important; + transition: none !important; + `}; `; interface SectionProps { diff --git a/packages/website/ts/components/relayer_index/relayer_top_tokens.tsx b/packages/website/ts/components/relayer_index/relayer_top_tokens.tsx index f544fc924..c48b672e9 100644 --- a/packages/website/ts/components/relayer_index/relayer_top_tokens.tsx +++ b/packages/website/ts/components/relayer_index/relayer_top_tokens.tsx @@ -2,44 +2,30 @@ import { colors, constants as sharedConstants, EtherscanLinkSuffixes, - Styles, utils as sharedUtils, } from '@0xproject/react-shared'; import * as _ from 'lodash'; import * as React from 'react'; -import { analytics } from 'ts/utils/analytics'; +import { Container } from 'ts/components/ui/container'; +import { Text } from 'ts/components/ui/text'; import { WebsiteBackendTokenInfo } from 'ts/types'; +import { analytics } from 'ts/utils/analytics'; +import { utils } from 'ts/utils/utils'; export interface TopTokensProps { tokens: WebsiteBackendTokenInfo[]; networkId: number; } -const styles: Styles = { - tokenLabel: { - textDecoration: 'none', - color: colors.mediumBlue, - fontSize: 14, - }, - followingTokenLabel: { - paddingLeft: 16, - }, -}; - export const TopTokens: React.StatelessComponent<TopTokensProps> = (props: TopTokensProps) => { return ( <div className="flex"> - {_.map(props.tokens, (tokenInfo: WebsiteBackendTokenInfo, index: number) => { - const firstItemStyle = { ...styles.tokenLabel, ...styles.followingTokenLabel }; - const style = index !== 0 ? firstItemStyle : styles.tokenLabel; + {_.map(props.tokens, (tokenInfo: WebsiteBackendTokenInfo) => { return ( - <TokenLink - key={tokenInfo.address} - tokenInfo={tokenInfo} - style={style} - networkId={props.networkId} - /> + <Container key={tokenInfo.address} marginRight="16px"> + <TokenLink tokenInfo={tokenInfo} networkId={props.networkId} /> + </Container> ); })} </div> @@ -48,12 +34,9 @@ export const TopTokens: React.StatelessComponent<TopTokensProps> = (props: TopTo interface TokenLinkProps { tokenInfo: WebsiteBackendTokenInfo; - style: React.CSSProperties; networkId: number; } -interface TokenLinkState { - isHovering: boolean; -} +interface TokenLinkState {} class TokenLink extends React.Component<TokenLinkProps, TokenLinkState> { constructor(props: TokenLinkProps) { @@ -63,37 +46,21 @@ class TokenLink extends React.Component<TokenLinkProps, TokenLinkState> { }; } public render(): React.ReactNode { - const style = { - ...this.props.style, - cursor: 'pointer', - opacity: this.state.isHovering ? 0.5 : 1, - }; const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId]; const eventLabel = `${this.props.tokenInfo.symbol}-${networkName}`; const onClick = (event: React.MouseEvent<HTMLElement>) => { event.stopPropagation(); analytics.logEvent('Portal', 'Token Click', eventLabel); + const tokenLink = this._tokenLinkFromToken(this.props.tokenInfo, this.props.networkId); + utils.openUrl(tokenLink); }; return ( - <a - href={tokenLinkFromToken(this.props.tokenInfo, this.props.networkId)} - target="_blank" - style={style} - onMouseEnter={this._onToggleHover.bind(this, true)} - onMouseLeave={this._onToggleHover.bind(this, false)} - onClick={onClick} - > + <Text fontSize="14px" fontColor={colors.mediumBlue} onClick={onClick}> {this.props.tokenInfo.symbol} - </a> + </Text> ); } - private _onToggleHover(isHovering: boolean): void { - this.setState({ - isHovering, - }); + private _tokenLinkFromToken(tokenInfo: WebsiteBackendTokenInfo, networkId: number): string { + return sharedUtils.getEtherScanLinkIfExists(tokenInfo.address, networkId, EtherscanLinkSuffixes.Address); } } - -function tokenLinkFromToken(tokenInfo: WebsiteBackendTokenInfo, networkId: number): string { - return sharedUtils.getEtherScanLinkIfExists(tokenInfo.address, networkId, EtherscanLinkSuffixes.Address); -} diff --git a/packages/website/ts/components/top_bar/provider_display.tsx b/packages/website/ts/components/top_bar/provider_display.tsx index 8743e4320..806eaeea5 100644 --- a/packages/website/ts/components/top_bar/provider_display.tsx +++ b/packages/website/ts/components/top_bar/provider_display.tsx @@ -1,23 +1,26 @@ import { Styles } from '@0xproject/react-shared'; import * as _ from 'lodash'; import CircularProgress from 'material-ui/CircularProgress'; -import RaisedButton from 'material-ui/RaisedButton'; import ActionAccountBalanceWallet from 'material-ui/svg-icons/action/account-balance-wallet'; -import Lock from 'material-ui/svg-icons/action/lock'; import * as React from 'react'; import { Blockchain } from 'ts/blockchain'; -import { ProviderPicker } from 'ts/components/top_bar/provider_picker'; import { AccountConnection } from 'ts/components/ui/account_connection'; import { Container } from 'ts/components/ui/container'; import { DropDown } from 'ts/components/ui/drop_down'; import { Identicon } from 'ts/components/ui/identicon'; +import { Image } from 'ts/components/ui/image'; import { Island } from 'ts/components/ui/island'; +import { + CopyAddressSimpleMenuItem, + DifferentWalletSimpleMenuItem, + GoToAccountManagementSimpleMenuItem, + SimpleMenu, +} from 'ts/components/ui/simple_menu'; import { Text } from 'ts/components/ui/text'; import { Dispatcher } from 'ts/redux/dispatcher'; import { colors } from 'ts/style/colors'; import { AccountState, ProviderType } from 'ts/types'; -import { constants } from 'ts/utils/constants'; import { utils } from 'ts/utils/utils'; const ROOT_HEIGHT = 24; @@ -44,11 +47,7 @@ const styles: Styles = { export class ProviderDisplay extends React.Component<ProviderDisplayProps, ProviderDisplayState> { public render(): React.ReactNode { - const isExternallyInjectedProvider = utils.isExternallyInjected( - this.props.providerType, - this.props.injectedProviderName, - ); - const hoverActiveNode = ( + const activeNode = ( <Island className="flex items-center py1 px2" style={styles.root}> {this._renderIcon()} <Container marginLeft="12px" marginRight="12px"> @@ -57,93 +56,34 @@ export class ProviderDisplay extends React.Component<ProviderDisplayProps, Provi {this._renderInjectedProvider()} </Island> ); - const hasLedgerProvider = this.props.providerType === ProviderType.Ledger; - const horizontalPosition = isExternallyInjectedProvider || hasLedgerProvider ? 'left' : 'middle'; return ( <div style={{ width: 'fit-content', height: 48, float: 'right' }}> <DropDown - hoverActiveNode={hoverActiveNode} - popoverContent={this.renderPopoverContent(isExternallyInjectedProvider, hasLedgerProvider)} - anchorOrigin={{ horizontal: horizontalPosition, vertical: 'bottom' }} - targetOrigin={{ horizontal: horizontalPosition, vertical: 'top' }} + activeNode={activeNode} + popoverContent={this._renderPopoverContent()} + anchorOrigin={{ horizontal: 'middle', vertical: 'bottom' }} + targetOrigin={{ horizontal: 'middle', vertical: 'top' }} zDepth={1} /> </div> ); } - public renderPopoverContent(hasInjectedProvider: boolean, hasLedgerProvider: boolean): React.ReactNode { - if (!this._isBlockchainReady()) { - return null; - } else if (hasInjectedProvider || hasLedgerProvider) { - return ( - <ProviderPicker - dispatcher={this.props.dispatcher} - networkId={this.props.networkId} - injectedProviderName={this.props.injectedProviderName} - providerType={this.props.providerType} - onToggleLedgerDialog={this.props.onToggleLedgerDialog} - blockchain={this.props.blockchain} - /> - ); - } else { - // Nothing to connect to, show install/info popover - return ( - <div className="px2" style={{ maxWidth: 420 }}> - <div className="center h4 py2" style={{ color: colors.grey700 }}> - Choose a wallet: - </div> - <div className="flex pb3"> - <div className="center px2"> - <div style={{ color: colors.darkGrey }}>Install a browser wallet</div> - <div className="py2"> - <img src="/images/metamask_or_parity.png" width="135" /> - </div> - <div> - Use{' '} - <a - href={constants.URL_METAMASK_CHROME_STORE} - target="_blank" - style={{ color: colors.lightBlueA700 }} - > - Metamask - </a>{' '} - or{' '} - <a - href={constants.URL_PARITY_CHROME_STORE} - target="_blank" - style={{ color: colors.lightBlueA700 }} - > - Parity Signer - </a> - </div> - </div> - <div> - <div - className="pl1 ml1" - style={{ borderLeft: `1px solid ${colors.grey300}`, height: 65 }} - /> - <div className="py1">or</div> - <div - className="pl1 ml1" - style={{ borderLeft: `1px solid ${colors.grey300}`, height: 68 }} - /> - </div> - <div className="px2 center"> - <div style={{ color: colors.darkGrey }}>Connect to a ledger hardware wallet</div> - <div style={{ paddingTop: 21, paddingBottom: 29 }}> - <img src="/images/ledger_icon.png" style={{ width: 80 }} /> - </div> - <div> - <RaisedButton - style={{ width: '100%' }} - label="Use Ledger" - onClick={this.props.onToggleLedgerDialog} - /> - </div> - </div> - </div> - </div> - ); + private _renderPopoverContent(): React.ReactNode { + const accountState = this._getAccountState(); + switch (accountState) { + case AccountState.Ready: + return ( + <SimpleMenu> + <CopyAddressSimpleMenuItem userAddress={this.props.userAddress} /> + <DifferentWalletSimpleMenuItem onClick={this.props.onToggleLedgerDialog} /> + <GoToAccountManagementSimpleMenuItem /> + </SimpleMenu> + ); + case AccountState.Disconnected: + case AccountState.Locked: + case AccountState.Loading: + default: + return null; } } private _renderIcon(): React.ReactNode { @@ -154,7 +94,7 @@ export class ProviderDisplay extends React.Component<ProviderDisplayProps, Provi case AccountState.Loading: return <CircularProgress size={ROOT_HEIGHT} thickness={2} />; case AccountState.Locked: - return <Lock color={colors.black} />; + return <Image src="/images/lock_icon.svg" height="20px" width="20px" />; case AccountState.Disconnected: return <ActionAccountBalanceWallet color={colors.mediumBlue} />; default: diff --git a/packages/website/ts/components/top_bar/provider_picker.tsx b/packages/website/ts/components/top_bar/provider_picker.tsx deleted file mode 100644 index 7937f2e9d..000000000 --- a/packages/website/ts/components/top_bar/provider_picker.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { colors, constants as sharedConstants } from '@0xproject/react-shared'; -import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton'; -import * as React from 'react'; -import { Blockchain } from 'ts/blockchain'; -import { Dispatcher } from 'ts/redux/dispatcher'; -import { ProviderType } from 'ts/types'; - -interface ProviderPickerProps { - networkId: number; - injectedProviderName: string; - providerType: ProviderType; - onToggleLedgerDialog: () => void; - dispatcher: Dispatcher; - blockchain: Blockchain; -} - -interface ProviderPickerState {} - -export class ProviderPicker extends React.Component<ProviderPickerProps, ProviderPickerState> { - public render(): React.ReactNode { - const isLedgerSelected = this.props.providerType === ProviderType.Ledger; - const menuStyle = { - padding: 10, - paddingTop: 15, - paddingBottom: 15, - }; - // Show dropdown with two options - return ( - <div style={{ width: 225, overflow: 'hidden' }}> - <RadioButtonGroup name="provider" defaultSelected={this.props.providerType}> - <RadioButton - onClick={this._onProviderRadioChanged.bind(this, ProviderType.Injected)} - style={{ ...menuStyle, backgroundColor: !isLedgerSelected && colors.grey50 }} - value={ProviderType.Injected} - label={this._renderLabel(this.props.injectedProviderName, !isLedgerSelected)} - /> - <RadioButton - onClick={this._onProviderRadioChanged.bind(this, ProviderType.Ledger)} - style={{ ...menuStyle, backgroundColor: isLedgerSelected && colors.grey50 }} - value={ProviderType.Ledger} - label={this._renderLabel('Ledger Nano S', isLedgerSelected)} - /> - </RadioButtonGroup> - </div> - ); - } - private _renderLabel(title: string, shouldShowNetwork: boolean): React.ReactNode { - const label = ( - <div className="flex"> - <div style={{ fontSize: 14 }}>{title}</div> - {shouldShowNetwork && this._renderNetwork()} - </div> - ); - return label; - } - private _renderNetwork(): React.ReactNode { - const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId]; - return ( - <div className="flex" style={{ marginTop: 1 }}> - <div className="relative" style={{ width: 14, paddingLeft: 14 }}> - <img - src={`/images/network_icons/${networkName.toLowerCase()}.png`} - className="absolute" - style={{ top: 6, width: 10 }} - /> - </div> - <div style={{ color: colors.lightGrey, fontSize: 11 }}>{networkName}</div> - </div> - ); - } - private _onProviderRadioChanged(value: string): void { - if (value === ProviderType.Ledger) { - this.props.onToggleLedgerDialog(); - } else { - // tslint:disable-next-line:no-floating-promises - this.props.blockchain.updateProviderToInjectedAsync(); - } - } -} diff --git a/packages/website/ts/components/top_bar/top_bar.tsx b/packages/website/ts/components/top_bar/top_bar.tsx index fac6c131f..960e5a824 100644 --- a/packages/website/ts/components/top_bar/top_bar.tsx +++ b/packages/website/ts/components/top_bar/top_bar.tsx @@ -11,6 +11,7 @@ import { LegacyPortalMenu } from 'ts/components/legacy_portal/legacy_portal_menu import { DrawerMenu } from 'ts/components/portal/drawer_menu'; import { ProviderDisplay } from 'ts/components/top_bar/provider_display'; import { TopBarMenuItem } from 'ts/components/top_bar/top_bar_menu_item'; +import { Container } from 'ts/components/ui/container'; import { DropDown } from 'ts/components/ui/drop_down'; import { Dispatcher } from 'ts/redux/dispatcher'; import { Deco, Key, ProviderType, WebsiteLegacyPaths, WebsitePaths } from 'ts/types'; @@ -45,6 +46,8 @@ export interface TopBarProps { onVersionSelected?: (semver: string) => void; sidebarHeader?: React.ReactNode; maxWidth?: number; + paddingLeft?: number; + paddingRight?: number; } interface TopBarState { @@ -67,13 +70,12 @@ const styles: Styles = { color: colors.darkestGrey, paddingTop: 6, paddingBottom: 6, - marginTop: 17, cursor: 'pointer', fontWeight: 400, }, }; -const DEFAULT_HEIGHT = 59; +const DEFAULT_HEIGHT = 68; const EXPANDED_HEIGHT = 75; export class TopBar extends React.Component<TopBarProps, TopBarState> { @@ -81,6 +83,8 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { displayType: TopBarDisplayType.Default, style: {}, isNightVersion: false, + paddingLeft: 20, + paddingRight: 20, }; public static heightForDisplayType(displayType: TopBarDisplayType): number { const result = displayType === TopBarDisplayType.Expanded ? EXPANDED_HEIGHT : DEFAULT_HEIGHT; @@ -102,7 +106,9 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { public render(): React.ReactNode { const isNightVersion = this.props.isNightVersion; const isExpandedDisplayType = this.props.displayType === TopBarDisplayType.Expanded; - const parentClassNames = `flex mx-auto ${isExpandedDisplayType ? 'pl3 py1' : 'max-width-4'}`; + const parentClassNames = !isExpandedDisplayType + ? 'flex mx-auto items-center max-width-4' + : 'flex mx-auto items-center'; const height = isExpandedDisplayType ? EXPANDED_HEIGHT : DEFAULT_HEIGHT; const developerSectionMenuItems = [ <Link key="subMenuItem-zeroEx" to={WebsitePaths.ZeroExJs} className="text-decoration-none"> @@ -197,9 +203,8 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { fontSize: 25, color: isNightVersion ? 'white' : 'black', cursor: 'pointer', - paddingTop: 16, }; - const hoverActiveNode = ( + const activeNode = ( <div className="flex relative" style={{ color: menuIconStyle.color }}> <div style={{ paddingRight: 10 }}>{this.props.translate.get(Key.Developers, Deco.Cap)}</div> <div className="absolute" style={{ paddingLeft: 3, right: 3, top: -2 }}> @@ -211,20 +216,26 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { // TODO : Remove this once we ship portal v2 const shouldShowPortalV2Drawer = this._isViewingPortal() && utils.shouldShowPortalV2(); return ( - <div style={{ ...styles.topBar, ...bottomBorderStyle, ...this.props.style, ...{ height } }} className="pb1"> - <div className={parentClassNames} style={{ maxWidth: this.props.maxWidth }}> - <div className="col col-2 sm-pl1 md-pl2 lg-pl0" style={{ paddingTop: 15 }}> - <Link to={`${WebsitePaths.Home}`} className="text-decoration-none"> - <img src={logoUrl} height="30" /> - </Link> - </div> - <div className={`col col-${isExpandedDisplayType ? '8' : '9'} lg-hide md-hide`} /> - <div className={`col col-${isExpandedDisplayType ? '6' : '5'} sm-hide xs-hide`} /> + <div + style={{ ...styles.topBar, ...bottomBorderStyle, ...this.props.style, ...{ height } }} + className="pb1 flex items-center" + > + <Container + className={parentClassNames} + width="100%" + maxWidth={this.props.maxWidth} + paddingLeft={this.props.paddingLeft} + paddingRight={this.props.paddingRight} + > + <Link to={`${WebsitePaths.Home}`} className="text-decoration-none"> + <img src={logoUrl} height="30" /> + </Link> + <div className="flex-auto" /> {!this._isViewingPortal() && ( <div className={menuClasses}> - <div className="flex justify-between"> + <div className="flex items-center justify-between"> <DropDown - hoverActiveNode={hoverActiveNode} + activeNode={activeNode} popoverContent={popoverContent} anchorOrigin={{ horizontal: 'middle', vertical: 'bottom' }} targetOrigin={{ horizontal: 'middle', vertical: 'top' }} @@ -252,7 +263,7 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { isExternal={false} /> <TopBarMenuItem - title={this.props.translate.get(Key.PortalDApp, Deco.CapWords)} + title={this.props.translate.get(Key.TradeCallToAction, Deco.Cap)} path={`${WebsitePaths.Portal}`} isPrimary={true} style={styles.menuItem} @@ -264,7 +275,7 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { </div> )} {this._isViewingPortal() && ( - <div className="sm-hide xs-hide col col-5" style={{ paddingTop: 8, marginRight: 36 }}> + <div className="sm-hide xs-hide"> <ProviderDisplay dispatcher={this.props.dispatcher} userAddress={this.props.userAddress} @@ -277,12 +288,12 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { /> </div> )} - <div className={`col ${isExpandedDisplayType ? 'col-2 pl2' : 'col-1'} md-hide lg-hide`}> + <div className={'md-hide lg-hide'}> <div style={menuIconStyle}> <i className="zmdi zmdi-menu" onClick={this._onMenuButtonClick.bind(this)} /> </div> </div> - </div> + </Container> {shouldShowPortalV2Drawer ? this._renderPortalV2Drawer() : this._renderDrawer()} </div> ); diff --git a/packages/website/ts/components/top_bar/top_bar_menu_item.tsx b/packages/website/ts/components/top_bar/top_bar_menu_item.tsx index 2e4254cfa..25fab2868 100644 --- a/packages/website/ts/components/top_bar/top_bar_menu_item.tsx +++ b/packages/website/ts/components/top_bar/top_bar_menu_item.tsx @@ -3,6 +3,8 @@ import * as _ from 'lodash'; import * as React from 'react'; import { Link } from 'react-router-dom'; +import { CallToAction } from 'ts/components/ui/button'; + const DEFAULT_STYLE = { color: colors.darkestGrey, }; @@ -27,23 +29,15 @@ export class TopBarMenuItem extends React.Component<TopBarMenuItemProps, TopBarM isNightVersion: false, }; public render(): React.ReactNode { - const primaryStyles = this.props.isPrimary - ? { - borderRadius: 4, - border: `1px solid ${this.props.isNightVersion ? colors.grey : colors.greyishPink}`, - marginTop: 15, - paddingLeft: 9, - paddingRight: 9, - minWidth: 77, - } - : {}; const menuItemColor = this.props.isNightVersion ? 'white' : this.props.style.color; const linkColor = _.isUndefined(menuItemColor) ? colors.darkestGrey : menuItemColor; + const itemContent = this.props.isPrimary ? ( + <CallToAction padding="0.8em 1.5em">{this.props.title}</CallToAction> + ) : ( + this.props.title + ); return ( - <div - className={`center ${this.props.className}`} - style={{ ...this.props.style, ...primaryStyles, color: menuItemColor }} - > + <div className={`center ${this.props.className}`} style={{ ...this.props.style, color: menuItemColor }}> {this.props.isExternal ? ( <a className="text-decoration-none" @@ -51,11 +45,11 @@ export class TopBarMenuItem extends React.Component<TopBarMenuItemProps, TopBarM target="_blank" href={this.props.path} > - {this.props.title} + {itemContent} </a> ) : ( <Link to={this.props.path} className="text-decoration-none" style={{ color: linkColor }}> - {this.props.title} + {itemContent} </Link> )} </div> diff --git a/packages/website/ts/components/ui/balance.tsx b/packages/website/ts/components/ui/balance.tsx new file mode 100644 index 000000000..9e5a256b6 --- /dev/null +++ b/packages/website/ts/components/ui/balance.tsx @@ -0,0 +1,27 @@ +import { BigNumber } from '@0xproject/utils'; +import * as React from 'react'; +import { Container } from 'ts/components/ui/container'; +import { Text } from 'ts/components/ui/text'; +import { utils } from 'ts/utils/utils'; + +export interface BalanceProps { + amount: BigNumber; + decimals: number; + symbol: string; +} + +export const Balance: React.StatelessComponent<BalanceProps> = ({ amount, decimals, symbol }) => { + const formattedAmout = utils.getFormattedAmount(amount, decimals); + return ( + <span> + <Text Tag="span" fontSize="16px" fontWeight="700" lineHeight="1em"> + {formattedAmout} + </Text> + <Container marginLeft="0.3em" Tag="span"> + <Text Tag="span" fontSize="12px" fontWeight="700" lineHeight="1em"> + {symbol} + </Text> + </Container> + </span> + ); +}; diff --git a/packages/website/ts/components/ui/button.tsx b/packages/website/ts/components/ui/button.tsx index 1489a74a6..2952c8859 100644 --- a/packages/website/ts/components/ui/button.tsx +++ b/packages/website/ts/components/ui/button.tsx @@ -11,6 +11,7 @@ export interface ButtonProps { backgroundColor?: string; borderColor?: string; width?: string; + padding?: string; type?: string; isDisabled?: boolean; onClick?: (event: React.MouseEvent<HTMLElement>) => void; @@ -27,9 +28,8 @@ export const Button = styled(PlainButton)` font-size: ${props => props.fontSize}; color: ${props => props.fontColor}; transition: background-color, opacity 0.5s ease; - padding: 0.8em 2.2em; + padding: ${props => props.padding}; border-radius: 6px; - box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.25); font-weight: 500; outline: none; font-family: ${props => props.fontFamily}; @@ -44,7 +44,6 @@ export const Button = styled(PlainButton)` } &:disabled { opacity: 0.5; - box-shadow: none; } &:focus { background-color: ${props => saturate(0.2, props.backgroundColor)}; @@ -57,6 +56,7 @@ Button.defaultProps = { width: 'auto', fontFamily: 'Roboto', isDisabled: false, + padding: '0.8em 2.2em', }; Button.displayName = 'Button'; @@ -67,20 +67,26 @@ export interface CallToActionProps { type?: CallToActionType; fontSize?: string; width?: string; + padding?: string; } -export const CallToAction: React.StatelessComponent<CallToActionProps> = ({ children, type, fontSize, width }) => { +export const CallToAction: React.StatelessComponent<CallToActionProps> = ({ + children, + type, + fontSize, + padding, + width, +}) => { const isLight = type === 'light'; - const backgroundColor = isLight ? colors.white : colors.heroGrey; + const backgroundColor = isLight ? colors.white : colors.mediumBlue; const fontColor = isLight ? colors.heroGrey : colors.white; - const borderColor = isLight ? undefined : colors.white; return ( <Button fontSize={fontSize} + padding={padding} backgroundColor={backgroundColor} fontColor={fontColor} width={width} - borderColor={borderColor} > {children} </Button> @@ -89,4 +95,5 @@ export const CallToAction: React.StatelessComponent<CallToActionProps> = ({ chil CallToAction.defaultProps = { type: 'dark', + fontSize: '14px', }; diff --git a/packages/website/ts/components/ui/container.tsx b/packages/website/ts/components/ui/container.tsx index fb718d731..427cc6cc7 100644 --- a/packages/website/ts/components/ui/container.tsx +++ b/packages/website/ts/components/ui/container.tsx @@ -2,6 +2,8 @@ import * as React from 'react'; type StringOrNum = string | number; +export type ContainerTag = 'div' | 'span'; + export interface ContainerProps { marginTop?: StringOrNum; marginBottom?: StringOrNum; @@ -17,6 +19,7 @@ export interface ContainerProps { maxHeight?: StringOrNum; width?: StringOrNum; height?: StringOrNum; + minWidth?: StringOrNum; minHeight?: StringOrNum; isHidden?: boolean; className?: string; @@ -27,15 +30,21 @@ export interface ContainerProps { right?: string; bottom?: string; zIndex?: number; + Tag?: ContainerTag; } -export const Container: React.StatelessComponent<ContainerProps> = ({ children, className, isHidden, ...style }) => { +export const Container: React.StatelessComponent<ContainerProps> = props => { + const { children, className, Tag, isHidden, ...style } = props; const visibility = isHidden ? 'hidden' : undefined; return ( - <div style={{ ...style, visibility }} className={className}> + <Tag style={{ ...style, visibility }} className={className}> {children} - </div> + </Tag> ); }; +Container.defaultProps = { + Tag: 'div', +}; + Container.displayName = 'Container'; diff --git a/packages/website/ts/components/ui/drop_down.tsx b/packages/website/ts/components/ui/drop_down.tsx index 22cb942f8..4d5caef08 100644 --- a/packages/website/ts/components/ui/drop_down.tsx +++ b/packages/website/ts/components/ui/drop_down.tsx @@ -1,4 +1,4 @@ -import Popover, { PopoverAnimationVertical } from 'material-ui/Popover'; +import Popover from 'material-ui/Popover'; import * as React from 'react'; import { MaterialUIPosition } from 'ts/types'; @@ -7,13 +7,20 @@ const DEFAULT_STYLE = { fontSize: 14, }; -interface DropDownProps { - hoverActiveNode: React.ReactNode; +export enum DropdownMouseEvent { + Hover = 'hover', + Click = 'click', +} + +export interface DropDownProps { + activeNode: React.ReactNode; popoverContent: React.ReactNode; anchorOrigin: MaterialUIPosition; targetOrigin: MaterialUIPosition; style?: React.CSSProperties; zDepth?: number; + activateEvent?: DropdownMouseEvent; + closeEvent?: DropdownMouseEvent; } interface DropDownState { @@ -25,6 +32,8 @@ export class DropDown extends React.Component<DropDownProps, DropDownState> { public static defaultProps: Partial<DropDownProps> = { style: DEFAULT_STYLE, zDepth: 1, + activateEvent: DropdownMouseEvent.Hover, + closeEvent: DropdownMouseEvent.Hover, }; private _isHovering: boolean; private _popoverCloseCheckIntervalId: number; @@ -58,46 +67,61 @@ export class DropDown extends React.Component<DropDownProps, DropDownState> { onMouseEnter={this._onHover.bind(this)} onMouseLeave={this._onHoverOff.bind(this)} > - {this.props.hoverActiveNode} + <div onClick={this._onActiveNodeClick.bind(this)}>{this.props.activeNode}</div> <Popover open={this.state.isDropDownOpen} anchorEl={this.state.anchorEl} anchorOrigin={this.props.anchorOrigin} targetOrigin={this.props.targetOrigin} onRequestClose={this._closePopover.bind(this)} - useLayerForClickAway={false} - animation={PopoverAnimationVertical} + useLayerForClickAway={this.props.closeEvent === DropdownMouseEvent.Click} + animated={false} zDepth={this.props.zDepth} > - <div onMouseEnter={this._onHover.bind(this)} onMouseLeave={this._onHoverOff.bind(this)}> + <div + onMouseEnter={this._onHover.bind(this)} + onMouseLeave={this._onHoverOff.bind(this)} + onClick={this._closePopover.bind(this)} + > {this.props.popoverContent} </div> </Popover> </div> ); } + private _onActiveNodeClick(event: React.FormEvent<HTMLInputElement>): void { + if (this.props.activateEvent === DropdownMouseEvent.Click) { + this.setState({ + isDropDownOpen: true, + anchorEl: event.currentTarget, + }); + } + } private _onHover(event: React.FormEvent<HTMLInputElement>): void { this._isHovering = true; - this._checkIfShouldOpenPopover(event); + if (this.props.activateEvent === DropdownMouseEvent.Hover) { + this._checkIfShouldOpenPopover(event); + } + } + private _onHoverOff(): void { + this._isHovering = false; } private _checkIfShouldOpenPopover(event: React.FormEvent<HTMLInputElement>): void { if (this.state.isDropDownOpen) { return; // noop } - this.setState({ isDropDownOpen: true, anchorEl: event.currentTarget, }); } - private _onHoverOff(): void { - this._isHovering = false; - } private _checkIfShouldClosePopover(): void { - if (!this.state.isDropDownOpen || this._isHovering) { + if (!this.state.isDropDownOpen) { return; // noop } - this._closePopover(); + if (this.props.closeEvent === DropdownMouseEvent.Hover && !this._isHovering) { + this._closePopover(); + } } private _closePopover(): void { this.setState({ diff --git a/packages/website/ts/components/ui/image.tsx b/packages/website/ts/components/ui/image.tsx index 369dc8b7e..c4ff93531 100644 --- a/packages/website/ts/components/ui/image.tsx +++ b/packages/website/ts/components/ui/image.tsx @@ -6,6 +6,7 @@ export interface ImageProps { src?: string; fallbackSrc?: string; height?: string | number; + borderRadius?: string; width?: string | number; } interface ImageState { @@ -26,6 +27,9 @@ export class Image extends React.Component<ImageProps, ImageState> { className={this.props.className} onError={this._onError.bind(this)} src={src} + style={{ + borderRadius: this.props.borderRadius, + }} height={this.props.height} width={this.props.width} /> diff --git a/packages/website/ts/components/ui/simple_menu.tsx b/packages/website/ts/components/ui/simple_menu.tsx new file mode 100644 index 000000000..74b8ef6ae --- /dev/null +++ b/packages/website/ts/components/ui/simple_menu.tsx @@ -0,0 +1,88 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import * as CopyToClipboard from 'react-copy-to-clipboard'; +import { Link } from 'react-router-dom'; + +import { Container } from 'ts/components/ui/container'; +import { Text } from 'ts/components/ui/text'; +import { colors } from 'ts/style/colors'; +import { WebsitePaths } from 'ts/types'; + +export interface SimpleMenuProps { + minWidth?: number | string; +} + +export const SimpleMenu: React.StatelessComponent<SimpleMenuProps> = ({ children, minWidth }) => { + return ( + <Container + marginLeft="16px" + marginRight="16px" + marginBottom="16px" + minWidth={minWidth} + className="flex flex-column" + > + {children} + </Container> + ); +}; + +SimpleMenu.defaultProps = { + minWidth: '220px', +}; + +export interface SimpleMenuItemProps { + displayText: string; + onClick?: () => void; +} +export const SimpleMenuItem: React.StatelessComponent<SimpleMenuItemProps> = ({ displayText, onClick }) => { + // Falling back to _.noop for onclick retains the hovering effect + return ( + <Container marginTop="16px" className="flex flex-column"> + <Text + fontSize="14px" + fontColor={colors.darkGrey} + onClick={onClick || _.noop} + hoverColor={colors.mediumBlue} + > + {displayText} + </Text> + </Container> + ); +}; + +export interface CopyAddressSimpleMenuItemProps { + userAddress: string; + onClick?: () => void; +} +export const CopyAddressSimpleMenuItem: React.StatelessComponent<CopyAddressSimpleMenuItemProps> = ({ + userAddress, + onClick, +}) => { + return ( + <CopyToClipboard text={userAddress}> + <SimpleMenuItem displayText="Copy Address to Clipboard" onClick={onClick} /> + </CopyToClipboard> + ); +}; + +export interface GoToAccountManagementSimpleMenuItemProps { + onClick?: () => void; +} +export const GoToAccountManagementSimpleMenuItem: React.StatelessComponent< + GoToAccountManagementSimpleMenuItemProps +> = ({ onClick }) => { + return ( + <Link to={`${WebsitePaths.Portal}/account`} style={{ textDecoration: 'none' }}> + <SimpleMenuItem displayText="Manage Account..." onClick={onClick} /> + </Link> + ); +}; + +export interface DifferentWalletSimpleMenuItemProps { + onClick?: () => void; +} +export const DifferentWalletSimpleMenuItem: React.StatelessComponent<DifferentWalletSimpleMenuItemProps> = ({ + onClick, +}) => { + return <SimpleMenuItem displayText="Use a Different Wallet..." onClick={onClick} />; +}; diff --git a/packages/website/ts/components/ui/text.tsx b/packages/website/ts/components/ui/text.tsx index 88f216d4e..315f72854 100644 --- a/packages/website/ts/components/ui/text.tsx +++ b/packages/website/ts/components/ui/text.tsx @@ -3,7 +3,7 @@ import { darken } from 'polished'; import * as React from 'react'; import { styled } from 'ts/style/theme'; -export type TextTag = 'p' | 'div' | 'span' | 'label' | 'h1' | 'h2' | 'h3' | 'h4'; +export type TextTag = 'p' | 'div' | 'span' | 'label' | 'h1' | 'h2' | 'h3' | 'h4' | 'i'; export interface TextProps { className?: string; @@ -16,7 +16,8 @@ export interface TextProps { center?: boolean; fontWeight?: number | string; textDecorationLine?: string; - onClick?: () => void; + onClick?: (event: React.MouseEvent<HTMLElement>) => void; + hoverColor?: string; } const PlainText: React.StatelessComponent<TextProps> = ({ children, className, onClick, Tag }) => ( @@ -37,7 +38,7 @@ export const Text = styled(PlainText)` ${props => (props.onClick ? 'cursor: pointer' : '')}; transition: color 0.5s ease; &:hover { - ${props => (props.onClick ? `color: ${darken(0.1, props.fontColor)}` : '')}; + ${props => (props.onClick ? `color: ${props.hoverColor || darken(0.3, props.fontColor)}` : '')}; } `; diff --git a/packages/website/ts/components/wallet/body_overlay.tsx b/packages/website/ts/components/wallet/body_overlay.tsx index 5ced704f9..26359d0d2 100644 --- a/packages/website/ts/components/wallet/body_overlay.tsx +++ b/packages/website/ts/components/wallet/body_overlay.tsx @@ -9,11 +9,11 @@ import { Text } from 'ts/components/ui/text'; import { Dispatcher } from 'ts/redux/dispatcher'; import { colors } from 'ts/style/colors'; import { styled } from 'ts/style/theme'; -import { AccountState, BrowserType, ProviderType } from 'ts/types'; -import { constants } from 'ts/utils/constants'; +import { AccountState, ProviderType } from 'ts/types'; import { utils } from 'ts/utils/utils'; const METAMASK_IMG_SRC = '/images/metamask_icon.png'; +const TOSHI_IMG_SRC = '/images/toshi_logo.jpg'; export interface BodyOverlayProps { dispatcher: Dispatcher; @@ -92,8 +92,10 @@ interface DisconnectedOverlayProps { const DisconnectedOverlay = (props: DisconnectedOverlayProps) => { return ( <div className="flex flex-column items-center"> - <GetMetaMask /> - <UseDifferentWallet fontColor={colors.mediumBlue} onClick={props.onUseDifferentWalletClicked} /> + <GetWalletCallToAction /> + {!utils.isMobileOperatingSystem() && ( + <UseDifferentWallet fontColor={colors.mediumBlue} onClick={props.onUseDifferentWalletClicked} /> + )} </div> ); }; @@ -112,32 +114,20 @@ const UseDifferentWallet = (props: UseDifferentWallet) => { ); }; -const GetMetaMask = () => { - const browserType = utils.getBrowserType(); - let extensionLink; - switch (browserType) { - case BrowserType.Chrome: - extensionLink = constants.URL_METAMASK_CHROME_STORE; - break; - case BrowserType.Firefox: - extensionLink = constants.URL_METAMASK_FIREFOX_STORE; - break; - case BrowserType.Opera: - extensionLink = constants.URL_METAMASK_OPERA_STORE; - break; - default: - extensionLink = constants.URL_METAMASK_HOMEPAGE; - } +const GetWalletCallToAction = () => { + const [downloadLink, isOnMobile] = utils.getBestWalletDownloadLinkAndIsMobile(); + const imageUrl = isOnMobile ? TOSHI_IMG_SRC : METAMASK_IMG_SRC; + const text = isOnMobile ? 'Get Toshi Wallet' : 'Get MetaMask Wallet'; return ( - <a href={extensionLink} target="_blank" style={{ textDecoration: 'none' }}> + <a href={downloadLink} target="_blank" style={{ textDecoration: 'none' }}> <Island className="flex items-center py1 px2" style={{ height: 28, borderRadius: 28, backgroundColor: colors.mediumBlue }} > - <Image src={METAMASK_IMG_SRC} width="28px" /> + <Image src={imageUrl} width="28px" borderRadius="22%" /> <Container marginLeft="8px" marginRight="12px"> <Text fontColor={colors.white} fontSize="16px" fontWeight={500}> - Get MetaMask Wallet + {text} </Text> </Container> </Island> diff --git a/packages/website/ts/components/wallet/wallet.tsx b/packages/website/ts/components/wallet/wallet.tsx index 1f1e3598a..6c1c495d7 100644 --- a/packages/website/ts/components/wallet/wallet.tsx +++ b/packages/website/ts/components/wallet/wallet.tsx @@ -1,19 +1,26 @@ import { constants as sharedConstants, EtherscanLinkSuffixes, utils as sharedUtils } from '@0xproject/react-shared'; import { BigNumber, errorUtils } from '@0xproject/utils'; -import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as _ from 'lodash'; import ActionAccountBalanceWallet from 'material-ui/svg-icons/action/account-balance-wallet'; import * as React from 'react'; -import { Link } from 'react-router-dom'; import firstBy = require('thenby'); import { Blockchain } from 'ts/blockchain'; import { AccountConnection } from 'ts/components/ui/account_connection'; +import { Balance } from 'ts/components/ui/balance'; import { Container } from 'ts/components/ui/container'; +import { DropDown, DropdownMouseEvent } from 'ts/components/ui/drop_down'; import { IconButton } from 'ts/components/ui/icon_button'; import { Identicon } from 'ts/components/ui/identicon'; import { Island } from 'ts/components/ui/island'; +import { + CopyAddressSimpleMenuItem, + DifferentWalletSimpleMenuItem, + GoToAccountManagementSimpleMenuItem, + SimpleMenu, + SimpleMenuItem, +} from 'ts/components/ui/simple_menu'; import { Text } from 'ts/components/ui/text'; import { TokenIcon } from 'ts/components/ui/token_icon'; import { BodyOverlay } from 'ts/components/wallet/body_overlay'; @@ -34,7 +41,6 @@ import { TokenByAddress, TokenState, TokenStateByAddress, - WebsitePaths, } from 'ts/types'; import { analytics } from 'ts/utils/analytics'; import { constants } from 'ts/utils/constants'; @@ -83,9 +89,7 @@ const ICON_DIMENSION = 28; const BODY_ITEM_KEY = 'BODY'; const HEADER_ITEM_KEY = 'HEADER'; const ETHER_ITEM_KEY = 'ETHER'; -const USD_DECIMAL_PLACES = 2; const NO_ALLOWANCE_TOGGLE_SPACE_WIDTH = 56; -const ACCOUNT_PATH = `${WebsitePaths.Portal}/account`; const PLACEHOLDER_COLOR = colors.grey300; const LOADING_ROWS_COUNT = 6; @@ -189,6 +193,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> { ); } private _renderConnectedHeaderRows(): React.ReactElement<{}> { + const isMobile = this.props.screenWidth === ScreenWidths.Sm; const userAddress = this.props.userAddress; const accountState = this._getAccountState(); const main = ( @@ -199,15 +204,49 @@ export class Wallet extends React.Component<WalletProps, WalletState> { <AccountConnection accountState={accountState} injectedProviderName={this.props.injectedProviderName} /> </div> ); + const onClick = _.noop; + const accessory = ( + <DropDown + activeNode={ + // this container gives the menu button more of a hover target for the drop down + // it prevents accidentally closing the menu by moving off of the button + <Container paddingLeft="100px" paddingRight="15px"> + <Text + className="zmdi zmdi-more-horiz" + Tag="i" + fontSize="32px" + fontFamily="Material-Design-Iconic-Font" + fontColor={colors.darkGrey} + onClick={onClick} + hoverColor={colors.mediumBlue} + /> + </Container> + } + popoverContent={ + <SimpleMenu minWidth="150px"> + <CopyAddressSimpleMenuItem userAddress={this.props.userAddress} /> + {!isMobile && <DifferentWalletSimpleMenuItem onClick={this.props.onToggleLedgerDialog} />} + <SimpleMenuItem displayText="Add Tokens..." onClick={this.props.onAddToken} /> + <SimpleMenuItem displayText="Remove Tokens..." onClick={this.props.onRemoveToken} /> + {!isMobile && <GoToAccountManagementSimpleMenuItem />} + </SimpleMenu> + } + anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }} + targetOrigin={{ horizontal: 'right', vertical: 'top' }} + zDepth={1} + activateEvent={DropdownMouseEvent.Click} + closeEvent={DropdownMouseEvent.Click} + /> + ); return ( - <Link key={HEADER_ITEM_KEY} to={ACCOUNT_PATH} style={{ textDecoration: 'none' }}> - <StandardIconRow - icon={<Identicon address={userAddress} diameter={ICON_DIMENSION} />} - main={main} - minHeight="60px" - backgroundColor={colors.white} - /> - </Link> + <StandardIconRow + key={HEADER_ITEM_KEY} + icon={<Identicon address={userAddress} diameter={ICON_DIMENSION} />} + main={main} + accessory={accessory} + minHeight="60px" + backgroundColor={colors.white} + /> ); } private _renderBody(): React.ReactElement<{}> { @@ -231,8 +270,8 @@ export class Wallet extends React.Component<WalletProps, WalletState> { position: 'relative', overflowY: this.state.isHoveringSidebar ? 'scroll' : 'hidden', marginRight: this.state.isHoveringSidebar ? 0 : 4, - // TODO: make this completely responsive - maxHeight: this.props.screenWidth !== ScreenWidths.Sm ? 475 : undefined, + minHeight: '250px', + maxHeight: !utils.isMobileWidth(this.props.screenWidth) ? 'calc(90vh - 300px)' : undefined, }; } private _onSidebarHover(_event: React.FormEvent<HTMLInputElement>): void { @@ -320,8 +359,24 @@ export class Wallet extends React.Component<WalletProps, WalletState> { this.state.wrappedEtherDirection === accessoryItemConfig.wrappedEtherDirection && !_.isUndefined(this.props.userEtherBalanceInWei); const etherToken = this._getEthToken(); + const wrapEtherItem = shouldShowWrapEtherItem ? ( + <WrapEtherItem + userAddress={this.props.userAddress} + networkId={this.props.networkId} + blockchain={this.props.blockchain} + dispatcher={this.props.dispatcher} + userEtherBalanceInWei={this.props.userEtherBalanceInWei} + direction={accessoryItemConfig.wrappedEtherDirection} + etherToken={etherToken} + lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch} + onConversionSuccessful={this._closeWrappedEtherActionRow.bind(this)} + // tslint:disable:jsx-no-lambda + refetchEthTokenStateAsync={async () => this.props.refetchTokenStateAsync(etherToken.address)} + /> + ) : null; return ( <div id={key} key={key} className={`flex flex-column ${className || ''}`}> + {this.state.wrappedEtherDirection === Side.Receive && wrapEtherItem} <StandardIconRow icon={icon} main={ @@ -331,23 +386,8 @@ export class Wallet extends React.Component<WalletProps, WalletState> { </div> } accessory={this._renderAccessoryItems(accessoryItemConfig)} - backgroundColor={shouldShowWrapEtherItem ? colors.walletFocusedItemBackground : undefined} /> - {shouldShowWrapEtherItem && ( - <WrapEtherItem - userAddress={this.props.userAddress} - networkId={this.props.networkId} - blockchain={this.props.blockchain} - dispatcher={this.props.dispatcher} - userEtherBalanceInWei={this.props.userEtherBalanceInWei} - direction={accessoryItemConfig.wrappedEtherDirection} - etherToken={etherToken} - lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch} - onConversionSuccessful={this._closeWrappedEtherActionRow.bind(this)} - // tslint:disable:jsx-no-lambda - refetchEthTokenStateAsync={async () => this.props.refetchTokenStateAsync(etherToken.address)} - /> - )} + {this.state.wrappedEtherDirection === Side.Deposit && wrapEtherItem} </div> ); } @@ -397,12 +437,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> { </PlaceHolder> ); } else { - const result = utils.getFormattedAmount(amount, decimals, symbol); - return ( - <Text fontSize="16px" fontWeight="bold" lineHeight="1em"> - {result} - </Text> - ); + return <Balance amount={amount} decimals={decimals} symbol={symbol} />; } } private _renderValue( @@ -411,19 +446,11 @@ export class Wallet extends React.Component<WalletProps, WalletState> { price?: BigNumber, isLoading: boolean = false, ): React.ReactNode { - let result; - if (!isLoading) { - if (_.isUndefined(price)) { - result = '--'; - } else { - const unitAmount = Web3Wrapper.toUnitAmount(amount, decimals); - const value = unitAmount.mul(price); - const formattedAmount = value.toFixed(USD_DECIMAL_PLACES); - result = `$${formattedAmount}`; - } - } else { - result = '$0.00'; - } + const result = !isLoading + ? _.isUndefined(price) + ? '--' + : utils.getUsdValueFormattedAmount(amount, decimals, price) + : '$0.00'; return ( <PlaceHolder hideChildren={isLoading} fillColor={PLACEHOLDER_COLOR}> <Text fontSize="14px" fontColor={colors.darkGrey} lineHeight="1em"> diff --git a/packages/website/ts/components/wallet/wrap_ether_item.tsx b/packages/website/ts/components/wallet/wrap_ether_item.tsx index 851b35f90..2b4cf93fe 100644 --- a/packages/website/ts/components/wallet/wrap_ether_item.tsx +++ b/packages/website/ts/components/wallet/wrap_ether_item.tsx @@ -8,6 +8,7 @@ import * as React from 'react'; import { Blockchain } from 'ts/blockchain'; import { EthAmountInput } from 'ts/components/inputs/eth_amount_input'; import { TokenAmountInput } from 'ts/components/inputs/token_amount_input'; +import { Container } from 'ts/components/ui/container'; import { Dispatcher } from 'ts/redux/dispatcher'; import { colors } from 'ts/style/colors'; import { BlockchainCallErrs, Side, Token } from 'ts/types'; @@ -94,7 +95,7 @@ export class WrapEtherItem extends React.Component<WrapEtherItemProps, WrapEther const topLabelText = isWrappingEth ? 'Convert ETH into WETH 1:1' : 'Convert WETH into ETH 1:1'; return ( - <div className="flex" style={{ backgroundColor: colors.walletFocusedItemBackground }}> + <Container className="flex" backgroundColor={colors.walletFocusedItemBackground} paddingTop="25px"> <div>{this._renderIsEthConversionHappeningSpinner()} </div> <div className="flex flex-column"> <div style={styles.topLabel}>{topLabelText}</div> @@ -142,7 +143,7 @@ export class WrapEtherItem extends React.Component<WrapEtherItemProps, WrapEther {this._renderErrorMsg()} </div> - </div> + </Container> ); } private _onValueChange(_isValid: boolean, amount?: BigNumber): void { diff --git a/packages/website/ts/containers/subproviders_documentation.ts b/packages/website/ts/containers/subproviders_documentation.ts index 6d4230e53..567f6a37e 100644 --- a/packages/website/ts/containers/subproviders_documentation.ts +++ b/packages/website/ts/containers/subproviders_documentation.ts @@ -25,6 +25,7 @@ const docSections = { emptyWalletSubprovider: 'emptyWalletSubprovider', fakeGasEstimateSubprovider: 'fakeGasEstimateSubprovider', injectedWeb3Subprovider: 'injectedWeb3Subprovider', + signerSubprovider: 'signerSubprovider', redundantRPCSubprovider: 'redundantRPCSubprovider', ganacheSubprovider: 'ganacheSubprovider', nonceTrackerSubprovider: 'nonceTrackerSubprovider', @@ -50,6 +51,7 @@ const docsInfoConfig: DocsInfoConfig = { ['emptyWallet-subprovider']: [docSections.emptyWalletSubprovider], ['fakeGasEstimate-subprovider']: [docSections.fakeGasEstimateSubprovider], ['injectedWeb3-subprovider']: [docSections.injectedWeb3Subprovider], + ['signer-subprovider']: [docSections.signerSubprovider], ['redundantRPC-subprovider']: [docSections.redundantRPCSubprovider], ['ganache-subprovider']: [docSections.ganacheSubprovider], ['nonceTracker-subprovider']: [docSections.nonceTrackerSubprovider], @@ -69,6 +71,7 @@ const docsInfoConfig: DocsInfoConfig = { [docSections.emptyWalletSubprovider]: ['"subproviders/src/subproviders/empty_wallet_subprovider"'], [docSections.fakeGasEstimateSubprovider]: ['"subproviders/src/subproviders/fake_gas_estimate_subprovider"'], [docSections.injectedWeb3Subprovider]: ['"subproviders/src/subproviders/injected_web3"'], + [docSections.signerSubprovider]: ['"subproviders/src/subproviders/signer"'], [docSections.redundantRPCSubprovider]: ['"subproviders/src/subproviders/redundant_rpc"'], [docSections.ganacheSubprovider]: ['"subproviders/src/subproviders/ganache"'], [docSections.nonceTrackerSubprovider]: ['"subproviders/src/subproviders/nonce_tracker"'], diff --git a/packages/website/ts/pages/about/about.tsx b/packages/website/ts/pages/about/about.tsx index 0259af36f..5bb5d06a9 100644 --- a/packages/website/ts/pages/about/about.tsx +++ b/packages/website/ts/pages/about/about.tsx @@ -165,16 +165,24 @@ const teamRow5: ProfileInfo[] = [ }, ]; -// const teamRow6: ProfileInfo[] = [ -// { -// name: 'Chris Kalani', -// title: 'Director of Design', -// description: `Previously founded Wake (acquired by InVision). Early Facebook product designer.`, -// image: 'images/team/chris.png', -// linkedIn: 'https://www.linkedin.com/in/chriskalani/', -// github: 'https://github.com/chriskalani', -// }, -// ]; +const teamRow6: ProfileInfo[] = [ + { + name: 'Alex Browne', + title: 'Engineer in Residence', + description: `Full-stack blockchain engineer. Previously at Plaid. Open source guru and footgun dismantler. Computer Science and Electrical Engineering at Duke.`, + image: 'images/team/alexbrowne.png', + linkedIn: 'https://www.linkedin.com/in/stephenalexbrowne/', + github: 'http://github.com/albrow', + }, + // { + // name: 'Chris Kalani', + // title: 'Director of Design', + // description: `Previously founded Wake (acquired by InVision). Early Facebook product designer.`, + // image: 'images/team/chris.png', + // linkedIn: 'https://www.linkedin.com/in/chriskalani/', + // github: 'https://github.com/chriskalani', + // }, +]; const advisors: ProfileInfo[] = [ { @@ -270,6 +278,7 @@ export class About extends React.Component<AboutProps, AboutState> { <div className="clearfix">{this._renderProfiles(teamRow3)}</div> <div className="clearfix">{this._renderProfiles(teamRow4)}</div> <div className="clearfix">{this._renderProfiles(teamRow5)}</div> + <div className="clearfix">{this._renderProfiles(teamRow6)}</div> </div> <div className="pt3 pb2"> <div diff --git a/packages/website/ts/pages/landing/landing.tsx b/packages/website/ts/pages/landing/landing.tsx index f091778f4..b2cf4d979 100644 --- a/packages/website/ts/pages/landing/landing.tsx +++ b/packages/website/ts/pages/landing/landing.tsx @@ -268,15 +268,11 @@ export class Landing extends React.Component<LandingProps, LandingState> { </Link> </div> <div className="lg-col lg-col-6 sm-center sm-col sm-col-12"> - <a - href={constants.URL_ZEROEX_CHAT} - target="_blank" - className="text-decoration-none" - > + <Link to={WebsitePaths.Portal} className="text-decoration-none"> <CallToAction width="175px"> - {this.props.translate.get(Key.CommunityCallToAction, Deco.Cap)} + {this.props.translate.get(Key.TradeCallToAction, Deco.Cap)} </CallToAction> - </a> + </Link> </div> </Container> </div> @@ -295,7 +291,7 @@ export class Landing extends React.Component<LandingProps, LandingState> { <div className="mr1 px1" style={{ - backgroundColor: colors.lightTurquois, + backgroundColor: colors.white, borderRadius: 3, color: colors.heroGrey, height: 23, diff --git a/packages/website/ts/redux/store.ts b/packages/website/ts/redux/store.ts index 0d0e6cea1..2672e3f61 100644 --- a/packages/website/ts/redux/store.ts +++ b/packages/website/ts/redux/store.ts @@ -13,9 +13,11 @@ export const store: ReduxStore<State> = createStore( ); store.subscribe( _.throttle(() => { + const state = store.getState(); // Persisted state stateStorage.saveState({ - hasPortalOnboardingBeenClosed: store.getState().hasPortalOnboardingBeenClosed, + hasPortalOnboardingBeenClosed: state.hasPortalOnboardingBeenClosed, + isPortalOnboardingShowing: state.isPortalOnboardingShowing, }); }, ONE_SECOND), ); diff --git a/packages/website/ts/style/media.ts b/packages/website/ts/style/media.ts new file mode 100644 index 000000000..870d9a277 --- /dev/null +++ b/packages/website/ts/style/media.ts @@ -0,0 +1,14 @@ +import { css } from 'ts/style/theme'; +import { ScreenWidths } from 'ts/types'; + +const generateMediaWrapper = (screenWidth: ScreenWidths) => (...args: any[]) => css` + @media (max-width: ${screenWidth}em) { + ${css.apply(css, args)}; + } +`; + +export const media = { + small: generateMediaWrapper(ScreenWidths.Sm), + medium: generateMediaWrapper(ScreenWidths.Md), + large: generateMediaWrapper(ScreenWidths.Lg), +}; diff --git a/packages/website/ts/types.ts b/packages/website/ts/types.ts index 498a0a5b8..e8dc694f6 100644 --- a/packages/website/ts/types.ts +++ b/packages/website/ts/types.ts @@ -1,5 +1,6 @@ import { ECSignature } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; +import { Provider } from 'ethereum-types'; import * as React from 'react'; export enum Side { @@ -215,10 +216,11 @@ export interface ContractEvent { } export type ValidatedBigNumberCallback = (isValid: boolean, amount?: BigNumber) => void; +// Associated values are in `em` units export enum ScreenWidths { - Sm = 'SM', - Md = 'MD', - Lg = 'LG', + Sm = 40, + Md = 52, + Lg = 64, } export enum AlertTypes { @@ -241,8 +243,8 @@ export enum BlockchainCallErrs { } export enum Environments { - DEVELOPMENT, - PRODUCTION, + DEVELOPMENT = 'DEVELOPMENT', + PRODUCTION = 'PRODUCTION', } export type ContractInstance = any; // TODO: add type definition for Contract @@ -454,6 +456,7 @@ export enum Key { Developers = 'DEVELOPERS', Home = 'HOME', RocketChat = 'ROCKETCHAT', + TradeCallToAction = 'TRADE_CALL_TO_ACTION', } export enum SmartContractDocSections { @@ -487,6 +490,8 @@ export enum Providers { Parity = 'PARITY', Metamask = 'METAMASK', Mist = 'MIST', + Toshi = 'TOSHI', + Cipher = 'CIPHER', } export interface InjectedProviderUpdate { @@ -547,7 +552,10 @@ export interface WebsiteBackendTokenInfo { } export interface WebsiteBackendGasInfo { + safeSlow: number; average: number; + fast: number; + fastest: number; } export interface WebsiteBackendJobInfo { @@ -565,10 +573,32 @@ export enum BrowserType { Other = 'Other', } +export enum OperatingSystemType { + Android = 'Android', + iOS = 'iOS', + Mac = 'Mac', + Windows = 'Windows', + WindowsPhone = 'WindowsPhone', + Linux = 'Linux', + Other = 'Other', +} + export enum AccountState { Disconnected = 'Disconnected', Ready = 'Ready', Loading = 'Loading', Locked = 'Locked', } + +export interface InjectedProvider extends Provider { + publicConfigStore?: InjectedProviderObservable; +} + +// Minimal expected interface for an injected web3 object +export interface InjectedWeb3 { + currentProvider: InjectedProvider; + version: { + getNetwork(cd: (err: Error, networkId: string) => void): void; + }; +} // tslint:disable:max-file-line-count diff --git a/packages/website/ts/utils/analytics.ts b/packages/website/ts/utils/analytics.ts index 928e45bc3..f4bfa083f 100644 --- a/packages/website/ts/utils/analytics.ts +++ b/packages/website/ts/utils/analytics.ts @@ -1,8 +1,8 @@ import * as _ from 'lodash'; import * as ReactGA from 'react-ga'; +import { InjectedWeb3 } from 'ts/types'; import { configs } from 'ts/utils/configs'; import { utils } from 'ts/utils/utils'; -import * as Web3 from 'web3'; export const analytics = { init(): void { @@ -16,11 +16,12 @@ export const analytics = { value, }); }, - async logProviderAsync(web3IfExists: Web3): Promise<void> { + async logProviderAsync(web3IfExists: InjectedWeb3): Promise<void> { await utils.onPageLoadAsync(); - const providerType = !_.isUndefined(web3IfExists) - ? utils.getProviderType(web3IfExists.currentProvider) - : 'NONE'; + const providerType = + !_.isUndefined(web3IfExists) && !_.isUndefined(web3IfExists.currentProvider) + ? utils.getProviderType(web3IfExists.currentProvider) + : 'NONE'; ReactGA.ga('set', 'dimension1', providerType); }, }; diff --git a/packages/website/ts/utils/configs.ts b/packages/website/ts/utils/configs.ts index e8a486c35..97aabd13d 100644 --- a/packages/website/ts/utils/configs.ts +++ b/packages/website/ts/utils/configs.ts @@ -22,50 +22,9 @@ export const configs = { DOMAIN_DEVELOPMENT: '0xproject.localhost:3572', DOMAIN_PRODUCTION: '0xproject.com', ENVIRONMENT: isDevelopment ? Environments.DEVELOPMENT : Environments.PRODUCTION, - ICON_URL_BY_SYMBOL: { - REP: '/images/token_icons/augur.png', - DGD: '/images/token_icons/digixdao.png', - WETH: '/images/token_icons/ether_erc20.png', - MLN: '/images/token_icons/melon.png', - GNT: '/images/token_icons/golem.png', - MKR: '/images/token_icons/makerdao.png', - ZRX: '/images/token_icons/zero_ex.png', - ANT: '/images/token_icons/aragon.png', - BNT: '/images/token_icons/bancor.png', - BAT: '/images/token_icons/basicattentiontoken.png', - CVC: '/images/token_icons/civic.png', - EOS: '/images/token_icons/eos.png', - FUN: '/images/token_icons/funfair.png', - GNO: '/images/token_icons/gnosis.png', - ICN: '/images/token_icons/iconomi.png', - OMG: '/images/token_icons/omisego.png', - SNT: '/images/token_icons/status.png', - STORJ: '/images/token_icons/storjcoinx.png', - PAY: '/images/token_icons/tenx.png', - QTUM: '/images/token_icons/qtum.png', - DNT: '/images/token_icons/district0x.png', - SNGLS: '/images/token_icons/singularity.png', - EDG: '/images/token_icons/edgeless.png', - '1ST': '/images/token_icons/firstblood.jpg', - WINGS: '/images/token_icons/wings.png', - BQX: '/images/token_icons/bitquence.png', - LUN: '/images/token_icons/lunyr.png', - RLC: '/images/token_icons/iexec.png', - MCO: '/images/token_icons/monaco.png', - ADT: '/images/token_icons/adtoken.png', - CFI: '/images/token_icons/cofound-it.png', - ROL: '/images/token_icons/etheroll.png', - WGNT: '/images/token_icons/golem.png', - MTL: '/images/token_icons/metal.png', - NMR: '/images/token_icons/numeraire.png', - SAN: '/images/token_icons/santiment.png', - TAAS: '/images/token_icons/taas.png', - TKN: '/images/token_icons/tokencard.png', - TRST: '/images/token_icons/trust.png', - } as { [symbol: string]: string }, GOOGLE_ANALYTICS_ID: 'UA-98720122-1', LAST_LOCAL_STORAGE_FILL_CLEARANCE_DATE: '2017-11-22', - LAST_LOCAL_STORAGE_TRACKED_TOKEN_CLEARANCE_DATE: '2018-6-25', + LAST_LOCAL_STORAGE_TRACKED_TOKEN_CLEARANCE_DATE: '2018-7-5', OUTDATED_WRAPPED_ETHERS: [ { 42: { diff --git a/packages/website/ts/utils/constants.ts b/packages/website/ts/utils/constants.ts index d3a2aa107..4b3443d21 100644 --- a/packages/website/ts/utils/constants.ts +++ b/packages/website/ts/utils/constants.ts @@ -6,7 +6,7 @@ export const constants = { ETHER_TOKEN_SYMBOL: 'WETH', ZRX_TOKEN_SYMBOL: 'ZRX', ETHER_SYMBOL: 'ETH', - TOKEN_AMOUNT_DISPLAY_PRECISION: 5, + TOKEN_AMOUNT_DISPLAY_PRECISION: 4, GENESIS_ORDER_BLOCK_BY_NETWORK_ID: { 1: 4145578, 42: 3117574, @@ -29,6 +29,8 @@ export const constants = { PROVIDER_NAME_METAMASK: 'MetaMask', PROVIDER_NAME_PARITY_SIGNER: 'Parity Signer', PROVIDER_NAME_MIST: 'Mist', + PROVIDER_NAME_CIPHER: 'Cipher Browser', + PROVIDER_NAME_TOSHI: 'Toshi', PROVIDER_NAME_GENERIC: 'Injected Web3', PROVIDER_NAME_PUBLIC: '0x Public', ROLLBAR_ACCESS_TOKEN: 'a6619002b51c4464928201e6ea94de65', @@ -38,6 +40,7 @@ export const constants = { UNAVAILABLE_STATUS: 503, TAKER_FEE: new BigNumber(0), TESTNET_NAME: 'Kovan', + NUMERAL_USD_FORMAT: '$0,0.00', PROJECT_URL_ETHFINEX: 'https://www.ethfinex.com/', PROJECT_URL_AMADEUS: 'http://amadeusrelay.org', PROJECT_URL_DDEX: 'https://ddex.io', @@ -71,6 +74,8 @@ export const constants = { URL_GITHUB_WIKI: 'https://github.com/0xProject/wiki', URL_METAMASK_CHROME_STORE: 'https://chrome.google.com/webstore/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn', URL_METAMASK_FIREFOX_STORE: 'https://addons.mozilla.org/en-US/firefox/addon/ether-metamask/', + URL_TOSHI_IOS_APP_STORE: 'https://itunes.apple.com/us/app/toshi-ethereum-wallet/id1278383455?mt=8', + URL_TOSHI_ANDROID_APP_STORE: 'https://play.google.com/store/apps/details?id=org.toshi&hl=en_US', URL_METAMASK_HOMEPAGE: 'https://metamask.io/', URL_METAMASK_OPERA_STORE: 'https://addons.opera.com/en/extensions/details/metamask/', URL_MIST_DOWNLOAD: 'https://github.com/ethereum/mist/releases', diff --git a/packages/website/ts/utils/translate.ts b/packages/website/ts/utils/translate.ts index 39924b6f7..1ee1a59c5 100644 --- a/packages/website/ts/utils/translate.ts +++ b/packages/website/ts/utils/translate.ts @@ -55,6 +55,19 @@ export class Translate { } public get(key: Key, decoration?: Deco): string { let text = this._translation[key]; + // if a translation does not exist for a certain language, fallback to english + // if it still doesn't exist in english, throw an error + if (_.isUndefined(text)) { + const englishTranslation: Translation = languageToTranslations[Language.English]; + const englishText = englishTranslation[key]; + if (!_.isUndefined(englishText)) { + text = englishText; + } else { + throw new Error( + `Translation key not available in ${this._selectedLanguage} or ${Language.English}: ${key}`, + ); + } + } if (!_.isUndefined(decoration) && !_.includes(languagesWithoutCaps, this._selectedLanguage)) { switch (decoration) { case Deco.Cap: diff --git a/packages/website/ts/utils/utils.ts b/packages/website/ts/utils/utils.ts index 726e1815f..8c76a7592 100644 --- a/packages/website/ts/utils/utils.ts +++ b/packages/website/ts/utils/utils.ts @@ -8,11 +8,14 @@ import * as bowser from 'bowser'; import deepEqual = require('deep-equal'); import * as _ from 'lodash'; import * as moment from 'moment'; +import * as numeral from 'numeral'; + import { AccountState, BlockchainCallErrs, BrowserType, Environments, + OperatingSystemType, Order, Providers, ProviderType, @@ -27,9 +30,6 @@ import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; import * as u2f from 'ts/vendor/u2f_api'; -const LG_MIN_EM = 64; -const MD_MIN_EM = 52; - const isDogfood = (): boolean => _.includes(window.location.href, configs.DOMAIN_DOGFOOD); export const utils = { @@ -134,9 +134,9 @@ export const utils = { // This logic mirrors the CSS media queries in BassCSS for the `lg-`, `md-` and `sm-` CSS // class prefixes. Do not edit these. - if (widthInEm > LG_MIN_EM) { + if (widthInEm > ScreenWidths.Lg) { return ScreenWidths.Lg; - } else if (widthInEm > MD_MIN_EM) { + } else if (widthInEm > ScreenWidths.Md) { return ScreenWidths.Md; } else { return ScreenWidths.Sm; @@ -324,6 +324,7 @@ export const utils = { getProviderType(provider: Provider): Providers | string { const constructorName = provider.constructor.name; let parsedProviderName = constructorName; + // https://ethereum.stackexchange.com/questions/24266/elegant-way-to-detect-current-provider-int-web3-js switch (constructorName) { case 'EthereumProvider': parsedProviderName = Providers.Mist; @@ -337,6 +338,10 @@ export const utils = { parsedProviderName = Providers.Parity; } else if ((provider as any).isMetaMask) { parsedProviderName = Providers.Metamask; + } else if (!_.isUndefined(_.get(window, 'SOFA'))) { + parsedProviderName = Providers.Toshi; + } else if (!_.isUndefined(_.get(window, '__CIPHER__'))) { + parsedProviderName = Providers.Cipher; } return parsedProviderName; }, @@ -354,7 +359,9 @@ export const utils = { }, isDogfood, shouldShowPortalV2(): boolean { - return this.isDevelopment() || this.isStaging() || this.isDogfood(); + // return this.isDevelopment() || this.isStaging() || this.isDogfood(); + // TODO: Remove this method entirely after launch. + return true; }, shouldShowJobsPage(): boolean { return this.isDevelopment() || this.isStaging() || this.isDogfood(); @@ -376,20 +383,33 @@ export const utils = { return trackedTokens; }, getFormattedAmountFromToken(token: Token, tokenState: TokenState): string { - return utils.getFormattedAmount(tokenState.balance, token.decimals, token.symbol); + return utils.getFormattedAmount(tokenState.balance, token.decimals); }, - getFormattedAmount(amount: BigNumber, decimals: number, symbol: string): string { + getFormattedAmount(amount: BigNumber, decimals: number): string { + const unitAmount = Web3Wrapper.toUnitAmount(amount, decimals); + // if the unit amount is less than 1, show the natural number of decimal places with a max of 4 + // if the unit amount is greater than or equal to 1, show only 2 decimal places + const precision = unitAmount.lt(1) + ? Math.min(constants.TOKEN_AMOUNT_DISPLAY_PRECISION, unitAmount.decimalPlaces()) + : 2; + const format = `0,0.${_.repeat('0', precision)}`; + const formattedAmount = numeral(unitAmount).format(format); + return formattedAmount; + }, + getUsdValueFormattedAmount(amount: BigNumber, decimals: number, price: BigNumber): string { const unitAmount = Web3Wrapper.toUnitAmount(amount, decimals); - const precision = Math.min(constants.TOKEN_AMOUNT_DISPLAY_PRECISION, unitAmount.decimalPlaces()); - const formattedAmount = unitAmount.toFixed(precision); - return `${formattedAmount} ${symbol}`; + const value = unitAmount.mul(price); + return numeral(value).format(constants.NUMERAL_USD_FORMAT); }, openUrl(url: string): void { window.open(url, '_blank'); }, - isMobile(screenWidth: ScreenWidths): boolean { + isMobileWidth(screenWidth: ScreenWidths): boolean { return screenWidth === ScreenWidths.Sm; }, + isMobileOperatingSystem(): boolean { + return bowser.mobile; + }, getBrowserType(): BrowserType { if (bowser.chrome) { return BrowserType.Chrome; @@ -401,7 +421,63 @@ export const utils = { return BrowserType.Other; } }, + getOperatingSystem(): OperatingSystemType { + if (bowser.android) { + return OperatingSystemType.Android; + } else if (bowser.ios) { + return OperatingSystemType.iOS; + } else if (bowser.mac) { + return OperatingSystemType.Mac; + } else if (bowser.windows) { + return OperatingSystemType.Windows; + } else if (bowser.windowsphone) { + return OperatingSystemType.WindowsPhone; + } else if (bowser.linux) { + return OperatingSystemType.Linux; + } else { + return OperatingSystemType.Other; + } + }, isTokenTracked(token: Token): boolean { return !_.isUndefined(token.trackedTimestamp); }, + // Returns a [downloadLink, isOnMobile] tuple. + getBestWalletDownloadLinkAndIsMobile(): [string, boolean] { + const browserType = utils.getBrowserType(); + const isOnMobile = utils.isMobileOperatingSystem(); + const operatingSystem = utils.getOperatingSystem(); + let downloadLink; + if (isOnMobile) { + switch (operatingSystem) { + case OperatingSystemType.Android: + downloadLink = constants.URL_TOSHI_ANDROID_APP_STORE; + break; + case OperatingSystemType.iOS: + downloadLink = constants.URL_TOSHI_IOS_APP_STORE; + break; + default: + // Toshi is only supported on these mobile OSes - just default to iOS + downloadLink = constants.URL_TOSHI_IOS_APP_STORE; + } + } else { + switch (browserType) { + case BrowserType.Chrome: + downloadLink = constants.URL_METAMASK_CHROME_STORE; + break; + case BrowserType.Firefox: + downloadLink = constants.URL_METAMASK_FIREFOX_STORE; + break; + case BrowserType.Opera: + downloadLink = constants.URL_METAMASK_OPERA_STORE; + break; + default: + downloadLink = constants.URL_METAMASK_HOMEPAGE; + } + } + return [downloadLink, isOnMobile]; + }, + getTokenIconUrl(symbol: string): string { + const result = `/images/token_icons/${symbol}.png`; + return result; + }, }; |