diff options
Diffstat (limited to 'packages/website')
119 files changed, 2110 insertions, 976 deletions
diff --git a/packages/website/README.md b/packages/website/README.md index d93d18935..7115a3b5c 100644 --- a/packages/website/README.md +++ b/packages/website/README.md @@ -30,18 +30,16 @@ yarn install ### Initial setup -The **first** time you work with this package, you must build **all** packages within the monorepo. This is because packages that depend on other packages located inside this monorepo are symlinked when run from **within** the monorepo. This allows you to make changes across multiple packages without first publishing dependent packages to NPM. To build all packages, run the following from the monorepo root directory: +To build this package and all other monorepo packages that it depends on, run the following from the monorepo root directory: ```bash -yarn lerna:rebuild +PKG=@0xproject/website yarn build ``` ### Run dev server -The the `website` root directory, run: - ```bash -yarn dev +PKG=@0xproject/website yarn watch ``` Visit [0xproject.localhost:3572](http://0xproject.localhost:3572) in your browser. diff --git a/packages/website/md/docs/deployer/installation.md b/packages/website/md/docs/deployer/installation.md deleted file mode 100644 index c02dbadc6..000000000 --- a/packages/website/md/docs/deployer/installation.md +++ /dev/null @@ -1,24 +0,0 @@ -#### CLI Installation - -```bash -yarn global add @0xproject/deployer -``` - -#### API Installation - -```bash -yarn add @0xproject/deployer -``` - -**Import** - -```typescript -import { Deployer, Compiler } from '@0xproject/deployer'; -``` - -or - -```javascript -var Deployer = require('@0xproject/deployer').Deployer; -var Compiler = require('@0xproject/deployer').Compiler; -``` diff --git a/packages/website/md/docs/deployer/introduction.md b/packages/website/md/docs/deployer/introduction.md deleted file mode 100644 index 7ebd26a3c..000000000 --- a/packages/website/md/docs/deployer/introduction.md +++ /dev/null @@ -1,18 +0,0 @@ -Welcome to the [Deployer](https://github.com/0xProject/0x-monorepo/tree/development/packages/deployer) documentation! Deployer is a tool for compiling and deploying Solidity smart contracts 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. - -Deployer has the following advantages over Truffle: - -* Deploy each smart contract with a specific version of Solidity. -* Improved artifact files: - * Properly segregated artifacts to support storing different versions of smart contract deployed on different networks. - * Storage of constructor args, source maps and paths to all requisite source files. - * An easy to maintain codebase: TypeScript + Single repo. - * Allows you to specify the deployer RPC address. - * Supports Solidity version ranges - contract compiled with latest Solidity version that satisfies the range. - * Migrations that work with `async/await`. - * Migrations that can be written synchronously in order to guarentee deterministic contract addresses. - * No race conditions when running migrations. - -Deployer can be used as a command-line tool or as an imported module. diff --git a/packages/website/md/docs/deployer/usage.md b/packages/website/md/docs/deployer/usage.md deleted file mode 100644 index 295af55e1..000000000 --- a/packages/website/md/docs/deployer/usage.md +++ /dev/null @@ -1,56 +0,0 @@ -#### CLI Usage - -```bash -$ 0x-deployer --help -0x-deployer [command] - -Commands: - 0x-deployer compile compile contracts - 0x-deployer deploy deploy a single contract with provided arguments - -Options: - --version Show version number [boolean] - --contracts-dir path of contracts directory to compile [string] [default: - "/path/to/contracts"] - --network-id mainnet=1, kovan=42, testrpc=50 [number] [default: 50] - --should-optimize enable optimizer [boolean] [default: false] - --artifacts-dir path to write contracts artifacts to [string] [default: - "/path/to/artifacts"] - --jsonrpc-port port connected to JSON RPC [number] [default: 8545] - --gas-price gasPrice to be used for transactions - [string] [default: "2000000000"] - --account account to use for deploying contracts [string] - --contracts comma separated list of contracts to compile - [string] [default: "*"] - --help Show help [boolean] -``` - -#### API Usage - -##### Migrations - -You can write migration scripts (similar to `truffle migrate`), that deploys multiple contracts and configures them. Below you'll find a simple example of such a script to help you get started. - -```typescript -import { Deployer } from '@0xproject/deployer'; -import * as path from 'path'; - -const deployerOpts = { - artifactsDir: path.resolve('src', 'artifacts'), - jsonrpcUrl: 'http://localhost:8545', - networkId: 50, - defaults: { - gas: 1000000, - }, -}; - -const deployer = new Deployer(deployerOpts); - -(async () => { - const etherToken = await deployer.deployAndSaveAsync('WETH9'); -})().catch(console.log); -``` - -**Tip:** Be sure to start an Ethereum node at the supplied `jsonrpcUrl`. We recommend testing with [Ganache-cli](https://github.com/trufflesuite/ganache-cli) - -A more sophisticated example can be found [here](https://github.com/0xProject/0x-monorepo/tree/development/packages/contracts/migrations) diff --git a/packages/website/md/docs/order_utils/installation.md b/packages/website/md/docs/order_utils/installation.md new file mode 100644 index 000000000..68a7cf960 --- /dev/null +++ b/packages/website/md/docs/order_utils/installation.md @@ -0,0 +1,17 @@ +**Install** + +```bash +yarn add @0xproject/order-utils +``` + +**Import** + +```javascript +import { createSignedOrderAsync } from '@0xproject/order-utils'; +``` + +or + +```javascript +var createSignedOrderAsync = require('@0xproject/order-utils').createSignedOrderAsync; +``` diff --git a/packages/website/md/docs/order_utils/introduction.md b/packages/website/md/docs/order_utils/introduction.md new file mode 100644 index 000000000..d5f3f2925 --- /dev/null +++ b/packages/website/md/docs/order_utils/introduction.md @@ -0,0 +1 @@ +Welcome to the [@0xproject/order-utils](https://github.com/0xProject/0x-monorepo/tree/development/packages/order-utils) documentation! Order utils is a set of utils around creating, signing, validating 0x orders. diff --git a/packages/website/md/docs/sol-compiler/installation.md b/packages/website/md/docs/sol-compiler/installation.md new file mode 100644 index 000000000..9c8561d9b --- /dev/null +++ b/packages/website/md/docs/sol-compiler/installation.md @@ -0,0 +1,23 @@ +#### CLI Installation + +```bash +yarn global add @0xproject/sol-compiler +``` + +#### API Installation + +```bash +yarn add @0xproject/sol-compiler +``` + +**Import** + +```typescript +import { Compiler } from '@0xproject/sol-compiler'; +``` + +or + +```javascript +var Compiler = require('@0xproject/sol-compiler').Compiler; +``` diff --git a/packages/website/md/docs/sol-compiler/introduction.md b/packages/website/md/docs/sol-compiler/introduction.md new file mode 100644 index 000000000..aa1939006 --- /dev/null +++ b/packages/website/md/docs/sol-compiler/introduction.md @@ -0,0 +1,13 @@ +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. diff --git a/packages/website/md/docs/sol-compiler/usage.md b/packages/website/md/docs/sol-compiler/usage.md new file mode 100644 index 000000000..79c9b32ba --- /dev/null +++ b/packages/website/md/docs/sol-compiler/usage.md @@ -0,0 +1,24 @@ +#### CLI Usage + +```bash +$ sol-compiler +Options: + --version Show version number [boolean] + --contracts-dir path of contracts directory to compile [string] + --artifacts-dir path to write contracts artifacts to [string] + --contracts comma separated list of contracts to compile + [string] [default: "*"] + --help Show help [boolean] +``` + +#### API Usage + +```typescript +import { Compiler } from '@0xproject/sol-compiler'; + +const compiler = new Compiler(); + +(async () => { + await compiler.compileAllAsync(); +})().catch(console.log); +``` diff --git a/packages/website/md/docs/sol_cov/usage.md b/packages/website/md/docs/sol_cov/usage.md index ea1982d97..63a88f595 100644 --- a/packages/website/md/docs/sol_cov/usage.md +++ b/packages/website/md/docs/sol_cov/usage.md @@ -12,7 +12,7 @@ const contractsPath = 'src/contracts'; const networkId = 50; // Some calls might not have `from` address specified. Nevertheless - transactions need to be submitted from an address with at least some funds. defaultFromAddress is the address that will be used to submit those calls as transactions from. const defaultFromAddress = '0x5409ed021d9299bf6814279a6a1411a7e866a631'; -const coverageSubprovider = new CoverageSubprovider(artifactsPath, contractsPath, networkId, defaultFromAddress); +const coverageSubprovider = new CoverageSubprovider(artifactsPath, contractsPath, defaultFromAddress); provider.addProvider(coverageSubprovider); ``` diff --git a/packages/website/package.json b/packages/website/package.json index 9eef5a88c..f1b2a1750 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -1,26 +1,30 @@ { "name": "@0xproject/website", - "version": "0.0.28", + "version": "0.0.32", + "engines": { + "node" : ">=6.12" + }, "private": true, "description": "Website and 0x portal dapp", "scripts": { "build": "NODE_ENV=production webpack; exit 0;", "clean": "shx rm -f public/bundle*", "lint": "tslint --project . 'ts/**/*.ts' 'ts/**/*.tsx'", - "dev": "webpack-dev-server --content-base public --https", + "watch": "webpack-dev-server --content-base public --https", + "deploy_dogfood": "npm run build; aws s3 sync ./public/. s3://dogfood-0xproject --profile 0xproject --region us-east-1 --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers", "deploy_staging": "npm run build; aws s3 sync ./public/. s3://staging-0xproject --profile 0xproject --region us-east-1 --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers", "deploy_live": "npm run build; aws s3 sync ./public/. s3://0xproject.com --profile 0xproject --region us-east-1 --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers" }, "author": "Fabio Berger", "license": "Apache-2.0", "dependencies": { - "0x.js": "^0.36.3", - "@0xproject/react-docs": "^0.0.8", - "@0xproject/react-shared": "^0.1.3", - "@0xproject/subproviders": "^0.9.0", - "@0xproject/typescript-typings": "^0.2.0", - "@0xproject/utils": "^0.5.2", - "@0xproject/web3-wrapper": "^0.6.1", + "0x.js": "^0.37.2", + "@0xproject/react-docs": "^0.0.11", + "@0xproject/react-shared": "^0.1.6", + "@0xproject/subproviders": "^0.10.1", + "@0xproject/typescript-typings": "^0.3.1", + "@0xproject/utils": "^0.6.1", + "@0xproject/web3-wrapper": "^0.6.3", "accounting": "^0.4.1", "basscss": "^8.0.3", "blockies": "^0.0.2", @@ -48,7 +52,7 @@ "thenby": "^1.2.3", "truffle-contract": "2.0.1", "web3": "^0.20.0", - "web3-provider-engine": "^13.0.1", + "web3-provider-engine": "^14.0.4", "whatwg-fetch": "^2.0.3", "xml-js": "^1.3.2" }, @@ -60,7 +64,7 @@ "@types/material-ui": "0.18.0", "@types/node": "^8.0.53", "@types/query-string": "^5.1.0", - "@types/react": "16.0.41", + "@types/react": "16.3.13", "@types/react-copy-to-clipboard": "^4.2.0", "@types/react-dom": "^16.0.3", "@types/react-redux": "^4.4.37", diff --git a/packages/website/public/images/landing/project_logos/the_ocean.png b/packages/website/public/images/landing/project_logos/the_ocean.png Binary files differindex 74678b5d4..0677abc29 100644 --- a/packages/website/public/images/landing/project_logos/the_ocean.png +++ b/packages/website/public/images/landing/project_logos/the_ocean.png diff --git a/packages/website/public/images/team/fragosti.png b/packages/website/public/images/team/fragosti.png Binary files differnew file mode 100644 index 000000000..60c168514 --- /dev/null +++ b/packages/website/public/images/team/fragosti.png diff --git a/packages/website/public/images/team/greg.jpeg b/packages/website/public/images/team/greg.jpeg Binary files differnew file mode 100644 index 000000000..a765e047f --- /dev/null +++ b/packages/website/public/images/team/greg.jpeg diff --git a/packages/website/public/images/team/philippe.png b/packages/website/public/images/team/philippe.png Binary files differdeleted file mode 100644 index cd43c2754..000000000 --- a/packages/website/public/images/team/philippe.png +++ /dev/null diff --git a/packages/website/public/images/team/remco.jpeg b/packages/website/public/images/team/remco.jpeg Binary files differnew file mode 100644 index 000000000..e87e7bfd6 --- /dev/null +++ b/packages/website/public/images/team/remco.jpeg diff --git a/packages/website/translations/chinese.json b/packages/website/translations/chinese.json index f610bf56c..966457a93 100644 --- a/packages/website/translations/chinese.json +++ b/packages/website/translations/chinese.json @@ -56,7 +56,7 @@ "ABOUT": "关于我们", "CAREERS": "人才招聘", "CONTACT": "联系方式", - "DEPLOYER": "Deployer", + "SOL_COMPILER": "Solidity Compiler", "JSON_SCHEMAS": "JSON Schemas", "SOL_COV": "Solidity Coverage", "SUBPROVIDERS": "Subproviders", @@ -66,6 +66,7 @@ "WHITEPAPER": "白皮书", "WIKI": "维基", "WEB3_WRAPPER": "Web3Wrapper", + "ORDER_UTILS": "Order Utils", "FAQ": "FAQ", "SMART_CONTRACTS": "0x 智能合约", "STANDARD_RELAYER_API": "中继方标准API", diff --git a/packages/website/translations/english.json b/packages/website/translations/english.json index 122d445cb..f3acea3be 100644 --- a/packages/website/translations/english.json +++ b/packages/website/translations/english.json @@ -57,7 +57,7 @@ "ABOUT": "about", "CAREERS": "careers", "CONTACT": "contact", - "DEPLOYER": "Deployer", + "SOL_COMPILER": "Solidity Compiler", "JSON_SCHEMAS": "JSON Schemas", "SOL_COV": "Solidity Coverage", "SUBPROVIDERS": "Subproviders", @@ -67,6 +67,7 @@ "WHITEPAPER": "whitepaper", "WIKI": "wiki", "WEB3_WRAPPER": "Web3Wrapper", + "ORDER_UTILS": "Order Utils", "FAQ": "FAQ", "SMART_CONTRACTS": "0x smart contracts", "STANDARD_RELAYER_API": "standard relayer API", diff --git a/packages/website/translations/korean.json b/packages/website/translations/korean.json index dd5f19b16..7414207f7 100644 --- a/packages/website/translations/korean.json +++ b/packages/website/translations/korean.json @@ -56,7 +56,7 @@ "ABOUT": "기업 정보", "CAREERS": "채용", "CONTACT": "문의", - "DEPLOYER": "Deployer", + "SOL_COMPILER": "Solidity Compiler", "JSON_SCHEMAS": "JSON Schemas", "SOL_COV": "Solidity Coverage", "SUBPROVIDERS": "Subproviders", @@ -66,6 +66,7 @@ "WHITEPAPER": "백서", "WIKI": "위키", "WEB3_WRAPPER": "Web3Wrapper", + "ORDER_UTILS": "Order Utils", "FAQ": "FAQ", "SMART_CONTRACTS": "0x 스마트 계약", "STANDARD_RELAYER_API": "Standard Relayer API", diff --git a/packages/website/translations/russian.json b/packages/website/translations/russian.json index 5a8e2c539..75ab02a27 100644 --- a/packages/website/translations/russian.json +++ b/packages/website/translations/russian.json @@ -56,7 +56,7 @@ "ABOUT": "Kоманда", "CAREERS": "Карьера", "CONTACT": "Связаться с нами", - "DEPLOYER": "Deployer", + "SOL_COMPILER": "Solidity Compiler", "JSON_SCHEMAS": "JSON Schemas", "SOL_COV": "Solidity Coverage", "SUBPROVIDERS": "Subproviders", @@ -66,6 +66,7 @@ "WHITEPAPER": "Whitepaper", "WIKI": "Вики", "WEB3_WRAPPER": "Web3Wrapper", + "ORDER_UTILS": "Order Utils", "FAQ": "Документация", "SMART_CONTRACTS": "0x Смарт-контракты ", "STANDARD_RELAYER_API": "standard relayer API", diff --git a/packages/website/translations/spanish.json b/packages/website/translations/spanish.json index dd34e805c..8f537ea40 100644 --- a/packages/website/translations/spanish.json +++ b/packages/website/translations/spanish.json @@ -57,7 +57,7 @@ "ABOUT": "equipo", "CAREERS": "empleo", "CONTACT": "contacto", - "DEPLOYER": "Deployer", + "SOL_COMPILER": "Solidity Compiler", "JSON_SCHEMAS": "JSON Schemas", "SOL_COV": "Solidity Coverage", "SUBPROVIDERS": "Subproviders", @@ -67,6 +67,7 @@ "WHITEPAPER": "documento técnico", "WIKI": "wiki", "WEB3_WRAPPER": "Web3Wrapper", + "ORDER_UTILS": "Order Utils", "FAQ": "preguntas frecuentes", "SMART_CONTRACTS": "0x contratos inteligentes", "STANDARD_RELAYER_API": "API de transmisión estándar", diff --git a/packages/website/ts/blockchain.ts b/packages/website/ts/blockchain.ts index b7ba2241c..32acb9d43 100644 --- a/packages/website/ts/blockchain.ts +++ b/packages/website/ts/blockchain.ts @@ -39,6 +39,7 @@ import { BlockchainCallErrs, BlockchainErrs, ContractInstance, + Fill, Order as PortalOrder, Providers, ProviderType, @@ -88,7 +89,7 @@ export class Blockchain { } return providerNameIfExists; } - private static async _getProviderAsync(injectedWeb3: Web3, networkIdIfExists: number) { + private static async _getProviderAsync(injectedWeb3: Web3, networkIdIfExists: number): Promise<Provider> { const doesInjectedWeb3Exist = !_.isUndefined(injectedWeb3); const publicNodeUrlsIfExistsForNetworkId = configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkIdIfExists]; const isPublicNodeAvailableForNetworkId = !_.isUndefined(publicNodeUrlsIfExistsForNetworkId); @@ -137,7 +138,7 @@ export class Blockchain { // tslint:disable-next-line:no-floating-promises this._onPageLoadInitFireAndForgetAsync(); } - public async networkIdUpdatedFireAndForgetAsync(newNetworkId: number) { + public async networkIdUpdatedFireAndForgetAsync(newNetworkId: number): Promise<void> { const isConnected = !_.isUndefined(newNetworkId); if (!isConnected) { this.networkId = newNetworkId; @@ -147,17 +148,17 @@ export class Blockchain { this.networkId = newNetworkId; this._dispatcher.encounteredBlockchainError(BlockchainErrs.NoError); await this.fetchTokenInformationAsync(); - await this._rehydrateStoreWithContractEvents(); + await this._rehydrateStoreWithContractEventsAsync(); } } - public async userAddressUpdatedFireAndForgetAsync(newUserAddress: string) { + public async userAddressUpdatedFireAndForgetAsync(newUserAddress: string): Promise<void> { if (this._userAddressIfExists !== newUserAddress) { this._userAddressIfExists = newUserAddress; await this.fetchTokenInformationAsync(); - await this._rehydrateStoreWithContractEvents(); + await this._rehydrateStoreWithContractEventsAsync(); } } - public async nodeVersionUpdatedFireAndForgetAsync(nodeVersion: string) { + public async nodeVersionUpdatedFireAndForgetAsync(nodeVersion: string): Promise<void> { if (this.nodeVersion !== nodeVersion) { this.nodeVersion = nodeVersion; } @@ -174,13 +175,13 @@ export class Blockchain { const path = this._ledgerSubprovider.getPath(); return path; } - public updateLedgerDerivationPathIfExists(path: string) { + public updateLedgerDerivationPathIfExists(path: string): void { if (_.isUndefined(this._ledgerSubprovider)) { return; // noop } this._ledgerSubprovider.setPath(path); } - public async updateProviderToLedgerAsync(networkId: number) { + public async updateProviderToLedgerAsync(networkId: number): Promise<void> { utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); const isU2FSupported = await utils.isU2FSupportedAsync(); @@ -228,7 +229,7 @@ export class Blockchain { this._blockchainWatcher.startEmittingNetworkConnectionAndUserBalanceState(); this._dispatcher.updateProviderType(ProviderType.Ledger); } - public async updateProviderToInjectedAsync() { + public async updateProviderToInjectedAsync(): Promise<void> { utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); if (_.isUndefined(this._cachedProvider)) { @@ -367,7 +368,7 @@ export class Blockchain { const unavailableTakerAmount = await this._zeroEx.exchange.getUnavailableTakerAmountAsync(orderHash); return unavailableTakerAmount; } - public getExchangeContractAddressIfExists() { + public getExchangeContractAddressIfExists(): string | undefined { return this._zeroEx.exchange.getContractAddress(); } public async validateFillOrderThrowIfInvalidAsync( @@ -391,7 +392,7 @@ export class Blockchain { const lowercaseAddress = address.toLowerCase(); return Web3Wrapper.isAddress(lowercaseAddress); } - public async pollTokenBalanceAsync(token: Token) { + public async pollTokenBalanceAsync(token: Token): Promise<BigNumber> { utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); const [currBalance] = await this.getTokenBalanceAndAllowanceAsync(this._userAddressIfExists, token.address); @@ -445,7 +446,7 @@ export class Blockchain { this._dispatcher.updateECSignature(ecSignature); return ecSignature; } - public async mintTestTokensAsync(token: Token) { + public async mintTestTokensAsync(token: Token): Promise<void> { utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); const mintableContract = await this._instantiateContractIfExistsAsync(MintableArtifacts, token.address); @@ -489,7 +490,7 @@ export class Blockchain { ); await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); } - public async doesContractExistAtAddressAsync(address: string) { + public async doesContractExistAtAddressAsync(address: string): Promise<boolean> { const doesContractExist = await this._web3Wrapper.doesContractExistAtAddressAsync(address); return doesContractExist; } @@ -520,7 +521,7 @@ export class Blockchain { } return [balance, allowance]; } - public async getUserAccountsAsync() { + public async getUserAccountsAsync(): Promise<string[]> { utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); const userAccountsIfExists = await this._zeroEx.getAvailableAddressesAsync(); return userAccountsIfExists; @@ -528,14 +529,14 @@ export class Blockchain { // HACK: When a user is using a Ledger, we simply dispatch the selected userAddress, which // by-passes the web3Wrapper logic for updating the prevUserAddress. We therefore need to // manually update it. This should only be called by the LedgerConfigDialog. - public updateWeb3WrapperPrevUserAddress(newUserAddress: string) { + public updateWeb3WrapperPrevUserAddress(newUserAddress: string): void { this._blockchainWatcher.updatePrevUserAddress(newUserAddress); } - public destroy() { + public destroy(): void { this._blockchainWatcher.destroy(); this._stopWatchingExchangeLogFillEvents(); } - public async fetchTokenInformationAsync() { + public async fetchTokenInformationAsync(): Promise<void> { utils.assert( !_.isUndefined(this.networkId), 'Cannot call fetchTokenInformationAsync if disconnected from Ethereum node', @@ -624,7 +625,7 @@ export class Blockchain { private _doesUserAddressExist(): boolean { return !_.isUndefined(this._userAddressIfExists); } - private async _rehydrateStoreWithContractEvents() { + private async _rehydrateStoreWithContractEventsAsync(): Promise<void> { // Ensure we are only ever listening to one set of events this._stopWatchingExchangeLogFillEvents(); @@ -675,7 +676,7 @@ export class Blockchain { }, ); } - private async _fetchHistoricalExchangeLogFillEventsAsync(indexFilterValues: IndexedFilterValues) { + private async _fetchHistoricalExchangeLogFillEventsAsync(indexFilterValues: IndexedFilterValues): Promise<void> { utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); const fromBlock = tradeHistoryStorage.getFillsLatestBlock(this._userAddressIfExists, this.networkId); @@ -697,7 +698,9 @@ export class Blockchain { tradeHistoryStorage.addFillToUser(this._userAddressIfExists, this.networkId, fill); } } - private async _convertDecodedLogToFillAsync(decodedLog: LogWithDecodedArgs<LogFillContractEventArgs>) { + private async _convertDecodedLogToFillAsync( + decodedLog: LogWithDecodedArgs<LogFillContractEventArgs>, + ): Promise<Fill> { const args = decodedLog.args; const blockTimestamp = await this._web3Wrapper.getBlockTimestampAsync(decodedLog.blockHash); const fill = { @@ -716,12 +719,12 @@ export class Blockchain { }; return fill; } - private _doesLogEventInvolveUser(decodedLog: LogWithDecodedArgs<LogFillContractEventArgs>) { + private _doesLogEventInvolveUser(decodedLog: LogWithDecodedArgs<LogFillContractEventArgs>): boolean { const args = decodedLog.args; const isUserMakerOrTaker = args.maker === this._userAddressIfExists || args.taker === this._userAddressIfExists; return isUserMakerOrTaker; } - private _updateLatestFillsBlockIfNeeded(blockNumber: number) { + private _updateLatestFillsBlockIfNeeded(blockNumber: number): void { utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); const isBlockPending = _.isNull(blockNumber); @@ -762,7 +765,7 @@ export class Blockchain { }); return tokenByAddress; } - private async _onPageLoadInitFireAndForgetAsync() { + private async _onPageLoadInitFireAndForgetAsync(): Promise<void> { await utils.onPageLoadAsync(); // wait for page to load // Hack: We need to know the networkId the injectedWeb3 is connected to (if it is defined) in @@ -807,9 +810,9 @@ export class Blockchain { this._dispatcher.updateUserAddress(this._userAddressIfExists); await this.fetchTokenInformationAsync(); this._blockchainWatcher.startEmittingNetworkConnectionAndUserBalanceState(); - await this._rehydrateStoreWithContractEvents(); + await this._rehydrateStoreWithContractEventsAsync(); } - private _updateProviderName(injectedWeb3: Web3) { + private _updateProviderName(injectedWeb3: Web3): void { const doesInjectedWeb3Exist = !_.isUndefined(injectedWeb3); const providerName = doesInjectedWeb3Exist ? Blockchain._getNameGivenProvider(injectedWeb3.currentProvider) @@ -851,12 +854,12 @@ export class Blockchain { } } } - private _showFlashMessageIfLedger() { + private _showFlashMessageIfLedger(): void { if (!_.isUndefined(this._ledgerSubprovider)) { this._dispatcher.showFlashMessage('Confirm the transaction on your Ledger Nano S'); } } - private async _updateDefaultGasPriceAsync() { + private async _updateDefaultGasPriceAsync(): Promise<void> { try { const gasInfo = await backendClient.getGasInfoAsync(); const gasPriceInGwei = new BigNumber(gasInfo.average / 10); diff --git a/packages/website/ts/blockchain_watcher.ts b/packages/website/ts/blockchain_watcher.ts index 2712b2c04..c420a98a4 100644 --- a/packages/website/ts/blockchain_watcher.ts +++ b/packages/website/ts/blockchain_watcher.ts @@ -23,8 +23,8 @@ export class BlockchainWatcher { this._shouldPollUserAddress = shouldPollUserAddress; this._web3Wrapper = web3Wrapper; } - public destroy() { - this._stopEmittingNetworkConnectionAndUserBalanceStateAsync(); + public destroy(): void { + this._stopEmittingNetworkConnectionAndUserBalanceState(); // HACK: stop() is only available on providerEngine instances const provider = this._web3Wrapper.getProvider(); if (!_.isUndefined((provider as any).stop)) { @@ -32,10 +32,10 @@ export class BlockchainWatcher { } } // This should only be called from the LedgerConfigDialog - public updatePrevUserAddress(userAddress: string) { + public updatePrevUserAddress(userAddress: string): void { this._prevUserAddressIfExists = userAddress; } - public startEmittingNetworkConnectionAndUserBalanceState() { + public startEmittingNetworkConnectionAndUserBalanceState(): void { if (!_.isUndefined(this._watchNetworkAndBalanceIntervalId)) { return; // we are already emitting the state } @@ -88,18 +88,18 @@ export class BlockchainWatcher { 5000, (err: Error) => { logUtils.log(`Watching network and balances failed: ${err.stack}`); - this._stopEmittingNetworkConnectionAndUserBalanceStateAsync(); + this._stopEmittingNetworkConnectionAndUserBalanceState(); }, ); } - private async _updateUserWeiBalanceAsync(userAddress: string) { + private async _updateUserWeiBalanceAsync(userAddress: string): Promise<void> { const balanceInWei = await this._web3Wrapper.getBalanceInWeiAsync(userAddress); if (!balanceInWei.eq(this._prevUserEtherBalanceInWei)) { this._prevUserEtherBalanceInWei = balanceInWei; this._dispatcher.updateUserWeiBalance(balanceInWei); } } - private _stopEmittingNetworkConnectionAndUserBalanceStateAsync() { + private _stopEmittingNetworkConnectionAndUserBalanceState(): void { if (!_.isUndefined(this._watchNetworkAndBalanceIntervalId)) { intervalUtils.clearAsyncExcludingInterval(this._watchNetworkAndBalanceIntervalId); } diff --git a/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx b/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx index 1c3b7458d..7156e700b 100644 --- a/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx +++ b/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx @@ -18,7 +18,7 @@ interface BlockchainErrDialogProps { } export class BlockchainErrDialog extends React.Component<BlockchainErrDialogProps, undefined> { - public render() { + public render(): React.ReactNode { const dialogActions = [ <FlatButton key="blockchainErrOk" @@ -45,7 +45,7 @@ export class BlockchainErrDialog extends React.Component<BlockchainErrDialogProp </Dialog> ); } - private _getTitle(hasWalletAddress: boolean) { + private _getTitle(hasWalletAddress: boolean): string { if (this.props.blockchainErr === BlockchainErrs.AContractNotDeployedOnNetwork) { return '0x smart contracts not found'; } else if (!hasWalletAddress) { @@ -58,7 +58,7 @@ export class BlockchainErrDialog extends React.Component<BlockchainErrDialogProp return 'Unexpected error'; } } - private _renderExplanation(hasWalletAddress: boolean) { + private _renderExplanation(hasWalletAddress: boolean): React.ReactNode { if (this.props.blockchainErr === BlockchainErrs.AContractNotDeployedOnNetwork) { return this._renderContractsNotDeployedExplanation(); } else if (!hasWalletAddress) { @@ -71,7 +71,7 @@ export class BlockchainErrDialog extends React.Component<BlockchainErrDialogProp return this._renderUnexpectedErrorExplanation(); } } - private _renderDisconnectedFromNode() { + private _renderDisconnectedFromNode(): React.ReactNode { return ( <div> You were disconnected from the backing Ethereum node. If using{' '} @@ -86,7 +86,7 @@ export class BlockchainErrDialog extends React.Component<BlockchainErrDialogProp </div> ); } - private _renderDefaultTokenNotInTokenRegistry() { + private _renderDefaultTokenNotInTokenRegistry(): React.ReactNode { return ( <div> The TokenRegistry deployed on your network does not contain the needed default tokens for 0x Portal to @@ -96,10 +96,10 @@ export class BlockchainErrDialog extends React.Component<BlockchainErrDialogProp </div> ); } - private _renderUnexpectedErrorExplanation() { + private _renderUnexpectedErrorExplanation(): React.ReactNode { return <div>We encountered an unexpected error. Please try refreshing the page.</div>; } - private _renderNoWalletFoundExplanation() { + private _renderNoWalletFoundExplanation(): React.ReactNode { return ( <div> <div> @@ -137,7 +137,7 @@ export class BlockchainErrDialog extends React.Component<BlockchainErrDialogProp </div> ); } - private _renderContractsNotDeployedExplanation() { + private _renderContractsNotDeployedExplanation(): React.ReactNode { return ( <div> <div> 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 42ca1713d..069a75560 100644 --- a/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx +++ b/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx @@ -47,14 +47,14 @@ export class EthWethConversionDialog extends React.Component< ethTokenBalance: new BigNumber(0), }; } - public componentWillMount() { + public componentWillMount(): void { // tslint:disable-next-line:no-floating-promises this._fetchEthTokenBalanceAsync(); } - public componentWillUnmount() { + public componentWillUnmount(): void { this._isUnmounted = true; } - public render() { + public render(): React.ReactNode { const convertDialogActions = [ <FlatButton key="cancel" label="Cancel" onTouchTap={this._onCancel.bind(this)} />, <FlatButton key="convert" label="Convert" primary={true} onTouchTap={this._onConvertClick.bind(this)} />, @@ -72,7 +72,7 @@ export class EthWethConversionDialog extends React.Component< </Dialog> ); } - private _renderConversionDialogBody() { + private _renderConversionDialogBody(): React.ReactNode { const explanation = this.props.direction === Side.Deposit ? 'Convert your Ether into a tokenized, tradable form.' @@ -137,7 +137,7 @@ export class EthWethConversionDialog extends React.Component< </div> ); } - private _renderCurrency(isWrappedVersion: boolean) { + private _renderCurrency(isWrappedVersion: boolean): React.ReactNode { const name = isWrappedVersion ? 'Wrapped Ether' : 'Ether'; const iconUrl = isWrappedVersion ? '/images/token_icons/ether_erc20.png' : '/images/ether.png'; const symbol = isWrappedVersion ? 'WETH' : 'ETH'; @@ -155,18 +155,18 @@ export class EthWethConversionDialog extends React.Component< </div> ); } - private _onMaxClick() { + private _onMaxClick(): void { this.setState({ value: this.state.ethTokenBalance, }); } - private _onValueChange(isValid: boolean, amount?: BigNumber) { + private _onValueChange(isValid: boolean, amount?: BigNumber): void { this.setState({ value: amount, hasErrors: !isValid, }); } - private _onConvertClick() { + private _onConvertClick(): void { if (this.state.hasErrors) { this.setState({ shouldShowIncompleteErrs: true, @@ -179,13 +179,13 @@ export class EthWethConversionDialog extends React.Component< this.props.onComplete(this.props.direction, value); } } - private _onCancel() { + private _onCancel(): void { this.setState({ value: undefined, }); this.props.onCancelled(); } - private async _fetchEthTokenBalanceAsync() { + private async _fetchEthTokenBalanceAsync(): Promise<void> { const userAddressIfExists = _.isEmpty(this.props.userAddress) ? undefined : this.props.userAddress; const [balance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( userAddressIfExists, diff --git a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx index a72d33183..3c839d6f5 100644 --- a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx +++ b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx @@ -59,7 +59,7 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps, preferredNetworkId: props.networkId, }; } - public render() { + public render(): React.ReactNode { const dialogActions = [ <FlatButton key="ledgerConnectCancel" label="Cancel" onTouchTap={this._onClose.bind(this)} />, ]; @@ -82,7 +82,7 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps, </Dialog> ); } - private _renderConnectStep() { + private _renderConnectStep(): React.ReactNode { const networkIds = _.values(sharedConstants.NETWORK_ID_BY_NAME); return ( <div> @@ -122,7 +122,7 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps, </div> ); } - private _renderSelectAddressStep() { + private _renderSelectAddressStep(): React.ReactNode { return ( <div> <div> @@ -159,7 +159,7 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps, </div> ); } - private _renderAddressTableRows() { + private _renderAddressTableRows(): React.ReactNode { const rows = _.map(this.state.userAddresses, (userAddress: string, i: number) => { const balanceInWei = this.state.addressBalances[i]; const addressTooltipId = `address-${userAddress}`; @@ -189,7 +189,7 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps, }); return rows; } - private _onClose() { + private _onClose(): void { this.setState({ connectionErrMsg: '', stepIndex: LedgerSteps.CONNECT, @@ -197,7 +197,7 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps, const isOpen = false; this.props.toggleDialogFn(isOpen); } - private _onAddressSelected(selectedRowIndexes: number[]) { + private _onAddressSelected(selectedRowIndexes: number[]): void { const selectedRowIndex = selectedRowIndexes[0]; const selectedAddress = this.state.userAddresses[selectedRowIndex]; const selectAddressBalance = this.state.addressBalances[selectedRowIndex]; @@ -228,7 +228,7 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps, } return didSucceed; } - private async _fetchAddressesAndBalancesAsync() { + private async _fetchAddressesAndBalancesAsync(): Promise<boolean> { let userAddresses: string[]; const addressBalances: BigNumber[] = []; try { @@ -250,7 +250,7 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps, }); return true; } - private _onDerivationPathChanged(e: any, derivationPath: string) { + private _onDerivationPathChanged(e: any, derivationPath: string): void { let derivationErrMsg = ''; if (!_.startsWith(derivationPath, VALID_ETHEREUM_DERIVATION_PATH_PREFIX)) { derivationErrMsg = 'Must be valid Ethereum path.'; @@ -261,7 +261,7 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps, derivationErrMsg, }); } - private async _onConnectLedgerClickAsync() { + private async _onConnectLedgerClickAsync(): Promise<boolean> { const isU2FSupported = await utils.isU2FSupportedAsync(); if (!isU2FSupported) { logUtils.log(`U2F not supported in this browser`); @@ -295,7 +295,7 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps, } return userAddresses; } - private _onSelectedNetworkUpdated(e: any, index: number, networkId: number) { + private _onSelectedNetworkUpdated(e: any, index: number, networkId: number): void { this.setState({ preferredNetworkId: networkId, }); diff --git a/packages/website/ts/components/dialogs/portal_disclaimer_dialog.tsx b/packages/website/ts/components/dialogs/portal_disclaimer_dialog.tsx index b31667121..41a17fe96 100644 --- a/packages/website/ts/components/dialogs/portal_disclaimer_dialog.tsx +++ b/packages/website/ts/components/dialogs/portal_disclaimer_dialog.tsx @@ -8,7 +8,7 @@ interface PortalDisclaimerDialogProps { onToggleDialog: () => void; } -export function PortalDisclaimerDialog(props: PortalDisclaimerDialogProps) { +export const PortalDisclaimerDialog = (props: PortalDisclaimerDialogProps) => { return ( <Dialog title="0x Portal Disclaimer" @@ -33,4 +33,4 @@ export function PortalDisclaimerDialog(props: PortalDisclaimerDialogProps) { </div> </Dialog> ); -} +}; diff --git a/packages/website/ts/components/dialogs/send_dialog.tsx b/packages/website/ts/components/dialogs/send_dialog.tsx index 2af7fd7ac..421f18b4f 100644 --- a/packages/website/ts/components/dialogs/send_dialog.tsx +++ b/packages/website/ts/components/dialogs/send_dialog.tsx @@ -35,7 +35,7 @@ export class SendDialog extends React.Component<SendDialogProps, SendDialogState isAmountValid: false, }; } - public render() { + public render(): React.ReactNode { const transferDialogActions = [ <FlatButton key="cancelTransfer" label="Cancel" onTouchTap={this._onCancel.bind(this)} />, <FlatButton @@ -57,7 +57,7 @@ export class SendDialog extends React.Component<SendDialogProps, SendDialogState </Dialog> ); } - private _renderSendDialogBody() { + private _renderSendDialogBody(): React.ReactNode { return ( <div className="mx-auto" style={{ maxWidth: 300 }}> <div style={{ height: 80 }}> @@ -86,19 +86,19 @@ export class SendDialog extends React.Component<SendDialogProps, SendDialogState </div> ); } - private _onRecipientChange(recipient?: string) { + private _onRecipientChange(recipient?: string): void { this.setState({ shouldShowIncompleteErrs: false, recipient, }); } - private _onValueChange(isValid: boolean, amount?: BigNumber) { + private _onValueChange(isValid: boolean, amount?: BigNumber): void { this.setState({ isAmountValid: isValid, value: amount, }); } - private _onSendClick() { + private _onSendClick(): void { if (this._hasErrors()) { this.setState({ shouldShowIncompleteErrs: true, @@ -112,13 +112,13 @@ export class SendDialog extends React.Component<SendDialogProps, SendDialogState this.props.onComplete(this.state.recipient, value); } } - private _onCancel() { + private _onCancel(): void { this.setState({ value: undefined, }); this.props.onCancelled(); } - private _hasErrors() { + private _hasErrors(): boolean { return _.isUndefined(this.state.recipient) || _.isUndefined(this.state.value) || !this.state.isAmountValid; } } diff --git a/packages/website/ts/components/dialogs/track_token_confirmation_dialog.tsx b/packages/website/ts/components/dialogs/track_token_confirmation_dialog.tsx index bb7e3ed1a..ac0b27cdc 100644 --- a/packages/website/ts/components/dialogs/track_token_confirmation_dialog.tsx +++ b/packages/website/ts/components/dialogs/track_token_confirmation_dialog.tsx @@ -33,7 +33,7 @@ export class TrackTokenConfirmationDialog extends React.Component< isAddingTokenToTracked: false, }; } - public render() { + public render(): React.ReactNode { const tokens = this.props.tokens; return ( <Dialog @@ -66,7 +66,7 @@ export class TrackTokenConfirmationDialog extends React.Component< </Dialog> ); } - private async _onTrackConfirmationRespondedAsync(didUserAcceptTracking: boolean) { + private async _onTrackConfirmationRespondedAsync(didUserAcceptTracking: boolean): Promise<void> { if (!didUserAcceptTracking) { this.props.onToggleDialog(didUserAcceptTracking); return; diff --git a/packages/website/ts/components/dialogs/u2f_not_supported_dialog.tsx b/packages/website/ts/components/dialogs/u2f_not_supported_dialog.tsx index 6ac9cf917..ce86df856 100644 --- a/packages/website/ts/components/dialogs/u2f_not_supported_dialog.tsx +++ b/packages/website/ts/components/dialogs/u2f_not_supported_dialog.tsx @@ -9,7 +9,7 @@ interface U2fNotSupportedDialogProps { onToggleDialog: () => void; } -export function U2fNotSupportedDialog(props: U2fNotSupportedDialogProps) { +export const U2fNotSupportedDialog = (props: U2fNotSupportedDialogProps) => { return ( <Dialog title="U2F Not Supported" @@ -43,4 +43,4 @@ export function U2fNotSupportedDialog(props: U2fNotSupportedDialogProps) { </div> </Dialog> ); -} +}; diff --git a/packages/website/ts/components/dialogs/wrapped_eth_section_notice_dialog.tsx b/packages/website/ts/components/dialogs/wrapped_eth_section_notice_dialog.tsx index 9e91ff12d..78b270c1e 100644 --- a/packages/website/ts/components/dialogs/wrapped_eth_section_notice_dialog.tsx +++ b/packages/website/ts/components/dialogs/wrapped_eth_section_notice_dialog.tsx @@ -8,7 +8,7 @@ interface WrappedEthSectionNoticeDialogProps { onToggleDialog: () => void; } -export function WrappedEthSectionNoticeDialog(props: WrappedEthSectionNoticeDialogProps) { +export const WrappedEthSectionNoticeDialog = (props: WrappedEthSectionNoticeDialogProps) => { return ( <Dialog title="Dedicated Wrapped Ether Section" @@ -30,4 +30,4 @@ export function WrappedEthSectionNoticeDialog(props: WrappedEthSectionNoticeDial </div> </Dialog> ); -} +}; diff --git a/packages/website/ts/components/dropdowns/network_drop_down.tsx b/packages/website/ts/components/dropdowns/network_drop_down.tsx index b569807dd..2fd2785d1 100644 --- a/packages/website/ts/components/dropdowns/network_drop_down.tsx +++ b/packages/website/ts/components/dropdowns/network_drop_down.tsx @@ -13,7 +13,7 @@ interface NetworkDropDownProps { interface NetworkDropDownState {} export class NetworkDropDown extends React.Component<NetworkDropDownProps, NetworkDropDownState> { - public render() { + public render(): React.ReactNode { return ( <div className="mx-auto" style={{ width: 120 }}> <DropDownMenu value={this.props.selectedNetworkId} onChange={this.props.updateSelectedNetwork}> @@ -22,7 +22,7 @@ export class NetworkDropDown extends React.Component<NetworkDropDownProps, Netwo </div> ); } - private _renderDropDownItems() { + private _renderDropDownItems(): React.ReactNode { const items = _.map(this.props.avialableNetworkIds, networkId => { const networkName = sharedConstants.NETWORK_NAME_BY_ID[networkId]; const primaryText = ( diff --git a/packages/website/ts/components/eth_weth_conversion_button.tsx b/packages/website/ts/components/eth_weth_conversion_button.tsx index 586d260fb..e8db42a7a 100644 --- a/packages/website/ts/components/eth_weth_conversion_button.tsx +++ b/packages/website/ts/components/eth_weth_conversion_button.tsx @@ -46,7 +46,7 @@ export class EthWethConversionButton extends React.Component< isEthConversionHappening: false, }; } - public render() { + public render(): React.ReactNode { const labelStyle = this.state.isEthConversionHappening ? { fontSize: 10 } : {}; let callToActionLabel; let inProgressLabel; @@ -81,12 +81,12 @@ export class EthWethConversionButton extends React.Component< </div> ); } - private _toggleConversionDialog() { + private _toggleConversionDialog(): void { this.setState({ isEthConversionDialogVisible: !this.state.isEthConversionDialogVisible, }); } - private async _onConversionAmountSelectedAsync(direction: Side, value: BigNumber) { + private async _onConversionAmountSelectedAsync(direction: Side, value: BigNumber): Promise<void> { this.setState({ isEthConversionHappening: true, }); diff --git a/packages/website/ts/components/eth_wrappers.tsx b/packages/website/ts/components/eth_wrappers.tsx index 59afeb50b..f19b05861 100644 --- a/packages/website/ts/components/eth_wrappers.tsx +++ b/packages/website/ts/components/eth_wrappers.tsx @@ -65,7 +65,7 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt }, }; } - public componentWillReceiveProps(nextProps: EthWrappersProps) { + public componentWillReceiveProps(nextProps: EthWrappersProps): void { if ( nextProps.userAddress !== this.props.userAddress || nextProps.networkId !== this.props.networkId || @@ -75,15 +75,15 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt this._fetchWETHStateAsync(); } } - public componentDidMount() { + public componentDidMount(): void { window.scrollTo(0, 0); // tslint:disable-next-line:no-floating-promises this._fetchWETHStateAsync(); } - public componentWillUnmount() { + public componentWillUnmount(): void { this._isUnmounted = true; } - public render() { + public render(): React.ReactNode { const etherToken = this._getEthToken(); const wethBalance = ZeroEx.toUnitAmount(this.state.ethTokenState.balance, constants.DECIMAL_PLACES_ETH); const isBidirectional = true; @@ -222,7 +222,7 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt </div> ); } - private _renderActionColumnTitle(isBidirectional: boolean) { + private _renderActionColumnTitle(isBidirectional: boolean): React.ReactNode { let iconClass = 'zmdi-long-arrow-right'; let leftSymbol = 'WETH'; let rightSymbol = 'ETH'; @@ -241,7 +241,7 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt </div> ); } - private _renderOutdatedWeths(etherToken: Token, etherTokenState: TokenState) { + private _renderOutdatedWeths(etherToken: Token, etherTokenState: TokenState): React.ReactNode { const rows = _.map( configs.OUTDATED_WRAPPED_ETHERS, (outdatedWETHByNetworkId: OutdatedWrappedEtherByNetworkId) => { @@ -313,7 +313,7 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt ); return rows; } - private _renderTokenLink(tokenLabel: React.ReactNode, etherscanUrl: string) { + private _renderTokenLink(tokenLabel: React.ReactNode, etherscanUrl: string): React.ReactNode { return ( <span> {_.isUndefined(etherscanUrl) ? ( @@ -326,7 +326,7 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt </span> ); } - private _renderToken(name: string, address: string, imgPath: string) { + private _renderToken(name: string, address: string, imgPath: string): React.ReactNode { const tooltipId = `tooltip-${address}`; return ( <div className="flex"> @@ -340,7 +340,7 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt </div> ); } - private async _onOutdatedConversionSuccessfulAsync(outdatedWETHAddress: string) { + private async _onOutdatedConversionSuccessfulAsync(outdatedWETHAddress: string): Promise<void> { const currentOutdatedWETHState = this.state.outdatedWETHStateByAddress[outdatedWETHAddress]; this.setState({ outdatedWETHStateByAddress: { @@ -368,7 +368,7 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt }, }); } - private async _fetchWETHStateAsync() { + private async _fetchWETHStateAsync(): Promise<void> { const tokens = _.values(this.props.tokenByAddress); const wethToken = _.find(tokens, token => token.symbol === 'WETH'); const userAddressIfExists = _.isEmpty(this.props.userAddress) ? undefined : this.props.userAddress; @@ -414,12 +414,12 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt ); return outdatedWETHAddresses; } - private _getEthToken() { + private _getEthToken(): Token { const tokens = _.values(this.props.tokenByAddress); const etherToken = _.find(tokens, { symbol: 'WETH' }); return etherToken; } - private async _refetchEthTokenStateAsync() { + private async _refetchEthTokenStateAsync(): Promise<void> { const etherToken = this._getEthToken(); const userAddressIfExists = _.isEmpty(this.props.userAddress) ? undefined : this.props.userAddress; const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( diff --git a/packages/website/ts/components/fill_order.tsx b/packages/website/ts/components/fill_order.tsx index ea94e0987..59c32cebc 100644 --- a/packages/website/ts/components/fill_order.tsx +++ b/packages/website/ts/components/fill_order.tsx @@ -82,19 +82,19 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> { tokensToTrack: [], }; } - public componentWillMount() { + public componentWillMount(): void { if (!_.isEmpty(this.state.orderJSON)) { // tslint:disable-next-line:no-floating-promises this._validateFillOrderFireAndForgetAsync(this.state.orderJSON); } } - public componentDidMount() { + public componentDidMount(): void { window.scrollTo(0, 0); } - public componentWillUnmount() { + public componentWillUnmount(): void { this._isUnmounted = true; } - public render() { + public render(): React.ReactNode { return ( <div className="clearfix lg-px4 md-px4 sm-px2" style={{ minHeight: 600 }}> <h3>Fill an order</h3> @@ -159,7 +159,7 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> { </div> ); } - private _renderOrderJsonNotices() { + private _renderOrderJsonNotices(): React.ReactNode { return ( <div> {!_.isUndefined(this.props.initialOrder) && @@ -177,7 +177,7 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> { </div> ); } - private _renderVisualOrder() { + private _renderVisualOrder(): React.ReactNode { const takerTokenAddress = this.state.parsedOrder.signedOrder.takerTokenAddress; const takerToken = this.props.tokenByAddress[takerTokenAddress]; const orderTakerAmount = new BigNumber(this.state.parsedOrder.signedOrder.takerTokenAmount); @@ -306,7 +306,7 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> { </div> ); } - private _renderFillSuccessMsg() { + private _renderFillSuccessMsg(): React.ReactNode { return ( <div> Order successfully filled. See the trade details in your{' '} @@ -316,10 +316,10 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> { </div> ); } - private _renderCancelSuccessMsg() { + private _renderCancelSuccessMsg(): React.ReactNode { return <div>Order successfully cancelled.</div>; } - private _onFillOrderClick() { + private _onFillOrderClick(): void { if (!this.state.isMakerTokenAddressInRegistry || !this.state.isTakerTokenAddressInRegistry) { this.setState({ isFillWarningDialogOpen: true, @@ -329,7 +329,7 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> { this._onFillOrderClickFireAndForgetAsync(); } } - private _onFillWarningClosed(didUserCancel: boolean) { + private _onFillWarningClosed(didUserCancel: boolean): void { this.setState({ isFillWarningDialogOpen: false, }); @@ -338,10 +338,10 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> { this._onFillOrderClickFireAndForgetAsync(); } } - private _onFillAmountChange(isValid: boolean, amount?: BigNumber) { + private _onFillAmountChange(isValid: boolean, amount?: BigNumber): void { this.props.dispatcher.updateOrderFillAmount(amount); } - private _onFillOrderJSONChanged(event: any) { + private _onFillOrderJSONChanged(event: any): void { const orderJSON = event.target.value; this.setState({ didOrderValidationRun: _.isEmpty(orderJSON) && _.isEmpty(this.state.orderJSONErrMsg), @@ -350,7 +350,7 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> { // tslint:disable-next-line:no-floating-promises this._validateFillOrderFireAndForgetAsync(orderJSON); } - private async _checkForUntrackedTokensAndAskToAdd() { + private async _checkForUntrackedTokensAndAskToAddAsync(): Promise<void> { if (!_.isEmpty(this.state.orderJSONErrMsg)) { return; } @@ -396,7 +396,7 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> { }); } } - private async _validateFillOrderFireAndForgetAsync(orderJSON: string) { + private async _validateFillOrderFireAndForgetAsync(orderJSON: string): Promise<void> { let orderJSONErrMsg = ''; let parsedOrder: Order; let orderHash: string; @@ -491,7 +491,7 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> { unavailableTakerAmount, }); - await this._checkForUntrackedTokensAndAskToAdd(); + await this._checkForUntrackedTokensAndAskToAddAsync(); } private async _onFillOrderClickFireAndForgetAsync(): Promise<void> { if (this.props.blockchainErr !== BlockchainErrs.NoError || _.isEmpty(this.props.userAddress)) { @@ -650,7 +650,7 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> { const roundedUnitAmount = Math.round(unitAmount.toNumber() * 100000) / 100000; return roundedUnitAmount; } - private _onToggleTrackConfirmDialog(didConfirmTokenTracking: boolean) { + private _onToggleTrackConfirmDialog(didConfirmTokenTracking: boolean): void { if (!didConfirmTokenTracking) { this.setState({ orderJSON: '', diff --git a/packages/website/ts/components/fill_order_json.tsx b/packages/website/ts/components/fill_order_json.tsx index 7d5351ec4..97297d5a1 100644 --- a/packages/website/ts/components/fill_order_json.tsx +++ b/packages/website/ts/components/fill_order_json.tsx @@ -19,7 +19,7 @@ interface FillOrderJSONProps { interface FillOrderJSONState {} export class FillOrderJSON extends React.Component<FillOrderJSONProps, FillOrderJSONState> { - public render() { + public render(): React.ReactNode { const tokenAddresses = _.keys(this.props.tokenByAddress); const exchangeContract = this.props.blockchain.getExchangeContractAddressIfExists(); const hintSideToAssetToken = { diff --git a/packages/website/ts/components/fill_warning_dialog.tsx b/packages/website/ts/components/fill_warning_dialog.tsx index d3215a6c1..83095b1d3 100644 --- a/packages/website/ts/components/fill_warning_dialog.tsx +++ b/packages/website/ts/components/fill_warning_dialog.tsx @@ -8,7 +8,7 @@ interface FillWarningDialogProps { onToggleDialog: (didUserCancel: boolean) => void; } -export function FillWarningDialog(props: FillWarningDialogProps) { +export const FillWarningDialog = (props: FillWarningDialogProps) => { const didCancel = true; return ( <Dialog @@ -42,4 +42,4 @@ export function FillWarningDialog(props: FillWarningDialogProps) { </div> </Dialog> ); -} +}; diff --git a/packages/website/ts/components/flash_messages/token_send_completed.tsx b/packages/website/ts/components/flash_messages/token_send_completed.tsx index a3b056758..bb5adfa4e 100644 --- a/packages/website/ts/components/flash_messages/token_send_completed.tsx +++ b/packages/website/ts/components/flash_messages/token_send_completed.tsx @@ -16,7 +16,7 @@ interface TokenSendCompletedProps { interface TokenSendCompletedState {} export class TokenSendCompleted extends React.Component<TokenSendCompletedProps, TokenSendCompletedState> { - public render() { + public render(): React.ReactNode { const etherScanLink = !_.isUndefined(this.props.etherScanLinkIfExists) && ( <a style={{ color: colors.white }} href={`${this.props.etherScanLinkIfExists}`} target="_blank"> Verify on Etherscan diff --git a/packages/website/ts/components/flash_messages/transaction_submitted.tsx b/packages/website/ts/components/flash_messages/transaction_submitted.tsx index 188f1f9a6..2a6d2a64b 100644 --- a/packages/website/ts/components/flash_messages/transaction_submitted.tsx +++ b/packages/website/ts/components/flash_messages/transaction_submitted.tsx @@ -9,7 +9,7 @@ interface TransactionSubmittedProps { interface TransactionSubmittedState {} export class TransactionSubmitted extends React.Component<TransactionSubmittedProps, TransactionSubmittedState> { - public render() { + public render(): React.ReactNode { if (_.isUndefined(this.props.etherScanLinkIfExists)) { return <div>Transaction submitted to the network</div>; } else { diff --git a/packages/website/ts/components/footer.tsx b/packages/website/ts/components/footer.tsx index 487b039b2..c44e41084 100644 --- a/packages/website/ts/components/footer.tsx +++ b/packages/website/ts/components/footer.tsx @@ -50,7 +50,7 @@ export class Footer extends React.Component<FooterProps, FooterState> { selectedLanguage: props.translate.getLanguage(), }; } - public render() { + public render(): React.ReactNode { const menuItemsBySection: MenuItemsBySection = { [Key.Documentation]: [ { @@ -180,14 +180,14 @@ export class Footer extends React.Component<FooterProps, FooterState> { </div> ); } - private _renderIcon(fileName: string) { + private _renderIcon(fileName: string): React.ReactNode { return ( <div style={{ height: ICON_DIMENSION, width: ICON_DIMENSION }}> <img src={`/images/social/${fileName}`} style={{ width: ICON_DIMENSION }} /> </div> ); } - private _renderMenuItem(item: FooterMenuItem) { + private _renderMenuItem(item: FooterMenuItem): React.ReactNode { const titleToIcon: { [title: string]: string } = { [this.props.translate.get(Key.RocketChat, Deco.Cap)]: 'rocketchat.png', [this.props.translate.get(Key.Blog, Deco.Cap)]: 'medium.png', @@ -222,7 +222,7 @@ export class Footer extends React.Component<FooterProps, FooterState> { </div> ); } - private _renderHeader(key: Key) { + private _renderHeader(key: Key): React.ReactNode { const headerStyle = { color: colors.grey400, letterSpacing: 2, @@ -235,7 +235,7 @@ export class Footer extends React.Component<FooterProps, FooterState> { </div> ); } - private _updateLanguage(e: any, index: number, value: Language) { + private _updateLanguage(e: any, index: number, value: Language): void { this.setState({ selectedLanguage: value, }); diff --git a/packages/website/ts/components/generate_order/asset_picker.tsx b/packages/website/ts/components/generate_order/asset_picker.tsx index 69fb32a21..d7cc554c4 100644 --- a/packages/website/ts/components/generate_order/asset_picker.tsx +++ b/packages/website/ts/components/generate_order/asset_picker.tsx @@ -79,7 +79,7 @@ export class AssetPicker extends React.Component<AssetPickerProps, AssetPickerSt }, }; } - public render() { + public render(): React.ReactNode { const dialogConfigs: DialogConfigs = this._dialogConfigsByAssetView[this.state.assetView]; return ( <Dialog @@ -102,7 +102,7 @@ export class AssetPicker extends React.Component<AssetPickerProps, AssetPickerSt </Dialog> ); } - private _renderConfirmTrackToken() { + private _renderConfirmTrackToken(): React.ReactNode { const token = this.props.tokenByAddress[this.state.chosenTrackTokenAddress]; return ( <TrackTokenConfirmation @@ -113,7 +113,7 @@ export class AssetPicker extends React.Component<AssetPickerProps, AssetPickerSt /> ); } - private _renderAssetPicker() { + private _renderAssetPicker(): React.ReactNode { return ( <div className="clearfix flex flex-wrap" @@ -128,7 +128,7 @@ export class AssetPicker extends React.Component<AssetPickerProps, AssetPickerSt </div> ); } - private _renderGridTiles() { + private _renderGridTiles(): React.ReactNode { let isHovered; let tileStyles; const gridTiles = _.map(this.props.tokenByAddress, (token: Token, address: string) => { @@ -195,19 +195,19 @@ export class AssetPicker extends React.Component<AssetPickerProps, AssetPickerSt } return gridTiles; } - private _onToggleHover(address: string, isHovered: boolean) { + private _onToggleHover(address: string, isHovered: boolean): void { const hoveredAddress = isHovered ? address : undefined; this.setState({ hoveredAddress, }); } - private _onCloseDialog() { + private _onCloseDialog(): void { this.setState({ assetView: AssetViews.ASSET_PICKER, }); this.props.onTokenChosen(this.props.currentTokenAddress); } - private _onChooseToken(tokenAddress: string) { + private _onChooseToken(tokenAddress: string): void { const token = this.props.tokenByAddress[tokenAddress]; if (token.isTracked) { this.props.onTokenChosen(tokenAddress); @@ -218,12 +218,12 @@ export class AssetPicker extends React.Component<AssetPickerProps, AssetPickerSt }); } } - private _onCustomAssetChosen() { + private _onCustomAssetChosen(): void { this.setState({ assetView: AssetViews.NEW_TOKEN_FORM, }); } - private _onNewTokenSubmitted(newToken: Token) { + private _onNewTokenSubmitted(newToken: Token): void { trackedTokenStorage.addTrackedTokenToUser(this.props.userAddress, this.props.networkId, newToken); this.props.dispatcher.addTokenToTokenByAddress(newToken); this.setState({ @@ -231,7 +231,7 @@ export class AssetPicker extends React.Component<AssetPickerProps, AssetPickerSt }); this.props.onTokenChosen(newToken.address); } - private async _onTrackConfirmationRespondedAsync(didUserAcceptTracking: boolean) { + private async _onTrackConfirmationRespondedAsync(didUserAcceptTracking: boolean): Promise<void> { if (!didUserAcceptTracking) { this.setState({ isAddingTokenToTracked: false, diff --git a/packages/website/ts/components/generate_order/generate_order_form.tsx b/packages/website/ts/components/generate_order/generate_order_form.tsx index eb76cb529..d46c29058 100644 --- a/packages/website/ts/components/generate_order/generate_order_form.tsx +++ b/packages/website/ts/components/generate_order/generate_order_form.tsx @@ -63,10 +63,10 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, G signingState: SigningState.UNSIGNED, }; } - public componentDidMount() { + public componentDidMount(): void { window.scrollTo(0, 0); } - public render() { + public render(): React.ReactNode { const dispatcher = this.props.dispatcher; const depositTokenAddress = this.props.sideToAssetToken[Side.Deposit].address; const depositToken = this.props.tokenByAddress[depositTokenAddress]; @@ -214,13 +214,13 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, G </div> ); } - private _onTokenAmountChange(token: Token, side: Side, isValid: boolean, amount?: BigNumber) { + private _onTokenAmountChange(token: Token, side: Side, isValid: boolean, amount?: BigNumber): void { this.props.dispatcher.updateChosenAssetToken(side, { address: token.address, amount, }); } - private _onCloseOrderJSONDialog() { + private _onCloseOrderJSONDialog(): void { // Upon closing the order JSON dialog, we update the orderSalt stored in the Redux store // with a new value so that if a user signs the identical order again, the newly signed // orderHash will not collide with the previously generated orderHash. diff --git a/packages/website/ts/components/generate_order/new_token_form.tsx b/packages/website/ts/components/generate_order/new_token_form.tsx index e7f3b93c6..10f71b430 100644 --- a/packages/website/ts/components/generate_order/new_token_form.tsx +++ b/packages/website/ts/components/generate_order/new_token_form.tsx @@ -42,7 +42,7 @@ export class NewTokenForm extends React.Component<NewTokenFormProps, NewTokenFor decimalsErrText: '', }; } - public render() { + public render(): React.ReactNode { return ( <div className="mx-auto pb2" style={{ width: 256 }}> <div> @@ -96,7 +96,7 @@ export class NewTokenForm extends React.Component<NewTokenFormProps, NewTokenFor </div> ); } - private async _onAddNewTokenClickAsync() { + private async _onAddNewTokenClickAsync(): Promise<void> { // Trigger validation of name and symbol this._onTokenNameChanged(undefined, this.state.name); this._onTokenSymbolChanged(undefined, this.state.symbol); @@ -152,7 +152,7 @@ export class NewTokenForm extends React.Component<NewTokenFormProps, NewTokenFor }; this.props.onNewTokenSubmitted(newToken); } - private _onTokenNameChanged(e: any, name: string) { + private _onTokenNameChanged(e: any, name: string): void { let nameErrText = ''; const maxLength = 30; const tokens = _.values(this.props.tokenByAddress); @@ -173,7 +173,7 @@ export class NewTokenForm extends React.Component<NewTokenFormProps, NewTokenFor nameErrText, }); } - private _onTokenSymbolChanged(e: any, symbol: string) { + private _onTokenSymbolChanged(e: any, symbol: string): void { let symbolErrText = ''; const maxLength = 5; const tokens = _.values(this.props.tokenByAddress); @@ -193,7 +193,7 @@ export class NewTokenForm extends React.Component<NewTokenFormProps, NewTokenFor symbolErrText, }); } - private _onTokenDecimalsChanged(e: any, decimals: string) { + private _onTokenDecimalsChanged(e: any, decimals: string): void { let decimalsErrText = ''; const maxLength = 2; if (decimals === '') { @@ -209,20 +209,20 @@ export class NewTokenForm extends React.Component<NewTokenFormProps, NewTokenFor decimalsErrText, }); } - private _onTokenAddressChanged(address?: string) { + private _onTokenAddressChanged(address?: string): void { if (!_.isUndefined(address)) { this.setState({ address, }); } } - private _isValidName(input: string) { + private _isValidName(input: string): boolean { return /^[a-z0-9 ]+$/i.test(input); } - private _isInteger(input: string) { + private _isInteger(input: string): boolean { return /^[0-9]+$/i.test(input); } - private _isAlphanumeric(input: string) { + private _isAlphanumeric(input: string): boolean { return /^[a-zA-Z0-9]+$/i.test(input); } } diff --git a/packages/website/ts/components/inputs/address_input.tsx b/packages/website/ts/components/inputs/address_input.tsx index 7ca4af968..39ec72f8a 100644 --- a/packages/website/ts/components/inputs/address_input.tsx +++ b/packages/website/ts/components/inputs/address_input.tsx @@ -29,14 +29,14 @@ export class AddressInput extends React.Component<AddressInputProps, AddressInpu errMsg: '', }; } - public componentWillReceiveProps(nextProps: AddressInputProps) { + public componentWillReceiveProps(nextProps: AddressInputProps): void { if (nextProps.shouldShowIncompleteErrs && this.props.isRequired && this.state.address === '') { this.setState({ errMsg: 'Address is required', }); } } - public render() { + public render(): React.ReactNode { const label = this.props.isRequired ? <RequiredLabel label={this.props.label} /> : this.props.label; const labelDisplay = this.props.shouldHideLabel ? 'hidden' : 'block'; const hintText = this.props.hintText ? this.props.hintText : ''; @@ -57,7 +57,7 @@ export class AddressInput extends React.Component<AddressInputProps, AddressInpu </div> ); } - private _onOrderTakerAddressUpdated(e: any) { + private _onOrderTakerAddressUpdated(e: any): void { const address = e.target.value.toLowerCase(); const isValidAddress = addressUtils.isAddress(address) || address === ''; const errMsg = isValidAddress ? '' : 'Invalid ethereum address'; diff --git a/packages/website/ts/components/inputs/allowance_toggle.tsx b/packages/website/ts/components/inputs/allowance_toggle.tsx index cfe75b751..48c7f9f57 100644 --- a/packages/website/ts/components/inputs/allowance_toggle.tsx +++ b/packages/website/ts/components/inputs/allowance_toggle.tsx @@ -63,7 +63,7 @@ export class AllowanceToggle extends React.Component<AllowanceToggleProps, Allow prevAllowance: props.tokenState.allowance, }; } - public componentWillReceiveProps(nextProps: AllowanceToggleProps) { + public componentWillReceiveProps(nextProps: AllowanceToggleProps): void { if (!nextProps.tokenState.allowance.eq(this.state.prevAllowance)) { this.setState({ isSpinnerVisible: false, @@ -71,7 +71,7 @@ export class AllowanceToggle extends React.Component<AllowanceToggleProps, Allow }); } } - public render() { + public render(): React.ReactNode { return ( <div className="flex"> <div> @@ -128,7 +128,7 @@ export class AllowanceToggle extends React.Component<AllowanceToggleProps, Allow await errorReporter.reportAsync(err); } } - private _isAllowanceSet() { + private _isAllowanceSet(): boolean { return !this.props.tokenState.allowance.eq(0); } } diff --git a/packages/website/ts/components/inputs/balance_bounded_input.tsx b/packages/website/ts/components/inputs/balance_bounded_input.tsx index e9b8dd369..f91a99355 100644 --- a/packages/website/ts/components/inputs/balance_bounded_input.tsx +++ b/packages/website/ts/components/inputs/balance_bounded_input.tsx @@ -5,7 +5,7 @@ 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 { InputErrMsg, ValidatedBigNumberCallback, WebsitePaths } from 'ts/types'; +import { ValidatedBigNumberCallback, WebsitePaths } from 'ts/types'; import { utils } from 'ts/utils/utils'; interface BalanceBoundedInputProps { @@ -14,18 +14,21 @@ interface BalanceBoundedInputProps { amount?: BigNumber; hintText?: string; onChange: ValidatedBigNumberCallback; + onErrorMsgChange?: (errorMsg: React.ReactNode) => void; shouldShowIncompleteErrs?: boolean; shouldCheckBalance: boolean; - validate?: (amount: BigNumber) => InputErrMsg; + validate?: (amount: BigNumber) => React.ReactNode; onVisitBalancesPageClick?: () => void; shouldHideVisitBalancesLink?: boolean; isDisabled?: boolean; shouldShowErrs?: boolean; shouldShowUnderline?: boolean; + inputStyle?: React.CSSProperties; + inputHintStyle?: React.CSSProperties; } interface BalanceBoundedInputState { - errMsg: InputErrMsg; + errMsg: React.ReactNode; amountString: string; } @@ -36,6 +39,7 @@ export class BalanceBoundedInput extends React.Component<BalanceBoundedInputProp isDisabled: false, shouldShowErrs: true, hintText: 'amount', + onErrorMsgChange: _.noop, shouldShowUnderline: true, }; constructor(props: BalanceBoundedInputProps) { @@ -46,7 +50,7 @@ export class BalanceBoundedInput extends React.Component<BalanceBoundedInputProp amountString, }; } - public componentWillReceiveProps(nextProps: BalanceBoundedInputProps) { + public componentWillReceiveProps(nextProps: BalanceBoundedInputProps): void { if (nextProps === this.props) { return; } @@ -63,20 +67,14 @@ export class BalanceBoundedInput extends React.Component<BalanceBoundedInputProp } if (shouldResetState) { const amountString = nextProps.amount.toString(); - this.setState({ - errMsg: this._validate(amountString, nextProps.balance), - amountString, - }); + this._setAmountState(amountString, nextProps.balance); } } else if (isCurrentAmountNumeric) { const amountString = ''; - this.setState({ - errMsg: this._validate(amountString, nextProps.balance), - amountString, - }); + this._setAmountState(amountString, nextProps.balance); } } - public render() { + public render(): React.ReactNode { let errorText; if (this.props.shouldShowErrs) { errorText = @@ -99,21 +97,21 @@ export class BalanceBoundedInput extends React.Component<BalanceBoundedInputProp hintText={<span style={{ textTransform: 'capitalize' }}>{this.props.hintText}</span>} onChange={this._onValueChange.bind(this)} underlineStyle={{ width: 'calc(100% + 50px)' }} + inputStyle={this.props.inputStyle} + hintStyle={this.props.inputHintStyle} underlineShow={this.props.shouldShowUnderline} disabled={this.props.isDisabled} /> ); } - private _onValueChange(e: any, amountString: string) { - const errMsg = this._validate(amountString, this.props.balance); - this.setState( - { - amountString, - errMsg, - }, + private _onValueChange(e: any, amountString: string): void { + this._setAmountState( + amountString, + this.props.balance, () => { - const isValid = _.isUndefined(errMsg); - if (utils.isNumeric(amountString) && !_.includes(amountString, '-')) { + const isValid = _.isUndefined(this._validate(amountString, this.props.balance)); + const isPositiveNumber = utils.isNumeric(amountString) && !_.includes(amountString, '-'); + if (isPositiveNumber) { this.props.onChange(isValid, new BigNumber(amountString)); } else { this.props.onChange(isValid); @@ -121,7 +119,7 @@ export class BalanceBoundedInput extends React.Component<BalanceBoundedInputProp }, ); } - private _validate(amountString: string, balance: BigNumber): InputErrMsg { + private _validate(amountString: string, balance: BigNumber): React.ReactNode { if (!utils.isNumeric(amountString)) { return amountString !== '' ? 'Must be a number' : ''; } @@ -135,7 +133,7 @@ export class BalanceBoundedInput extends React.Component<BalanceBoundedInputProp const errMsg = _.isUndefined(this.props.validate) ? undefined : this.props.validate(amount); return errMsg; } - private _renderIncreaseBalanceLink() { + private _renderIncreaseBalanceLink(): React.ReactNode { if (this.props.shouldHideVisitBalancesLink) { return null; } @@ -161,4 +159,13 @@ export class BalanceBoundedInput extends React.Component<BalanceBoundedInputProp ); } } + + private _setAmountState(amount: string, balance: BigNumber, callback: () => void = _.noop): void { + const errorMsg = this._validate(amount, balance); + this.props.onErrorMsgChange(errorMsg); + this.setState({ + amountString: amount, + errMsg: errorMsg, + }, callback); + } } diff --git a/packages/website/ts/components/inputs/eth_amount_input.tsx b/packages/website/ts/components/inputs/eth_amount_input.tsx index f3a879065..fa684d85c 100644 --- a/packages/website/ts/components/inputs/eth_amount_input.tsx +++ b/packages/website/ts/components/inputs/eth_amount_input.tsx @@ -12,6 +12,7 @@ interface EthAmountInputProps { amount?: BigNumber; hintText?: string; onChange: ValidatedBigNumberCallback; + onErrorMsgChange?: (errorMsg: React.ReactNode) => void; shouldShowIncompleteErrs: boolean; onVisitBalancesPageClick?: () => void; shouldCheckBalance: boolean; @@ -19,6 +20,8 @@ interface EthAmountInputProps { shouldShowErrs?: boolean; shouldShowUnderline?: boolean; style?: React.CSSProperties; + labelStyle?: React.CSSProperties; + inputHintStyle?: React.CSSProperties; } interface EthAmountInputState {} @@ -29,7 +32,7 @@ export class EthAmountInput extends React.Component<EthAmountInputProps, EthAmou shouldShowUnderline: true, style: { height: 63 }, }; - public render() { + public render(): React.ReactNode { const amount = this.props.amount ? ZeroEx.toUnitAmount(this.props.amount, constants.DECIMAL_PLACES_ETH) : undefined; @@ -40,6 +43,7 @@ export class EthAmountInput extends React.Component<EthAmountInputProps, EthAmou balance={this.props.balance} amount={amount} onChange={this._onChange.bind(this)} + onErrorMsgChange={this.props.onErrorMsgChange} shouldCheckBalance={this.props.shouldCheckBalance} shouldShowIncompleteErrs={this.props.shouldShowIncompleteErrs} onVisitBalancesPageClick={this.props.onVisitBalancesPageClick} @@ -47,15 +51,20 @@ export class EthAmountInput extends React.Component<EthAmountInputProps, EthAmou hintText={this.props.hintText} shouldShowErrs={this.props.shouldShowErrs} shouldShowUnderline={this.props.shouldShowUnderline} + inputStyle={this.props.style} + inputHintStyle={this.props.inputHintStyle} /> - <div style={{ paddingTop: _.isUndefined(this.props.label) ? 15 : 40 }}>ETH</div> + <div style={this._getLabelStyle()}>ETH</div> </div> ); } - private _onChange(isValid: boolean, amount?: BigNumber) { + private _onChange(isValid: boolean, amount?: BigNumber): void { const baseUnitAmountIfExists = _.isUndefined(amount) ? undefined : ZeroEx.toBaseUnitAmount(amount, constants.DECIMAL_PLACES_ETH); this.props.onChange(isValid, baseUnitAmountIfExists); } + private _getLabelStyle(): React.CSSProperties { + return this.props.labelStyle || { paddingTop: _.isUndefined(this.props.label) ? 15 : 40 }; + } } diff --git a/packages/website/ts/components/inputs/expiration_input.tsx b/packages/website/ts/components/inputs/expiration_input.tsx index e473648d2..5c68080fe 100644 --- a/packages/website/ts/components/inputs/expiration_input.tsx +++ b/packages/website/ts/components/inputs/expiration_input.tsx @@ -30,7 +30,7 @@ export class ExpirationInput extends React.Component<ExpirationInputProps, Expir timeMoment: didUserSetExpiry ? expirationMoment : undefined, }; } - public render() { + public render(): React.ReactNode { const date = this.state.dateMoment ? this.state.dateMoment.toDate() : undefined; const time = this.state.timeMoment ? this.state.timeMoment.toDate() : undefined; return ( @@ -72,7 +72,7 @@ export class ExpirationInput extends React.Component<ExpirationInputProps, Expir .startOf('day') .isBefore(this._earliestPickableMoment); } - private _clearDates() { + private _clearDates(): void { this.setState({ dateMoment: undefined, timeMoment: undefined, @@ -80,7 +80,7 @@ export class ExpirationInput extends React.Component<ExpirationInputProps, Expir const defaultDateTime = utils.initialOrderExpiryUnixTimestampSec(); this.props.updateOrderExpiry(defaultDateTime); } - private _onDateChanged(e: any, date: Date) { + private _onDateChanged(e: any, date: Date): void { const dateMoment = moment(date); this.setState({ dateMoment, @@ -88,7 +88,7 @@ export class ExpirationInput extends React.Component<ExpirationInputProps, Expir const timestamp = utils.convertToUnixTimestampSeconds(dateMoment, this.state.timeMoment); this.props.updateOrderExpiry(timestamp); } - private _onTimeChanged(e: any, time: Date) { + private _onTimeChanged(e: any, time: Date): void { const timeMoment = moment(time); this.setState({ timeMoment, diff --git a/packages/website/ts/components/inputs/hash_input.tsx b/packages/website/ts/components/inputs/hash_input.tsx index 28305637d..37d4af138 100644 --- a/packages/website/ts/components/inputs/hash_input.tsx +++ b/packages/website/ts/components/inputs/hash_input.tsx @@ -27,7 +27,7 @@ interface HashInputProps { interface HashInputState {} export class HashInput extends React.Component<HashInputProps, HashInputState> { - public render() { + public render(): React.ReactNode { const msgHashHex = this.props.blockchainIsLoaded ? this._generateMessageHashHex() : ''; return ( <div> @@ -40,7 +40,7 @@ export class HashInput extends React.Component<HashInputProps, HashInputState> { </div> ); } - private _generateMessageHashHex() { + private _generateMessageHashHex(): string { const exchangeContractAddress = this.props.blockchain.getExchangeContractAddressIfExists(); const hashData = this.props.hashData; const order: Order = { diff --git a/packages/website/ts/components/inputs/identicon_address_input.tsx b/packages/website/ts/components/inputs/identicon_address_input.tsx index 4cf9af64d..a4dc01ba8 100644 --- a/packages/website/ts/components/inputs/identicon_address_input.tsx +++ b/packages/website/ts/components/inputs/identicon_address_input.tsx @@ -23,7 +23,7 @@ export class IdenticonAddressInput extends React.Component<IdenticonAddressInput address: props.initialAddress, }; } - public render() { + public render(): React.ReactNode { const label = this.props.isRequired ? <RequiredLabel label={this.props.label} /> : this.props.label; return ( <div className="relative" style={{ width: '100%' }}> diff --git a/packages/website/ts/components/inputs/token_amount_input.tsx b/packages/website/ts/components/inputs/token_amount_input.tsx index 9e638b67b..f040928f1 100644 --- a/packages/website/ts/components/inputs/token_amount_input.tsx +++ b/packages/website/ts/components/inputs/token_amount_input.tsx @@ -6,7 +6,7 @@ import * as React from 'react'; import { Link } from 'react-router-dom'; import { Blockchain } from 'ts/blockchain'; import { BalanceBoundedInput } from 'ts/components/inputs/balance_bounded_input'; -import { InputErrMsg, Token, ValidatedBigNumberCallback, WebsitePaths } from 'ts/types'; +import { Token, ValidatedBigNumberCallback, WebsitePaths } from 'ts/types'; interface TokenAmountInputProps { userAddress: string; @@ -20,11 +20,14 @@ interface TokenAmountInputProps { shouldCheckBalance: boolean; shouldCheckAllowance: boolean; onChange: ValidatedBigNumberCallback; + onErrorMsgChange?: (errorMsg: React.ReactNode) => void; onVisitBalancesPageClick?: () => void; lastForceTokenStateRefetch: number; shouldShowErrs?: boolean; shouldShowUnderline?: boolean; style?: React.CSSProperties; + labelStyle?: React.CSSProperties; + inputHintStyle?: React.CSSProperties; } interface TokenAmountInputState { @@ -52,14 +55,14 @@ export class TokenAmountInput extends React.Component<TokenAmountInputProps, Tok isBalanceAndAllowanceLoaded: false, }; } - public componentWillMount() { + public componentWillMount(): void { // tslint:disable-next-line:no-floating-promises this._fetchBalanceAndAllowanceAsync(this.props.token.address, this.props.userAddress); } - public componentWillUnmount() { + public componentWillUnmount(): void { this._isUnmounted = true; } - public componentWillReceiveProps(nextProps: TokenAmountInputProps) { + public componentWillReceiveProps(nextProps: TokenAmountInputProps): void { if ( nextProps.userAddress !== this.props.userAddress || nextProps.networkId !== this.props.networkId || @@ -70,21 +73,18 @@ export class TokenAmountInput extends React.Component<TokenAmountInputProps, Tok this._fetchBalanceAndAllowanceAsync(nextProps.token.address, nextProps.userAddress); } } - public render() { + public render(): React.ReactNode { const amount = this.props.amount ? ZeroEx.toUnitAmount(this.props.amount, this.props.token.decimals) : undefined; - const hasLabel = !_.isUndefined(this.props.label); - const style = !_.isUndefined(this.props.style) - ? this.props.style - : { height: hasLabel ? HEIGHT_WITH_LABEL : HEIGHT_WITHOUT_LABEL }; return ( - <div className="flex overflow-hidden" style={style}> + <div className="flex overflow-hidden" style={this._getStyle()}> <BalanceBoundedInput label={this.props.label} amount={amount} balance={ZeroEx.toUnitAmount(this.state.balance, this.props.token.decimals)} onChange={this._onChange.bind(this)} + onErrorMsgChange={this.props.onErrorMsgChange} validate={this._validate.bind(this)} shouldCheckBalance={this.props.shouldCheckBalance} shouldShowIncompleteErrs={this.props.shouldShowIncompleteErrs} @@ -93,19 +93,21 @@ export class TokenAmountInput extends React.Component<TokenAmountInputProps, Tok hintText={this.props.hintText} shouldShowErrs={this.props.shouldShowErrs} shouldShowUnderline={this.props.shouldShowUnderline} + inputStyle={this.props.style} + inputHintStyle={this.props.inputHintStyle} /> - <div style={{ paddingTop: hasLabel ? 39 : 14 }}>{this.props.token.symbol}</div> + <div style={this._getLabelStyle()}>{this.props.token.symbol}</div> </div> ); } - private _onChange(isValid: boolean, amount?: BigNumber) { + private _onChange(isValid: boolean, amount?: BigNumber): void { let baseUnitAmount; if (!_.isUndefined(amount)) { baseUnitAmount = ZeroEx.toBaseUnitAmount(amount, this.props.token.decimals); } this.props.onChange(isValid, baseUnitAmount); } - private _validate(amount: BigNumber): InputErrMsg { + private _validate(amount: BigNumber): React.ReactNode { if (this.props.shouldCheckAllowance && amount.gt(this.state.allowance)) { return ( <span> @@ -122,7 +124,7 @@ export class TokenAmountInput extends React.Component<TokenAmountInputProps, Tok return undefined; } } - private async _fetchBalanceAndAllowanceAsync(tokenAddress: string, userAddress: string) { + private async _fetchBalanceAndAllowanceAsync(tokenAddress: string, userAddress: string): Promise<void> { this.setState({ isBalanceAndAllowanceLoaded: false, }); @@ -139,4 +141,14 @@ export class TokenAmountInput extends React.Component<TokenAmountInputProps, Tok }); } } + private _getStyle(): React.CSSProperties { + const hasLabel = !_.isUndefined(this.props.label); + return !_.isUndefined(this.props.style) + ? this.props.style + : { height: hasLabel ? HEIGHT_WITH_LABEL : HEIGHT_WITHOUT_LABEL }; + } + private _getLabelStyle(): React.CSSProperties { + const hasLabel = !_.isUndefined(this.props.label); + return this.props.labelStyle || { paddingTop: hasLabel ? 39 : 14 }; + } } diff --git a/packages/website/ts/components/inputs/token_input.tsx b/packages/website/ts/components/inputs/token_input.tsx index 545e9a095..c2c4dd63b 100644 --- a/packages/website/ts/components/inputs/token_input.tsx +++ b/packages/website/ts/components/inputs/token_input.tsx @@ -38,7 +38,7 @@ export class TokenInput extends React.Component<TokenInputProps, TokenInputState isPickerOpen: false, }; } - public render() { + public render(): React.ReactNode { const token = this.props.tokenByAddress[this.props.assetToken.address]; const iconStyles = { cursor: 'pointer', @@ -76,7 +76,7 @@ export class TokenInput extends React.Component<TokenInputProps, TokenInputState </div> ); } - private _onTokenChosen(tokenAddress: string) { + private _onTokenChosen(tokenAddress: string): void { const assetToken: AssetToken = { address: tokenAddress, amount: this.props.assetToken.amount, @@ -86,12 +86,12 @@ export class TokenInput extends React.Component<TokenInputProps, TokenInputState isPickerOpen: false, }); } - private _onToggleHover(isHoveringIcon: boolean) { + private _onToggleHover(isHoveringIcon: boolean): void { this.setState({ isHoveringIcon, }); } - private _onAssetClicked() { + private _onAssetClicked(): void { if (this.props.blockchainErr !== BlockchainErrs.NoError) { this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); return; diff --git a/packages/website/ts/components/portal.tsx b/packages/website/ts/components/legacy_portal/legacy_portal.tsx index b79f5e288..a5ea95629 100644 --- a/packages/website/ts/components/portal.tsx +++ b/packages/website/ts/components/legacy_portal/legacy_portal.tsx @@ -14,7 +14,7 @@ import { WrappedEthSectionNoticeDialog } from 'ts/components/dialogs/wrapped_eth import { EthWrappers } from 'ts/components/eth_wrappers'; import { FillOrder } from 'ts/components/fill_order'; import { Footer } from 'ts/components/footer'; -import { PortalMenu } from 'ts/components/portal_menu'; +import { LegacyPortalMenu } from 'ts/components/legacy_portal/legacy_portal_menu'; import { RelayerIndex } from 'ts/components/relayer_index/relayer_index'; import { TokenBalances } from 'ts/components/token_balances'; import { TopBar } from 'ts/components/top_bar/top_bar'; @@ -43,9 +43,7 @@ import { utils } from 'ts/utils/utils'; const THROTTLE_TIMEOUT = 100; -export interface PortalPassedProps {} - -export interface PortalAllProps { +export interface LegacyPortalProps { blockchainErr: BlockchainErrs; blockchainIsLoaded: boolean; dispatcher: Dispatcher; @@ -67,7 +65,7 @@ export interface PortalAllProps { translate: Translate; } -interface PortalAllState { +interface LegacyPortalState { prevNetworkId: number; prevNodeVersion: string; prevUserAddress: string; @@ -77,22 +75,22 @@ interface PortalAllState { isLedgerDialogOpen: boolean; } -export class Portal extends React.Component<PortalAllProps, PortalAllState> { +export class LegacyPortal extends React.Component<LegacyPortalProps, LegacyPortalState> { private _blockchain: Blockchain; private _sharedOrderIfExists: Order; private _throttledScreenWidthUpdate: () => void; - public static hasAlreadyDismissedWethNotice() { + public static hasAlreadyDismissedWethNotice(): boolean { const didDismissWethNotice = localStorage.getItemIfExists(constants.LOCAL_STORAGE_KEY_DISMISS_WETH_NOTICE); const hasAlreadyDismissedWethNotice = !_.isUndefined(didDismissWethNotice) && !_.isEmpty(didDismissWethNotice); return hasAlreadyDismissedWethNotice; } - constructor(props: PortalAllProps) { + constructor(props: LegacyPortalProps) { super(props); this._sharedOrderIfExists = this._getSharedOrderIfExists(); this._throttledScreenWidthUpdate = _.throttle(this._updateScreenWidth.bind(this), THROTTLE_TIMEOUT); const isViewingBalances = _.includes(props.location.pathname, `${WebsitePaths.Portal}/balances`); - const hasAlreadyDismissedWethNotice = Portal.hasAlreadyDismissedWethNotice(); + const hasAlreadyDismissedWethNotice = LegacyPortal.hasAlreadyDismissedWethNotice(); const didAcceptPortalDisclaimer = localStorage.getItemIfExists(constants.LOCAL_STORAGE_KEY_ACCEPT_DISCLAIMER); const hasAcceptedDisclaimer = @@ -107,14 +105,14 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> { isLedgerDialogOpen: false, }; } - public componentDidMount() { + public componentDidMount(): void { window.addEventListener('resize', this._throttledScreenWidthUpdate); window.scrollTo(0, 0); } - public componentWillMount() { + public componentWillMount(): void { this._blockchain = new Blockchain(this.props.dispatcher); } - public componentWillUnmount() { + public componentWillUnmount(): void { this._blockchain.destroy(); window.removeEventListener('resize', this._throttledScreenWidthUpdate); // We re-set the entire redux state when the portal is unmounted so that when it is re-rendered @@ -123,7 +121,7 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> { // become disconnected from their backing Ethereum node, changes user accounts, etc...) this.props.dispatcher.resetState(); } - public componentWillReceiveProps(nextProps: PortalAllProps) { + public componentWillReceiveProps(nextProps: LegacyPortalProps): void { if (nextProps.networkId !== this.state.prevNetworkId) { // tslint:disable-next-line:no-floating-promises this._blockchain.networkIdUpdatedFireAndForgetAsync(nextProps.networkId); @@ -145,18 +143,17 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> { } if (nextProps.location.pathname !== this.state.prevPathname) { const isViewingBalances = _.includes(nextProps.location.pathname, `${WebsitePaths.Portal}/balances`); - const hasAlreadyDismissedWethNotice = Portal.hasAlreadyDismissedWethNotice(); + const hasAlreadyDismissedWethNotice = LegacyPortal.hasAlreadyDismissedWethNotice(); this.setState({ prevPathname: nextProps.location.pathname, isWethNoticeDialogOpen: !hasAlreadyDismissedWethNotice && isViewingBalances, }); } } - public render() { + public render(): React.ReactNode { const updateShouldBlockchainErrDialogBeOpen = this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen.bind( this.props.dispatcher, ); - const isDevelopment = configs.ENVIRONMENT === Environments.DEVELOPMENT; const portalStyle: React.CSSProperties = { minHeight: '100vh', display: 'flex', @@ -200,24 +197,12 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> { ) : ( <div className="mx-auto flex"> <div className="col col-2 pr2 pt1 sm-hide xs-hide" style={portalMenuContainerStyle}> - <PortalMenu menuItemStyle={{ color: colors.white }} /> + <LegacyPortalMenu menuItemStyle={{ color: colors.white }} /> </div> <div className="col col-12 lg-col-10 md-col-10 sm-col sm-col-12"> <div className="py2" style={{ backgroundColor: colors.grey50 }}> {this.props.blockchainIsLoaded ? ( <Switch> - {isDevelopment && ( - <Route - path={`${WebsitePaths.Portal}/wallet`} - render={this._renderWallet.bind(this)} - /> - )} - {isDevelopment && ( - <Route - path={`${WebsitePaths.Portal}/relayers`} - render={this._renderRelayers.bind(this)} - /> - )} <Route path={`${WebsitePaths.Portal}/weth`} render={this._renderEthWrapper.bind(this)} @@ -232,7 +217,7 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> { /> <Route path={`${WebsitePaths.Portal}/trades`} - component={this._renderTradeHistory.bind(this)} + render={this._renderTradeHistory.bind(this)} /> <Route path={`${WebsitePaths.Home}`} @@ -291,46 +276,12 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> { </div> ); } - public onToggleLedgerDialog() { + public onToggleLedgerDialog(): void { this.setState({ isLedgerDialogOpen: !this.state.isLedgerDialogOpen, }); } - private _renderWallet() { - const allTokens = _.values(this.props.tokenByAddress); - const trackedTokens = _.filter(allTokens, t => t.isTracked); - return ( - <div className="flex flex-center"> - <div className="mx-auto"> - <Wallet - userAddress={this.props.userAddress} - networkId={this.props.networkId} - blockchain={this._blockchain} - blockchainIsLoaded={this.props.blockchainIsLoaded} - blockchainErr={this.props.blockchainErr} - dispatcher={this.props.dispatcher} - tokenByAddress={this.props.tokenByAddress} - trackedTokens={trackedTokens} - userEtherBalanceInWei={this.props.userEtherBalanceInWei} - lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch} - injectedProviderName={this.props.injectedProviderName} - providerType={this.props.providerType} - onToggleLedgerDialog={this.onToggleLedgerDialog.bind(this)} - /> - </div> - </div> - ); - } - private _renderRelayers() { - return ( - <div className="flex flex-center"> - <div className="mx-auto" style={{ width: 800 }}> - <RelayerIndex networkId={this.props.networkId} /> - </div> - </div> - ); - } - private _renderEthWrapper() { + private _renderEthWrapper(): React.ReactNode { return ( <EthWrappers networkId={this.props.networkId} @@ -343,7 +294,7 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> { /> ); } - private _renderTradeHistory() { + private _renderTradeHistory(): React.ReactNode { return ( <TradeHistory tokenByAddress={this.props.tokenByAddress} @@ -352,7 +303,7 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> { /> ); } - private _renderTokenBalances() { + private _renderTokenBalances(): React.ReactNode { const allTokens = _.values(this.props.tokenByAddress); const trackedTokens = _.filter(allTokens, t => t.isTracked); return ( @@ -371,7 +322,7 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> { /> ); } - private _renderFillOrder(match: any, location: Location, history: History) { + private _renderFillOrder(match: any, location: Location, history: History): React.ReactNode { const initialFillOrder = !_.isUndefined(this.props.userSuppliedOrderCache) ? this.props.userSuppliedOrderCache : this._sharedOrderIfExists; @@ -390,7 +341,7 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> { /> ); } - private _renderGenerateOrderForm(match: any, location: Location, history: History) { + private _renderGenerateOrderForm(match: any, location: Location, history: History): React.ReactNode { return ( <GenerateOrderForm blockchain={this._blockchain} @@ -399,13 +350,13 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> { /> ); } - private _onPortalDisclaimerAccepted() { + private _onPortalDisclaimerAccepted(): void { localStorage.setItem(constants.LOCAL_STORAGE_KEY_ACCEPT_DISCLAIMER, 'set'); this.setState({ isDisclaimerDialogOpen: false, }); } - private _onWethNoticeAccepted() { + private _onWethNoticeAccepted(): void { localStorage.setItem(constants.LOCAL_STORAGE_KEY_DISMISS_WETH_NOTICE, 'set'); this.setState({ isWethNoticeDialogOpen: false, @@ -437,7 +388,7 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> { } return order; } - private _updateScreenWidth() { + private _updateScreenWidth(): void { const newScreenWidth = utils.getScreenWidth(); this.props.dispatcher.updateScreenWidth(newScreenWidth); } diff --git a/packages/website/ts/components/portal_menu.tsx b/packages/website/ts/components/legacy_portal/legacy_portal_menu.tsx index 2b4d7eea2..7469ca14e 100644 --- a/packages/website/ts/components/portal_menu.tsx +++ b/packages/website/ts/components/legacy_portal/legacy_portal_menu.tsx @@ -4,18 +4,18 @@ import { MenuItem } from 'ts/components/ui/menu_item'; import { Environments, WebsitePaths } from 'ts/types'; import { configs } from 'ts/utils/configs'; -export interface PortalMenuProps { +export interface LegacyPortalMenuProps { menuItemStyle: React.CSSProperties; onClick?: () => void; } -interface PortalMenuState {} +interface LegacyPortalMenuState {} -export class PortalMenu extends React.Component<PortalMenuProps, PortalMenuState> { - public static defaultProps: Partial<PortalMenuProps> = { +export class LegacyPortalMenu extends React.Component<LegacyPortalMenuProps, LegacyPortalMenuState> { + public static defaultProps: Partial<LegacyPortalMenuProps> = { onClick: _.noop, }; - public render() { + public render(): React.ReactNode { return ( <div> <MenuItem @@ -58,30 +58,10 @@ export class PortalMenu extends React.Component<PortalMenuProps, PortalMenuState > {this._renderMenuItemWithIcon('Wrap ETH', 'zmdi-circle-o')} </MenuItem> - {configs.ENVIRONMENT === Environments.DEVELOPMENT && ( - <div> - <MenuItem - style={this.props.menuItemStyle} - className="py2" - to={`${WebsitePaths.Portal}/wallet`} - onClick={this.props.onClick.bind(this)} - > - {this._renderMenuItemWithIcon('Wallet', 'zmdi-balance-wallet')} - </MenuItem> - <MenuItem - style={this.props.menuItemStyle} - className="py2" - to={`${WebsitePaths.Portal}/relayers`} - onClick={this.props.onClick.bind(this)} - > - {this._renderMenuItemWithIcon('Relayers', 'zmdi-input-antenna')} - </MenuItem> - </div> - )} </div> ); } - private _renderMenuItemWithIcon(title: string, iconName: string) { + private _renderMenuItemWithIcon(title: string, iconName: string): React.ReactNode { return ( <div className="flex" style={{ fontWeight: 100 }}> <div className="pr1 pl2"> diff --git a/packages/website/ts/components/order_json.tsx b/packages/website/ts/components/order_json.tsx index 02b88b888..6feefea50 100644 --- a/packages/website/ts/components/order_json.tsx +++ b/packages/website/ts/components/order_json.tsx @@ -38,7 +38,7 @@ export class OrderJSON extends React.Component<OrderJSONProps, OrderJSONState> { // tslint:disable-next-line:no-floating-promises this._setShareLinkAsync(); } - public render() { + public render(): React.ReactNode { const order = utils.generateOrder( this.props.exchangeContractIfExists, this.props.sideToAssetToken, @@ -116,11 +116,11 @@ export class OrderJSON extends React.Component<OrderJSONProps, OrderJSONState> { </div> ); } - private async _shareViaTwitterAsync() { + private _shareViaTwitterAsync(): void { const tweetText = encodeURIComponent(`Fill my order using the 0x protocol: ${this.state.shareLink}`); window.open(`https://twitter.com/intent/tweet?text=${tweetText}`, 'Share your order', 'width=500,height=400'); } - private async _shareViaFacebook() { + private _shareViaFacebook(): void { (window as any).FB.ui( { display: 'popup', @@ -130,14 +130,14 @@ export class OrderJSON extends React.Component<OrderJSONProps, OrderJSONState> { _.noop, ); } - private async _shareViaEmailAsync() { + private _shareViaEmailAsync(): void { const encodedSubject = encodeURIComponent("Let's trade using the 0x protocol"); const encodedBody = encodeURIComponent(`I generated an order with the 0x protocol. You can see and fill it here: ${this.state.shareLink}`); const mailToLink = `mailto:mail@example.org?subject=${encodedSubject}&body=${encodedBody}`; window.open(mailToLink, '_blank'); } - private async _setShareLinkAsync() { + private async _setShareLinkAsync(): Promise<void> { const shareLink = await this._generateShareLinkAsync(); this.setState({ shareLink, @@ -159,7 +159,7 @@ You can see and fill it here: ${this.state.shareLink}`); } return bodyObj.data.url; } - private _getOrderUrl() { + private _getOrderUrl(): string { const order = utils.generateOrder( this.props.exchangeContractIfExists, this.props.sideToAssetToken, diff --git a/packages/website/ts/components/portal/back_button.tsx b/packages/website/ts/components/portal/back_button.tsx new file mode 100644 index 000000000..68934f88e --- /dev/null +++ b/packages/website/ts/components/portal/back_button.tsx @@ -0,0 +1,41 @@ +import { colors, Styles } from '@0xproject/react-shared'; +import * as React from 'react'; +import { Link } from 'react-router-dom'; + +export interface BackButtonProps { + to: string; + labelText: string; +} + +const BACK_BUTTON_HEIGHT = 28; + +const styles: Styles = { + backButton: { + height: BACK_BUTTON_HEIGHT, + paddingTop: 10, + backgroundColor: colors.white, + borderRadius: BACK_BUTTON_HEIGHT, + boxShadow: `0px 4px 6px ${colors.walletBoxShadow}`, + }, + backButtonIcon: { + color: colors.mediumBlue, + fontSize: 20, + }, +}; + +export const BackButton = (props: BackButtonProps) => { + return ( + <div style={{ height: 65, paddingTop: 25 }}> + <Link to={props.to} style={{ textDecoration: 'none' }}> + <div className="flex right" style={styles.backButton}> + <div style={{ marginLeft: 12 }}> + <i style={styles.backButtonIcon} className={`zmdi zmdi-arrow-left`} /> + </div> + <div style={{ marginLeft: 12, marginRight: 12 }}> + <div style={{ fontSize: 16, color: colors.lightGrey }}>{props.labelText}</div> + </div> + </div> + </Link> + </div> + ); +}; diff --git a/packages/website/ts/components/portal/loading.tsx b/packages/website/ts/components/portal/loading.tsx new file mode 100644 index 000000000..d804dd1b8 --- /dev/null +++ b/packages/website/ts/components/portal/loading.tsx @@ -0,0 +1,21 @@ +import CircularProgress from 'material-ui/CircularProgress'; +import * as React from 'react'; + +const CIRCULAR_PROGRESS_SIZE = 40; +const CIRCULAR_PROGRESS_THICKNESS = 5; + +export interface LoadingProps { + isLoading: boolean; + content: React.ReactNode; +} +export const Loading = (props: LoadingProps) => { + if (props.isLoading) { + return ( + <div className="center"> + <CircularProgress size={CIRCULAR_PROGRESS_SIZE} thickness={CIRCULAR_PROGRESS_THICKNESS} /> + </div> + ); + } else { + return <div>{props.content}</div>; + } +}; diff --git a/packages/website/ts/components/portal/menu.tsx b/packages/website/ts/components/portal/menu.tsx new file mode 100644 index 000000000..9014d8d42 --- /dev/null +++ b/packages/website/ts/components/portal/menu.tsx @@ -0,0 +1,88 @@ +import { colors, Styles } from '@0xproject/react-shared'; +import * as _ from 'lodash'; +import * as React from 'react'; +import { MenuItem } from 'ts/components/ui/menu_item'; +import { Environments, WebsitePaths } from 'ts/types'; +import { configs } from 'ts/utils/configs'; + +export interface MenuProps { + selectedPath?: string; +} + +interface MenuItemEntry { + to: string; + labelText: string; + iconName: string; +} + +const menuItemEntries: MenuItemEntry[] = [ + { + to: `${WebsitePaths.Portal}/account`, + labelText: 'Account overview', + iconName: 'zmdi-balance-wallet', + }, + { + to: `${WebsitePaths.Portal}/trades`, + labelText: 'Trade history', + iconName: 'zmdi-format-list-bulleted', + }, + { + to: `${WebsitePaths.Portal}/weth`, + labelText: 'Wrapped ETH', + iconName: 'zmdi-circle-o', + }, + { + to: `${WebsitePaths.Portal}/direct`, + labelText: 'Trade direct', + iconName: 'zmdi-swap', + }, +]; + +const DEFAULT_LABEL_COLOR = colors.darkerGrey; +const DEFAULT_ICON_COLOR = colors.darkerGrey; +const SELECTED_ICON_COLOR = colors.yellow900; + +const LEFT_PADDING = 185; + +export const Menu: React.StatelessComponent<MenuProps> = (props: MenuProps) => { + return ( + <div style={{ paddingLeft: LEFT_PADDING }}> + {_.map(menuItemEntries, entry => { + const selected = entry.to === props.selectedPath; + return ( + <MenuItem key={entry.to} className="py2" to={entry.to}> + <MenuItemLabel title={entry.labelText} iconName={entry.iconName} selected={selected} /> + </MenuItem> + ); + })} + </div> + ); +}; + +interface MenuItemLabelProps { + title: string; + iconName: string; + selected: boolean; +} +const MenuItemLabel: React.StatelessComponent<MenuItemLabelProps> = (props: MenuItemLabelProps) => { + const styles: Styles = { + iconStyle: { + color: props.selected ? SELECTED_ICON_COLOR : DEFAULT_ICON_COLOR, + fontSize: 20, + }, + textStyle: { + color: DEFAULT_LABEL_COLOR, + fontWeight: props.selected ? 'bold' : 'normal', + }, + }; + return ( + <div className="flex"> + <div className="pr1"> + <i style={styles.iconStyle} className={`zmdi ${props.iconName}`} /> + </div> + <div className="pl1" style={styles.textStyle}> + {props.title} + </div> + </div> + ); +}; diff --git a/packages/website/ts/components/portal/portal.tsx b/packages/website/ts/components/portal/portal.tsx new file mode 100644 index 000000000..d9d50c5ab --- /dev/null +++ b/packages/website/ts/components/portal/portal.tsx @@ -0,0 +1,468 @@ +import { colors, Styles } from '@0xproject/react-shared'; +import { BigNumber } from '@0xproject/utils'; +import * as _ from 'lodash'; +import * as React from 'react'; +import * as DocumentTitle from 'react-document-title'; +import { Link, Route, RouteComponentProps, Switch } from 'react-router-dom'; + +import { Blockchain } from 'ts/blockchain'; +import { BlockchainErrDialog } from 'ts/components/dialogs/blockchain_err_dialog'; +import { LedgerConfigDialog } from 'ts/components/dialogs/ledger_config_dialog'; +import { PortalDisclaimerDialog } from 'ts/components/dialogs/portal_disclaimer_dialog'; +import { EthWrappers } from 'ts/components/eth_wrappers'; +import { AssetPicker } from 'ts/components/generate_order/asset_picker'; +import { BackButton } from 'ts/components/portal/back_button'; +import { Loading } from 'ts/components/portal/loading'; +import { Menu } from 'ts/components/portal/menu'; +import { Section } from 'ts/components/portal/section'; +import { TextHeader } from 'ts/components/portal/text_header'; +import { RelayerIndex } from 'ts/components/relayer_index/relayer_index'; +import { TokenBalances } from 'ts/components/token_balances'; +import { TopBar, TopBarDisplayType } from 'ts/components/top_bar/top_bar'; +import { TradeHistory } from 'ts/components/trade_history/trade_history'; +import { FlashMessage } from 'ts/components/ui/flash_message'; +import { Wallet } from 'ts/components/wallet/wallet'; +import { GenerateOrderForm } from 'ts/containers/generate_order_form'; +import { localStorage } from 'ts/local_storage/local_storage'; +import { trackedTokenStorage } from 'ts/local_storage/tracked_token_storage'; +import { FullscreenMessage } from 'ts/pages/fullscreen_message'; +import { Dispatcher } from 'ts/redux/dispatcher'; +import { + BlockchainErrs, + HashData, + Order, + ProviderType, + ScreenWidths, + TokenByAddress, + TokenVisibility, + WebsitePaths, +} from 'ts/types'; +import { configs } from 'ts/utils/configs'; +import { constants } from 'ts/utils/constants'; +import { Translate } from 'ts/utils/translate'; +import { utils } from 'ts/utils/utils'; + +export interface PortalProps { + blockchainErr: BlockchainErrs; + blockchainIsLoaded: boolean; + dispatcher: Dispatcher; + hashData: HashData; + injectedProviderName: string; + networkId: number; + nodeVersion: string; + orderFillAmount: BigNumber; + providerType: ProviderType; + screenWidth: ScreenWidths; + tokenByAddress: TokenByAddress; + userEtherBalanceInWei: BigNumber; + userAddress: string; + shouldBlockchainErrDialogBeOpen: boolean; + userSuppliedOrderCache: Order; + location: Location; + flashMessage?: string | React.ReactNode; + lastForceTokenStateRefetch: number; + translate: Translate; +} + +interface PortalState { + prevNetworkId: number; + prevNodeVersion: string; + prevUserAddress: string; + prevPathname: string; + isDisclaimerDialogOpen: boolean; + isLedgerDialogOpen: boolean; + tokenManagementState: TokenManagementState; +} + +interface AccountManagementItem { + pathName: string; + headerText: string; + render: () => React.ReactNode; +} + +enum TokenManagementState { + Add = 'Add', + Remove = 'Remove', + None = 'None', +} + +const THROTTLE_TIMEOUT = 100; +const TOP_BAR_HEIGHT = TopBar.heightForDisplayType(TopBarDisplayType.Expanded); +const LEFT_COLUMN_WIDTH = 346; + +const styles: Styles = { + root: { + width: '100%', + height: '100%', + backgroundColor: colors.lightestGrey, + }, + body: { + height: `calc(100vh - ${TOP_BAR_HEIGHT}px)`, + }, + leftColumn: { + width: LEFT_COLUMN_WIDTH, + height: '100%', + }, + scrollContainer: { + height: `calc(100vh - ${TOP_BAR_HEIGHT}px)`, + WebkitOverflowScrolling: 'touch', + overflow: 'auto', + }, +}; + +export class Portal extends React.Component<PortalProps, PortalState> { + private _blockchain: Blockchain; + private _throttledScreenWidthUpdate: () => void; + constructor(props: PortalProps) { + super(props); + this._throttledScreenWidthUpdate = _.throttle(this._updateScreenWidth.bind(this), THROTTLE_TIMEOUT); + const didAcceptPortalDisclaimer = localStorage.getItemIfExists(constants.LOCAL_STORAGE_KEY_ACCEPT_DISCLAIMER); + const hasAcceptedDisclaimer = + !_.isUndefined(didAcceptPortalDisclaimer) && !_.isEmpty(didAcceptPortalDisclaimer); + this.state = { + prevNetworkId: this.props.networkId, + prevNodeVersion: this.props.nodeVersion, + prevUserAddress: this.props.userAddress, + prevPathname: this.props.location.pathname, + isDisclaimerDialogOpen: !hasAcceptedDisclaimer, + tokenManagementState: TokenManagementState.None, + isLedgerDialogOpen: false, + }; + } + public componentDidMount(): void { + window.addEventListener('resize', this._throttledScreenWidthUpdate); + window.scrollTo(0, 0); + } + public componentWillMount(): void { + this._blockchain = new Blockchain(this.props.dispatcher); + } + public componentWillUnmount(): void { + this._blockchain.destroy(); + window.removeEventListener('resize', this._throttledScreenWidthUpdate); + // We re-set the entire redux state when the portal is unmounted so that when it is re-rendered + // the initialization process always occurs from the same base state. This helps avoid + // initialization inconsistencies (i.e While the portal was unrendered, the user might have + // become disconnected from their backing Ethereum node, changed user accounts, etc...) + this.props.dispatcher.resetState(); + } + public componentWillReceiveProps(nextProps: PortalProps): void { + if (nextProps.networkId !== this.state.prevNetworkId) { + // tslint:disable-next-line:no-floating-promises + this._blockchain.networkIdUpdatedFireAndForgetAsync(nextProps.networkId); + this.setState({ + prevNetworkId: nextProps.networkId, + }); + } + if (nextProps.userAddress !== this.state.prevUserAddress) { + const newUserAddress = _.isEmpty(nextProps.userAddress) ? undefined : nextProps.userAddress; + // tslint:disable-next-line:no-floating-promises + this._blockchain.userAddressUpdatedFireAndForgetAsync(newUserAddress); + this.setState({ + prevUserAddress: nextProps.userAddress, + }); + } + if (nextProps.nodeVersion !== this.state.prevNodeVersion) { + // tslint:disable-next-line:no-floating-promises + this._blockchain.nodeVersionUpdatedFireAndForgetAsync(nextProps.nodeVersion); + } + if (nextProps.location.pathname !== this.state.prevPathname) { + this.setState({ + prevPathname: nextProps.location.pathname, + }); + } + } + public render(): React.ReactNode { + const updateShouldBlockchainErrDialogBeOpen = this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen.bind( + this.props.dispatcher, + ); + const isAssetPickerDialogOpen = this.state.tokenManagementState !== TokenManagementState.None; + const tokenVisibility = + this.state.tokenManagementState === TokenManagementState.Add + ? TokenVisibility.UNTRACKED + : TokenVisibility.TRACKED; + return ( + <div style={styles.root}> + <DocumentTitle title="0x Portal DApp" /> + <TopBar + userAddress={this.props.userAddress} + networkId={this.props.networkId} + injectedProviderName={this.props.injectedProviderName} + onToggleLedgerDialog={this._onToggleLedgerDialog.bind(this)} + dispatcher={this.props.dispatcher} + providerType={this.props.providerType} + blockchainIsLoaded={this.props.blockchainIsLoaded} + location={this.props.location} + blockchain={this._blockchain} + translate={this.props.translate} + displayType={TopBarDisplayType.Expanded} + style={{ backgroundColor: colors.lightestGrey }} + /> + <div id="portal" style={styles.body}> + <Switch> + <Route + path={`${WebsitePaths.Portal}/:route`} + render={this._renderMenuAndAccountManagement.bind(this)} + /> + <Route + exact={true} + path={`${WebsitePaths.Portal}/`} + render={this._renderWalletAndRelayerIndex.bind(this)} + /> + </Switch> + <BlockchainErrDialog + blockchain={this._blockchain} + blockchainErr={this.props.blockchainErr} + isOpen={this.props.shouldBlockchainErrDialogBeOpen} + userAddress={this.props.userAddress} + toggleDialogFn={updateShouldBlockchainErrDialogBeOpen} + networkId={this.props.networkId} + /> + <PortalDisclaimerDialog + isOpen={this.state.isDisclaimerDialogOpen} + onToggleDialog={this._onPortalDisclaimerAccepted.bind(this)} + /> + <FlashMessage dispatcher={this.props.dispatcher} flashMessage={this.props.flashMessage} /> + {this.props.blockchainIsLoaded && ( + <LedgerConfigDialog + providerType={this.props.providerType} + networkId={this.props.networkId} + blockchain={this._blockchain} + dispatcher={this.props.dispatcher} + toggleDialogFn={this._onToggleLedgerDialog.bind(this)} + isOpen={this.state.isLedgerDialogOpen} + /> + )} + <AssetPicker + userAddress={this.props.userAddress} + networkId={this.props.networkId} + blockchain={this._blockchain} + dispatcher={this.props.dispatcher} + isOpen={isAssetPickerDialogOpen} + currentTokenAddress={''} + onTokenChosen={this._onTokenChosen.bind(this)} + tokenByAddress={this.props.tokenByAddress} + tokenVisibility={tokenVisibility} + /> + </div> + </div> + ); + } + private _renderWalletAndRelayerIndex(): React.ReactNode { + return <PortalLayout left={this._renderWallet()} right={this._renderRelayerIndexSection()} />; + } + private _renderMenuAndAccountManagement(routeComponentProps: RouteComponentProps<any>): React.ReactNode { + return <PortalLayout left={this._renderMenu(routeComponentProps)} right={this._renderAccountManagement()} />; + } + private _renderMenu(routeComponentProps: RouteComponentProps<any>): React.ReactNode { + return ( + <Section + header={<BackButton to={`${WebsitePaths.Portal}`} labelText="back to Relayers" />} + body={<Menu selectedPath={routeComponentProps.location.pathname} />} + /> + ); + } + private _renderWallet(): React.ReactNode { + const allTokens = _.values(this.props.tokenByAddress); + const trackedTokens = _.filter(allTokens, t => t.isTracked); + return ( + <Section + header={<TextHeader labelText="Your Account" />} + body={ + <Wallet + userAddress={this.props.userAddress} + networkId={this.props.networkId} + blockchain={this._blockchain} + blockchainIsLoaded={this.props.blockchainIsLoaded} + blockchainErr={this.props.blockchainErr} + dispatcher={this.props.dispatcher} + tokenByAddress={this.props.tokenByAddress} + trackedTokens={trackedTokens} + userEtherBalanceInWei={this.props.userEtherBalanceInWei} + lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch} + injectedProviderName={this.props.injectedProviderName} + providerType={this.props.providerType} + onToggleLedgerDialog={this._onToggleLedgerDialog.bind(this)} + onAddToken={this._onAddToken.bind(this)} + onRemoveToken={this._onRemoveToken.bind(this)} + /> + } + /> + ); + } + private _renderAccountManagement(): React.ReactNode { + const accountManagementItems: AccountManagementItem[] = [ + { + pathName: `${WebsitePaths.Portal}/weth`, + headerText: 'Wrapped ETH', + render: this._renderEthWrapper.bind(this), + }, + { + pathName: `${WebsitePaths.Portal}/account`, + headerText: 'Your Account', + render: this._renderTokenBalances.bind(this), + }, + { + pathName: `${WebsitePaths.Portal}/trades`, + headerText: 'Trade History', + render: this._renderTradeHistory.bind(this), + }, + { + pathName: `${WebsitePaths.Portal}/direct`, + headerText: 'Trade Direct', + render: this._renderTradeDirect.bind(this), + }, + ]; + return ( + <Switch> + {_.map(accountManagementItems, item => { + return <Route path={item.pathName} render={this._renderAccountManagementItem.bind(this, item)} />; + })}} + <Route render={this._renderNotFoundMessage.bind(this)} /> + </Switch> + ); + } + private _renderAccountManagementItem(item: AccountManagementItem): React.ReactNode { + return ( + <Section + header={<TextHeader labelText={item.headerText} />} + body={<Loading isLoading={!this.props.blockchainIsLoaded} content={item.render()} />} + /> + ); + } + private _renderEthWrapper(): React.ReactNode { + return ( + <EthWrappers + networkId={this.props.networkId} + blockchain={this._blockchain} + dispatcher={this.props.dispatcher} + tokenByAddress={this.props.tokenByAddress} + userAddress={this.props.userAddress} + userEtherBalanceInWei={this.props.userEtherBalanceInWei} + lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch} + /> + ); + } + private _renderTradeHistory(): React.ReactNode { + return ( + <TradeHistory + tokenByAddress={this.props.tokenByAddress} + userAddress={this.props.userAddress} + networkId={this.props.networkId} + /> + ); + } + private _renderTradeDirect(match: any, location: Location, history: History): React.ReactNode { + return ( + <GenerateOrderForm + blockchain={this._blockchain} + hashData={this.props.hashData} + dispatcher={this.props.dispatcher} + /> + ); + } + private _renderTokenBalances(): React.ReactNode { + const allTokens = _.values(this.props.tokenByAddress); + const trackedTokens = _.filter(allTokens, t => t.isTracked); + return ( + <TokenBalances + blockchain={this._blockchain} + blockchainErr={this.props.blockchainErr} + blockchainIsLoaded={this.props.blockchainIsLoaded} + dispatcher={this.props.dispatcher} + screenWidth={this.props.screenWidth} + tokenByAddress={this.props.tokenByAddress} + trackedTokens={trackedTokens} + userAddress={this.props.userAddress} + userEtherBalanceInWei={this.props.userEtherBalanceInWei} + networkId={this.props.networkId} + lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch} + /> + ); + } + private _renderRelayerIndexSection(): React.ReactNode { + return ( + <Section + header={<TextHeader labelText="Explore 0x Relayers" />} + body={<RelayerIndex networkId={this.props.networkId} />} + /> + ); + } + private _renderNotFoundMessage(): React.ReactNode { + return ( + <FullscreenMessage + headerText="404 Not Found" + bodyText="Hm... looks like we couldn't find what you are looking for." + /> + ); + } + private _onTokenChosen(tokenAddress: string): void { + if (_.isEmpty(tokenAddress)) { + this.setState({ + tokenManagementState: TokenManagementState.None, + }); + return; + } + const token = this.props.tokenByAddress[tokenAddress]; + const isDefaultTrackedToken = _.includes(configs.DEFAULT_TRACKED_TOKEN_SYMBOLS, token.symbol); + if (this.state.tokenManagementState === TokenManagementState.Remove && !isDefaultTrackedToken) { + if (token.isRegistered) { + // Remove the token from tracked tokens + const newToken = { + ...token, + isTracked: false, + }; + this.props.dispatcher.updateTokenByAddress([newToken]); + } else { + this.props.dispatcher.removeTokenToTokenByAddress(token); + } + trackedTokenStorage.removeTrackedToken(this.props.userAddress, this.props.networkId, tokenAddress); + } else if (isDefaultTrackedToken) { + this.props.dispatcher.showFlashMessage(`Cannot remove ${token.name} because it's a default token`); + } + this.setState({ + tokenManagementState: TokenManagementState.None, + }); + } + private _onToggleLedgerDialog(): void { + this.setState({ + isLedgerDialogOpen: !this.state.isLedgerDialogOpen, + }); + } + private _onAddToken(): void { + this.setState({ + tokenManagementState: TokenManagementState.Add, + }); + } + private _onRemoveToken(): void { + this.setState({ + tokenManagementState: TokenManagementState.Remove, + }); + } + private _onPortalDisclaimerAccepted(): void { + localStorage.setItem(constants.LOCAL_STORAGE_KEY_ACCEPT_DISCLAIMER, 'set'); + this.setState({ + isDisclaimerDialogOpen: false, + }); + } + private _updateScreenWidth(): void { + const newScreenWidth = utils.getScreenWidth(); + this.props.dispatcher.updateScreenWidth(newScreenWidth); + } +} + +interface PortalLayoutProps { + left: React.ReactNode; + right: React.ReactNode; +} +const PortalLayout = (props: PortalLayoutProps) => { + return ( + <div className="sm-flex flex-center"> + <div className="flex-last px3"> + <div style={styles.leftColumn}>{props.left}</div> + </div> + <div className="flex-auto px3" style={styles.scrollContainer}> + {props.right} + </div> + </div> + ); +}; // tslint:disable:max-file-line-count diff --git a/packages/website/ts/components/portal/section.tsx b/packages/website/ts/components/portal/section.tsx new file mode 100644 index 000000000..9b172aae0 --- /dev/null +++ b/packages/website/ts/components/portal/section.tsx @@ -0,0 +1,15 @@ +import { Styles } from '@0xproject/react-shared'; +import * as React from 'react'; + +export interface SectionProps { + header: React.ReactNode; + body: React.ReactNode; +} +export const Section = (props: SectionProps) => { + return ( + <div className="flex flex-column" style={{ height: '100%' }}> + {props.header} + <div className="flex-auto">{props.body}</div> + </div> + ); +}; diff --git a/packages/website/ts/components/portal/text_header.tsx b/packages/website/ts/components/portal/text_header.tsx new file mode 100644 index 000000000..4aabd47d0 --- /dev/null +++ b/packages/website/ts/components/portal/text_header.tsx @@ -0,0 +1,21 @@ +import { Styles } from '@0xproject/react-shared'; +import * as React from 'react'; + +export interface TextHeaderProps { + labelText: string; +} + +const styles: Styles = { + title: { + fontWeight: 'bold', + fontSize: 20, + }, +}; + +export const TextHeader = (props: TextHeaderProps) => { + return ( + <div className="py3" style={styles.title}> + {props.labelText} + </div> + ); +}; diff --git a/packages/website/ts/components/redirecter.tsx b/packages/website/ts/components/redirecter.tsx index 2e705af58..629522bbb 100644 --- a/packages/website/ts/components/redirecter.tsx +++ b/packages/website/ts/components/redirecter.tsx @@ -5,6 +5,6 @@ interface RedirecterProps { location: string; } -export function Redirecter(props: RedirecterProps) { +export function Redirecter(props: RedirecterProps): void { window.location.href = constants.URL_ANGELLIST; } 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 0c4b2841c..5964dcd56 100644 --- a/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx +++ b/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx @@ -12,38 +12,6 @@ export interface RelayerGridTileProps { networkId: number; } -// TODO: Get top tokens and headerurl from remote -const headerUrl = '/images/og_image.png'; -const topTokens = [ - { - address: '0x1dad4783cf3fe3085c1426157ab175a6119a04ba', - decimals: 18, - iconUrl: '/images/token_icons/makerdao.png', - isRegistered: true, - isTracked: true, - name: 'Maker DAO', - symbol: 'MKR', - }, - { - address: '0x323b5d4c32345ced77393b3530b1eed0f346429d', - decimals: 18, - iconUrl: '/images/token_icons/melon.png', - isRegistered: true, - isTracked: true, - name: 'Melon Token', - symbol: 'MLN', - }, - { - address: '0xb18845c260f680d5b9d84649638813e342e4f8c9', - decimals: 18, - iconUrl: '/images/token_icons/augur.png', - isRegistered: true, - isTracked: true, - name: 'Augur Reputation Token', - symbol: 'REP', - }, -]; - const styles: Styles = { root: { backgroundColor: colors.white, @@ -68,6 +36,9 @@ const styles: Styles = { borderBottomLeftRadius: 4, borderTopRightRadius: 4, borderTopLeftRadius: 4, + borderWidth: 1, + borderStyle: 'solid', + borderColor: colors.walletBorder, }, body: { paddingLeft: 6, @@ -76,7 +47,7 @@ const styles: Styles = { width: '100%', boxSizing: 'border-box', }, - dailyTradeVolumeLabel: { + weeklyTradeVolumeLabel: { fontSize: 14, color: colors.mediumBlue, }, @@ -91,20 +62,29 @@ const styles: Styles = { }, }; +const FALLBACK_IMG_SRC = '/images/landing/hero_chip_image.png'; + export const RelayerGridTile: React.StatelessComponent<RelayerGridTileProps> = (props: RelayerGridTileProps) => { + const link = props.relayerInfo.appUrl || props.relayerInfo.url; return ( <GridTile style={styles.root}> <div style={styles.innerDiv}> - <img src={headerUrl} style={styles.header} /> + <a href={link} target="_blank" style={{ textDecoration: 'none' }}> + <ImgWithFallback + src={props.relayerInfo.headerImgUrl} + fallbackSrc={FALLBACK_IMG_SRC} + style={styles.header} + /> + </a> <div style={styles.body}> <div className="py1" style={styles.relayerNameLabel}> {props.relayerInfo.name} </div> - <div style={styles.dailyTradeVolumeLabel}>{props.relayerInfo.dailyTxnVolume}</div> + <div style={styles.weeklyTradeVolumeLabel}>{props.relayerInfo.weeklyTxnVolume}</div> <div className="py1" style={styles.subLabel}> - Daily Trade Volume + Weekly Trade Volume </div> - <TopTokens tokens={topTokens} networkId={props.networkId} /> + <TopTokens tokens={props.relayerInfo.topTokens} networkId={props.networkId} /> <div className="py1" style={styles.subLabel}> Top tokens </div> @@ -113,3 +93,32 @@ export const RelayerGridTile: React.StatelessComponent<RelayerGridTileProps> = ( </GridTile> ); }; + +interface ImgWithFallbackProps { + src?: string; + fallbackSrc: string; + style: React.CSSProperties; +} +interface ImgWithFallbackState { + imageLoadFailed: boolean; +} +class ImgWithFallback extends React.Component<ImgWithFallbackProps, ImgWithFallbackState> { + constructor(props: ImgWithFallbackProps) { + super(props); + this.state = { + imageLoadFailed: false, + }; + } + public render(): React.ReactNode { + if (this.state.imageLoadFailed || _.isUndefined(this.props.src)) { + return <img src={this.props.fallbackSrc} style={this.props.style} />; + } else { + return <img src={this.props.src} onError={this._onError.bind(this)} style={this.props.style} />; + } + } + private _onError(): void { + this.setState({ + imageLoadFailed: true, + }); + } +} diff --git a/packages/website/ts/components/relayer_index/relayer_index.tsx b/packages/website/ts/components/relayer_index/relayer_index.tsx index 50760c32d..b327c9817 100644 --- a/packages/website/ts/components/relayer_index/relayer_index.tsx +++ b/packages/website/ts/components/relayer_index/relayer_index.tsx @@ -1,5 +1,7 @@ import { colors, Styles } from '@0xproject/react-shared'; import * as _ from 'lodash'; +import CircularProgress from 'material-ui/CircularProgress'; +import FlatButton from 'material-ui/FlatButton'; import { GridList } from 'material-ui/GridList'; import * as React from 'react'; @@ -32,9 +34,9 @@ const styles: Styles = { }, }; -const CELL_HEIGHT = 260; +const CELL_HEIGHT = 290; const NUMBER_OF_COLUMNS = 4; -const GRID_PADDING = 16; +const GRID_PADDING = 20; export class RelayerIndex extends React.Component<RelayerIndexProps, RelayerIndexState> { private _isUnmounted: boolean; @@ -46,16 +48,27 @@ export class RelayerIndex extends React.Component<RelayerIndexProps, RelayerInde error: undefined, }; } - public componentWillMount() { + public componentWillMount(): void { // tslint:disable-next-line:no-floating-promises this._fetchRelayerInfosAsync(); } - public componentWillUnmount() { + public componentWillUnmount(): void { this._isUnmounted = true; } - public render() { + public render(): React.ReactNode { const readyToRender = _.isUndefined(this.state.error) && !_.isUndefined(this.state.relayerInfos); - if (readyToRender) { + if (!readyToRender) { + return ( + // TODO: consolidate this loading component with the one in portal + <div className="center"> + {_.isUndefined(this.state.error) ? ( + <CircularProgress size={40} thickness={5} /> + ) : ( + <Retry onRetry={this._fetchRelayerInfosAsync.bind(this)} /> + )} + </div> + ); + } else { return ( <div style={styles.root}> <GridList @@ -64,23 +77,22 @@ export class RelayerIndex extends React.Component<RelayerIndexProps, RelayerInde padding={GRID_PADDING} style={styles.gridList} > - {this.state.relayerInfos.map((relayerInfo: WebsiteBackendRelayerInfo) => ( - <RelayerGridTile - key={relayerInfo.id} - relayerInfo={relayerInfo} - networkId={this.props.networkId} - /> + {this.state.relayerInfos.map((relayerInfo: WebsiteBackendRelayerInfo, index) => ( + <RelayerGridTile key={index} relayerInfo={relayerInfo} networkId={this.props.networkId} /> ))} </GridList> </div> ); - } else { - // TODO: loading and error states with a scrolling container - return null; } } private async _fetchRelayerInfosAsync(): Promise<void> { try { + if (!this._isUnmounted) { + this.setState({ + relayerInfos: undefined, + error: undefined, + }); + } const relayerInfos = await backendClient.getRelayerInfosAsync(); if (!this._isUnmounted) { this.setState({ @@ -96,3 +108,31 @@ export class RelayerIndex extends React.Component<RelayerIndexProps, RelayerInde } } } + +interface RetryProps { + onRetry: () => void; +} +const Retry = (props: RetryProps) => ( + <div className="clearfix center" style={{ color: colors.black }}> + <div className="mx-auto inline-block align-middle" style={{ lineHeight: '44px', textAlign: 'center' }}> + <div className="h2" style={{ fontFamily: 'Roboto Mono' }}> + Something went wrong. + </div> + <div className="py3"> + <FlatButton + label={'reload'} + backgroundColor={colors.black} + labelStyle={{ + fontSize: 18, + fontFamily: 'Roboto Mono', + fontWeight: 'lighter', + color: colors.white, + textTransform: 'lowercase', + }} + style={{ width: 280, height: 62, borderRadius: 5 }} + onClick={props.onRetry} + /> + </div> + </div> + </div> +); 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 233590b78..03c70c9dd 100644 --- a/packages/website/ts/components/relayer_index/relayer_top_tokens.tsx +++ b/packages/website/ts/components/relayer_index/relayer_top_tokens.tsx @@ -3,10 +3,10 @@ import * as _ from 'lodash'; import * as React from 'react'; import { TokenIcon } from 'ts/components/ui/token_icon'; -import { Token } from 'ts/types'; +import { WebsiteBackendTokenInfo } from 'ts/types'; export interface TopTokensProps { - tokens: Token[]; + tokens: WebsiteBackendTokenInfo[]; networkId: number; } @@ -23,24 +23,57 @@ const styles: Styles = { export const TopTokens: React.StatelessComponent<TopTokensProps> = (props: TopTokensProps) => { return ( <div className="flex"> - {_.map(props.tokens, (token: Token, index: number) => { + {_.map(props.tokens, (tokenInfo: WebsiteBackendTokenInfo, index: number) => { const firstItemStyle = { ...styles.tokenLabel, ...styles.followingTokenLabel }; const style = index !== 0 ? firstItemStyle : styles.tokenLabel; - return ( - <a - key={token.address} - href={tokenLinkFromToken(token, props.networkId)} - target="_blank" - style={style} - > - {token.symbol} - </a> - ); + return <TokenLink tokenInfo={tokenInfo} style={style} networkId={props.networkId} />; })} </div> ); }; -function tokenLinkFromToken(token: Token, networkId: number) { - return sharedUtils.getEtherScanLinkIfExists(token.address, networkId, EtherscanLinkSuffixes.Address); +interface TokenLinkProps { + tokenInfo: WebsiteBackendTokenInfo; + style: React.CSSProperties; + networkId: number; +} +interface TokenLinkState { + isHovering: boolean; +} + +class TokenLink extends React.Component<TokenLinkProps, TokenLinkState> { + constructor(props: TokenLinkProps) { + super(props); + this.state = { + isHovering: false, + }; + } + public render(): React.ReactNode { + const style = { + ...this.props.style, + cursor: 'pointer', + opacity: this.state.isHovering ? 0.5 : 1, + }; + return ( + <a + key={this.props.tokenInfo.address} + href={tokenLinkFromToken(this.props.tokenInfo, this.props.networkId)} + target="_blank" + style={style} + onMouseEnter={this._onToggleHover.bind(this, true)} + onMouseLeave={this._onToggleHover.bind(this, false)} + > + {this.props.tokenInfo.symbol} + </a> + ); + } + private _onToggleHover(isHovering: boolean): void { + this.setState({ + isHovering, + }); + } +} + +function tokenLinkFromToken(tokenInfo: WebsiteBackendTokenInfo, networkId: number): string { + return sharedUtils.getEtherScanLinkIfExists(tokenInfo.address, networkId, EtherscanLinkSuffixes.Address); } diff --git a/packages/website/ts/components/send_button.tsx b/packages/website/ts/components/send_button.tsx index 79c103e05..8486dbd8b 100644 --- a/packages/website/ts/components/send_button.tsx +++ b/packages/website/ts/components/send_button.tsx @@ -33,7 +33,7 @@ export class SendButton extends React.Component<SendButtonProps, SendButtonState isSending: false, }; } - public render() { + public render(): React.ReactNode { const labelStyle = this.state.isSending ? { fontSize: 10 } : {}; return ( <div> @@ -57,12 +57,12 @@ export class SendButton extends React.Component<SendButtonProps, SendButtonState </div> ); } - private _toggleSendDialog() { + private _toggleSendDialog(): void { this.setState({ isSendDialogVisible: !this.state.isSendDialogVisible, }); } - private async _onSendAmountSelectedAsync(recipient: string, value: BigNumber) { + private async _onSendAmountSelectedAsync(recipient: string, value: BigNumber): Promise<void> { this.setState({ isSending: true, }); diff --git a/packages/website/ts/components/sidebar_header.tsx b/packages/website/ts/components/sidebar_header.tsx index a0ea869fb..bf46caad9 100644 --- a/packages/website/ts/components/sidebar_header.tsx +++ b/packages/website/ts/components/sidebar_header.tsx @@ -12,7 +12,7 @@ interface SidebarHeaderProps { interface SidebarHeaderState {} export class SidebarHeader extends React.Component<SidebarHeaderProps, SidebarHeaderState> { - public render() { + public render(): React.ReactNode { return ( <div className="pt2 md-px1 sm-px2" style={{ color: colors.black, paddingBottom: 18 }}> <div className="flex" style={{ fontSize: 25 }}> diff --git a/packages/website/ts/components/token_balances.tsx b/packages/website/ts/components/token_balances.tsx index 618b5fe8f..83948e5c2 100644 --- a/packages/website/ts/components/token_balances.tsx +++ b/packages/website/ts/components/token_balances.tsx @@ -99,15 +99,15 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala trackedTokenStateByAddress: initialTrackedTokenStateByAddress, }; } - public componentWillMount() { + public componentWillMount(): void { const trackedTokenAddresses = _.keys(this.state.trackedTokenStateByAddress); // tslint:disable-next-line:no-floating-promises this._fetchBalancesAndAllowancesAsync(trackedTokenAddresses); } - public componentWillUnmount() { + public componentWillUnmount(): void { this._isUnmounted = true; } - public componentWillReceiveProps(nextProps: TokenBalancesProps) { + public componentWillReceiveProps(nextProps: TokenBalancesProps): void { if (nextProps.userEtherBalanceInWei !== this.props.userEtherBalanceInWei) { if (this.state.isBalanceSpinnerVisible) { const receivedAmountInWei = nextProps.userEtherBalanceInWei.minus(this.props.userEtherBalanceInWei); @@ -153,10 +153,10 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala this._fetchBalancesAndAllowancesAsync(newTokenAddresses); } } - public componentDidMount() { + public componentDidMount(): void { window.scrollTo(0, 0); } - public render() { + public render(): React.ReactNode { const errorDialogActions = [ <FlatButton key="errorOkBtn" @@ -294,7 +294,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala </div> ); } - private _renderTokenTableRows() { + private _renderTokenTableRows(): React.ReactNode { if (!this.props.blockchainIsLoaded || this.props.blockchainErr !== BlockchainErrs.NoError) { return ''; } @@ -313,7 +313,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala ); return tableRows; } - private _renderTokenRow(tokenColSpan: number, actionPaddingX: number, token: Token) { + private _renderTokenRow(tokenColSpan: number, actionPaddingX: number, token: Token): React.ReactNode { const tokenState = this.state.trackedTokenStateByAddress[token.address]; const tokenLink = sharedUtils.getEtherScanLinkIfExists( token.address, @@ -411,7 +411,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala </TableRow> ); } - private _onAssetTokenPicked(tokenAddress: string) { + private _onAssetTokenPicked(tokenAddress: string): void { if (_.isEmpty(tokenAddress)) { this.setState({ isTokenPickerOpen: false, @@ -439,16 +439,16 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala isTokenPickerOpen: false, }); } - private _onSendFailed() { + private _onSendFailed(): void { this.setState({ errorType: BalanceErrs.sendFailed, }); } - private _renderAmount(amount: BigNumber, decimals: number) { + private _renderAmount(amount: BigNumber, decimals: number): React.ReactNode { const unitAmount = ZeroEx.toUnitAmount(amount, decimals); return unitAmount.toNumber().toFixed(configs.AMOUNT_DISPLAY_PRECSION); } - private _renderTokenName(token: Token) { + private _renderTokenName(token: Token): React.ReactNode { const tooltipId = `tooltip-${token.address}`; return ( <div className="flex"> @@ -460,7 +460,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala </div> ); } - private _renderErrorDialogBody() { + private _renderErrorDialogBody(): React.ReactNode { switch (this.state.errorType) { case BalanceErrs.incorrectNetworkForFaucet: return ( @@ -499,7 +499,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala throw utils.spawnSwitchErr('errorType', this.state.errorType); } } - private _onErrorOccurred(errorType: BalanceErrs) { + private _onErrorOccurred(errorType: BalanceErrs): void { this.setState({ errorType, }); @@ -577,24 +577,24 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala } return true; } - private _onErrorDialogToggle(isOpen: boolean) { + private _onErrorDialogToggle(isOpen: boolean): void { this.setState({ errorType: undefined, }); } - private _onAddTokenClicked() { + private _onAddTokenClicked(): void { this.setState({ isTokenPickerOpen: true, isAddingToken: true, }); } - private _onRemoveTokenClicked() { + private _onRemoveTokenClicked(): void { this.setState({ isTokenPickerOpen: true, isAddingToken: false, }); } - private async _startPollingZrxBalanceAsync() { + private async _startPollingZrxBalanceAsync(): Promise<void> { const tokens = _.values(this.props.tokenByAddress); const zrxToken = _.find(tokens, t => t.symbol === ZRX_TOKEN_SYMBOL); @@ -609,7 +609,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala isZRXSpinnerVisible: false, }); } - private async _fetchBalancesAndAllowancesAsync(tokenAddresses: string[]) { + private async _fetchBalancesAndAllowancesAsync(tokenAddresses: string[]): Promise<void> { const trackedTokenStateByAddress = this.state.trackedTokenStateByAddress; const userAddressIfExists = _.isEmpty(this.props.userAddress) ? undefined : this.props.userAddress; for (const tokenAddress of tokenAddresses) { @@ -629,7 +629,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala }); } } - private _getInitialTrackedTokenStateByAddress(trackedTokens: Token[]) { + private _getInitialTrackedTokenStateByAddress(trackedTokens: Token[]): TokenStateByAddress { const trackedTokenStateByAddress: TokenStateByAddress = {}; _.each(trackedTokens, token => { trackedTokenStateByAddress[token.address] = { @@ -640,7 +640,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala }); return trackedTokenStateByAddress; } - private async _refetchTokenStateAsync(tokenAddress: string) { + private async _refetchTokenStateAsync(tokenAddress: string): Promise<void> { const userAddressIfExists = _.isEmpty(this.props.userAddress) ? undefined : this.props.userAddress; const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( userAddressIfExists, diff --git a/packages/website/ts/components/top_bar/provider_display.tsx b/packages/website/ts/components/top_bar/provider_display.tsx index 79e7c3e2d..bebaa5341 100644 --- a/packages/website/ts/components/top_bar/provider_display.tsx +++ b/packages/website/ts/components/top_bar/provider_display.tsx @@ -1,4 +1,4 @@ -import { colors } from '@0xproject/react-shared'; +import { colors, Styles } from '@0xproject/react-shared'; import * as _ from 'lodash'; import RaisedButton from 'material-ui/RaisedButton'; import * as React from 'react'; @@ -11,9 +11,9 @@ import { ProviderType } from 'ts/types'; import { constants } from 'ts/utils/constants'; import { utils } from 'ts/utils/utils'; -const IDENTICON_DIAMETER = 32; +const ROOT_HEIGHT = 24; -interface ProviderDisplayProps { +export interface ProviderDisplayProps { dispatcher: Dispatcher; userAddress: string; networkId: number; @@ -25,8 +25,17 @@ interface ProviderDisplayProps { interface ProviderDisplayState {} +const styles: Styles = { + root: { + height: ROOT_HEIGHT, + backgroundColor: colors.white, + borderRadius: ROOT_HEIGHT, + boxShadow: `0px 4px 6px ${colors.walletBoxShadow}`, + }, +}; + export class ProviderDisplay extends React.Component<ProviderDisplayProps, ProviderDisplayState> { - public render() { + public render(): React.ReactNode { const isAddressAvailable = !_.isEmpty(this.props.userAddress); const isExternallyInjectedProvider = this.props.providerType === ProviderType.Injected && this.props.injectedProviderName !== '0x Public'; @@ -42,21 +51,20 @@ export class ProviderDisplay extends React.Component<ProviderDisplayProps, Provi : 'Connect a wallet'; const providerTitle = this.props.providerType === ProviderType.Injected ? injectedProviderName : 'Ledger Nano S'; + const isProviderMetamask = providerTitle === constants.PROVIDER_NAME_METAMASK; const hoverActiveNode = ( - <div className="flex right lg-pr0 md-pr2 sm-pr2" style={{ paddingTop: 16 }}> + <div className="flex right lg-pr0 md-pr2 sm-pr2 p1" style={styles.root}> <div> - <Identicon address={this.props.userAddress} diameter={IDENTICON_DIAMETER} /> - </div> - <div style={{ marginLeft: 12, paddingTop: 1 }}> - <div style={{ fontSize: 12, color: colors.amber800 }}>{providerTitle}</div> - <div style={{ fontSize: 14 }}>{displayAddress}</div> + <Identicon address={this.props.userAddress} diameter={ROOT_HEIGHT} /> </div> - <div - style={{ borderLeft: `1px solid ${colors.grey300}`, marginLeft: 17, paddingTop: 1 }} - className="px2" - > - <i style={{ fontSize: 30, color: colors.grey300 }} className="zmdi zmdi zmdi-chevron-down" /> + <div style={{ marginLeft: 12, paddingTop: 3 }}> + <div style={{ fontSize: 16, color: colors.darkGrey }}>{displayAddress}</div> </div> + {isProviderMetamask && ( + <div style={{ marginLeft: 16 }}> + <img src="/images/metamask_icon.png" style={{ width: ROOT_HEIGHT, height: ROOT_HEIGHT }} /> + </div> + )} </div> ); const hasInjectedProvider = @@ -75,7 +83,7 @@ export class ProviderDisplay extends React.Component<ProviderDisplayProps, Provi </div> ); } - public renderPopoverContent(hasInjectedProvider: boolean, hasLedgerProvider: boolean) { + public renderPopoverContent(hasInjectedProvider: boolean, hasLedgerProvider: boolean): React.ReactNode { if (hasInjectedProvider || hasLedgerProvider) { return ( <ProviderPicker diff --git a/packages/website/ts/components/top_bar/provider_picker.tsx b/packages/website/ts/components/top_bar/provider_picker.tsx index b986da873..1ecb8389c 100644 --- a/packages/website/ts/components/top_bar/provider_picker.tsx +++ b/packages/website/ts/components/top_bar/provider_picker.tsx @@ -19,7 +19,7 @@ interface ProviderPickerProps { interface ProviderPickerState {} export class ProviderPicker extends React.Component<ProviderPickerProps, ProviderPickerState> { - public render() { + public render(): React.ReactNode { const isLedgerSelected = this.props.providerType === ProviderType.Ledger; const menuStyle = { padding: 10, @@ -46,7 +46,7 @@ export class ProviderPicker extends React.Component<ProviderPickerProps, Provide </div> ); } - private _renderLabel(title: string, shouldShowNetwork: boolean) { + private _renderLabel(title: string, shouldShowNetwork: boolean): React.ReactNode { const label = ( <div className="flex"> <div style={{ fontSize: 14 }}>{title}</div> @@ -55,7 +55,7 @@ export class ProviderPicker extends React.Component<ProviderPickerProps, Provide ); return label; } - private _renderNetwork() { + private _renderNetwork(): React.ReactNode { const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId]; return ( <div className="flex" style={{ marginTop: 1 }}> @@ -70,7 +70,7 @@ export class ProviderPicker extends React.Component<ProviderPickerProps, Provide </div> ); } - private _onProviderRadioChanged(value: string) { + private _onProviderRadioChanged(value: string): void { if (value === ProviderType.Ledger) { this.props.onToggleLedgerDialog(); } else { diff --git a/packages/website/ts/components/top_bar/top_bar.tsx b/packages/website/ts/components/top_bar/top_bar.tsx index 13351dcdc..5fde007d6 100644 --- a/packages/website/ts/components/top_bar/top_bar.tsx +++ b/packages/website/ts/components/top_bar/top_bar.tsx @@ -8,7 +8,7 @@ import * as React from 'react'; import { Link } from 'react-router-dom'; import ReactTooltip = require('react-tooltip'); import { Blockchain } from 'ts/blockchain'; -import { PortalMenu } from 'ts/components/portal_menu'; +import { LegacyPortalMenu } from 'ts/components/legacy_portal/legacy_portal_menu'; import { SidebarHeader } from 'ts/components/sidebar_header'; import { ProviderDisplay } from 'ts/components/top_bar/provider_display'; import { TopBarMenuItem } from 'ts/components/top_bar/top_bar_menu_item'; @@ -19,7 +19,12 @@ import { Deco, Key, ProviderType, WebsiteLegacyPaths, WebsitePaths } from 'ts/ty import { constants } from 'ts/utils/constants'; import { Translate } from 'ts/utils/translate'; -interface TopBarProps { +export enum TopBarDisplayType { + Default, + Expanded, +} + +export interface TopBarProps { userAddress?: string; networkId?: number; injectedProviderName?: string; @@ -34,7 +39,7 @@ interface TopBarProps { availableDocVersions?: string[]; menu?: DocsMenu; menuSubsectionsBySection?: MenuSubsectionsBySection; - shouldFullWidth?: boolean; + displayType?: TopBarDisplayType; docsInfo?: DocsInfo; style?: React.CSSProperties; isNightVersion?: boolean; @@ -47,17 +52,8 @@ interface TopBarState { } const styles: Styles = { - address: { - marginRight: 12, - overflow: 'hidden', - paddingTop: 4, - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', - width: 70, - }, topBar: { - backgroundcolor: colors.white, - height: 59, + backgroundColor: colors.white, width: '100%', position: 'relative', top: 0, @@ -78,22 +74,30 @@ const styles: Styles = { }, }; +const DEFAULT_HEIGHT = 59; +const EXPANDED_HEIGHT = 75; + export class TopBar extends React.Component<TopBarProps, TopBarState> { public static defaultProps: Partial<TopBarProps> = { - shouldFullWidth: false, + displayType: TopBarDisplayType.Default, style: {}, isNightVersion: false, }; + public static heightForDisplayType(displayType: TopBarDisplayType): number { + const result = displayType === TopBarDisplayType.Expanded ? EXPANDED_HEIGHT : DEFAULT_HEIGHT; + return result + 1; + } constructor(props: TopBarProps) { super(props); this.state = { isDrawerOpen: false, }; } - public render() { + public render(): React.ReactNode { const isNightVersion = this.props.isNightVersion; - const isFullWidthPage = this.props.shouldFullWidth; - const parentClassNames = `flex mx-auto ${isFullWidthPage ? 'pl2' : 'max-width-4'}`; + const isExpandedDisplayType = this.props.displayType === TopBarDisplayType.Expanded; + const parentClassNames = `flex mx-auto ${isExpandedDisplayType ? 'pl3 py1' : 'max-width-4'}`; + const height = isExpandedDisplayType ? EXPANDED_HEIGHT : DEFAULT_HEIGHT; const developerSectionMenuItems = [ <Link key="subMenuItem-zeroEx" to={WebsitePaths.ZeroExJs} className="text-decoration-none"> <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="0x.js" /> @@ -139,10 +143,16 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { primaryText={this.props.translate.get(Key.Web3Wrapper, Deco.CapWords)} /> </Link>, - <Link key="subMenuItem-deployer" to={WebsitePaths.Deployer} className="text-decoration-none"> + <Link key="subMenuItem-order-utils" to={WebsitePaths.OrderUtils} className="text-decoration-none"> <MenuItem style={{ fontSize: styles.menuItem.fontSize }} - primaryText={this.props.translate.get(Key.Deployer, Deco.CapWords)} + primaryText={this.props.translate.get(Key.OrderUtils, Deco.CapWords)} + /> + </Link>, + <Link key="subMenuItem-sol-compiler" to={WebsitePaths.SolCompiler} className="text-decoration-none"> + <MenuItem + style={{ fontSize: styles.menuItem.fontSize }} + primaryText={this.props.translate.get(Key.SolCompiler, Deco.CapWords)} /> </Link>, <Link key="subMenuItem-sol-cov" to={WebsitePaths.SolCov} className="text-decoration-none"> @@ -172,9 +182,11 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { </a>, ]; const bottomBorderStyle = this._shouldDisplayBottomBar() ? styles.bottomBar : {}; - const fullWidthClasses = isFullWidthPage ? 'pr4' : ''; + const fullWidthClasses = isExpandedDisplayType ? 'pr4' : ''; const logoUrl = isNightVersion ? '/images/protocol_logo_white.png' : '/images/protocol_logo_black.png'; - const menuClasses = `col col-${isFullWidthPage ? '4' : '5'} ${fullWidthClasses} lg-pr0 md-pr2 sm-hide xs-hide`; + const menuClasses = `col col-${ + isExpandedDisplayType ? '4' : '5' + } ${fullWidthClasses} lg-pr0 md-pr2 sm-hide xs-hide`; const menuIconStyle = { fontSize: 25, color: isNightVersion ? 'white' : 'black', @@ -191,15 +203,15 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { ); const popoverContent = <Menu style={{ color: colors.darkGrey }}>{developerSectionMenuItems}</Menu>; return ( - <div style={{ ...styles.topBar, ...bottomBorderStyle, ...this.props.style }} className="pb1"> + <div style={{ ...styles.topBar, ...bottomBorderStyle, ...this.props.style, ...{ height } }} className="pb1"> <div className={parentClassNames}> - <div className="col col-2 sm-pl2 md-pl2 lg-pl0" style={{ paddingTop: 15 }}> + <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-${isFullWidthPage ? '8' : '9'} lg-hide md-hide`} /> - <div className={`col col-${isFullWidthPage ? '6' : '5'} sm-hide xs-hide`} /> + <div className={`col col-${isExpandedDisplayType ? '8' : '9'} lg-hide md-hide`} /> + <div className={`col col-${isExpandedDisplayType ? '6' : '5'} sm-hide xs-hide`} /> {!this._isViewingPortal() && ( <div className={menuClasses}> <div className="flex justify-between"> @@ -236,7 +248,7 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { path={`${WebsitePaths.Portal}`} isPrimary={true} style={styles.menuItem} - className={`${isFullWidthPage && 'md-hide'}`} + className={`${isExpandedDisplayType && 'md-hide'}`} isNightVersion={isNightVersion} isExternal={false} /> @@ -244,7 +256,7 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { </div> )} {this.props.blockchainIsLoaded && ( - <div className="sm-hide xs-hide col col-5"> + <div className="sm-hide xs-hide col col-5" style={{ paddingTop: 8, marginRight: 36 }}> <ProviderDisplay dispatcher={this.props.dispatcher} userAddress={this.props.userAddress} @@ -256,7 +268,7 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { /> </div> )} - <div className={`col ${isFullWidthPage ? 'col-2 pl2' : 'col-1'} md-hide lg-hide`}> + <div className={`col ${isExpandedDisplayType ? 'col-2 pl2' : 'col-1'} md-hide lg-hide`}> <div style={menuIconStyle}> <i className="zmdi zmdi-menu" onClick={this._onMenuButtonClick.bind(this)} /> </div> @@ -266,7 +278,7 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { </div> ); } - private _renderDrawer() { + private _renderDrawer(): React.ReactNode { return ( <Drawer open={this.state.isDrawerOpen} @@ -316,10 +328,10 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { </MenuItem> </Link> )} - {!this._isViewingDeployerDocs() && ( - <Link to={WebsitePaths.Deployer} className="text-decoration-none"> + {!this._isViewingSolCompilerDocs() && ( + <Link to={WebsitePaths.SolCompiler} className="text-decoration-none"> <MenuItem className="py2"> - {this.props.translate.get(Key.Deployer, Deco.Cap)}{' '} + {this.props.translate.get(Key.SolCompiler, Deco.Cap)}{' '} {this.props.translate.get(Key.Docs, Deco.Cap)} </MenuItem> </Link> @@ -378,7 +390,7 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { (!this._isViewing0xjsDocs() && !this._isViewingSmartContractsDocs() && !this._isViewingWeb3WrapperDocs() && - !this._isViewingDeployerDocs() && + !this._isViewingSolCompilerDocs() && !this._isViewingJsonSchemasDocs() && !this._isViewingSolCovDocs() && !this._isViewingSubprovidersDocs() && @@ -431,81 +443,67 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { <div className="pl1 py1" style={{ backgroundColor: colors.lightGrey }}> {this.props.translate.get(Key.PortalDApp, Deco.CapWords)} </div> - <PortalMenu menuItemStyle={{ color: 'black' }} onClick={this._onMenuButtonClick.bind(this)} /> - </div> - ); - } - private _renderUser() { - const userAddress = this.props.userAddress; - const identiconDiameter = 26; - return ( - <div className="flex right lg-pr0 md-pr2 sm-pr2" style={{ paddingTop: 16 }}> - <div style={styles.address} data-tip={true} data-for="userAddressTooltip"> - {!_.isEmpty(userAddress) ? userAddress : ''} - </div> - <ReactTooltip id="userAddressTooltip">{userAddress}</ReactTooltip> - <div> - <Identicon address={userAddress} diameter={identiconDiameter} /> - </div> + <LegacyPortalMenu menuItemStyle={{ color: 'black' }} onClick={this._onMenuButtonClick.bind(this)} /> </div> ); } - private _onMenuButtonClick() { + private _onMenuButtonClick(): void { this.setState({ isDrawerOpen: !this.state.isDrawerOpen, }); } - private _isViewingPortal() { + private _isViewingPortal(): boolean { return _.includes(this.props.location.pathname, WebsitePaths.Portal); } - private _isViewingFAQ() { + private _isViewingFAQ(): boolean { return _.includes(this.props.location.pathname, WebsitePaths.FAQ); } - private _isViewing0xjsDocs() { + private _isViewing0xjsDocs(): boolean { return ( _.includes(this.props.location.pathname, WebsitePaths.ZeroExJs) || _.includes(this.props.location.pathname, WebsiteLegacyPaths.ZeroExJs) ); } - private _isViewingConnectDocs() { + private _isViewingConnectDocs(): boolean { return _.includes(this.props.location.pathname, WebsitePaths.Connect); } - private _isViewingSmartContractsDocs() { + private _isViewingSmartContractsDocs(): boolean { return _.includes(this.props.location.pathname, WebsitePaths.SmartContracts); } - private _isViewingWeb3WrapperDocs() { + private _isViewingWeb3WrapperDocs(): boolean { return ( _.includes(this.props.location.pathname, WebsitePaths.Web3Wrapper) || _.includes(this.props.location.pathname, WebsiteLegacyPaths.Web3Wrapper) ); } - private _isViewingDeployerDocs() { - return _.includes(this.props.location.pathname, WebsitePaths.Deployer); + private _isViewingSolCompilerDocs(): boolean { + return _.includes(this.props.location.pathname, WebsitePaths.SolCompiler); } - private _isViewingJsonSchemasDocs() { + private _isViewingJsonSchemasDocs(): boolean { return _.includes(this.props.location.pathname, WebsitePaths.JSONSchemas); } - private _isViewingSolCovDocs() { + private _isViewingSolCovDocs(): boolean { return _.includes(this.props.location.pathname, WebsitePaths.SolCov); } - private _isViewingSubprovidersDocs() { + private _isViewingSubprovidersDocs(): boolean { return _.includes(this.props.location.pathname, WebsitePaths.Subproviders); } - private _isViewingWiki() { + private _isViewingWiki(): boolean { return _.includes(this.props.location.pathname, WebsitePaths.Wiki); } - private _shouldDisplayBottomBar() { + private _shouldDisplayBottomBar(): boolean { return ( this._isViewingWiki() || this._isViewing0xjsDocs() || this._isViewingFAQ() || this._isViewingSmartContractsDocs() || this._isViewingWeb3WrapperDocs() || - this._isViewingDeployerDocs() || + this._isViewingSolCompilerDocs() || this._isViewingJsonSchemasDocs() || this._isViewingSolCovDocs() || this._isViewingSubprovidersDocs() || - this._isViewingConnectDocs() + this._isViewingConnectDocs() || + this._isViewingPortal() ); } } // tslint:disable:max-file-line-count 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 c0e674b17..2e4254cfa 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 @@ -26,7 +26,7 @@ export class TopBarMenuItem extends React.Component<TopBarMenuItemProps, TopBarM className: '', isNightVersion: false, }; - public render() { + public render(): React.ReactNode { const primaryStyles = this.props.isPrimary ? { borderRadius: 4, diff --git a/packages/website/ts/components/track_token_confirmation.tsx b/packages/website/ts/components/track_token_confirmation.tsx index 8c5ba7e6f..294fb8590 100644 --- a/packages/website/ts/components/track_token_confirmation.tsx +++ b/packages/website/ts/components/track_token_confirmation.tsx @@ -15,7 +15,7 @@ interface TrackTokenConfirmationProps { interface TrackTokenConfirmationState {} export class TrackTokenConfirmation extends React.Component<TrackTokenConfirmationProps, TrackTokenConfirmationState> { - public render() { + public render(): React.ReactNode { const isMultipleTokens = this.props.tokens.length > 1; const allTokens = _.values(this.props.tokenByAddress); return ( diff --git a/packages/website/ts/components/trade_history/trade_history.tsx b/packages/website/ts/components/trade_history/trade_history.tsx index 635358627..1ca9d866f 100644 --- a/packages/website/ts/components/trade_history/trade_history.tsx +++ b/packages/website/ts/components/trade_history/trade_history.tsx @@ -28,16 +28,16 @@ export class TradeHistory extends React.Component<TradeHistoryProps, TradeHistor sortedFills, }; } - public componentWillMount() { + public componentWillMount(): void { this._startPollingForFills(); } - public componentWillUnmount() { + public componentWillUnmount(): void { this._stopPollingForFills(); } - public componentDidMount() { + public componentDidMount(): void { window.scrollTo(0, 0); } - public render() { + public render(): React.ReactNode { return ( <div className="lg-px4 md-px4 sm-px2"> <h3>Trade history</h3> @@ -48,7 +48,7 @@ export class TradeHistory extends React.Component<TradeHistoryProps, TradeHistor </div> ); } - private _renderTrades() { + private _renderTrades(): React.ReactNode { const numNonCustomFills = this._numFillsWithoutCustomERC20Tokens(); if (numNonCustomFills === 0) { return this._renderEmptyNotice(); @@ -66,14 +66,14 @@ export class TradeHistory extends React.Component<TradeHistoryProps, TradeHistor ); }); } - private _renderEmptyNotice() { + private _renderEmptyNotice(): React.ReactNode { return ( <Paper className="mt1 p2 mx-auto center" style={{ width: '80%' }}> No filled orders yet. </Paper> ); } - private _numFillsWithoutCustomERC20Tokens() { + private _numFillsWithoutCustomERC20Tokens(): number { let numNonCustomFills = 0; const tokens = _.values(this.props.tokenByAddress); _.each(this.state.sortedFills, fill => { @@ -93,7 +93,7 @@ export class TradeHistory extends React.Component<TradeHistoryProps, TradeHistor }); return numNonCustomFills; } - private _startPollingForFills() { + private _startPollingForFills(): void { this._fillPollingIntervalId = window.setInterval(() => { const sortedFills = this._getSortedFills(); if (!utils.deepEqual(sortedFills, this.state.sortedFills)) { @@ -103,10 +103,10 @@ export class TradeHistory extends React.Component<TradeHistoryProps, TradeHistor } }, FILL_POLLING_INTERVAL); } - private _stopPollingForFills() { + private _stopPollingForFills(): void { clearInterval(this._fillPollingIntervalId); } - private _getSortedFills() { + private _getSortedFills(): Fill[] { const fillsByHash = tradeHistoryStorage.getUserFillsByHash(this.props.userAddress, this.props.networkId); const fills = _.values(fillsByHash); const sortedFills = _.sortBy(fills, [(fill: Fill) => fill.blockTimestamp * -1]); diff --git a/packages/website/ts/components/trade_history/trade_history_item.tsx b/packages/website/ts/components/trade_history/trade_history_item.tsx index dbe72259b..adca4d58c 100644 --- a/packages/website/ts/components/trade_history/trade_history_item.tsx +++ b/packages/website/ts/components/trade_history/trade_history_item.tsx @@ -23,7 +23,7 @@ interface TradeHistoryItemProps { interface TradeHistoryItemState {} export class TradeHistoryItem extends React.Component<TradeHistoryItemProps, TradeHistoryItemState> { - public render() { + public render(): React.ReactNode { const fill = this.props.fill; const tokens = _.values(this.props.tokenByAddress); const takerToken = _.find(tokens, token => { @@ -88,7 +88,7 @@ export class TradeHistoryItem extends React.Component<TradeHistoryItemProps, Tra </Paper> ); } - private _renderAmounts(makerToken: Token, takerToken: Token) { + private _renderAmounts(makerToken: Token, takerToken: Token): React.ReactNode { const fill = this.props.fill; const filledTakerTokenAmountInUnits = ZeroEx.toUnitAmount(fill.filledTakerTokenAmount, takerToken.decimals); const filledMakerTokenAmountInUnits = ZeroEx.toUnitAmount(fill.filledMakerTokenAmount, takerToken.decimals); @@ -136,7 +136,7 @@ export class TradeHistoryItem extends React.Component<TradeHistoryItemProps, Tra </div> ); } - private _renderDate() { + private _renderDate(): React.ReactNode { const blockMoment = moment.unix(this.props.fill.blockTimestamp); if (!blockMoment.isValid()) { return null; @@ -159,7 +159,7 @@ export class TradeHistoryItem extends React.Component<TradeHistoryItemProps, Tra </div> ); } - private _renderAmount(amount: BigNumber, symbol: string, decimals: number) { + private _renderAmount(amount: BigNumber, symbol: string, decimals: number): React.ReactNode { const unitAmount = ZeroEx.toUnitAmount(amount, decimals); return ( <span> diff --git a/packages/website/ts/components/ui/alert.tsx b/packages/website/ts/components/ui/alert.tsx index f81939255..b571d8c1c 100644 --- a/packages/website/ts/components/ui/alert.tsx +++ b/packages/website/ts/components/ui/alert.tsx @@ -7,7 +7,7 @@ interface AlertProps { message: string | React.ReactNode; } -export function Alert(props: AlertProps) { +export const Alert = (props: AlertProps) => { const isAlert = props.type === AlertTypes.ERROR; const errMsgStyles = { background: isAlert ? colors.red200 : colors.lightestGreen, @@ -22,4 +22,4 @@ export function Alert(props: AlertProps) { {props.message} </div> ); -} +}; diff --git a/packages/website/ts/components/ui/copy_icon.tsx b/packages/website/ts/components/ui/copy_icon.tsx index d58e50815..2c2941067 100644 --- a/packages/website/ts/components/ui/copy_icon.tsx +++ b/packages/website/ts/components/ui/copy_icon.tsx @@ -23,14 +23,14 @@ export class CopyIcon extends React.Component<CopyIconProps, CopyIconState> { isHovering: false, }; } - public componentDidUpdate() { + public componentDidUpdate(): void { // Remove tooltip if hover away if (!this.state.isHovering && this._copyTooltipTimeoutId) { clearInterval(this._copyTooltipTimeoutId); this._hideTooltip(); } } - public render() { + public render(): React.ReactNode { return ( <div className="inline-block"> <CopyToClipboard text={this.props.data} onCopy={this._onCopy.bind(this)}> @@ -55,15 +55,15 @@ export class CopyIcon extends React.Component<CopyIconProps, CopyIconState> { </div> ); } - private _setRefToProperty(el: HTMLInputElement) { + private _setRefToProperty(el: HTMLInputElement): void { this._copyable = el; } - private _setHoverState(isHovering: boolean) { + private _setHoverState(isHovering: boolean): void { this.setState({ isHovering, }); } - private _onCopy() { + private _onCopy(): void { if (this._copyTooltipTimeoutId) { clearInterval(this._copyTooltipTimeoutId); } @@ -73,7 +73,7 @@ export class CopyIcon extends React.Component<CopyIconProps, CopyIconState> { this._hideTooltip(); }, tooltipLifespanMs); } - private _hideTooltip() { + private _hideTooltip(): void { ReactTooltip.hide(ReactDOM.findDOMNode(this._copyable)); } } diff --git a/packages/website/ts/components/ui/drop_down.tsx b/packages/website/ts/components/ui/drop_down.tsx index 63b9eec0b..98a495581 100644 --- a/packages/website/ts/components/ui/drop_down.tsx +++ b/packages/website/ts/components/ui/drop_down.tsx @@ -35,15 +35,15 @@ export class DropDown extends React.Component<DropDownProps, DropDownState> { isDropDownOpen: false, }; } - public componentDidMount() { + public componentDidMount(): void { this._popoverCloseCheckIntervalId = window.setInterval(() => { this._checkIfShouldClosePopover(); }, CHECK_CLOSE_POPOVER_INTERVAL_MS); } - public componentWillUnmount() { + public componentWillUnmount(): void { window.clearInterval(this._popoverCloseCheckIntervalId); } - public componentWillReceiveProps(nextProps: DropDownProps) { + public componentWillReceiveProps(nextProps: DropDownProps): void { // HACK: If the popoverContent is updated to a different dimension and the users // mouse is no longer above it, the dropdown can enter an inconsistent state where // it believes the user is still hovering over it. In order to remedy this, we @@ -52,7 +52,7 @@ export class DropDown extends React.Component<DropDownProps, DropDownState> { // dropdowns from having dynamic content. this._onHoverOff(); } - public render() { + public render(): React.ReactNode { return ( <div style={{ ...this.props.style, width: 'fit-content', height: '100%' }} @@ -77,11 +77,11 @@ export class DropDown extends React.Component<DropDownProps, DropDownState> { </div> ); } - private _onHover(event: React.FormEvent<HTMLInputElement>) { + private _onHover(event: React.FormEvent<HTMLInputElement>): void { this._isHovering = true; this._checkIfShouldOpenPopover(event); } - private _checkIfShouldOpenPopover(event: React.FormEvent<HTMLInputElement>) { + private _checkIfShouldOpenPopover(event: React.FormEvent<HTMLInputElement>): void { if (this.state.isDropDownOpen) { return; // noop } @@ -91,16 +91,16 @@ export class DropDown extends React.Component<DropDownProps, DropDownState> { anchorEl: event.currentTarget, }); } - private _onHoverOff() { + private _onHoverOff(): void { this._isHovering = false; } - private _checkIfShouldClosePopover() { + private _checkIfShouldClosePopover(): void { if (!this.state.isDropDownOpen || this._isHovering) { return; // noop } this._closePopover(); } - private _closePopover() { + private _closePopover(): void { this.setState({ isDropDownOpen: false, }); diff --git a/packages/website/ts/components/ui/etherscan_icon.tsx b/packages/website/ts/components/ui/etherscan_icon.tsx index c1154d3d6..1b032c112 100644 --- a/packages/website/ts/components/ui/etherscan_icon.tsx +++ b/packages/website/ts/components/ui/etherscan_icon.tsx @@ -35,6 +35,6 @@ export const EtherScanIcon = (props: EtherScanIconProps) => { ); }; -function renderIcon() { +function renderIcon(): React.ReactNode { return <i style={{ color: colors.amber600 }} className="zmdi zmdi-open-in-new" />; } diff --git a/packages/website/ts/components/ui/fake_text_field.tsx b/packages/website/ts/components/ui/fake_text_field.tsx index 646ae98f6..ac3c30fec 100644 --- a/packages/website/ts/components/ui/fake_text_field.tsx +++ b/packages/website/ts/components/ui/fake_text_field.tsx @@ -21,7 +21,7 @@ interface FakeTextFieldProps { children?: any; } -export function FakeTextField(props: FakeTextFieldProps) { +export const FakeTextField = (props: FakeTextFieldProps) => { return ( <div className="relative"> {props.label !== '' && <InputLabel text={props.label} />} @@ -31,4 +31,4 @@ export function FakeTextField(props: FakeTextFieldProps) { <hr style={styles.hr} /> </div> ); -} +}; diff --git a/packages/website/ts/components/ui/flash_message.tsx b/packages/website/ts/components/ui/flash_message.tsx index 2cb1fc764..2b866676d 100644 --- a/packages/website/ts/components/ui/flash_message.tsx +++ b/packages/website/ts/components/ui/flash_message.tsx @@ -19,7 +19,7 @@ export class FlashMessage extends React.Component<FlashMessageProps, FlashMessag showDurationMs: SHOW_DURATION_MS, bodyStyle: {}, }; - public render() { + public render(): React.ReactNode { if (!_.isUndefined(this.props.flashMessage)) { return ( <Snackbar @@ -34,7 +34,7 @@ export class FlashMessage extends React.Component<FlashMessageProps, FlashMessag return null; } } - private _onClose() { + private _onClose(): void { this.props.dispatcher.hideFlashMessage(); } } diff --git a/packages/website/ts/components/ui/identicon.tsx b/packages/website/ts/components/ui/identicon.tsx index bad6c2a78..83c86a144 100644 --- a/packages/website/ts/components/ui/identicon.tsx +++ b/packages/website/ts/components/ui/identicon.tsx @@ -15,7 +15,7 @@ export class Identicon extends React.Component<IdenticonProps, IdenticonState> { public static defaultProps: Partial<IdenticonProps> = { style: {}, }; - public render() { + public render(): React.ReactNode { let address = this.props.address; if (_.isEmpty(address)) { address = constants.NULL_ADDRESS; diff --git a/packages/website/ts/components/ui/input_label.tsx b/packages/website/ts/components/ui/input_label.tsx index 2506db423..8eda45a5d 100644 --- a/packages/website/ts/components/ui/input_label.tsx +++ b/packages/website/ts/components/ui/input_label.tsx @@ -17,7 +17,7 @@ const styles: Styles = { userSelect: 'none', width: 240, zIndex: 1, - }, + } as React.CSSProperties, }; export const InputLabel = (props: InputLabelProps) => { diff --git a/packages/website/ts/components/ui/lifecycle_raised_button.tsx b/packages/website/ts/components/ui/lifecycle_raised_button.tsx index c85e11884..b06978f16 100644 --- a/packages/website/ts/components/ui/lifecycle_raised_button.tsx +++ b/packages/website/ts/components/ui/lifecycle_raised_button.tsx @@ -42,11 +42,11 @@ export class LifeCycleRaisedButton extends React.Component<LifeCycleRaisedButton buttonState: ButtonState.READY, }; } - public componentWillUnmount() { + public componentWillUnmount(): void { clearTimeout(this._buttonTimeoutId); this._didUnmount = true; } - public render() { + public render(): React.ReactNode { if (this.props.isHidden) { return <span />; } @@ -77,7 +77,7 @@ export class LifeCycleRaisedButton extends React.Component<LifeCycleRaisedButton /> ); } - public async onClickAsync() { + public async onClickAsync(): Promise<void> { this.setState({ buttonState: ButtonState.LOADING, }); diff --git a/packages/website/ts/components/ui/menu_item.tsx b/packages/website/ts/components/ui/menu_item.tsx index 3482f436c..64c0dc49d 100644 --- a/packages/website/ts/components/ui/menu_item.tsx +++ b/packages/website/ts/components/ui/menu_item.tsx @@ -24,7 +24,7 @@ export class MenuItem extends React.Component<MenuItemProps, MenuItemState> { isHovering: false, }; } - public render() { + public render(): React.ReactNode { const menuItemStyles = { cursor: 'pointer', opacity: this.state.isHovering ? 0.5 : 1, @@ -43,7 +43,7 @@ export class MenuItem extends React.Component<MenuItemProps, MenuItemState> { </Link> ); } - private _onToggleHover(isHovering: boolean) { + private _onToggleHover(isHovering: boolean): void { this.setState({ isHovering, }); diff --git a/packages/website/ts/components/ui/party.tsx b/packages/website/ts/components/ui/party.tsx index 3d94903d1..0d86a6db5 100644 --- a/packages/website/ts/components/ui/party.tsx +++ b/packages/website/ts/components/ui/party.tsx @@ -27,7 +27,7 @@ export class Party extends React.Component<PartyProps, PartyState> { identiconStyle: {}, identiconDiameter: IDENTICON_DIAMETER, }; - public render() { + public render(): React.ReactNode { const label = this.props.label; const address = this.props.address; const identiconDiameter = this.props.identiconDiameter; diff --git a/packages/website/ts/components/ui/swap_icon.tsx b/packages/website/ts/components/ui/swap_icon.tsx index e465a8074..4a6000d1b 100644 --- a/packages/website/ts/components/ui/swap_icon.tsx +++ b/packages/website/ts/components/ui/swap_icon.tsx @@ -17,7 +17,7 @@ export class SwapIcon extends React.Component<SwapIconProps, SwapIconState> { isHovering: false, }; } - public render() { + public render(): React.ReactNode { const swapStyles = { color: this.state.isHovering ? colors.amber600 : colors.amber800, fontSize: 50, @@ -34,7 +34,7 @@ export class SwapIcon extends React.Component<SwapIconProps, SwapIconState> { </div> ); } - private _onToggleHover(isHovering: boolean) { + private _onToggleHover(isHovering: boolean): void { this.setState({ isHovering, }); diff --git a/packages/website/ts/components/ui/token_icon.tsx b/packages/website/ts/components/ui/token_icon.tsx index ff57a96de..a9ad567ef 100644 --- a/packages/website/ts/components/ui/token_icon.tsx +++ b/packages/website/ts/components/ui/token_icon.tsx @@ -11,7 +11,7 @@ interface TokenIconProps { interface TokenIconState {} export class TokenIcon extends React.Component<TokenIconProps, TokenIconState> { - public render() { + public render(): React.ReactNode { const token = this.props.token; const diameter = this.props.diameter; return ( diff --git a/packages/website/ts/components/visual_order.tsx b/packages/website/ts/components/visual_order.tsx index 3bf464e92..76a283547 100644 --- a/packages/website/ts/components/visual_order.tsx +++ b/packages/website/ts/components/visual_order.tsx @@ -20,7 +20,7 @@ interface VisualOrderProps { interface VisualOrderState {} export class VisualOrder extends React.Component<VisualOrderProps, VisualOrderState> { - public render() { + public render(): React.ReactNode { const allTokens = _.values(this.props.tokenByAddress); const makerImage = this.props.makerToken.iconUrl; const takerImage = this.props.takerToken.iconUrl; @@ -62,7 +62,7 @@ export class VisualOrder extends React.Component<VisualOrderProps, VisualOrderSt </div> ); } - private _renderAmount(assetToken: AssetToken, token: Token) { + private _renderAmount(assetToken: AssetToken, token: Token): React.ReactNode { const unitAmount = ZeroEx.toUnitAmount(assetToken.amount, token.decimals); return ( <div style={{ fontSize: 13 }}> diff --git a/packages/website/ts/components/wallet/wallet.tsx b/packages/website/ts/components/wallet/wallet.tsx index d1ae38550..75dbd12e9 100644 --- a/packages/website/ts/components/wallet/wallet.tsx +++ b/packages/website/ts/components/wallet/wallet.tsx @@ -9,12 +9,16 @@ import { import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import FlatButton from 'material-ui/FlatButton'; +import FloatingActionButton from 'material-ui/FloatingActionButton'; import { List, ListItem } from 'material-ui/List'; import ActionAccountBalanceWallet from 'material-ui/svg-icons/action/account-balance-wallet'; +import ContentAdd from 'material-ui/svg-icons/content/add'; +import ContentRemove from 'material-ui/svg-icons/content/remove'; import NavigationArrowDownward from 'material-ui/svg-icons/navigation/arrow-downward'; import NavigationArrowUpward from 'material-ui/svg-icons/navigation/arrow-upward'; import Close from 'material-ui/svg-icons/navigation/close'; import * as React from 'react'; +import { Link } from 'react-router-dom'; import ReactTooltip = require('react-tooltip'); import firstBy = require('thenby'); @@ -35,6 +39,7 @@ import { TokenByAddress, TokenState, TokenStateByAddress, + WebsitePaths, } from 'ts/types'; import { backendClient } from 'ts/utils/backend_client'; import { constants } from 'ts/utils/constants'; @@ -55,11 +60,14 @@ export interface WalletProps { injectedProviderName: string; providerType: ProviderType; onToggleLedgerDialog: () => void; + onAddToken: () => void; + onRemoveToken: () => void; } interface WalletState { trackedTokenStateByAddress: TokenStateByAddress; wrappedEtherDirection?: Side; + isHoveringSidebar: boolean; } interface AllowanceToggleConfig { @@ -74,7 +82,7 @@ interface AccessoryItemConfig { const styles: Styles = { root: { - width: 346, + width: '100%', backgroundColor: colors.white, borderBottomRightRadius: 10, borderBottomLeftRadius: 10, @@ -94,6 +102,9 @@ const styles: Styles = { }, footerItemInnerDiv: { paddingLeft: 24, + borderTopColor: colors.walletBorder, + borderTopStyle: 'solid', + borderWidth: 1, }, borderedItem: { borderBottomColor: colors.walletBorder, @@ -114,7 +125,21 @@ const styles: Styles = { paddingTop: 8, paddingBottom: 8, }, - accessoryItemsContainer: { width: 150, right: 8 }, + accessoryItemsContainer: { + width: 150, + right: 8, + }, + bodyInnerDiv: { + padding: 0, + // TODO: make this completely responsive + maxHeight: 475, + overflow: 'auto', + WebkitOverflowScrolling: 'touch', + }, + manageYourWalletText: { + color: colors.mediumBlue, + fontWeight: 'bold', + }, }; const ETHER_ICON_PATH = '/images/ether.png'; @@ -123,6 +148,7 @@ const ZRX_TOKEN_SYMBOL = 'ZRX'; const ETHER_SYMBOL = 'ETH'; const ICON_DIMENSION = 24; const TOKEN_AMOUNT_DISPLAY_PRECISION = 3; +const BODY_ITEM_KEY = 'BODY'; const HEADER_ITEM_KEY = 'HEADER'; const FOOTER_ITEM_KEY = 'FOOTER'; const DISCONNECTED_ITEM_KEY = 'DISCONNECTED'; @@ -139,17 +165,18 @@ export class Wallet extends React.Component<WalletProps, WalletState> { this.state = { trackedTokenStateByAddress: initialTrackedTokenStateByAddress, wrappedEtherDirection: undefined, + isHoveringSidebar: false, }; } - public componentWillMount() { + public componentWillMount(): void { const trackedTokenAddresses = _.keys(this.state.trackedTokenStateByAddress); // tslint:disable-next-line:no-floating-promises this._fetchBalancesAndAllowancesAsync(trackedTokenAddresses); } - public componentWillUnmount() { + public componentWillUnmount(): void { this._isUnmounted = true; } - public componentWillReceiveProps(nextProps: WalletProps) { + public componentWillReceiveProps(nextProps: WalletProps): void { if ( nextProps.userAddress !== this.props.userAddress || nextProps.networkId !== this.props.networkId || @@ -175,26 +202,21 @@ export class Wallet extends React.Component<WalletProps, WalletState> { this._fetchBalancesAndAllowancesAsync(newTokenAddresses); } } - public render() { + public render(): React.ReactNode { const isReadyToRender = this.props.blockchainIsLoaded && this.props.blockchainErr === BlockchainErrs.NoError; return <div style={styles.root}>{isReadyToRender && this._renderRows()}</div>; } - private _renderRows() { + private _renderRows(): React.ReactNode { const isAddressAvailable = !_.isEmpty(this.props.userAddress); return ( <List style={styles.list}> {isAddressAvailable - ? _.concat( - this._renderConnectedHeaderRows(), - this._renderEthRows(), - this._renderTokenRows(), - this._renderFooterRows(), - ) + ? _.concat(this._renderConnectedHeaderRows(), this._renderBody(), this._renderFooterRows()) : _.concat(this._renderDisconnectedHeaderRows(), this._renderDisconnectedRows())} </List> ); } - private _renderDisconnectedHeaderRows() { + private _renderDisconnectedHeaderRows(): React.ReactElement<{}> { const userAddress = this.props.userAddress; const primaryText = 'wallet'; return ( @@ -207,7 +229,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> { /> ); } - private _renderDisconnectedRows() { + private _renderDisconnectedRows(): React.ReactElement<{}> { return ( <WalletDisconnectedItem key={DISCONNECTED_ITEM_KEY} @@ -217,24 +239,96 @@ export class Wallet extends React.Component<WalletProps, WalletState> { /> ); } - private _renderConnectedHeaderRows() { + private _renderConnectedHeaderRows(): React.ReactElement<{}> { const userAddress = this.props.userAddress; const primaryText = utils.getAddressBeginAndEnd(userAddress); return ( + <Link key={HEADER_ITEM_KEY} to={`${WebsitePaths.Portal}/account`} style={{ textDecoration: 'none' }}> + <ListItem + primaryText={primaryText} + leftIcon={<Identicon address={userAddress} diameter={ICON_DIMENSION} />} + style={{ ...styles.paddedItem, ...styles.borderedItem }} + innerDivStyle={styles.headerItemInnerDiv} + /> + </Link> + ); + } + private _renderBody(): React.ReactElement<{}> { + const bodyStyle: React.CSSProperties = { + ...styles.bodyInnerDiv, + overflow: this.state.isHoveringSidebar ? 'auto' : 'hidden', + }; + return ( <ListItem - key={HEADER_ITEM_KEY} - primaryText={primaryText} - leftIcon={<Identicon address={userAddress} diameter={ICON_DIMENSION} />} - style={{ ...styles.paddedItem, ...styles.borderedItem }} - innerDivStyle={styles.headerItemInnerDiv} - /> + key={BODY_ITEM_KEY} + innerDivStyle={bodyStyle} + onMouseEnter={this._onSidebarHover.bind(this)} + onMouseLeave={this._onSidebarHoverOff.bind(this)} + > + {this._renderEthRows()} + {this._renderTokenRows()} + </ListItem> ); } - private _renderFooterRows() { - const primaryText = '+ other tokens'; - return <ListItem key={FOOTER_ITEM_KEY} primaryText={primaryText} innerDivStyle={styles.footerItemInnerDiv} />; + private _onSidebarHover(event: React.FormEvent<HTMLInputElement>): void { + this.setState({ + isHoveringSidebar: true, + }); + } + private _onSidebarHoverOff(): void { + this.setState({ + isHoveringSidebar: false, + }); + } + private _renderFooterRows(): React.ReactElement<{}> { + return ( + <div key={FOOTER_ITEM_KEY}> + <ListItem + primaryText={ + <div className="flex"> + <FloatingActionButton mini={true} zDepth={0} onClick={this.props.onAddToken}> + <ContentAdd /> + </FloatingActionButton> + <FloatingActionButton + mini={true} + zDepth={0} + className="px1" + onClick={this.props.onRemoveToken} + > + <ContentRemove /> + </FloatingActionButton> + <div + style={{ + paddingLeft: 10, + position: 'relative', + top: '50%', + transform: 'translateY(33%)', + }} + > + add/remove tokens + </div> + </div> + } + disabled={true} + innerDivStyle={styles.footerItemInnerDiv} + style={styles.borderedItem} + /> + <Link to={`${WebsitePaths.Portal}/account`} style={{ textDecoration: 'none' }}> + <ListItem + primaryText={ + <div className="flex right" style={styles.manageYourWalletText}> + {'manage your wallet'} + </div> + // https://github.com/palantir/tslint-react/issues/140 + // tslint:disable-next-line:jsx-curly-spacing + } + style={{ ...styles.paddedItem, ...styles.borderedItem }} + /> + </Link> + </div> + ); } - private _renderEthRows() { + private _renderEthRows(): React.ReactNode { const primaryText = this._renderAmount( this.props.userEtherBalanceInWei, constants.DECIMAL_PLACES_ETH, @@ -284,7 +378,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> { </div> ); } - private _renderTokenRows() { + private _renderTokenRows(): React.ReactNode { const trackedTokens = this.props.trackedTokens; const trackedTokensStartingWithEtherToken = trackedTokens.sort( firstBy((t: Token) => t.symbol !== ETHER_TOKEN_SYMBOL) @@ -293,7 +387,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> { ); return _.map(trackedTokensStartingWithEtherToken, this._renderTokenRow.bind(this)); } - private _renderTokenRow(token: Token) { + private _renderTokenRow(token: Token, index: number): React.ReactNode { const tokenState = this.state.trackedTokenStateByAddress[token.address]; const tokenLink = sharedUtils.getEtherScanLinkIfExists( token.address, @@ -310,12 +404,14 @@ export class Wallet extends React.Component<WalletProps, WalletState> { tokenState, }, }; + // if this is the last item in the list, do not render the border, it is rendered by the footer + const borderedStyle = index !== this.props.trackedTokens.length - 1 ? styles.borderedItem : {}; const shouldShowWrapEtherItem = !_.isUndefined(this.state.wrappedEtherDirection) && this.state.wrappedEtherDirection === accessoryItemConfig.wrappedEtherDirection; const style = shouldShowWrapEtherItem ? { ...walletItemStyles.focusedItem, ...styles.paddedItem } - : { ...styles.tokenItem, ...styles.borderedItem, ...styles.paddedItem }; + : { ...styles.tokenItem, ...borderedStyle, ...styles.paddedItem }; const etherToken = this._getEthToken(); return ( <div key={token.address}> @@ -345,7 +441,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> { </div> ); } - private _renderAccessoryItems(config: AccessoryItemConfig) { + private _renderAccessoryItems(config: AccessoryItemConfig): React.ReactElement<{}> { const shouldShowWrappedEtherAction = !_.isUndefined(config.wrappedEtherDirection); const shouldShowToggle = !_.isUndefined(config.allowanceToggleConfig); return ( @@ -361,7 +457,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> { </div> ); } - private _renderAllowanceToggle(config: AllowanceToggleConfig) { + private _renderAllowanceToggle(config: AllowanceToggleConfig): React.ReactNode { return ( <AllowanceToggle networkId={this.props.networkId} @@ -376,13 +472,13 @@ export class Wallet extends React.Component<WalletProps, WalletState> { /> ); } - private _renderAmount(amount: BigNumber, decimals: number, symbol: string) { + private _renderAmount(amount: BigNumber, decimals: number, symbol: string): React.ReactNode { const unitAmount = ZeroEx.toUnitAmount(amount, decimals); const formattedAmount = unitAmount.toPrecision(TOKEN_AMOUNT_DISPLAY_PRECISION); const result = `${formattedAmount} ${symbol}`; return <div style={styles.amountLabel}>{result}</div>; } - private _renderValue(amount: BigNumber, decimals: number, price?: BigNumber) { + private _renderValue(amount: BigNumber, decimals: number, price?: BigNumber): React.ReactNode { if (_.isUndefined(price)) { return null; } @@ -392,7 +488,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> { const result = `$${formattedAmount}`; return result; } - private _renderTokenIcon(token: Token, tokenLink?: string) { + private _renderTokenIcon(token: Token, tokenLink?: string): React.ReactElement<{}> { const tooltipId = `tooltip-${token.address}`; const tokenIcon = <TokenIcon token={token} diameter={ICON_DIMENSION} />; if (_.isUndefined(tokenLink)) { @@ -405,7 +501,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> { ); } } - private _renderWrappedEtherButton(wrappedEtherDirection: Side) { + private _renderWrappedEtherButton(wrappedEtherDirection: Side): React.ReactNode { const isWrappedEtherDirectionOpen = this.state.wrappedEtherDirection === wrappedEtherDirection; let buttonLabel; let buttonIcon; @@ -440,7 +536,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> { /> ); } - private _getInitialTrackedTokenStateByAddress(tokenAddresses: string[]) { + private _getInitialTrackedTokenStateByAddress(tokenAddresses: string[]): TokenStateByAddress { const trackedTokenStateByAddress: TokenStateByAddress = {}; _.each(tokenAddresses, tokenAddress => { trackedTokenStateByAddress[tokenAddress] = { @@ -451,7 +547,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> { }); return trackedTokenStateByAddress; } - private async _fetchBalancesAndAllowancesAsync(tokenAddresses: string[]) { + private async _fetchBalancesAndAllowancesAsync(tokenAddresses: string[]): Promise<void> { const balanceAndAllowanceTupleByAddress: ItemByAddress<BigNumber[]> = {}; const userAddressIfExists = _.isEmpty(this.props.userAddress) ? undefined : this.props.userAddress; for (const tokenAddress of tokenAddresses) { @@ -461,16 +557,16 @@ export class Wallet extends React.Component<WalletProps, WalletState> { ); balanceAndAllowanceTupleByAddress[tokenAddress] = balanceAndAllowanceTuple; } - const pricesByAddress = await this._getPricesByAddressAsync(tokenAddresses); + const priceByAddress = await this._getPriceByAddressAsync(tokenAddresses); const trackedTokenStateByAddress = _.reduce( tokenAddresses, (acc, address) => { const [balance, allowance] = balanceAndAllowanceTupleByAddress[address]; - const price = pricesByAddress[address]; + const priceIfExists = _.get(priceByAddress, address); acc[address] = { balance, allowance, - price, + price: priceIfExists, isLoaded: true, }; return acc; @@ -484,34 +580,47 @@ export class Wallet extends React.Component<WalletProps, WalletState> { }); } } - private async _refetchTokenStateAsync(tokenAddress: string) { + private async _refetchTokenStateAsync(tokenAddress: string): Promise<void> { await this._fetchBalancesAndAllowancesAsync([tokenAddress]); } - private async _getPricesByAddressAsync(tokenAddresses: string[]): Promise<ItemByAddress<BigNumber>> { + private async _getPriceByAddressAsync(tokenAddresses: string[]): Promise<ItemByAddress<BigNumber>> { if (_.isEmpty(tokenAddresses)) { return {}; } + // for each input token address, search for the corresponding symbol in this.props.tokenByAddress, if it exists + // create a mapping from existing symbols -> address + const tokenAddressBySymbol: { [symbol: string]: string } = {}; + _.each(tokenAddresses, address => { + const tokenIfExists = _.get(this.props.tokenByAddress, address); + if (!_.isUndefined(tokenIfExists)) { + const symbol = tokenIfExists.symbol; + tokenAddressBySymbol[symbol] = address; + } + }); + const tokenSymbols = _.keys(tokenAddressBySymbol); try { - const websiteBackendPriceInfos = await backendClient.getPriceInfosAsync(tokenAddresses); - const addresses = _.map(websiteBackendPriceInfos, info => info.address); - const prices = _.map(websiteBackendPriceInfos, info => new BigNumber(info.price)); - const pricesByAddress = _.zipObject(addresses, prices); - return pricesByAddress; + const priceBySymbol = await backendClient.getPriceInfoAsync(tokenSymbols); + const priceByAddress = _.mapKeys(priceBySymbol, (value, symbol) => _.get(tokenAddressBySymbol, symbol)); + const result = _.mapValues(priceByAddress, price => { + const priceBigNumber = new BigNumber(price); + return priceBigNumber; + }); + return result; } catch (err) { return {}; } } - private _openWrappedEtherActionRow(wrappedEtherDirection: Side) { + private _openWrappedEtherActionRow(wrappedEtherDirection: Side): void { this.setState({ wrappedEtherDirection, }); } - private _closeWrappedEtherActionRow() { + private _closeWrappedEtherActionRow(): void { this.setState({ wrappedEtherDirection: undefined, }); } - private _getEthToken() { + private _getEthToken(): Token { const tokens = _.values(this.props.tokenByAddress); const etherToken = _.find(tokens, { symbol: ETHER_TOKEN_SYMBOL }); return etherToken; diff --git a/packages/website/ts/components/wallet/wrap_ether_item.tsx b/packages/website/ts/components/wallet/wrap_ether_item.tsx index a38163770..581d2ba97 100644 --- a/packages/website/ts/components/wallet/wrap_ether_item.tsx +++ b/packages/website/ts/components/wallet/wrap_ether_item.tsx @@ -31,8 +31,8 @@ export interface WrapEtherItemProps { interface WrapEtherItemState { currentInputAmount?: BigNumber; - currentInputHasErrors: boolean; isEthConversionHappening: boolean; + errorMsg: React.ReactNode; } const styles: Styles = { @@ -46,13 +46,29 @@ const styles: Styles = { padding: 4, width: 125, }, - ethAmountInput: { height: 32 }, - innerDiv: { paddingLeft: 60, paddingTop: 0 }, - wrapEtherConfirmationButtonContainer: { width: 128, top: 16 }, + amountInput: { height: 34 }, + amountInputLabel: { + paddingTop: 10, + paddingRight: 10, + paddingLeft: 5, + color: colors.grey, + fontSize: 14, + }, + amountInputHint: { + bottom: 18, + }, + innerDiv: { paddingLeft: 60, paddingTop: 0, paddingBottom: 10 }, + wrapEtherConfirmationButtonContainer: { width: 128, top: 19 }, wrapEtherConfirmationButtonLabel: { - fontSize: 10, + fontSize: 12, color: colors.white, }, + errorMsg: { + fontSize: 12, + marginTop: 4, + color: colors.red, + minHeight: 20, + }, }; export class WrapEtherItem extends React.Component<WrapEtherItemProps, WrapEtherItemState> { @@ -60,11 +76,11 @@ export class WrapEtherItem extends React.Component<WrapEtherItemProps, WrapEther super(props); this.state = { currentInputAmount: undefined, - currentInputHasErrors: false, isEthConversionHappening: false, + errorMsg: null, }; } - public render() { + public render(): React.ReactNode { const etherBalanceInEth = ZeroEx.toUnitAmount(this.props.userEtherBalanceInWei, constants.DECIMAL_PLACES_ETH); const isWrappingEth = this.props.direction === Side.Deposit; const topLabelText = isWrappingEth ? 'Convert ETH into WETH 1:1' : 'Convert WETH into ETH 1:1'; @@ -84,7 +100,10 @@ export class WrapEtherItem extends React.Component<WrapEtherItemProps, WrapEther shouldShowIncompleteErrs={false} shouldShowErrs={false} shouldShowUnderline={false} - style={styles.ethAmountInput} + style={styles.amountInput} + labelStyle={styles.amountInputLabel} + inputHintStyle={styles.amountInputHint} + onErrorMsgChange={this._onErrorMsgChange.bind(this)} /> ) : ( <TokenAmountInput @@ -99,12 +118,16 @@ export class WrapEtherItem extends React.Component<WrapEtherItemProps, WrapEther onChange={this._onValueChange.bind(this)} amount={this.state.currentInputAmount} hintText="0.00" - shouldShowErrs={false} // TODO: error handling + shouldShowErrs={false} shouldShowUnderline={false} - style={styles.ethAmountInput} + style={styles.amountInput} + labelStyle={styles.amountInputLabel} + inputHintStyle={styles.amountInputHint} + onErrorMsgChange={this._onErrorMsgChange.bind(this)} /> )} </div> + {this._renderErrorMsg()} </div> } secondaryTextLines={2} @@ -116,20 +139,24 @@ export class WrapEtherItem extends React.Component<WrapEtherItemProps, WrapEther /> ); } - private _onValueChange(isValid: boolean, amount?: BigNumber) { + private _onValueChange(isValid: boolean, amount?: BigNumber): void { this.setState({ currentInputAmount: amount, - currentInputHasErrors: !isValid, }); } - private _renderIsEthConversionHappeningSpinner() { + private _onErrorMsgChange(errorMsg: React.ReactNode): void { + this.setState({ + errorMsg, + }); + } + private _renderIsEthConversionHappeningSpinner(): React.ReactElement<{}> { return this.state.isEthConversionHappening ? ( <div className="pl1" style={{ paddingTop: 10 }}> <i className="zmdi zmdi-spinner zmdi-hc-spin" /> </div> ) : null; } - private _renderWrapEtherConfirmationButton() { + private _renderWrapEtherConfirmationButton(): React.ReactElement<{}> { const isWrappingEth = this.props.direction === Side.Deposit; const labelText = isWrappingEth ? 'wrap' : 'unwrap'; return ( @@ -138,13 +165,20 @@ export class WrapEtherItem extends React.Component<WrapEtherItemProps, WrapEther backgroundColor={colors.wrapEtherConfirmationButton} label={labelText} labelStyle={styles.wrapEtherConfirmationButtonLabel} - onClick={this._wrapEtherConfirmationAction.bind(this)} + onClick={this._wrapEtherConfirmationActionAsync.bind(this)} disabled={this.state.isEthConversionHappening} /> </div> ); } - private async _wrapEtherConfirmationAction() { + private _renderErrorMsg(): React.ReactNode { + return ( + <div style={styles.errorMsg}> + {this.state.errorMsg} + </div> + ); + } + private async _wrapEtherConfirmationActionAsync(): Promise<void> { this.setState({ isEthConversionHappening: true, }); diff --git a/packages/website/ts/containers/legacy_portal.ts b/packages/website/ts/containers/legacy_portal.ts new file mode 100644 index 000000000..3b1172a44 --- /dev/null +++ b/packages/website/ts/containers/legacy_portal.ts @@ -0,0 +1,92 @@ +import { BigNumber } from '@0xproject/utils'; +import * as _ from 'lodash'; +import * as React from 'react'; +import { connect } from 'react-redux'; +import { Dispatch } from 'redux'; +import { + LegacyPortal as LegacyPortalComponent, + LegacyPortalProps as LegacyPortalComponentProps, +} from 'ts/components/legacy_portal/legacy_portal'; +import { Dispatcher } from 'ts/redux/dispatcher'; +import { State } from 'ts/redux/reducer'; +import { BlockchainErrs, HashData, Order, ProviderType, ScreenWidths, Side, TokenByAddress } from 'ts/types'; +import { constants } from 'ts/utils/constants'; +import { Translate } from 'ts/utils/translate'; + +interface ConnectedState { + blockchainErr: BlockchainErrs; + blockchainIsLoaded: boolean; + hashData: HashData; + injectedProviderName: string; + networkId: number; + nodeVersion: string; + orderFillAmount: BigNumber; + providerType: ProviderType; + tokenByAddress: TokenByAddress; + lastForceTokenStateRefetch: number; + userEtherBalanceInWei: BigNumber; + screenWidth: ScreenWidths; + shouldBlockchainErrDialogBeOpen: boolean; + userAddress: string; + userSuppliedOrderCache: Order; + flashMessage?: string | React.ReactNode; + translate: Translate; +} + +interface ConnectedDispatch { + dispatcher: Dispatcher; +} + +const mapStateToProps = (state: State, ownProps: LegacyPortalComponentProps): ConnectedState => { + const receiveAssetToken = state.sideToAssetToken[Side.Receive]; + const depositAssetToken = state.sideToAssetToken[Side.Deposit]; + const receiveAddress = !_.isUndefined(receiveAssetToken.address) + ? receiveAssetToken.address + : constants.NULL_ADDRESS; + const depositAddress = !_.isUndefined(depositAssetToken.address) + ? depositAssetToken.address + : constants.NULL_ADDRESS; + const receiveAmount = !_.isUndefined(receiveAssetToken.amount) ? receiveAssetToken.amount : new BigNumber(0); + const depositAmount = !_.isUndefined(depositAssetToken.amount) ? depositAssetToken.amount : new BigNumber(0); + const hashData = { + depositAmount, + depositTokenContractAddr: depositAddress, + feeRecipientAddress: constants.NULL_ADDRESS, + makerFee: constants.MAKER_FEE, + orderExpiryTimestamp: state.orderExpiryTimestamp, + orderMakerAddress: state.userAddress, + orderTakerAddress: state.orderTakerAddress !== '' ? state.orderTakerAddress : constants.NULL_ADDRESS, + receiveAmount, + receiveTokenContractAddr: receiveAddress, + takerFee: constants.TAKER_FEE, + orderSalt: state.orderSalt, + }; + return { + blockchainErr: state.blockchainErr, + blockchainIsLoaded: state.blockchainIsLoaded, + hashData, + injectedProviderName: state.injectedProviderName, + networkId: state.networkId, + nodeVersion: state.nodeVersion, + orderFillAmount: state.orderFillAmount, + providerType: state.providerType, + screenWidth: state.screenWidth, + shouldBlockchainErrDialogBeOpen: state.shouldBlockchainErrDialogBeOpen, + tokenByAddress: state.tokenByAddress, + lastForceTokenStateRefetch: state.lastForceTokenStateRefetch, + userAddress: state.userAddress, + userEtherBalanceInWei: state.userEtherBalanceInWei, + userSuppliedOrderCache: state.userSuppliedOrderCache, + flashMessage: state.flashMessage, + translate: state.translate, + }; +}; + +const mapDispatchToProps = (dispatch: Dispatch<State>): ConnectedDispatch => ({ + dispatcher: new Dispatcher(dispatch), +}); + +export const LegacyPortal: React.ComponentClass<LegacyPortalComponentProps> = connect( + mapStateToProps, + mapDispatchToProps, +)(LegacyPortalComponent); diff --git a/packages/website/ts/containers/order_utils_documentation.ts b/packages/website/ts/containers/order_utils_documentation.ts new file mode 100644 index 000000000..64aa7300f --- /dev/null +++ b/packages/website/ts/containers/order_utils_documentation.ts @@ -0,0 +1,99 @@ +import { constants as docConstants, DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0xproject/react-docs'; +import * as _ from 'lodash'; +import * as React from 'react'; +import { connect } from 'react-redux'; +import { Dispatch } from 'redux'; +import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; +import { Dispatcher } from 'ts/redux/dispatcher'; +import { State } from 'ts/redux/reducer'; +import { DocPackages, Environments, WebsitePaths } from 'ts/types'; +import { configs } from 'ts/utils/configs'; +import { constants } from 'ts/utils/constants'; +import { Translate } from 'ts/utils/translate'; + +/* tslint:disable:no-var-requires */ +const IntroMarkdown = require('md/docs/order_utils/introduction'); +const InstallationMarkdown = require('md/docs/order_utils/installation'); +/* tslint:enable:no-var-requires */ + +const docSections = { + introduction: 'introduction', + installation: 'installation', + usage: 'usage', + types: 'types', +}; + +const docsInfoConfig: DocsInfoConfig = { + id: DocPackages.OrderUtils, + type: SupportedDocJson.TypeDoc, + displayName: 'Order utils', + packageUrl: 'https://github.com/0xProject/0x-monorepo', + menu: { + introduction: [docSections.introduction], + install: [docSections.installation], + usage: [docSections.usage], + types: [docSections.types], + }, + sectionNameToMarkdown: { + [docSections.introduction]: IntroMarkdown, + [docSections.installation]: InstallationMarkdown, + }, + sectionNameToModulePath: { + [docSections.usage]: [ + '"order-utils/src/order_hash"', + '"order-utils/src/signature_utils"', + '"order-utils/src/order_factory"', + '"order-utils/src/salt"', + '"order-utils/src/assert"', + '"order-utils/src/constants"', + ], + [docSections.types]: ['"order-utils/src/types"', '"types/src/index"'], + }, + menuSubsectionToVersionWhenIntroduced: {}, + sections: docSections, + visibleConstructors: [], + typeConfigs: { + // Note: This needs to be kept in sync with the types exported in index.ts. Unfortunately there is + // currently no way to extract the re-exported types from index.ts via TypeDoc :( + publicTypes: [ + 'OrderError', + 'Order', + 'SignedOrder', + 'ECSignature', + 'Provider', + 'JSONRPCRequestPayload', + 'JSONRPCResponsePayload', + 'JSONRPCErrorCallback', + ], + typeNameToExternalLink: { + BigNumber: constants.URL_BIGNUMBERJS_GITHUB, + }, + }, +}; +const docsInfo = new DocsInfo(docsInfoConfig); + +interface ConnectedState { + docsVersion: string; + availableDocVersions: string[]; + docsInfo: DocsInfo; + translate: Translate; +} + +interface ConnectedDispatch { + dispatcher: Dispatcher; +} + +const mapStateToProps = (state: State, ownProps: DocPageProps): ConnectedState => ({ + docsVersion: state.docsVersion, + availableDocVersions: state.availableDocVersions, + translate: state.translate, + docsInfo, +}); + +const mapDispatchToProps = (dispatch: Dispatch<State>): ConnectedDispatch => ({ + dispatcher: new Dispatcher(dispatch), +}); + +export const Documentation: React.ComponentClass<DocPageProps> = connect(mapStateToProps, mapDispatchToProps)( + DocPageComponent, +); diff --git a/packages/website/ts/containers/portal.ts b/packages/website/ts/containers/portal.ts index 725564ead..3f0feb6e9 100644 --- a/packages/website/ts/containers/portal.ts +++ b/packages/website/ts/containers/portal.ts @@ -3,7 +3,7 @@ import * as _ from 'lodash'; import * as React from 'react'; import { connect } from 'react-redux'; import { Dispatch } from 'redux'; -import { Portal as PortalComponent, PortalAllProps as PortalComponentAllProps } from 'ts/components/portal'; +import { Portal as PortalComponent, PortalProps as PortalComponentProps } from 'ts/components/portal/portal'; import { Dispatcher } from 'ts/redux/dispatcher'; import { State } from 'ts/redux/reducer'; import { BlockchainErrs, HashData, Order, ProviderType, ScreenWidths, Side, TokenByAddress } from 'ts/types'; @@ -34,7 +34,7 @@ interface ConnectedDispatch { dispatcher: Dispatcher; } -const mapStateToProps = (state: State, ownProps: PortalComponentAllProps): ConnectedState => { +const mapStateToProps = (state: State, ownProps: PortalComponentProps): ConnectedState => { const receiveAssetToken = state.sideToAssetToken[Side.Receive]; const depositAssetToken = state.sideToAssetToken[Side.Deposit]; const receiveAddress = !_.isUndefined(receiveAssetToken.address) @@ -83,6 +83,6 @@ const mapDispatchToProps = (dispatch: Dispatch<State>): ConnectedDispatch => ({ dispatcher: new Dispatcher(dispatch), }); -export const Portal: React.ComponentClass<PortalComponentAllProps> = connect(mapStateToProps, mapDispatchToProps)( +export const Portal: React.ComponentClass<PortalComponentProps> = connect(mapStateToProps, mapDispatchToProps)( PortalComponent, ); diff --git a/packages/website/ts/containers/deployer_documentation.ts b/packages/website/ts/containers/sol_compiler_documentation.ts index e20cc195b..2f6486146 100644 --- a/packages/website/ts/containers/deployer_documentation.ts +++ b/packages/website/ts/containers/sol_compiler_documentation.ts @@ -12,9 +12,9 @@ import { constants } from 'ts/utils/constants'; import { Translate } from 'ts/utils/translate'; /* tslint:disable:no-var-requires */ -const IntroMarkdown = require('md/docs/deployer/introduction'); -const InstallationMarkdown = require('md/docs/deployer/installation'); -const UsageMarkdown = require('md/docs/deployer/usage'); +const IntroMarkdown = require('md/docs/sol-compiler/introduction'); +const InstallationMarkdown = require('md/docs/sol-compiler/installation'); +const UsageMarkdown = require('md/docs/sol-compiler/usage'); /* tslint:enable:no-var-requires */ const docSections = { @@ -22,21 +22,19 @@ const docSections = { installation: 'installation', usage: 'usage', compiler: 'compiler', - deployer: 'deployer', types: docConstants.TYPES_SECTION_NAME, }; const docsInfoConfig: DocsInfoConfig = { - id: DocPackages.Deployer, + id: DocPackages.SolCompiler, type: SupportedDocJson.TypeDoc, - displayName: 'Deployer', + displayName: 'Solidity Compiler', packageUrl: 'https://github.com/0xProject/0x-monorepo', menu: { introduction: [docSections.introduction], install: [docSections.installation], usage: [docSections.usage], compiler: [docSections.compiler], - deployer: [docSections.deployer], types: [docSections.types], }, sectionNameToMarkdown: { @@ -45,32 +43,18 @@ const docsInfoConfig: DocsInfoConfig = { [docSections.usage]: UsageMarkdown, }, sectionNameToModulePath: { - [docSections.compiler]: ['"deployer/src/compiler"'], - [docSections.deployer]: ['"deployer/src/deployer"'], - [docSections.types]: ['"deployer/src/utils/types"', '"types/src/index"'], + [docSections.compiler]: ['"sol-compiler/src/compiler"'], + [docSections.types]: ['"sol-compiler/src/utils/types"', '"types/src/index"'], }, menuSubsectionToVersionWhenIntroduced: {}, sections: docSections, - visibleConstructors: [docSections.compiler, docSections.deployer], + visibleConstructors: [docSections.compiler], typeConfigs: { // Note: This needs to be kept in sync with the types exported in index.ts. Unfortunately there is // currently no way to extract the re-exported types from index.ts via TypeDoc :( - publicTypes: [ - 'CompilerOptions', - 'DeployerOptions', - 'BaseDeployerOptions', - 'UrlDeployerOptions', - 'ProviderDeployerOptions', - 'TxData', - ], - typeNameToExternalLink: { - Web3: constants.URL_WEB3_DOCS, - BigNumber: constants.URL_BIGNUMBERJS_GITHUB, - ContractInstance: 'https://github.com/0xProject/web3-typescript-typings/blob/f5bcb96/index.d.ts#L98', - }, - typeNameToPrefix: { - ContractInstance: 'Web3', - }, + publicTypes: ['CompilerOptions'], + typeNameToExternalLink: {}, + typeNameToPrefix: {}, }, }; const docsInfo = new DocsInfo(docsInfoConfig); diff --git a/packages/website/ts/containers/subproviders_documentation.ts b/packages/website/ts/containers/subproviders_documentation.ts index a14d06a3f..2178baea8 100644 --- a/packages/website/ts/containers/subproviders_documentation.ts +++ b/packages/website/ts/containers/subproviders_documentation.ts @@ -74,7 +74,7 @@ const docsInfoConfig: DocsInfoConfig = { [docSections.redundantRPCSubprovider]: ['"subproviders/src/subproviders/redundant_rpc"'], [docSections.ganacheSubprovider]: ['"subproviders/src/subproviders/ganache"'], [docSections.nonceTrackerSubprovider]: ['"subproviders/src/subproviders/nonce_tracker"'], - [docSections.types]: ['"deployer/src/utils/types"', '"types/src/index"', '"subproviders/src/types"'], + [docSections.types]: ['"sol-compiler/src/utils/types"', '"types/src/index"', '"subproviders/src/types"'], }, menuSubsectionToVersionWhenIntroduced: {}, sections: docSections, diff --git a/packages/website/ts/containers/zero_ex_js_documentation.ts b/packages/website/ts/containers/zero_ex_js_documentation.ts index 40853cb9e..f68e2335f 100644 --- a/packages/website/ts/containers/zero_ex_js_documentation.ts +++ b/packages/website/ts/containers/zero_ex_js_documentation.ts @@ -68,32 +68,38 @@ const docsInfoConfig: DocsInfoConfig = { [zeroExJsDocSections.exchange]: [ '"0x.js/src/contract_wrappers/exchange_wrapper"', '"src/contract_wrappers/exchange_wrapper"', + '"contract-wrappers/src/contract_wrappers/exchange_wrapper"', ], [zeroExJsDocSections.tokenRegistry]: [ '"0x.js/src/contract_wrappers/token_registry_wrapper"', '"src/contract_wrappers/token_registry_wrapper"', + '"contract-wrappers/src/contract_wrappers/token_registry_wrapper"', ], [zeroExJsDocSections.token]: [ '"0x.js/src/contract_wrappers/token_wrapper"', '"src/contract_wrappers/token_wrapper"', + '"contract-wrappers/src/contract_wrappers/token_wrapper"', ], [zeroExJsDocSections.etherToken]: [ '"0x.js/src/contract_wrappers/ether_token_wrapper"', '"src/contract_wrappers/ether_token_wrapper"', + '"contract-wrappers/src/contract_wrappers/ether_token_wrapper"', ], [zeroExJsDocSections.proxy]: [ '"0x.js/src/contract_wrappers/proxy_wrapper"', '"0x.js/src/contract_wrappers/token_transfer_proxy_wrapper"', - '"src/contract_wrappers/token_transfer_proxy_wrapper"', + '"contract-wrappers/src/contract_wrappers/token_transfer_proxy_wrapper"', ], [zeroExJsDocSections.orderWatcher]: [ '"0x.js/src/order_watcher/order_state_watcher"', '"src/order_watcher/order_state_watcher"', + '"order-watcher/src/order_watcher/order_watcher"', ], [zeroExJsDocSections.types]: [ '"0x.js/src/types"', '"src/types"', '"types/src/index"', + '"contract-wrappers/src/types"', '"0x.js/src/contract_wrappers/generated/ether_token"', '"0x.js/src/contract_wrappers/generated/token"', '"0x.js/src/contract_wrappers/generated/exchange"', @@ -114,7 +120,7 @@ const docsInfoConfig: DocsInfoConfig = { 'Order', 'SignedOrder', 'ECSignature', - 'ZeroExError', + 'ContractWrappersError', 'EventCallback', 'EventCallbackAsync', 'EventCallbackSync', diff --git a/packages/website/ts/index.tsx b/packages/website/ts/index.tsx index 6b347145f..f255f81e7 100644 --- a/packages/website/ts/index.tsx +++ b/packages/website/ts/index.tsx @@ -34,9 +34,14 @@ import 'less/all.less'; // cause we only want to import the module when the user navigates to the page. // At the same time webpack statically parses for System.import() to determine bundle chunk split points // so each lazy import needs it's own `System.import()` declaration. -const LazyPortal = createLazyComponent('Portal', async () => - System.import<any>(/* webpackChunkName: "portal" */ 'ts/containers/portal'), -); +const LazyPortal = + utils.isDevelopment() || utils.isStaging() || utils.isDogfood() + ? createLazyComponent('Portal', async () => + System.import<any>(/* webpackChunkName: "portal" */ 'ts/containers/portal'), + ) + : createLazyComponent('LegacyPortal', async () => + System.import<any>(/* webpackChunkName: "legacyPortal" */ 'ts/containers/legacy_portal'), + ); const LazyZeroExJSDocumentation = createLazyComponent('Documentation', async () => System.import<any>(/* webpackChunkName: "zeroExDocs" */ 'ts/containers/zero_ex_js_documentation'), ); @@ -49,8 +54,8 @@ const LazyConnectDocumentation = createLazyComponent('Documentation', async () = const LazyWeb3WrapperDocumentation = createLazyComponent('Documentation', async () => System.import<any>(/* webpackChunkName: "web3WrapperDocs" */ 'ts/containers/web3_wrapper_documentation'), ); -const LazyDeployerDocumentation = createLazyComponent('Documentation', async () => - System.import<any>(/* webpackChunkName: "deployerDocs" */ 'ts/containers/deployer_documentation'), +const LazySolCompilerDocumentation = createLazyComponent('Documentation', async () => + System.import<any>(/* webpackChunkName: "solCompilerDocs" */ 'ts/containers/sol_compiler_documentation'), ); const LazyJSONSchemasDocumentation = createLazyComponent('Documentation', async () => System.import<any>(/* webpackChunkName: "jsonSchemasDocs" */ 'ts/containers/json_schemas_documentation'), @@ -61,6 +66,9 @@ const LazySolCovDocumentation = createLazyComponent('Documentation', async () => const LazySubprovidersDocumentation = createLazyComponent('Documentation', async () => System.import<any>(/* webpackChunkName: "subproviderDocs" */ 'ts/containers/subproviders_documentation'), ); +const LazyOrderUtilsDocumentation = createLazyComponent('Documentation', async () => + System.import<any>(/* webpackChunkName: "orderUtilsDocs" */ 'ts/containers/order_utils_documentation'), +); analytics.init(); // tslint:disable-next-line:no-floating-promises @@ -83,7 +91,10 @@ render( <Route path={WebsitePaths.Wiki} component={Wiki as any} /> <Route path={`${WebsitePaths.ZeroExJs}/:version?`} component={LazyZeroExJSDocumentation} /> <Route path={`${WebsitePaths.Connect}/:version?`} component={LazyConnectDocumentation} /> - <Route path={`${WebsitePaths.Deployer}/:version?`} component={LazyDeployerDocumentation} /> + <Route + path={`${WebsitePaths.SolCompiler}/:version?`} + component={LazySolCompilerDocumentation} + /> <Route path={`${WebsitePaths.SolCov}/:version?`} component={LazySolCovDocumentation} /> <Route path={`${WebsitePaths.JSONSchemas}/:version?`} @@ -94,6 +105,10 @@ render( component={LazySubprovidersDocumentation} /> <Route + path={`${WebsitePaths.OrderUtils}/:version?`} + component={LazyOrderUtilsDocumentation} + /> + <Route path={`${WebsitePaths.Web3Wrapper}/:version?`} component={LazyWeb3WrapperDocumentation} /> @@ -111,6 +126,10 @@ render( path={`${WebsiteLegacyPaths.Web3Wrapper}/:version?`} component={LazyWeb3WrapperDocumentation} /> + <Route + path={`${WebsiteLegacyPaths.Deployer}/:version?`} + component={LazySolCompilerDocumentation} + /> <Route component={NotFound as any} /> </Switch> diff --git a/packages/website/ts/lazy_component.tsx b/packages/website/ts/lazy_component.tsx index 48800c2dd..dce06ed8d 100644 --- a/packages/website/ts/lazy_component.tsx +++ b/packages/website/ts/lazy_component.tsx @@ -21,22 +21,22 @@ export class LazyComponent extends React.Component<LazyComponentProps, LazyCompo component: undefined, }; } - public componentWillMount() { + public componentWillMount(): void { // tslint:disable-next-line:no-floating-promises this._loadComponentFireAndForgetAsync(this.props); } - public componentWillReceiveProps(nextProps: LazyComponentProps) { + public componentWillReceiveProps(nextProps: LazyComponentProps): void { if (nextProps.reactComponentPromise !== this.props.reactComponentPromise) { // tslint:disable-next-line:no-floating-promises this._loadComponentFireAndForgetAsync(nextProps); } } - public render() { + public render(): React.ReactNode { return _.isUndefined(this.state.component) ? null : React.createElement(this.state.component, this.props.reactComponentProps); } - private async _loadComponentFireAndForgetAsync(props: LazyComponentProps) { + private async _loadComponentFireAndForgetAsync(props: LazyComponentProps): Promise<void> { const component = await props.reactComponentPromise; this.setState({ component, diff --git a/packages/website/ts/local_storage/local_storage.ts b/packages/website/ts/local_storage/local_storage.ts index d94e6877b..20a533a91 100644 --- a/packages/website/ts/local_storage/local_storage.ts +++ b/packages/website/ts/local_storage/local_storage.ts @@ -1,7 +1,7 @@ import * as _ from 'lodash'; export const localStorage = { - doesExist() { + doesExist(): boolean { return !!window.localStorage; }, getItemIfExists(key: string): string { @@ -14,13 +14,13 @@ export const localStorage = { } return item; }, - setItem(key: string, value: string) { + setItem(key: string, value: string): void { if (!this.doesExist || _.isUndefined(value)) { return; } window.localStorage.setItem(key, value); }, - removeItem(key: string) { + removeItem(key: string): void { if (!this.doesExist) { return; } diff --git a/packages/website/ts/local_storage/trade_history_storage.tsx b/packages/website/ts/local_storage/trade_history_storage.tsx index df731236e..cc764d98e 100644 --- a/packages/website/ts/local_storage/trade_history_storage.tsx +++ b/packages/website/ts/local_storage/trade_history_storage.tsx @@ -14,7 +14,7 @@ export const tradeHistoryStorage = { // Clear all fill related localStorage if we've updated the config variable in an update // that introduced a backward incompatible change requiring the user to re-fetch the fills from // the blockchain - clearIfRequired() { + clearIfRequired(): void { const lastClearFillDate = localStorage.getItemIfExists(FILL_CLEAR_KEY); if (lastClearFillDate !== configs.LAST_LOCAL_STORAGE_FILL_CLEARANCE_DATE) { const localStorageKeys = localStorage.getAllKeys(); @@ -26,7 +26,7 @@ export const tradeHistoryStorage = { } localStorage.setItem(FILL_CLEAR_KEY, configs.LAST_LOCAL_STORAGE_FILL_CLEARANCE_DATE); }, - addFillToUser(userAddress: string, networkId: number, fill: Fill) { + addFillToUser(userAddress: string, networkId: number, fill: Fill): void { const fillsByHash = this.getUserFillsByHash(userAddress, networkId); const fillHash = this._getFillHash(fill); const doesFillExist = !_.isUndefined(fillsByHash[fillHash]); @@ -38,7 +38,7 @@ export const tradeHistoryStorage = { const userFillsKey = this._getUserFillsKey(userAddress, networkId); localStorage.setItem(userFillsKey, userFillsJSONString); }, - removeFillFromUser(userAddress: string, networkId: number, fill: Fill) { + removeFillFromUser(userAddress: string, networkId: number, fill: Fill): void { const fillsByHash = this.getUserFillsByHash(userAddress, networkId); const fillHash = this._getFillHash(fill); const doesFillExist = !_.isUndefined(fillsByHash[fillHash]); @@ -74,15 +74,15 @@ export const tradeHistoryStorage = { const blockNumber = _.parseInt(blockNumberStr); return blockNumber; }, - setFillsLatestBlock(userAddress: string, networkId: number, blockNumber: number) { + setFillsLatestBlock(userAddress: string, networkId: number, blockNumber: number): void { const userFillsLatestBlockKey = this._getFillsLatestBlockKey(userAddress, networkId); localStorage.setItem(userFillsLatestBlockKey, `${blockNumber}`); }, - _getUserFillsKey(userAddress: string, networkId: number) { + _getUserFillsKey(userAddress: string, networkId: number): string { const userFillsKey = `${FILLS_KEY}-${userAddress}-${networkId}`; return userFillsKey; }, - _getFillsLatestBlockKey(userAddress: string, networkId: number) { + _getFillsLatestBlockKey(userAddress: string, networkId: number): string { const userFillsLatestBlockKey = `${FILLS_LATEST_BLOCK}-${userAddress}-${networkId}`; return userFillsLatestBlockKey; }, diff --git a/packages/website/ts/pages/about/about.tsx b/packages/website/ts/pages/about/about.tsx index 97be59526..24ea7614f 100644 --- a/packages/website/ts/pages/about/about.tsx +++ b/packages/website/ts/pages/about/about.tsx @@ -37,7 +37,7 @@ const teamRow1: ProfileInfo[] = [ name: 'Fabio Berger', title: 'Senior Engineer', description: `Full-stack blockchain engineer. Previously software engineer \ - at Airtable and founder of WealthLift. Computer science at Duke.`, + at Airtable and founder of WealthLift. Computer Science at Duke.`, image: '/images/team/fabio.jpg', linkedIn: 'https://www.linkedin.com/in/fabio-berger-03ab261a/', github: 'https://github.com/fabioberger', @@ -60,7 +60,7 @@ const teamRow2: ProfileInfo[] = [ name: 'Leonid Logvinov', title: 'Engineer', description: `Full-stack blockchain engineer. Previously blockchain engineer \ - at Neufund. Computer science at University of Warsaw.`, + at Neufund. Computer Science at University of Warsaw.`, image: '/images/team/leonid.png', linkedIn: 'https://www.linkedin.com/in/leonidlogvinov/', github: 'https://github.com/LogvinovLeon', @@ -113,7 +113,7 @@ const teamRow4: ProfileInfo[] = [ { name: 'Blake Henderson', title: 'Operations Associate', - description: `Operations and Analytics. Previously analytics at LinkedIn. Economics at UC San Diego. `, + description: `Operations and Analytics. Previously analytics at LinkedIn. Economics at UC San Diego.`, image: '/images/team/blake.jpg', linkedIn: 'https://www.linkedin.com/in/blakerhenderson/', github: '', @@ -128,6 +128,35 @@ const teamRow4: ProfileInfo[] = [ github: '', medium: '', }, + { + name: 'Greg Hysen', + title: 'Blockchain Engineer', + description: `Smart contract R&D. Previously lead distributed systems engineer at Hivemapper. Computer Science at University of Waterloo.`, + image: '/images/team/greg.jpeg', + linkedIn: 'https://www.linkedin.com/in/gregory-hysen-71741463/', + github: 'https://github.com/hysz', + medium: '', + }, +]; + +const teamRow5: ProfileInfo[] = [ + { + name: 'Remco Bloemen', + title: 'Technical Fellow', + description: `Previously cofounder at Neufund and Coblue. Part III at Cambridge. PhD dropout at Twente Business School.`, + image: '/images/team/remco.jpeg', + linkedIn: 'https://www.linkedin.com/in/remcobloemen/', + github: 'http://github.com/recmo', + medium: '', + }, + { + name: 'Francesco Agosti', + title: 'Engineer', + description: `Full-stack engineer. Previously senior software engineer at Yelp. Computer Science at Duke.`, + image: 'images/team/fragosti.png', + linkedIn: 'https://www.linkedin.com/in/fragosti/', + github: 'http://github.com/fragosti', + }, ]; const advisors: ProfileInfo[] = [ @@ -189,10 +218,10 @@ const styles: Styles = { }; export class About extends React.Component<AboutProps, AboutState> { - public componentDidMount() { + public componentDidMount(): void { window.scrollTo(0, 0); } - public render() { + public render(): React.ReactNode { return ( <div style={{ backgroundColor: colors.lightestGrey }}> <DocumentTitle title="0x About Us" /> @@ -223,6 +252,7 @@ export class About extends React.Component<AboutProps, AboutState> { <div className="clearfix">{this._renderProfiles(teamRow2)}</div> <div className="clearfix">{this._renderProfiles(teamRow3)}</div> <div className="clearfix">{this._renderProfiles(teamRow4)}</div> + <div className="clearfix">{this._renderProfiles(teamRow5)}</div> </div> <div className="pt3 pb2"> <div @@ -262,7 +292,7 @@ export class About extends React.Component<AboutProps, AboutState> { </div> ); } - private _renderProfiles(profiles: ProfileInfo[]) { + private _renderProfiles(profiles: ProfileInfo[]): React.ReactNode { const numIndiv = profiles.length; const colSize = utils.getColSize(numIndiv); return _.map(profiles, profile => { diff --git a/packages/website/ts/pages/about/profile.tsx b/packages/website/ts/pages/about/profile.tsx index 4361da103..dd046a8cb 100644 --- a/packages/website/ts/pages/about/profile.tsx +++ b/packages/website/ts/pages/about/profile.tsx @@ -22,7 +22,7 @@ interface ProfileProps { profileInfo: ProfileInfo; } -export function Profile(props: ProfileProps) { +export const Profile = (props: ProfileProps) => { return ( <div className={`lg-col md-col lg-col-${props.colSize} md-col-6`}> <div style={{ maxWidth: 300 }} className="mx-auto lg-px3 md-px3 sm-px4 sm-pb3"> @@ -53,9 +53,9 @@ export function Profile(props: ProfileProps) { </div> </div> ); -} +}; -function renderSocialMediaIcons(profileInfo: ProfileInfo) { +function renderSocialMediaIcons(profileInfo: ProfileInfo): React.ReactNode { const icons = [ renderSocialMediaIcon('zmdi-github-box', profileInfo.github), renderSocialMediaIcon('zmdi-linkedin-box', profileInfo.linkedIn), @@ -64,7 +64,7 @@ function renderSocialMediaIcons(profileInfo: ProfileInfo) { return icons; } -function renderSocialMediaIcon(iconName: string, url: string) { +function renderSocialMediaIcon(iconName: string, url: string): React.ReactNode { if (_.isEmpty(url)) { return null; } diff --git a/packages/website/ts/pages/documentation/doc_page.tsx b/packages/website/ts/pages/documentation/doc_page.tsx index aca20447b..17efc56ed 100644 --- a/packages/website/ts/pages/documentation/doc_page.tsx +++ b/packages/website/ts/pages/documentation/doc_page.tsx @@ -30,10 +30,11 @@ const docIdToSubpackageName: { [id: string]: string } = { [DocPackages.Connect]: 'connect', [DocPackages.SmartContracts]: 'contracts', [DocPackages.Web3Wrapper]: 'web3-wrapper', - [DocPackages.Deployer]: 'deployer', + [DocPackages.SolCompiler]: 'sol-compiler', [DocPackages.JSONSchemas]: 'json-schemas', [DocPackages.SolCov]: 'sol-cov', [DocPackages.Subproviders]: 'subproviders', + [DocPackages.OrderUtils]: 'order-utils', }; export interface DocPageProps { @@ -58,7 +59,7 @@ export class DocPage extends React.Component<DocPageProps, DocPageState> { docAgnosticFormat: undefined, }; } - public componentWillMount() { + public componentWillMount(): void { const pathName = this.props.location.pathname; const lastSegment = pathName.substr(pathName.lastIndexOf('/') + 1); const versions = findVersions(lastSegment); @@ -66,10 +67,10 @@ export class DocPage extends React.Component<DocPageProps, DocPageState> { // tslint:disable-next-line:no-floating-promises this._fetchJSONDocsFireAndForgetAsync(preferredVersionIfExists); } - public componentWillUnmount() { + public componentWillUnmount(): void { this._isUnmounted = true; } - public render() { + public render(): React.ReactNode { const menuSubsectionsBySection = _.isUndefined(this.state.docAgnosticFormat) ? {} : this.props.docsInfo.getMenuSubsectionsBySection(this.state.docAgnosticFormat); @@ -134,7 +135,7 @@ export class DocPage extends React.Component<DocPageProps, DocPageState> { }); } } - private _getSourceUrl() { + private _getSourceUrl(): string { const url = this.props.docsInfo.packageUrl; let pkg = docIdToSubpackageName[this.props.docsInfo.id]; let tagPrefix = pkg; @@ -154,7 +155,7 @@ export class DocPage extends React.Component<DocPageProps, DocPageState> { const sourceUrl = `${url}/blob/${tagPrefix}%40${this.props.docsVersion}/packages${pkg}`; return sourceUrl; } - private _onVersionSelected(semver: string) { + private _onVersionSelected(semver: string): void { let path = window.location.pathname; const lastChar = path[path.length - 1]; if (_.isFinite(_.parseInt(lastChar))) { diff --git a/packages/website/ts/pages/faq/faq.tsx b/packages/website/ts/pages/faq/faq.tsx index 701031d44..c9295d806 100644 --- a/packages/website/ts/pages/faq/faq.tsx +++ b/packages/website/ts/pages/faq/faq.tsx @@ -404,10 +404,10 @@ const sections: FAQSection[] = [ ]; export class FAQ extends React.Component<FAQProps, FAQState> { - public componentDidMount() { + public componentDidMount(): void { window.scrollTo(0, 0); } - public render() { + public render(): React.ReactNode { return ( <div> <DocumentTitle title="0x FAQ" /> @@ -422,7 +422,7 @@ export class FAQ extends React.Component<FAQProps, FAQState> { </div> ); } - private _renderSections() { + private _renderSections(): React.ReactNode { const renderedSections = _.map(sections, (section: FAQSection, i: number) => { const isFirstSection = i === 0; return ( @@ -434,7 +434,7 @@ export class FAQ extends React.Component<FAQProps, FAQState> { }); return renderedSections; } - private _renderQuestions(questions: FAQQuestion[], isFirstSection: boolean) { + private _renderQuestions(questions: FAQQuestion[], isFirstSection: boolean): React.ReactNode { const renderedQuestions = _.map(questions, (question: FAQQuestion, i: number) => { const isFirstQuestion = i === 0; return ( diff --git a/packages/website/ts/pages/faq/question.tsx b/packages/website/ts/pages/faq/question.tsx index 240dae910..28ea6881a 100644 --- a/packages/website/ts/pages/faq/question.tsx +++ b/packages/website/ts/pages/faq/question.tsx @@ -20,7 +20,7 @@ export class Question extends React.Component<QuestionProps, QuestionState> { isExpanded: props.shouldDisplayExpanded, }; } - public render() { + public render(): React.ReactNode { return ( <div className="py1"> <Card @@ -43,7 +43,7 @@ export class Question extends React.Component<QuestionProps, QuestionState> { </div> ); } - private _onExchangeChange() { + private _onExchangeChange(): void { this.setState({ isExpanded: !this.state.isExpanded, }); diff --git a/packages/website/ts/pages/fullscreen_message.tsx b/packages/website/ts/pages/fullscreen_message.tsx new file mode 100644 index 000000000..6fcf7b32c --- /dev/null +++ b/packages/website/ts/pages/fullscreen_message.tsx @@ -0,0 +1,30 @@ +import { Styles } from '@0xproject/react-shared'; +import * as React from 'react'; + +export interface FullscreenMessageProps { + headerText: string; + bodyText: string; +} + +const styles: Styles = { + thin: { + fontWeight: 100, + }, +}; + +export const FullscreenMessage = (props: FullscreenMessageProps) => { + return ( + <div className="mx-auto max-width-4 py4"> + <div className="center py4"> + <div className="py4"> + <div className="py4"> + <h1 style={styles.thin}>{props.headerText}</h1> + <div className="py1"> + <div className="py3">{props.bodyText}</div> + </div> + </div> + </div> + </div> + </div> + ); +}; diff --git a/packages/website/ts/pages/landing/landing.tsx b/packages/website/ts/pages/landing/landing.tsx index f961220fd..02ecfa117 100644 --- a/packages/website/ts/pages/landing/landing.tsx +++ b/packages/website/ts/pages/landing/landing.tsx @@ -37,6 +37,8 @@ interface Project { } const THROTTLE_TIMEOUT = 100; +const WHATS_NEW_TITLE = '18 ideas for 0x relayers in 2018'; +const WHATS_NEW_URL = 'https://blog.0xproject.com/18-ideas-for-0x-relayers-in-2018-80a1498b955f'; const relayersAndDappProjects: Project[] = [ { @@ -175,14 +177,14 @@ export class Landing extends React.Component<LandingProps, LandingState> { }; this._throttledScreenWidthUpdate = _.throttle(this._updateScreenWidth.bind(this), THROTTLE_TIMEOUT); } - public componentDidMount() { + public componentDidMount(): void { window.addEventListener('resize', this._throttledScreenWidthUpdate); window.scrollTo(0, 0); } - public componentWillUnmount() { + public componentWillUnmount(): void { window.removeEventListener('resize', this._throttledScreenWidthUpdate); } - public render() { + public render(): React.ReactNode { return ( <div id="landing" className="clearfix" style={{ color: colors.grey500 }}> <DocumentTitle title="0x Protocol" /> @@ -216,7 +218,7 @@ export class Landing extends React.Component<LandingProps, LandingState> { </div> ); } - private _renderHero() { + private _renderHero(): React.ReactNode { const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; const buttonLabelStyle: React.CSSProperties = { textTransform: 'none', @@ -233,6 +235,7 @@ export class Landing extends React.Component<LandingProps, LandingState> { return ( <div className="clearfix py4" style={{ backgroundColor: colors.heroGrey }}> <div className="mx-auto max-width-4 clearfix"> + {this._renderWhatsNew()} <div className="lg-pt4 md-pt4 sm-pt2 lg-pb4 md-pb4 lg-my4 md-my4 sm-mt2 sm-mb4 clearfix"> <div className="col lg-col-5 md-col-5 col-12 sm-center"> <img src="/images/landing/hero_chip_image.png" height={isSmallScreen ? 300 : 395} /> @@ -302,7 +305,34 @@ export class Landing extends React.Component<LandingProps, LandingState> { </div> ); } - private _renderProjects(projects: Project[], title: string, backgroundColor: string, isTitleCenter: boolean) { + private _renderWhatsNew(): React.ReactNode { + return ( + <div className="sm-center sm-px1"> + <a href={WHATS_NEW_URL} target="_blank" className="inline-block text-decoration-none"> + <div className="flex sm-pl0 md-pl2 lg-pl0" style={{ fontFamily: 'Roboto Mono', fontWeight: 600 }}> + <div + className="mr1 px1" + style={{ + backgroundColor: colors.lightTurquois, + borderRadius: 3, + color: colors.heroGrey, + height: 23, + }} + > + New + </div> + <div style={{ color: colors.darkGrey }}>{WHATS_NEW_TITLE}</div> + </div> + </a> + </div> + ); + } + private _renderProjects( + projects: Project[], + title: string, + backgroundColor: string, + isTitleCenter: boolean, + ): React.ReactNode { const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; const projectList = _.map(projects, (project: Project, i: number) => { const isRelayersOnly = projects.length === 12; @@ -368,7 +398,7 @@ export class Landing extends React.Component<LandingProps, LandingState> { </div> ); } - private _renderTokenizationSection() { + private _renderTokenizationSection(): React.ReactNode { const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; return ( <div className="clearfix lg-py4 md-py4 sm-pb4 sm-pt2" style={{ backgroundColor: colors.grey100 }}> @@ -399,7 +429,7 @@ export class Landing extends React.Component<LandingProps, LandingState> { </div> ); } - private _renderProtocolSection() { + private _renderProtocolSection(): React.ReactNode { const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; return ( <div className="clearfix pt4" style={{ backgroundColor: colors.heroGrey }}> @@ -444,7 +474,7 @@ export class Landing extends React.Component<LandingProps, LandingState> { </div> ); } - private _renderBuildingBlocksSection() { + private _renderBuildingBlocksSection(): React.ReactNode { const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; const descriptionStyle: React.CSSProperties = { fontFamily: 'Roboto Mono', @@ -501,7 +531,7 @@ export class Landing extends React.Component<LandingProps, LandingState> { </div> ); } - private _renderBlockChipImage() { + private _renderBlockChipImage(): React.ReactNode { const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; return ( <div className="col lg-col-6 md-col-6 col-12 sm-center"> @@ -509,7 +539,7 @@ export class Landing extends React.Component<LandingProps, LandingState> { </div> ); } - private _renderTokenCloud() { + private _renderTokenCloud(): React.ReactNode { const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; return ( <div className="col lg-col-6 md-col-6 col-12 center"> @@ -517,7 +547,7 @@ export class Landing extends React.Component<LandingProps, LandingState> { </div> ); } - private _renderAssetTypes() { + private _renderAssetTypes(): React.ReactNode { const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; const assetTypes: AssetType[] = [ { @@ -560,7 +590,7 @@ export class Landing extends React.Component<LandingProps, LandingState> { }); return assets; } - private _renderInfoBoxes() { + private _renderInfoBoxes(): React.ReactNode { const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; const boxStyle: React.CSSProperties = { maxWidth: 253, @@ -623,7 +653,7 @@ export class Landing extends React.Component<LandingProps, LandingState> { </div> ); } - private _renderUseCases() { + private _renderUseCases(): React.ReactNode { const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; const useCases: UseCase[] = [ @@ -721,7 +751,7 @@ export class Landing extends React.Component<LandingProps, LandingState> { </div> ); } - private _renderCallToAction() { + private _renderCallToAction(): React.ReactNode { const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; const buttonLabelStyle: React.CSSProperties = { textTransform: 'none', @@ -768,7 +798,7 @@ export class Landing extends React.Component<LandingProps, LandingState> { </div> ); } - private _updateScreenWidth() { + private _updateScreenWidth(): void { const newScreenWidth = utils.getScreenWidth(); if (newScreenWidth !== this.state.screenWidth) { this.setState({ @@ -776,7 +806,7 @@ export class Landing extends React.Component<LandingProps, LandingState> { }); } } - private _onLanguageSelected(language: Language) { + private _onLanguageSelected(language: Language): void { this.props.dispatcher.updateSelectedLanguage(language); } } // tslint:disable:max-file-line-count diff --git a/packages/website/ts/pages/not_found.tsx b/packages/website/ts/pages/not_found.tsx index ff25a35e9..674271636 100644 --- a/packages/website/ts/pages/not_found.tsx +++ b/packages/website/ts/pages/not_found.tsx @@ -3,6 +3,7 @@ import * as _ from 'lodash'; import * as React from 'react'; import { Footer } from 'ts/components/footer'; import { TopBar } from 'ts/components/top_bar/top_bar'; +import { FullscreenMessage } from 'ts/pages/fullscreen_message'; import { Dispatcher } from 'ts/redux/dispatcher'; import { Translate } from 'ts/utils/translate'; @@ -12,35 +13,15 @@ export interface NotFoundProps { dispatcher: Dispatcher; } -interface NotFoundState {} - -const styles: Styles = { - thin: { - fontWeight: 100, - }, +export const NotFound = (props: NotFoundProps) => { + return ( + <div> + <TopBar blockchainIsLoaded={false} location={this.props.location} translate={this.props.translate} /> + <FullscreenMessage + headerText={'404 Not Found'} + bodyText={"Hm... looks like we couldn't find what you are looking for."} + /> + <Footer translate={this.props.translate} dispatcher={this.props.dispatcher} /> + </div> + ); }; - -export class NotFound extends React.Component<NotFoundProps, NotFoundState> { - public render() { - return ( - <div> - <TopBar blockchainIsLoaded={false} location={this.props.location} translate={this.props.translate} /> - <div className="mx-auto max-width-4 py4"> - <div className="center py4"> - <div className="py4"> - <div className="py4"> - <h1 style={{ ...styles.thin }}>404 Not Found</h1> - <div className="py1"> - <div className="py3"> - Hm... looks like we couldn't find what you are looking for. - </div> - </div> - </div> - </div> - </div> - </div> - <Footer translate={this.props.translate} dispatcher={this.props.dispatcher} /> - </div> - ); - } -} diff --git a/packages/website/ts/pages/wiki/wiki.tsx b/packages/website/ts/pages/wiki/wiki.tsx index 7ed2b750d..720c1cc37 100644 --- a/packages/website/ts/pages/wiki/wiki.tsx +++ b/packages/website/ts/pages/wiki/wiki.tsx @@ -47,7 +47,7 @@ const styles: Styles = { left: 0, bottom: 0, right: 0, - overflowZ: 'hidden', + overflow: 'hidden', height: `calc(100vh - ${TOP_BAR_HEIGHT}px)`, WebkitOverflowScrolling: 'touch', }, @@ -69,19 +69,19 @@ export class Wiki extends React.Component<WikiProps, WikiState> { isHoveringSidebar: false, }; } - public componentDidMount() { + public componentDidMount(): void { window.addEventListener('hashchange', this._onHashChanged.bind(this), false); } - public componentWillMount() { + public componentWillMount(): void { // tslint:disable-next-line:no-floating-promises this._fetchArticlesBySectionAsync(); } - public componentWillUnmount() { + public componentWillUnmount(): void { this._isUnmounted = true; clearTimeout(this._wikiBackoffTimeoutId); window.removeEventListener('hashchange', this._onHashChanged.bind(this), false); } - public render() { + public render(): React.ReactNode { const menuSubsectionsBySection = _.isUndefined(this.state.articlesBySection) ? {} : this._getMenuSubsectionsBySection(this.state.articlesBySection); @@ -171,7 +171,7 @@ export class Wiki extends React.Component<WikiProps, WikiState> { const sections = _.map(sectionNames, sectionName => this._renderSection(sectionName)); return sections; } - private _renderSection(sectionName: string) { + private _renderSection(sectionName: string): React.ReactNode { const articles = this.state.articlesBySection[sectionName]; const renderedArticles = _.map(articles, (article: Article) => { const githubLink = `${constants.URL_GITHUB_WIKI}/edit/master/${sectionName}/${article.fileName}`; @@ -227,7 +227,7 @@ export class Wiki extends React.Component<WikiProps, WikiState> { } } } - private _getMenuSubsectionsBySection(articlesBySection: ArticlesBySection) { + private _getMenuSubsectionsBySection(articlesBySection: ArticlesBySection): { [section: string]: string[] } { const sectionNames = _.keys(articlesBySection); const menuSubsectionsBySection: { [section: string]: string[] } = {}; for (const sectionName of sectionNames) { @@ -237,17 +237,17 @@ export class Wiki extends React.Component<WikiProps, WikiState> { } return menuSubsectionsBySection; } - private _onSidebarHover(event: React.FormEvent<HTMLInputElement>) { + private _onSidebarHover(event: React.FormEvent<HTMLInputElement>): void { this.setState({ isHoveringSidebar: true, }); } - private _onSidebarHoverOff() { + private _onSidebarHoverOff(): void { this.setState({ isHoveringSidebar: false, }); } - private _onHashChanged(event: any) { + private _onHashChanged(event: any): void { const hash = window.location.hash.slice(1); sharedUtils.scrollToHash(hash, sharedConstants.SCROLL_CONTAINER_ID); } diff --git a/packages/website/ts/redux/dispatcher.ts b/packages/website/ts/redux/dispatcher.ts index 13e9a10cc..981522360 100644 --- a/packages/website/ts/redux/dispatcher.ts +++ b/packages/website/ts/redux/dispatcher.ts @@ -22,47 +22,47 @@ export class Dispatcher { this._dispatch = dispatch; } // Portal - public resetState() { + public resetState(): void { this._dispatch({ type: ActionTypes.ResetState, }); } - public updateNodeVersion(nodeVersion: string) { + public updateNodeVersion(nodeVersion: string): void { this._dispatch({ data: nodeVersion, type: ActionTypes.UpdateNodeVersion, }); } - public updateScreenWidth(screenWidth: ScreenWidths) { + public updateScreenWidth(screenWidth: ScreenWidths): void { this._dispatch({ data: screenWidth, type: ActionTypes.UpdateScreenWidth, }); } - public swapAssetTokenSymbols() { + public swapAssetTokenSymbols(): void { this._dispatch({ type: ActionTypes.SwapAssetTokens, }); } - public updateOrderSalt(salt: BigNumber) { + public updateOrderSalt(salt: BigNumber): void { this._dispatch({ data: salt, type: ActionTypes.UpdateOrderSalt, }); } - public updateUserSuppliedOrderCache(order: Order) { + public updateUserSuppliedOrderCache(order: Order): void { this._dispatch({ data: order, type: ActionTypes.UpdateUserSuppliedOrderCache, }); } - public updateShouldBlockchainErrDialogBeOpen(shouldBeOpen: boolean) { + public updateShouldBlockchainErrDialogBeOpen(shouldBeOpen: boolean): void { this._dispatch({ data: shouldBeOpen, type: ActionTypes.UpdateShouldBlockchainErrDialogBeOpen, }); } - public updateChosenAssetToken(side: Side, token: AssetToken) { + public updateChosenAssetToken(side: Side, token: AssetToken): void { this._dispatch({ data: { side, @@ -71,7 +71,7 @@ export class Dispatcher { type: ActionTypes.UpdateChosenAssetToken, }); } - public updateChosenAssetTokenAddress(side: Side, address: string) { + public updateChosenAssetTokenAddress(side: Side, address: string): void { this._dispatch({ data: { address, @@ -80,43 +80,43 @@ export class Dispatcher { type: ActionTypes.UpdateChosenAssetTokenAddress, }); } - public updateOrderTakerAddress(address: string) { + public updateOrderTakerAddress(address: string): void { this._dispatch({ data: address, type: ActionTypes.UpdateOrderTakerAddress, }); } - public updateUserAddress(address?: string) { + public updateUserAddress(address?: string): void { this._dispatch({ data: address, type: ActionTypes.UpdateUserAddress, }); } - public updateOrderExpiry(unixTimestampSec: BigNumber) { + public updateOrderExpiry(unixTimestampSec: BigNumber): void { this._dispatch({ data: unixTimestampSec, type: ActionTypes.UpdateOrderExpiry, }); } - public encounteredBlockchainError(err: BlockchainErrs) { + public encounteredBlockchainError(err: BlockchainErrs): void { this._dispatch({ data: err, type: ActionTypes.BlockchainErrEncountered, }); } - public updateBlockchainIsLoaded(isLoaded: boolean) { + public updateBlockchainIsLoaded(isLoaded: boolean): void { this._dispatch({ data: isLoaded, type: ActionTypes.UpdateBlockchainIsLoaded, }); } - public addTokenToTokenByAddress(token: Token) { + public addTokenToTokenByAddress(token: Token): void { this._dispatch({ data: token, type: ActionTypes.AddTokenToTokenByAddress, }); } - public removeTokenToTokenByAddress(token: Token) { + public removeTokenToTokenByAddress(token: Token): void { this._dispatch({ data: token, type: ActionTypes.RemoveTokenFromTokenByAddress, @@ -127,7 +127,7 @@ export class Dispatcher { networkId: number, userAddressIfExists: string | undefined, sideToAssetToken: SideToAssetToken, - ) { + ): void { this._dispatch({ data: { tokenByAddress, @@ -138,36 +138,36 @@ export class Dispatcher { type: ActionTypes.BatchDispatch, }); } - public updateTokenByAddress(tokens: Token[]) { + public updateTokenByAddress(tokens: Token[]): void { this._dispatch({ data: tokens, type: ActionTypes.UpdateTokenByAddress, }); } - public forceTokenStateRefetch() { + public forceTokenStateRefetch(): void { this._dispatch({ type: ActionTypes.ForceTokenStateRefetch, }); } - public updateECSignature(ecSignature: ECSignature) { + public updateECSignature(ecSignature: ECSignature): void { this._dispatch({ data: ecSignature, type: ActionTypes.UpdateOrderECSignature, }); } - public updateUserWeiBalance(balance: BigNumber) { + public updateUserWeiBalance(balance: BigNumber): void { this._dispatch({ data: balance, type: ActionTypes.UpdateUserEtherBalance, }); } - public updateNetworkId(networkId: number) { + public updateNetworkId(networkId: number): void { this._dispatch({ data: networkId, type: ActionTypes.UpdateNetworkId, }); } - public updateOrderFillAmount(amount: BigNumber) { + public updateOrderFillAmount(amount: BigNumber): void { this._dispatch({ data: amount, type: ActionTypes.UpdateOrderFillAmount, @@ -175,13 +175,13 @@ export class Dispatcher { } // Docs - public updateCurrentDocsVersion(version: string) { + public updateCurrentDocsVersion(version: string): void { this._dispatch({ data: version, type: ActionTypes.UpdateLibraryVersion, }); } - public updateAvailableDocVersions(versions: string[]) { + public updateAvailableDocVersions(versions: string[]): void { this._dispatch({ data: versions, type: ActionTypes.UpdateAvailableLibraryVersions, @@ -189,30 +189,30 @@ export class Dispatcher { } // Shared - public showFlashMessage(msg: string | React.ReactNode) { + public showFlashMessage(msg: string | React.ReactNode): void { this._dispatch({ data: msg, type: ActionTypes.ShowFlashMessage, }); } - public hideFlashMessage() { + public hideFlashMessage(): void { this._dispatch({ type: ActionTypes.HideFlashMessage, }); } - public updateProviderType(providerType: ProviderType) { + public updateProviderType(providerType: ProviderType): void { this._dispatch({ type: ActionTypes.UpdateProviderType, data: providerType, }); } - public updateInjectedProviderName(injectedProviderName: string) { + public updateInjectedProviderName(injectedProviderName: string): void { this._dispatch({ type: ActionTypes.UpdateInjectedProviderName, data: injectedProviderName, }); } - public updateSelectedLanguage(language: Language) { + public updateSelectedLanguage(language: Language): void { this._dispatch({ type: ActionTypes.UpdateSelectedLanguage, data: language, diff --git a/packages/website/ts/redux/reducer.ts b/packages/website/ts/redux/reducer.ts index a628f65c2..bb8f1472b 100644 --- a/packages/website/ts/redux/reducer.ts +++ b/packages/website/ts/redux/reducer.ts @@ -91,7 +91,7 @@ const INITIAL_STATE: State = { translate: new Translate(), }; -export function reducer(state: State = INITIAL_STATE, action: Action) { +export function reducer(state: State = INITIAL_STATE, action: Action): State { switch (action.type) { // Portal case ActionTypes.ResetState: diff --git a/packages/website/ts/types.ts b/packages/website/ts/types.ts index 2544e6735..33e4d6c30 100644 --- a/packages/website/ts/types.ts +++ b/packages/website/ts/types.ts @@ -207,7 +207,6 @@ export interface ContractEvent { args: any; } -export type InputErrMsg = React.ReactNode | string | undefined; export type ValidatedBigNumberCallback = (isValid: boolean, amount?: BigNumber) => void; export enum ScreenWidths { Sm = 'SM', @@ -344,6 +343,7 @@ export enum Docs { export enum WebsiteLegacyPaths { ZeroExJs = '/docs/0xjs', Web3Wrapper = '/docs/web3_wrapper', + Deployer = '/docs/deployer', } export enum WebsitePaths { @@ -357,10 +357,11 @@ export enum WebsitePaths { SmartContracts = '/docs/contracts', Connect = '/docs/connect', Web3Wrapper = '/docs/web3-wrapper', - Deployer = '/docs/deployer', + SolCompiler = '/docs/sol-compiler', JSONSchemas = '/docs/json-schemas', SolCov = '/docs/sol-cov', Subproviders = '/docs/subproviders', + OrderUtils = '/docs/order-utils', Jobs = '/jobs', } @@ -369,10 +370,11 @@ export enum DocPackages { ZeroExJs = 'ZERO_EX_JS', SmartContracts = 'SMART_CONTRACTS', Web3Wrapper = 'WEB3_WRAPPER', - Deployer = 'DEPLOYER', + SolCompiler = 'SOL_COMPILER', JSONSchemas = 'JSON_SCHEMAS', SolCov = 'SOL_COV', Subproviders = 'SUBPROVIDERS', + OrderUtils = 'ORDER_UTILS', } export enum Key { @@ -421,7 +423,7 @@ export enum Key { About = 'ABOUT', Careers = 'CAREERS', Contact = 'CONTACT', - Deployer = 'DEPLOYER', + SolCompiler = 'SOL_COMPILER', JsonSchemas = 'JSON_SCHEMAS', SolCov = 'SOL_COV', Subproviders = 'SUBPROVIDERS', @@ -431,6 +433,7 @@ export enum Key { Whitepaper = 'WHITEPAPER', Wiki = 'WIKI', Web3Wrapper = 'WEB3_WRAPPER', + OrderUtils = 'ORDER_UTILS', And = 'AND', Faq = 'FAQ', SmartContracts = 'SMART_CONTRACTS', @@ -500,17 +503,24 @@ export interface TokenState { price?: BigNumber; } -// TODO: Add topTokens and headerUrl properties once available from backend export interface WebsiteBackendRelayerInfo { - id: string; name: string; - dailyTxnVolume: string; + weeklyTxnVolume: string; url: string; + appUrl?: string; + headerImgUrl?: string; + topTokens: WebsiteBackendTokenInfo[]; } export interface WebsiteBackendPriceInfo { - price: string; + [symbol: string]: string; +} + +export interface WebsiteBackendTokenInfo { address: string; + decimals: number; + name: string; + symbol: string; } export interface WebsiteBackendGasInfo { diff --git a/packages/website/ts/utils/analytics.ts b/packages/website/ts/utils/analytics.ts index 37c47c7b0..928e45bc3 100644 --- a/packages/website/ts/utils/analytics.ts +++ b/packages/website/ts/utils/analytics.ts @@ -5,10 +5,10 @@ import { utils } from 'ts/utils/utils'; import * as Web3 from 'web3'; export const analytics = { - init() { + init(): void { ReactGA.initialize(configs.GOOGLE_ANALYTICS_ID); }, - logEvent(category: string, action: string, label: string, value?: any) { + logEvent(category: string, action: string, label: string, value?: any): void { ReactGA.event({ category, action, @@ -16,7 +16,7 @@ export const analytics = { value, }); }, - async logProviderAsync(web3IfExists: Web3) { + async logProviderAsync(web3IfExists: Web3): Promise<void> { await utils.onPageLoadAsync(); const providerType = !_.isUndefined(web3IfExists) ? utils.getProviderType(web3IfExists.currentProvider) diff --git a/packages/website/ts/utils/backend_client.ts b/packages/website/ts/utils/backend_client.ts index fdbb3e03a..c440b1604 100644 --- a/packages/website/ts/utils/backend_client.ts +++ b/packages/website/ts/utils/backend_client.ts @@ -1,16 +1,8 @@ -import { BigNumber, logUtils } from '@0xproject/utils'; import * as _ from 'lodash'; -import * as queryString from 'query-string'; -import { - ArticlesBySection, - ItemByAddress, - WebsiteBackendGasInfo, - WebsiteBackendPriceInfo, - WebsiteBackendRelayerInfo, -} from 'ts/types'; -import { configs } from 'ts/utils/configs'; -import { errorReporter } from 'ts/utils/error_reporter'; +import { ArticlesBySection, WebsiteBackendGasInfo, WebsiteBackendPriceInfo, WebsiteBackendRelayerInfo } from 'ts/types'; +import { fetchUtils } from 'ts/utils/fetch_utils'; +import { utils } from 'ts/utils/utils'; const ETH_GAS_STATION_ENDPOINT = '/eth_gas_station'; const PRICES_ENDPOINT = '/prices'; @@ -19,52 +11,26 @@ const WIKI_ENDPOINT = '/wiki'; export const backendClient = { async getGasInfoAsync(): Promise<WebsiteBackendGasInfo> { - const result = await requestAsync(ETH_GAS_STATION_ENDPOINT); + const result = await fetchUtils.requestAsync(utils.getBackendBaseUrl(), ETH_GAS_STATION_ENDPOINT); return result; }, - async getPriceInfosAsync(tokenAddresses: string[]): Promise<WebsiteBackendPriceInfo[]> { - if (_.isEmpty(tokenAddresses)) { - return []; + async getPriceInfoAsync(tokenSymbols: string[]): Promise<WebsiteBackendPriceInfo> { + if (_.isEmpty(tokenSymbols)) { + return {}; } - const joinedTokenAddresses = tokenAddresses.join(','); + const joinedTokenSymbols = tokenSymbols.join(','); const queryParams = { - tokens: joinedTokenAddresses, + tokens: joinedTokenSymbols, }; - const result = await requestAsync(PRICES_ENDPOINT, queryParams); + const result = await fetchUtils.requestAsync(utils.getBackendBaseUrl(), PRICES_ENDPOINT, queryParams); return result; }, async getRelayerInfosAsync(): Promise<WebsiteBackendRelayerInfo[]> { - const result = await requestAsync(RELAYERS_ENDPOINT); + const result = await fetchUtils.requestAsync(utils.getBackendBaseUrl(), RELAYERS_ENDPOINT); return result; }, async getWikiArticlesBySectionAsync(): Promise<ArticlesBySection> { - const result = await requestAsync(WIKI_ENDPOINT); + const result = await fetchUtils.requestAsync(utils.getBackendBaseUrl(), WIKI_ENDPOINT); return result; }, }; - -async function requestAsync(endpoint: string, queryParams?: object): Promise<any> { - const query = queryStringFromQueryParams(queryParams); - const url = `${configs.BACKEND_BASE_URL}${endpoint}${query}`; - const response = await fetch(url); - if (response.status !== 200) { - const errorText = `Error requesting url: ${url}, ${response.status}: ${response.statusText}`; - logUtils.log(errorText); - const error = Error(errorText); - // tslint:disable-next-line:no-floating-promises - errorReporter.reportAsync(error); - throw error; - } - const result = await response.json(); - return result; -} - -function queryStringFromQueryParams(queryParams?: object): string { - // if params are undefined or empty, return an empty string - if (_.isUndefined(queryParams) || _.isEmpty(queryParams)) { - return ''; - } - // stringify the formatted object - const stringifiedParams = queryString.stringify(queryParams); - return `?${stringifiedParams}`; -} diff --git a/packages/website/ts/utils/configs.ts b/packages/website/ts/utils/configs.ts index a54fc56a8..9fec814b7 100644 --- a/packages/website/ts/utils/configs.ts +++ b/packages/website/ts/utils/configs.ts @@ -1,5 +1,6 @@ import * as _ from 'lodash'; import { Environments, OutdatedWrappedEtherByNetworkId, PublicNodeUrlsByNetworkId } from 'ts/types'; +import { utils } from 'ts/utils/utils'; const BASE_URL = window.location.origin; const isDevelopment = _.includes( @@ -10,13 +11,15 @@ const INFURA_API_KEY = 'T5WSC8cautR4KXyYgsRs'; export const configs = { AMOUNT_DISPLAY_PRECSION: 5, - BACKEND_BASE_URL: 'https://website-api.0xproject.com', + BACKEND_BASE_PROD_URL: 'https://website-api.0xproject.com', + BACKEND_BASE_STAGING_URL: 'http://ec2-52-91-181-85.compute-1.amazonaws.com', BASE_URL, BITLY_ACCESS_TOKEN: 'ffc4c1a31e5143848fb7c523b39f91b9b213d208', DEFAULT_DERIVATION_PATH: `44'/60'/0'`, // WARNING: ZRX & WETH MUST always be default trackedTokens DEFAULT_TRACKED_TOKEN_SYMBOLS: ['WETH', 'ZRX'], DOMAIN_STAGING: 'staging-0xproject.s3-website-us-east-1.amazonaws.com', + DOMAIN_DOGFOOD: 'dogfood-0xproject.s3-website-us-east-1.amazonaws.com', DOMAIN_DEVELOPMENT: '0xproject.localhost:3572', DOMAIN_PRODUCTION: '0xproject.com', ENVIRONMENT: isDevelopment ? Environments.DEVELOPMENT : Environments.PRODUCTION, diff --git a/packages/website/ts/utils/fetch_utils.ts b/packages/website/ts/utils/fetch_utils.ts new file mode 100644 index 000000000..d2e902db5 --- /dev/null +++ b/packages/website/ts/utils/fetch_utils.ts @@ -0,0 +1,33 @@ +import { logUtils } from '@0xproject/utils'; +import * as _ from 'lodash'; +import * as queryString from 'query-string'; + +import { errorReporter } from 'ts/utils/error_reporter'; + +export const fetchUtils = { + async requestAsync(baseUrl: string, path: string, queryParams?: object): Promise<any> { + const query = queryStringFromQueryParams(queryParams); + const url = `${baseUrl}${path}${query}`; + const response = await fetch(url); + if (response.status !== 200) { + const errorText = `Error requesting url: ${url}, ${response.status}: ${response.statusText}`; + logUtils.log(errorText); + const error = Error(errorText); + // tslint:disable-next-line:no-floating-promises + errorReporter.reportAsync(error); + throw error; + } + const result = await response.json(); + return result; + }, +}; + +function queryStringFromQueryParams(queryParams?: object): string { + // if params are undefined or empty, return an empty string + if (_.isUndefined(queryParams) || _.isEmpty(queryParams)) { + return ''; + } + // stringify the formatted object + const stringifiedParams = queryString.stringify(queryParams); + return `?${stringifiedParams}`; +} diff --git a/packages/website/ts/utils/translate.ts b/packages/website/ts/utils/translate.ts index 5148e48ad..39924b6f7 100644 --- a/packages/website/ts/utils/translate.ts +++ b/packages/website/ts/utils/translate.ts @@ -42,10 +42,10 @@ export class Translate { } this.setLanguage(language); } - public getLanguage() { + public getLanguage(): Language { return this._selectedLanguage; } - public setLanguage(language: Language) { + public setLanguage(language: Language): void { const isLanguageSupported = !_.isUndefined(languageToTranslations[language]); if (!isLanguageSupported) { throw new Error(`${language} not supported`); @@ -53,7 +53,7 @@ export class Translate { this._selectedLanguage = language; this._translation = languageToTranslations[language]; } - public get(key: Key, decoration?: Deco) { + public get(key: Key, decoration?: Deco): string { let text = this._translation[key]; if (!_.isUndefined(decoration) && !_.includes(languagesWithoutCaps, this._selectedLanguage)) { switch (decoration) { @@ -77,7 +77,7 @@ export class Translate { } return text; } - private _capitalize(text: string) { + private _capitalize(text: string): string { return `${text.charAt(0).toUpperCase()}${text.slice(1)}`; } } diff --git a/packages/website/ts/utils/utils.ts b/packages/website/ts/utils/utils.ts index 53d60b3ce..3c99bd2fe 100644 --- a/packages/website/ts/utils/utils.ts +++ b/packages/website/ts/utils/utils.ts @@ -1,11 +1,22 @@ -import { ECSignature, ExchangeContractErrs, ZeroEx, ZeroExError } from '0x.js'; +import { ContractWrappersError, ECSignature, ExchangeContractErrs, ZeroEx } from '0x.js'; +import { OrderError } from '@0xproject/order-utils'; import { constants as sharedConstants, EtherscanLinkSuffixes, Networks } from '@0xproject/react-shared'; import { Provider } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import deepEqual = require('deep-equal'); import * as _ from 'lodash'; import * as moment from 'moment'; -import { Environments, Order, Providers, ScreenWidths, Side, SideToAssetToken, Token, TokenByAddress } from 'ts/types'; +import { + BlockchainCallErrs, + Environments, + Order, + Providers, + ScreenWidths, + Side, + SideToAssetToken, + Token, + TokenByAddress, +} from 'ts/types'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; import * as u2f from 'ts/vendor/u2f_api'; @@ -13,16 +24,18 @@ 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 = { - assert(condition: boolean, message: string) { + assert(condition: boolean, message: string): void { if (!condition) { throw new Error(message); } }, - spawnSwitchErr(name: string, value: any) { + spawnSwitchErr(name: string, value: any): Error { return new Error(`Unexpected switch value: ${value} encountered for ${name}`); }, - isNumeric(n: string) { + isNumeric(n: string): boolean { return !isNaN(parseFloat(n)) && isFinite(Number(n)); }, // This default unix timestamp is used for orders where the user does not specify an expiry date. @@ -95,13 +108,13 @@ export const utils = { }; return order; }, - async sleepAsync(ms: number) { - return new Promise(resolve => setTimeout(resolve, ms)); + async sleepAsync(ms: number): Promise<NodeJS.Timer> { + return new Promise<NodeJS.Timer>(resolve => setTimeout(resolve, ms)); }, - deepEqual(actual: any, expected: any, opts?: { strict: boolean }) { + deepEqual(actual: any, expected: any, opts?: { strict: boolean }): boolean { return deepEqual(actual, expected, opts); }, - getColSize(items: number) { + getColSize(items: number): number { const bassCssGridSize = 12; // Source: http://basscss.com/#basscss-grid const colSize = bassCssGridSize / items; if (!_.isInteger(colSize)) { @@ -109,7 +122,7 @@ export const utils = { } return colSize; }, - getScreenWidth() { + getScreenWidth(): ScreenWidths { const documentEl = document.documentElement; const body = document.getElementsByTagName('body')[0]; const widthInPx = window.innerWidth || documentEl.clientWidth || body.clientWidth; @@ -151,7 +164,7 @@ export const utils = { // This checks the error message returned from an injected Web3 instance on the page // after a user was prompted to sign a message or send a transaction and decided to // reject the request. - didUserDenyWeb3Request(errMsg: string) { + didUserDenyWeb3Request(errMsg: string): boolean { const metamaskDenialErrMsg = 'User denied'; const paritySignerDenialErrMsg = 'Request has been rejected'; const ledgerDenialErrMsg = 'Invalid status 6985'; @@ -161,7 +174,7 @@ export const utils = { _.includes(errMsg, ledgerDenialErrMsg); return isUserDeniedErrMsg; }, - getCurrentEnvironment() { + getCurrentEnvironment(): string { switch (location.host) { case configs.DOMAIN_DEVELOPMENT: return 'development'; @@ -177,7 +190,7 @@ export const utils = { const truncatedAddress = `${address.substring(0, 6)}...${address.substr(-4)}`; // 0x3d5a...b287 return truncatedAddress; }, - hasUniqueNameAndSymbol(tokens: Token[], token: Token) { + hasUniqueNameAndSymbol(tokens: Token[], token: Token): boolean { if (token.isRegistered) { return true; // Since it's registered, it is the canonical token } @@ -192,21 +205,20 @@ export const utils = { const isUniqueSymbol = _.isUndefined(tokenWithSameSymbolIfExists); return isUniqueName && isUniqueSymbol; }, - zeroExErrToHumanReadableErrMsg(error: ZeroExError | ExchangeContractErrs, takerAddress: string): string { - const ZeroExErrorToHumanReadableError: { [error: string]: string } = { - [ZeroExError.ExchangeContractDoesNotExist]: 'Exchange contract does not exist', - [ZeroExError.EtherTokenContractDoesNotExist]: 'EtherToken contract does not exist', - [ZeroExError.TokenTransferProxyContractDoesNotExist]: 'TokenTransferProxy contract does not exist', - [ZeroExError.TokenRegistryContractDoesNotExist]: 'TokenRegistry contract does not exist', - [ZeroExError.TokenContractDoesNotExist]: 'Token contract does not exist', - [ZeroExError.ZRXContractDoesNotExist]: 'ZRX contract does not exist', - [ZeroExError.UnhandledError]: 'Unhandled error occured', - [ZeroExError.UserHasNoAssociatedAddress]: 'User has no addresses available', - [ZeroExError.InvalidSignature]: 'Order signature is not valid', - [ZeroExError.ContractNotDeployedOnNetwork]: 'Contract is not deployed on the detected network', - [ZeroExError.InvalidJump]: 'Invalid jump occured while executing the transaction', - [ZeroExError.OutOfGas]: 'Transaction ran out of gas', - [ZeroExError.NoNetworkId]: 'No network id detected', + zeroExErrToHumanReadableErrMsg(error: ContractWrappersError | ExchangeContractErrs, takerAddress: string): string { + const ContractWrappersErrorToHumanReadableError: { [error: string]: string } = { + [ContractWrappersError.ExchangeContractDoesNotExist]: 'Exchange contract does not exist', + [ContractWrappersError.EtherTokenContractDoesNotExist]: 'EtherToken contract does not exist', + [ContractWrappersError.TokenTransferProxyContractDoesNotExist]: + 'TokenTransferProxy contract does not exist', + [ContractWrappersError.TokenRegistryContractDoesNotExist]: 'TokenRegistry contract does not exist', + [ContractWrappersError.TokenContractDoesNotExist]: 'Token contract does not exist', + [ContractWrappersError.ZRXContractDoesNotExist]: 'ZRX contract does not exist', + [BlockchainCallErrs.UserHasNoAssociatedAddresses]: 'User has no addresses available', + [OrderError.InvalidSignature]: 'Order signature is not valid', + [ContractWrappersError.ContractNotDeployedOnNetwork]: 'Contract is not deployed on the detected network', + [ContractWrappersError.InvalidJump]: 'Invalid jump occured while executing the transaction', + [ContractWrappersError.OutOfGas]: 'Transaction ran out of gas', }; const exchangeContractErrorToHumanReadableError: { [error: string]: string; @@ -239,7 +251,7 @@ export const utils = { [ExchangeContractErrs.InsufficientRemainingFillAmount]: 'Insufficient remaining fill amount', }; const humanReadableErrorMsg = - exchangeContractErrorToHumanReadableError[error] || ZeroExErrorToHumanReadableError[error]; + exchangeContractErrorToHumanReadableError[error] || ContractWrappersErrorToHumanReadableError[error]; return humanReadableErrorMsg; }, isParityNode(nodeVersion: string): boolean { @@ -259,7 +271,7 @@ export const utils = { ); return isTestNetwork; }, - getCurrentBaseUrl() { + getCurrentBaseUrl(): string { const port = window.location.port; const hasPort = !_.isUndefined(port); const baseUrl = `https://${window.location.hostname}${hasPort ? `:${port}` : ''}`; @@ -292,10 +304,14 @@ export const utils = { } return parsedProviderName; }, - isDevelopment() { + getBackendBaseUrl(): string { + return isDogfood() ? configs.BACKEND_BASE_STAGING_URL : configs.BACKEND_BASE_PROD_URL; + }, + isDevelopment(): boolean { return configs.ENVIRONMENT === Environments.DEVELOPMENT; }, - isStaging() { + isStaging(): boolean { return _.includes(window.location.href, configs.DOMAIN_STAGING); }, + isDogfood, }; |