diff options
35 files changed, 370 insertions, 371 deletions
diff --git a/packages/contract-wrappers/CHANGELOG.json b/packages/contract-wrappers/CHANGELOG.json index 33d9bc4c0..e34b50c1a 100644 --- a/packages/contract-wrappers/CHANGELOG.json +++ b/packages/contract-wrappers/CHANGELOG.json @@ -1,5 +1,13 @@ [ { + "version": "0.0.6", + "changes": [ + { + "note": "Update blockstream to v5.0 and propogate up caught errors to active subscriptions" + } + ] + }, + { "timestamp": 1529397769, "version": "0.0.5", "changes": [ diff --git a/packages/contract-wrappers/package.json b/packages/contract-wrappers/package.json index 1b4835be6..373e023bd 100644 --- a/packages/contract-wrappers/package.json +++ b/packages/contract-wrappers/package.json @@ -74,7 +74,7 @@ "source-map-support": "^0.5.0", "tslint": "5.8.0", "typescript": "2.7.1", - "web3-provider-engine": "^14.0.4" + "web3-provider-engine": "14.0.6" }, "dependencies": { "@0xproject/assert": "^0.2.12", @@ -87,7 +87,7 @@ "@0xproject/utils": "^0.7.1", "@0xproject/web3-wrapper": "^0.7.1", "ethereum-types": "^0.0.2", - "ethereumjs-blockstream": "^2.0.6", + "ethereumjs-blockstream": "5.0.0", "ethereumjs-util": "^5.1.1", "ethers": "3.0.22", "js-sha3": "^0.7.0", diff --git a/packages/contract-wrappers/src/contract_wrappers/contract_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/contract_wrapper.ts index 04f69bc3d..9cc661080 100644 --- a/packages/contract-wrappers/src/contract_wrappers/contract_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/contract_wrapper.ts @@ -9,7 +9,7 @@ import { } from '@0xproject/types'; import { intervalUtils } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; -import { Block, BlockAndLogStreamer } from 'ethereumjs-blockstream'; +import { Block, BlockAndLogStreamer, Log } from 'ethereumjs-blockstream'; import * as _ from 'lodash'; import { @@ -39,7 +39,7 @@ export abstract class ContractWrapper { public abstract abi: ContractAbi; protected _web3Wrapper: Web3Wrapper; protected _networkId: number; - private _blockAndLogStreamerIfExists?: BlockAndLogStreamer; + private _blockAndLogStreamerIfExists: BlockAndLogStreamer<Block, Log> | undefined; private _blockAndLogStreamIntervalIfExists?: NodeJS.Timer; private _filters: { [filterToken: string]: FilterObject }; private _filterCallbacks: { @@ -163,6 +163,7 @@ export abstract class ContractWrapper { this._blockAndLogStreamerIfExists = new BlockAndLogStreamer( this._web3Wrapper.getBlockAsync.bind(this._web3Wrapper), this._web3Wrapper.getLogsAsync.bind(this._web3Wrapper), + this._onBlockAndLogStreamerError.bind(this), ); const catchAllLogFilter = {}; this._blockAndLogStreamerIfExists.addLogFilter(catchAllLogFilter); @@ -180,6 +181,14 @@ export abstract class ContractWrapper { this._onLogStateChanged.bind(this, isRemoved), ); } + private _onBlockAndLogStreamerError(err: Error): void { + // Propogate all Blockstream subscriber errors to all + // top-level subscriptions + const filterCallbacks = _.values(this._filterCallbacks); + _.each(filterCallbacks, filterCallback => { + filterCallback(err); + }); + } private _onReconcileBlockError(err: Error): void { const filterTokens = _.keys(this._filterCallbacks); _.each(filterTokens, filterToken => { diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index 621053ed7..ebb2aeb41 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -53,7 +53,7 @@ "@0xproject/web3-wrapper": "^0.7.1", "lodash": "^4.17.4", "web3": "^0.20.0", - "web3-provider-engine": "^14.0.4" + "web3-provider-engine": "14.0.6" }, "publishConfig": { "access": "public" diff --git a/packages/metacoin/package.json b/packages/metacoin/package.json index 6b6ab9c40..3fa79387e 100644 --- a/packages/metacoin/package.json +++ b/packages/metacoin/package.json @@ -45,7 +45,7 @@ "ethers": "3.0.22", "lodash": "^4.17.4", "run-s": "^0.0.0", - "web3-provider-engine": "^14.0.4" + "web3-provider-engine": "14.0.6" }, "devDependencies": { "@0xproject/dev-utils": "^0.4.4", diff --git a/packages/migrations/package.json b/packages/migrations/package.json index f4190a771..a5fd3756a 100644 --- a/packages/migrations/package.json +++ b/packages/migrations/package.json @@ -70,7 +70,7 @@ "ethereum-types": "^0.0.2", "ethers": "3.0.22", "lodash": "^4.17.4", - "web3-provider-engine": "^14.0.4" + "web3-provider-engine": "14.0.6" }, "optionalDependencies": { "@ledgerhq/hw-transport-node-hid": "^4.3.0" diff --git a/packages/subproviders/package.json b/packages/subproviders/package.json index e87d7bf94..a3ff31f53 100644 --- a/packages/subproviders/package.json +++ b/packages/subproviders/package.json @@ -42,7 +42,7 @@ "dependencies": { "@0xproject/assert": "^0.2.12", "@0xproject/types": "^0.8.1", - "@0xproject/typescript-typings": "^0.4.1", + "@0xproject/typescript-typings": "^0.4.2", "@0xproject/utils": "^0.7.1", "@ledgerhq/hw-app-eth": "^4.3.0", "@ledgerhq/hw-transport-u2f": "^4.3.0", @@ -56,12 +56,11 @@ "lodash": "^4.17.4", "semaphore-async-await": "^1.5.1", "web3": "^0.20.0", - "web3-provider-engine": "^14.0.4" + "web3-provider-engine": "14.0.6" }, "devDependencies": { "@0xproject/monorepo-scripts": "^0.2.1", "@0xproject/tslint-config": "^0.4.20", - "@0xproject/typescript-typings": "^0.4.2", "@0xproject/utils": "^0.7.1", "@types/bip39": "^2.4.0", "@types/bn.js": "^4.11.0", diff --git a/packages/testnet-faucets/package.json b/packages/testnet-faucets/package.json index fdd71656a..0ec62f097 100644 --- a/packages/testnet-faucets/package.json +++ b/packages/testnet-faucets/package.json @@ -29,7 +29,7 @@ "lodash": "^4.17.4", "rollbar": "^0.6.5", "web3": "^0.20.0", - "web3-provider-engine": "^14.0.4" + "web3-provider-engine": "14.0.6" }, "devDependencies": { "@0xproject/tslint-config": "^0.4.20", diff --git a/packages/website/package.json b/packages/website/package.json index c7b689356..1bfc385b5 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -38,6 +38,7 @@ "lodash": "^4.17.4", "material-ui": "^0.17.1", "moment": "2.21.0", + "numeral": "^2.0.6", "polished": "^1.9.2", "query-string": "^6.0.0", "react": "15.6.1", @@ -58,7 +59,7 @@ "thenby": "^1.2.3", "truffle-contract": "2.0.1", "web3": "^0.20.0", - "web3-provider-engine": "^14.0.4", + "web3-provider-engine": "14.0.6", "whatwg-fetch": "^2.0.3", "xml-js": "^1.6.4" }, @@ -71,6 +72,7 @@ "@types/lodash": "4.14.104", "@types/material-ui": "0.18.0", "@types/node": "^8.0.53", + "@types/numeral": "^0.0.22", "@types/query-string": "^5.1.0", "@types/react": "16.3.13", "@types/react-copy-to-clipboard": "^4.2.0", diff --git a/packages/website/public/images/lock_icon.svg b/packages/website/public/images/lock_icon.svg new file mode 100644 index 000000000..83e8191a1 --- /dev/null +++ b/packages/website/public/images/lock_icon.svg @@ -0,0 +1,3 @@ +<svg width="26" height="32" viewBox="0 0 26 32" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M6.47619 0C3.79509 0 1.60489 2.21216 1.60489 4.92014V6.33135C0.717479 6.33135 -3.60127e-08 7.05602 -3.60127e-08 7.95232V14.379C-3.60127e-08 15.2753 0.717479 16 1.60489 16H11.3475C12.2349 16 12.9524 15.2753 12.9524 14.379V7.95232C12.9524 7.05602 12.2349 6.33135 11.3475 6.33135V4.92014C11.3475 2.21216 9.1573 0 6.47619 0ZM9.6482 6.33135H3.30418V4.92014C3.30418 3.16567 4.72026 1.71633 6.47619 1.71633C8.23213 1.71633 9.6482 3.16567 9.6482 4.92014V6.33135Z" transform="scale(2)" fill="black"/> +</svg> diff --git a/packages/website/public/images/setup_account_icon.svg b/packages/website/public/images/setup_account_icon.svg new file mode 100644 index 000000000..eaa5b2fd6 --- /dev/null +++ b/packages/website/public/images/setup_account_icon.svg @@ -0,0 +1,3 @@ +<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M16.5 9C16.5 13.1421 13.1421 16.5 9 16.5C4.85791 16.5 1.5 13.1421 1.5 9C1.5 4.85791 4.85791 1.5 9 1.5C13.1421 1.5 16.5 4.85791 16.5 9ZM18 9C18 13.9706 13.9707 18 9 18C4.0293 18 0 13.9706 0 9C0 4.02942 4.0293 0 9 0C13.9707 0 18 4.02942 18 9ZM9.21973 5.7196C9.5127 5.42664 9.9873 5.42664 10.2803 5.7196L13.0806 8.51953C13.373 8.8125 13.373 9.28735 13.0806 9.5802L10.2803 12.3802C9.9873 12.6731 9.5127 12.6731 9.21973 12.3802C8.92676 12.0873 8.92676 11.6124 9.21973 11.3196L10.7393 9.7998H4.75C4.33594 9.7998 4 9.46399 4 9.0498C4 8.63562 4.33594 8.2998 4.75 8.2998H10.7393L9.21973 6.78015C8.92676 6.4873 8.92676 6.01245 9.21973 5.7196Z" transform="scale(2)" fill="#3289F1"/> +</svg> diff --git a/packages/website/public/images/team/alexbrowne.png b/packages/website/public/images/team/alexbrowne.png Binary files differnew file mode 100644 index 000000000..76a61913e --- /dev/null +++ b/packages/website/public/images/team/alexbrowne.png diff --git a/packages/website/ts/blockchain.ts b/packages/website/ts/blockchain.ts index d18c34c32..b59306974 100644 --- a/packages/website/ts/blockchain.ts +++ b/packages/website/ts/blockchain.ts @@ -73,6 +73,8 @@ const providerToName: { [provider: string]: string } = { [Providers.Metamask]: constants.PROVIDER_NAME_METAMASK, [Providers.Parity]: constants.PROVIDER_NAME_PARITY_SIGNER, [Providers.Mist]: constants.PROVIDER_NAME_MIST, + [Providers.Toshi]: constants.PROVIDER_NAME_TOSHI, + [Providers.Cipher]: constants.PROVIDER_NAME_CIPHER, }; export class Blockchain { diff --git a/packages/website/ts/blockchain_watcher.ts b/packages/website/ts/blockchain_watcher.ts index df5f73fd1..4b23aa98a 100644 --- a/packages/website/ts/blockchain_watcher.ts +++ b/packages/website/ts/blockchain_watcher.ts @@ -10,6 +10,7 @@ export class BlockchainWatcher { private _watchBalanceIntervalId: NodeJS.Timer; private _prevUserEtherBalanceInWei?: BigNumber; private _prevUserAddressIfExists: string; + private _prevNodeVersionIfExists: string; constructor(dispatcher: Dispatcher, web3Wrapper: Web3Wrapper, shouldPollUserAddress: boolean) { this._dispatcher = dispatcher; this._shouldPollUserAddress = shouldPollUserAddress; @@ -43,11 +44,9 @@ export class BlockchainWatcher { ); } private async _updateBalanceAsync(): Promise<void> { - let prevNodeVersion: string; - // Check for node version changes const currentNodeVersion = await this._web3Wrapper.getNodeVersionAsync(); - if (currentNodeVersion !== prevNodeVersion) { - prevNodeVersion = currentNodeVersion; + if (this._prevNodeVersionIfExists !== currentNodeVersion) { + this._prevNodeVersionIfExists = currentNodeVersion; this._dispatcher.updateNodeVersion(currentNodeVersion); } diff --git a/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx b/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx index 9ac78e80e..7b09cc92c 100644 --- a/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx +++ b/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx @@ -103,7 +103,6 @@ export class EthWethConversionDialog extends React.Component< shouldCheckAllowance={false} onChange={this._onValueChange.bind(this)} amount={this.state.value} - onVisitBalancesPageClick={this.props.onCancelled} /> ) : ( <EthAmountInput @@ -112,7 +111,6 @@ export class EthWethConversionDialog extends React.Component< onChange={this._onValueChange.bind(this)} shouldCheckBalance={true} shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs} - onVisitBalancesPageClick={this.props.onCancelled} /> )} <div className="pt1" style={{ fontSize: 12 }}> diff --git a/packages/website/ts/components/dialogs/send_dialog.tsx b/packages/website/ts/components/dialogs/send_dialog.tsx index 421f18b4f..8a98fdf69 100644 --- a/packages/website/ts/components/dialogs/send_dialog.tsx +++ b/packages/website/ts/components/dialogs/send_dialog.tsx @@ -80,7 +80,6 @@ export class SendDialog extends React.Component<SendDialogProps, SendDialogState shouldCheckAllowance={false} onChange={this._onValueChange.bind(this)} amount={this.state.value} - onVisitBalancesPageClick={this.props.onCancelled} lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch} /> </div> diff --git a/packages/website/ts/components/inputs/balance_bounded_input.tsx b/packages/website/ts/components/inputs/balance_bounded_input.tsx index 968609030..f23beb436 100644 --- a/packages/website/ts/components/inputs/balance_bounded_input.tsx +++ b/packages/website/ts/components/inputs/balance_bounded_input.tsx @@ -3,9 +3,8 @@ import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import TextField from 'material-ui/TextField'; import * as React from 'react'; -import { Link } from 'react-router-dom'; import { RequiredLabel } from 'ts/components/ui/required_label'; -import { ValidatedBigNumberCallback, WebsitePaths } from 'ts/types'; +import { ValidatedBigNumberCallback } from 'ts/types'; import { utils } from 'ts/utils/utils'; interface BalanceBoundedInputProps { @@ -18,8 +17,6 @@ interface BalanceBoundedInputProps { shouldShowIncompleteErrs?: boolean; shouldCheckBalance: boolean; validate?: (amount: BigNumber) => React.ReactNode; - onVisitBalancesPageClick?: () => void; - shouldHideVisitBalancesLink?: boolean; isDisabled?: boolean; shouldShowErrs?: boolean; shouldShowUnderline?: boolean; @@ -35,7 +32,6 @@ interface BalanceBoundedInputState { export class BalanceBoundedInput extends React.Component<BalanceBoundedInputProps, BalanceBoundedInputState> { public static defaultProps: Partial<BalanceBoundedInputProps> = { shouldShowIncompleteErrs: false, - shouldHideVisitBalancesLink: false, isDisabled: false, shouldShowErrs: true, hintText: 'amount', @@ -124,38 +120,11 @@ export class BalanceBoundedInput extends React.Component<BalanceBoundedInputProp return 'Cannot be zero'; } if (this.props.shouldCheckBalance && amount.gt(balance)) { - return <span>Insufficient balance. {this._renderIncreaseBalanceLink()}</span>; + return <span>Insufficient balance.</span>; } const errMsg = _.isUndefined(this.props.validate) ? undefined : this.props.validate(amount); return errMsg; } - private _renderIncreaseBalanceLink(): React.ReactNode { - if (this.props.shouldHideVisitBalancesLink) { - return null; - } - - const increaseBalanceText = 'Increase balance'; - const linkStyle = { - cursor: 'pointer', - color: colors.darkestGrey, - textDecoration: 'underline', - display: 'inline', - }; - if (_.isUndefined(this.props.onVisitBalancesPageClick)) { - return ( - <Link to={`${WebsitePaths.Portal}/balances`} style={linkStyle}> - {increaseBalanceText} - </Link> - ); - } else { - return ( - <div onClick={this.props.onVisitBalancesPageClick} style={linkStyle}> - {increaseBalanceText} - </div> - ); - } - } - private _setAmountState(amount: string, balance: BigNumber, callback: () => void = _.noop): void { const errorMsg = this._validate(amount, balance); this.props.onErrorMsgChange(errorMsg); diff --git a/packages/website/ts/components/inputs/eth_amount_input.tsx b/packages/website/ts/components/inputs/eth_amount_input.tsx index 1f0f27410..552d4277a 100644 --- a/packages/website/ts/components/inputs/eth_amount_input.tsx +++ b/packages/website/ts/components/inputs/eth_amount_input.tsx @@ -14,9 +14,7 @@ interface EthAmountInputProps { onChange: ValidatedBigNumberCallback; onErrorMsgChange?: (errorMsg: React.ReactNode) => void; shouldShowIncompleteErrs: boolean; - onVisitBalancesPageClick?: () => void; shouldCheckBalance: boolean; - shouldHideVisitBalancesLink?: boolean; shouldShowErrs?: boolean; shouldShowUnderline?: boolean; style?: React.CSSProperties; @@ -46,8 +44,6 @@ export class EthAmountInput extends React.Component<EthAmountInputProps, EthAmou onErrorMsgChange={this.props.onErrorMsgChange} shouldCheckBalance={this.props.shouldCheckBalance} shouldShowIncompleteErrs={this.props.shouldShowIncompleteErrs} - onVisitBalancesPageClick={this.props.onVisitBalancesPageClick} - shouldHideVisitBalancesLink={this.props.shouldHideVisitBalancesLink} hintText={this.props.hintText} shouldShowErrs={this.props.shouldShowErrs} shouldShowUnderline={this.props.shouldShowUnderline} diff --git a/packages/website/ts/components/inputs/token_amount_input.tsx b/packages/website/ts/components/inputs/token_amount_input.tsx index a67120320..93ef516cf 100644 --- a/packages/website/ts/components/inputs/token_amount_input.tsx +++ b/packages/website/ts/components/inputs/token_amount_input.tsx @@ -21,7 +21,6 @@ interface TokenAmountInputProps { shouldCheckAllowance: boolean; onChange: ValidatedBigNumberCallback; onErrorMsgChange?: (errorMsg: React.ReactNode) => void; - onVisitBalancesPageClick?: () => void; lastForceTokenStateRefetch: number; shouldShowErrs?: boolean; shouldShowUnderline?: boolean; @@ -88,7 +87,6 @@ export class TokenAmountInput extends React.Component<TokenAmountInputProps, Tok validate={this._validate.bind(this)} shouldCheckBalance={this.props.shouldCheckBalance} shouldShowIncompleteErrs={this.props.shouldShowIncompleteErrs} - onVisitBalancesPageClick={this.props.onVisitBalancesPageClick} isDisabled={!this.state.isBalanceAndAllowanceLoaded} hintText={this.props.hintText} shouldShowErrs={this.props.shouldShowErrs} diff --git a/packages/website/ts/components/portal/portal.tsx b/packages/website/ts/components/portal/portal.tsx index 9c0cb866d..8c3b5cfd7 100644 --- a/packages/website/ts/components/portal/portal.tsx +++ b/packages/website/ts/components/portal/portal.tsx @@ -1,7 +1,6 @@ import { colors, constants as sharedConstants } from '@0xproject/react-shared'; import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; -import Help from 'material-ui/svg-icons/action/help'; import * as React from 'react'; import * as DocumentTitle from 'react-document-title'; import { Link, Route, RouteComponentProps, Switch } from 'react-router-dom'; @@ -24,6 +23,7 @@ import { TopBar, TopBarDisplayType } from 'ts/components/top_bar/top_bar'; import { TradeHistory } from 'ts/components/trade_history/trade_history'; import { Container } from 'ts/components/ui/container'; import { FlashMessage } from 'ts/components/ui/flash_message'; +import { Image } from 'ts/components/ui/image'; import { Text } from 'ts/components/ui/text'; import { Wallet } from 'ts/components/wallet/wallet'; import { GenerateOrderForm } from 'ts/containers/generate_order_form'; @@ -368,11 +368,11 @@ export class Portal extends React.Component<PortalProps, PortalState> { const shouldStartOnboarding = !isMobile || this.props.location.pathname === `${WebsitePaths.Portal}/account`; const startOnboarding = ( <Container className="flex items-center center"> - <Help style={{ width: '20px', height: '20px' }} color={colors.mediumBlue} /> - <Container marginLeft="8px"> - <Text fontColor={colors.mediumBlue} fontSize="16px" onClick={this._startOnboarding.bind(this)}> - Learn how to set up your account - </Text> + <Text fontColor={colors.mediumBlue} fontSize="16px" onClick={this._startOnboarding.bind(this)}> + Set up your account to start trading + </Text> + <Container marginLeft="8px" paddingTop="3px"> + <Image src="/images/setup_account_icon.svg" height="20px" width="20x" /> </Container> </Container> ); diff --git a/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx b/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx index b26bf512b..80ff8c7ff 100644 --- a/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx +++ b/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx @@ -55,7 +55,7 @@ const styles: Styles = { }; const FALLBACK_IMG_SRC = '/images/relayer_fallback.png'; -const FALLBACK_PRIMARY_COLOR = colors.grey200; +const FALLBACK_PRIMARY_COLOR = colors.grey300; const NO_CONTENT_MESSAGE = '--'; const RELAYER_ICON_HEIGHT = '110px'; diff --git a/packages/website/ts/components/top_bar/provider_display.tsx b/packages/website/ts/components/top_bar/provider_display.tsx index 8743e4320..806eaeea5 100644 --- a/packages/website/ts/components/top_bar/provider_display.tsx +++ b/packages/website/ts/components/top_bar/provider_display.tsx @@ -1,23 +1,26 @@ import { Styles } from '@0xproject/react-shared'; import * as _ from 'lodash'; import CircularProgress from 'material-ui/CircularProgress'; -import RaisedButton from 'material-ui/RaisedButton'; import ActionAccountBalanceWallet from 'material-ui/svg-icons/action/account-balance-wallet'; -import Lock from 'material-ui/svg-icons/action/lock'; import * as React from 'react'; import { Blockchain } from 'ts/blockchain'; -import { ProviderPicker } from 'ts/components/top_bar/provider_picker'; import { AccountConnection } from 'ts/components/ui/account_connection'; import { Container } from 'ts/components/ui/container'; import { DropDown } from 'ts/components/ui/drop_down'; import { Identicon } from 'ts/components/ui/identicon'; +import { Image } from 'ts/components/ui/image'; import { Island } from 'ts/components/ui/island'; +import { + CopyAddressSimpleMenuItem, + DifferentWalletSimpleMenuItem, + GoToAccountManagementSimpleMenuItem, + SimpleMenu, +} from 'ts/components/ui/simple_menu'; import { Text } from 'ts/components/ui/text'; import { Dispatcher } from 'ts/redux/dispatcher'; import { colors } from 'ts/style/colors'; import { AccountState, ProviderType } from 'ts/types'; -import { constants } from 'ts/utils/constants'; import { utils } from 'ts/utils/utils'; const ROOT_HEIGHT = 24; @@ -44,11 +47,7 @@ const styles: Styles = { export class ProviderDisplay extends React.Component<ProviderDisplayProps, ProviderDisplayState> { public render(): React.ReactNode { - const isExternallyInjectedProvider = utils.isExternallyInjected( - this.props.providerType, - this.props.injectedProviderName, - ); - const hoverActiveNode = ( + const activeNode = ( <Island className="flex items-center py1 px2" style={styles.root}> {this._renderIcon()} <Container marginLeft="12px" marginRight="12px"> @@ -57,93 +56,34 @@ export class ProviderDisplay extends React.Component<ProviderDisplayProps, Provi {this._renderInjectedProvider()} </Island> ); - const hasLedgerProvider = this.props.providerType === ProviderType.Ledger; - const horizontalPosition = isExternallyInjectedProvider || hasLedgerProvider ? 'left' : 'middle'; return ( <div style={{ width: 'fit-content', height: 48, float: 'right' }}> <DropDown - hoverActiveNode={hoverActiveNode} - popoverContent={this.renderPopoverContent(isExternallyInjectedProvider, hasLedgerProvider)} - anchorOrigin={{ horizontal: horizontalPosition, vertical: 'bottom' }} - targetOrigin={{ horizontal: horizontalPosition, vertical: 'top' }} + activeNode={activeNode} + popoverContent={this._renderPopoverContent()} + anchorOrigin={{ horizontal: 'middle', vertical: 'bottom' }} + targetOrigin={{ horizontal: 'middle', vertical: 'top' }} zDepth={1} /> </div> ); } - public renderPopoverContent(hasInjectedProvider: boolean, hasLedgerProvider: boolean): React.ReactNode { - if (!this._isBlockchainReady()) { - return null; - } else if (hasInjectedProvider || hasLedgerProvider) { - return ( - <ProviderPicker - dispatcher={this.props.dispatcher} - networkId={this.props.networkId} - injectedProviderName={this.props.injectedProviderName} - providerType={this.props.providerType} - onToggleLedgerDialog={this.props.onToggleLedgerDialog} - blockchain={this.props.blockchain} - /> - ); - } else { - // Nothing to connect to, show install/info popover - return ( - <div className="px2" style={{ maxWidth: 420 }}> - <div className="center h4 py2" style={{ color: colors.grey700 }}> - Choose a wallet: - </div> - <div className="flex pb3"> - <div className="center px2"> - <div style={{ color: colors.darkGrey }}>Install a browser wallet</div> - <div className="py2"> - <img src="/images/metamask_or_parity.png" width="135" /> - </div> - <div> - Use{' '} - <a - href={constants.URL_METAMASK_CHROME_STORE} - target="_blank" - style={{ color: colors.lightBlueA700 }} - > - Metamask - </a>{' '} - or{' '} - <a - href={constants.URL_PARITY_CHROME_STORE} - target="_blank" - style={{ color: colors.lightBlueA700 }} - > - Parity Signer - </a> - </div> - </div> - <div> - <div - className="pl1 ml1" - style={{ borderLeft: `1px solid ${colors.grey300}`, height: 65 }} - /> - <div className="py1">or</div> - <div - className="pl1 ml1" - style={{ borderLeft: `1px solid ${colors.grey300}`, height: 68 }} - /> - </div> - <div className="px2 center"> - <div style={{ color: colors.darkGrey }}>Connect to a ledger hardware wallet</div> - <div style={{ paddingTop: 21, paddingBottom: 29 }}> - <img src="/images/ledger_icon.png" style={{ width: 80 }} /> - </div> - <div> - <RaisedButton - style={{ width: '100%' }} - label="Use Ledger" - onClick={this.props.onToggleLedgerDialog} - /> - </div> - </div> - </div> - </div> - ); + private _renderPopoverContent(): React.ReactNode { + const accountState = this._getAccountState(); + switch (accountState) { + case AccountState.Ready: + return ( + <SimpleMenu> + <CopyAddressSimpleMenuItem userAddress={this.props.userAddress} /> + <DifferentWalletSimpleMenuItem onClick={this.props.onToggleLedgerDialog} /> + <GoToAccountManagementSimpleMenuItem /> + </SimpleMenu> + ); + case AccountState.Disconnected: + case AccountState.Locked: + case AccountState.Loading: + default: + return null; } } private _renderIcon(): React.ReactNode { @@ -154,7 +94,7 @@ export class ProviderDisplay extends React.Component<ProviderDisplayProps, Provi case AccountState.Loading: return <CircularProgress size={ROOT_HEIGHT} thickness={2} />; case AccountState.Locked: - return <Lock color={colors.black} />; + return <Image src="/images/lock_icon.svg" height="20px" width="20px" />; case AccountState.Disconnected: return <ActionAccountBalanceWallet color={colors.mediumBlue} />; default: diff --git a/packages/website/ts/components/top_bar/provider_picker.tsx b/packages/website/ts/components/top_bar/provider_picker.tsx deleted file mode 100644 index 7937f2e9d..000000000 --- a/packages/website/ts/components/top_bar/provider_picker.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { colors, constants as sharedConstants } from '@0xproject/react-shared'; -import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton'; -import * as React from 'react'; -import { Blockchain } from 'ts/blockchain'; -import { Dispatcher } from 'ts/redux/dispatcher'; -import { ProviderType } from 'ts/types'; - -interface ProviderPickerProps { - networkId: number; - injectedProviderName: string; - providerType: ProviderType; - onToggleLedgerDialog: () => void; - dispatcher: Dispatcher; - blockchain: Blockchain; -} - -interface ProviderPickerState {} - -export class ProviderPicker extends React.Component<ProviderPickerProps, ProviderPickerState> { - public render(): React.ReactNode { - const isLedgerSelected = this.props.providerType === ProviderType.Ledger; - const menuStyle = { - padding: 10, - paddingTop: 15, - paddingBottom: 15, - }; - // Show dropdown with two options - return ( - <div style={{ width: 225, overflow: 'hidden' }}> - <RadioButtonGroup name="provider" defaultSelected={this.props.providerType}> - <RadioButton - onClick={this._onProviderRadioChanged.bind(this, ProviderType.Injected)} - style={{ ...menuStyle, backgroundColor: !isLedgerSelected && colors.grey50 }} - value={ProviderType.Injected} - label={this._renderLabel(this.props.injectedProviderName, !isLedgerSelected)} - /> - <RadioButton - onClick={this._onProviderRadioChanged.bind(this, ProviderType.Ledger)} - style={{ ...menuStyle, backgroundColor: isLedgerSelected && colors.grey50 }} - value={ProviderType.Ledger} - label={this._renderLabel('Ledger Nano S', isLedgerSelected)} - /> - </RadioButtonGroup> - </div> - ); - } - private _renderLabel(title: string, shouldShowNetwork: boolean): React.ReactNode { - const label = ( - <div className="flex"> - <div style={{ fontSize: 14 }}>{title}</div> - {shouldShowNetwork && this._renderNetwork()} - </div> - ); - return label; - } - private _renderNetwork(): React.ReactNode { - const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId]; - return ( - <div className="flex" style={{ marginTop: 1 }}> - <div className="relative" style={{ width: 14, paddingLeft: 14 }}> - <img - src={`/images/network_icons/${networkName.toLowerCase()}.png`} - className="absolute" - style={{ top: 6, width: 10 }} - /> - </div> - <div style={{ color: colors.lightGrey, fontSize: 11 }}>{networkName}</div> - </div> - ); - } - private _onProviderRadioChanged(value: string): void { - if (value === ProviderType.Ledger) { - this.props.onToggleLedgerDialog(); - } else { - // tslint:disable-next-line:no-floating-promises - this.props.blockchain.updateProviderToInjectedAsync(); - } - } -} diff --git a/packages/website/ts/components/top_bar/top_bar.tsx b/packages/website/ts/components/top_bar/top_bar.tsx index fac6c131f..778536663 100644 --- a/packages/website/ts/components/top_bar/top_bar.tsx +++ b/packages/website/ts/components/top_bar/top_bar.tsx @@ -199,7 +199,7 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { cursor: 'pointer', paddingTop: 16, }; - const hoverActiveNode = ( + const activeNode = ( <div className="flex relative" style={{ color: menuIconStyle.color }}> <div style={{ paddingRight: 10 }}>{this.props.translate.get(Key.Developers, Deco.Cap)}</div> <div className="absolute" style={{ paddingLeft: 3, right: 3, top: -2 }}> @@ -224,7 +224,7 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { <div className={menuClasses}> <div className="flex justify-between"> <DropDown - hoverActiveNode={hoverActiveNode} + activeNode={activeNode} popoverContent={popoverContent} anchorOrigin={{ horizontal: 'middle', vertical: 'bottom' }} targetOrigin={{ horizontal: 'middle', vertical: 'top' }} diff --git a/packages/website/ts/components/ui/container.tsx b/packages/website/ts/components/ui/container.tsx index fb718d731..edbf8814b 100644 --- a/packages/website/ts/components/ui/container.tsx +++ b/packages/website/ts/components/ui/container.tsx @@ -17,6 +17,7 @@ export interface ContainerProps { maxHeight?: StringOrNum; width?: StringOrNum; height?: StringOrNum; + minWidth?: StringOrNum; minHeight?: StringOrNum; isHidden?: boolean; className?: string; diff --git a/packages/website/ts/components/ui/drop_down.tsx b/packages/website/ts/components/ui/drop_down.tsx index 22cb942f8..4d5caef08 100644 --- a/packages/website/ts/components/ui/drop_down.tsx +++ b/packages/website/ts/components/ui/drop_down.tsx @@ -1,4 +1,4 @@ -import Popover, { PopoverAnimationVertical } from 'material-ui/Popover'; +import Popover from 'material-ui/Popover'; import * as React from 'react'; import { MaterialUIPosition } from 'ts/types'; @@ -7,13 +7,20 @@ const DEFAULT_STYLE = { fontSize: 14, }; -interface DropDownProps { - hoverActiveNode: React.ReactNode; +export enum DropdownMouseEvent { + Hover = 'hover', + Click = 'click', +} + +export interface DropDownProps { + activeNode: React.ReactNode; popoverContent: React.ReactNode; anchorOrigin: MaterialUIPosition; targetOrigin: MaterialUIPosition; style?: React.CSSProperties; zDepth?: number; + activateEvent?: DropdownMouseEvent; + closeEvent?: DropdownMouseEvent; } interface DropDownState { @@ -25,6 +32,8 @@ export class DropDown extends React.Component<DropDownProps, DropDownState> { public static defaultProps: Partial<DropDownProps> = { style: DEFAULT_STYLE, zDepth: 1, + activateEvent: DropdownMouseEvent.Hover, + closeEvent: DropdownMouseEvent.Hover, }; private _isHovering: boolean; private _popoverCloseCheckIntervalId: number; @@ -58,46 +67,61 @@ export class DropDown extends React.Component<DropDownProps, DropDownState> { onMouseEnter={this._onHover.bind(this)} onMouseLeave={this._onHoverOff.bind(this)} > - {this.props.hoverActiveNode} + <div onClick={this._onActiveNodeClick.bind(this)}>{this.props.activeNode}</div> <Popover open={this.state.isDropDownOpen} anchorEl={this.state.anchorEl} anchorOrigin={this.props.anchorOrigin} targetOrigin={this.props.targetOrigin} onRequestClose={this._closePopover.bind(this)} - useLayerForClickAway={false} - animation={PopoverAnimationVertical} + useLayerForClickAway={this.props.closeEvent === DropdownMouseEvent.Click} + animated={false} zDepth={this.props.zDepth} > - <div onMouseEnter={this._onHover.bind(this)} onMouseLeave={this._onHoverOff.bind(this)}> + <div + onMouseEnter={this._onHover.bind(this)} + onMouseLeave={this._onHoverOff.bind(this)} + onClick={this._closePopover.bind(this)} + > {this.props.popoverContent} </div> </Popover> </div> ); } + private _onActiveNodeClick(event: React.FormEvent<HTMLInputElement>): void { + if (this.props.activateEvent === DropdownMouseEvent.Click) { + this.setState({ + isDropDownOpen: true, + anchorEl: event.currentTarget, + }); + } + } private _onHover(event: React.FormEvent<HTMLInputElement>): void { this._isHovering = true; - this._checkIfShouldOpenPopover(event); + if (this.props.activateEvent === DropdownMouseEvent.Hover) { + this._checkIfShouldOpenPopover(event); + } + } + private _onHoverOff(): void { + this._isHovering = false; } private _checkIfShouldOpenPopover(event: React.FormEvent<HTMLInputElement>): void { if (this.state.isDropDownOpen) { return; // noop } - this.setState({ isDropDownOpen: true, anchorEl: event.currentTarget, }); } - private _onHoverOff(): void { - this._isHovering = false; - } private _checkIfShouldClosePopover(): void { - if (!this.state.isDropDownOpen || this._isHovering) { + if (!this.state.isDropDownOpen) { return; // noop } - this._closePopover(); + if (this.props.closeEvent === DropdownMouseEvent.Hover && !this._isHovering) { + this._closePopover(); + } } private _closePopover(): void { this.setState({ diff --git a/packages/website/ts/components/ui/simple_menu.tsx b/packages/website/ts/components/ui/simple_menu.tsx new file mode 100644 index 000000000..74b8ef6ae --- /dev/null +++ b/packages/website/ts/components/ui/simple_menu.tsx @@ -0,0 +1,88 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import * as CopyToClipboard from 'react-copy-to-clipboard'; +import { Link } from 'react-router-dom'; + +import { Container } from 'ts/components/ui/container'; +import { Text } from 'ts/components/ui/text'; +import { colors } from 'ts/style/colors'; +import { WebsitePaths } from 'ts/types'; + +export interface SimpleMenuProps { + minWidth?: number | string; +} + +export const SimpleMenu: React.StatelessComponent<SimpleMenuProps> = ({ children, minWidth }) => { + return ( + <Container + marginLeft="16px" + marginRight="16px" + marginBottom="16px" + minWidth={minWidth} + className="flex flex-column" + > + {children} + </Container> + ); +}; + +SimpleMenu.defaultProps = { + minWidth: '220px', +}; + +export interface SimpleMenuItemProps { + displayText: string; + onClick?: () => void; +} +export const SimpleMenuItem: React.StatelessComponent<SimpleMenuItemProps> = ({ displayText, onClick }) => { + // Falling back to _.noop for onclick retains the hovering effect + return ( + <Container marginTop="16px" className="flex flex-column"> + <Text + fontSize="14px" + fontColor={colors.darkGrey} + onClick={onClick || _.noop} + hoverColor={colors.mediumBlue} + > + {displayText} + </Text> + </Container> + ); +}; + +export interface CopyAddressSimpleMenuItemProps { + userAddress: string; + onClick?: () => void; +} +export const CopyAddressSimpleMenuItem: React.StatelessComponent<CopyAddressSimpleMenuItemProps> = ({ + userAddress, + onClick, +}) => { + return ( + <CopyToClipboard text={userAddress}> + <SimpleMenuItem displayText="Copy Address to Clipboard" onClick={onClick} /> + </CopyToClipboard> + ); +}; + +export interface GoToAccountManagementSimpleMenuItemProps { + onClick?: () => void; +} +export const GoToAccountManagementSimpleMenuItem: React.StatelessComponent< + GoToAccountManagementSimpleMenuItemProps +> = ({ onClick }) => { + return ( + <Link to={`${WebsitePaths.Portal}/account`} style={{ textDecoration: 'none' }}> + <SimpleMenuItem displayText="Manage Account..." onClick={onClick} /> + </Link> + ); +}; + +export interface DifferentWalletSimpleMenuItemProps { + onClick?: () => void; +} +export const DifferentWalletSimpleMenuItem: React.StatelessComponent<DifferentWalletSimpleMenuItemProps> = ({ + onClick, +}) => { + return <SimpleMenuItem displayText="Use a Different Wallet..." onClick={onClick} />; +}; diff --git a/packages/website/ts/components/ui/text.tsx b/packages/website/ts/components/ui/text.tsx index c1cb2ade4..315f72854 100644 --- a/packages/website/ts/components/ui/text.tsx +++ b/packages/website/ts/components/ui/text.tsx @@ -3,7 +3,7 @@ import { darken } from 'polished'; import * as React from 'react'; import { styled } from 'ts/style/theme'; -export type TextTag = 'p' | 'div' | 'span' | 'label' | 'h1' | 'h2' | 'h3' | 'h4'; +export type TextTag = 'p' | 'div' | 'span' | 'label' | 'h1' | 'h2' | 'h3' | 'h4' | 'i'; export interface TextProps { className?: string; @@ -17,6 +17,7 @@ export interface TextProps { fontWeight?: number | string; textDecorationLine?: string; onClick?: (event: React.MouseEvent<HTMLElement>) => void; + hoverColor?: string; } const PlainText: React.StatelessComponent<TextProps> = ({ children, className, onClick, Tag }) => ( @@ -37,7 +38,7 @@ export const Text = styled(PlainText)` ${props => (props.onClick ? 'cursor: pointer' : '')}; transition: color 0.5s ease; &:hover { - ${props => (props.onClick ? `color: ${darken(0.3, props.fontColor)}` : '')}; + ${props => (props.onClick ? `color: ${props.hoverColor || darken(0.3, props.fontColor)}` : '')}; } `; diff --git a/packages/website/ts/components/wallet/wallet.tsx b/packages/website/ts/components/wallet/wallet.tsx index 1f1e3598a..1e0b9ec48 100644 --- a/packages/website/ts/components/wallet/wallet.tsx +++ b/packages/website/ts/components/wallet/wallet.tsx @@ -1,19 +1,25 @@ import { constants as sharedConstants, EtherscanLinkSuffixes, utils as sharedUtils } from '@0xproject/react-shared'; import { BigNumber, errorUtils } from '@0xproject/utils'; -import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as _ from 'lodash'; import ActionAccountBalanceWallet from 'material-ui/svg-icons/action/account-balance-wallet'; import * as React from 'react'; -import { Link } from 'react-router-dom'; import firstBy = require('thenby'); import { Blockchain } from 'ts/blockchain'; import { AccountConnection } from 'ts/components/ui/account_connection'; import { Container } from 'ts/components/ui/container'; +import { DropDown, DropdownMouseEvent } from 'ts/components/ui/drop_down'; import { IconButton } from 'ts/components/ui/icon_button'; import { Identicon } from 'ts/components/ui/identicon'; import { Island } from 'ts/components/ui/island'; +import { + CopyAddressSimpleMenuItem, + DifferentWalletSimpleMenuItem, + GoToAccountManagementSimpleMenuItem, + SimpleMenu, + SimpleMenuItem, +} from 'ts/components/ui/simple_menu'; import { Text } from 'ts/components/ui/text'; import { TokenIcon } from 'ts/components/ui/token_icon'; import { BodyOverlay } from 'ts/components/wallet/body_overlay'; @@ -34,7 +40,6 @@ import { TokenByAddress, TokenState, TokenStateByAddress, - WebsitePaths, } from 'ts/types'; import { analytics } from 'ts/utils/analytics'; import { constants } from 'ts/utils/constants'; @@ -83,9 +88,7 @@ const ICON_DIMENSION = 28; const BODY_ITEM_KEY = 'BODY'; const HEADER_ITEM_KEY = 'HEADER'; const ETHER_ITEM_KEY = 'ETHER'; -const USD_DECIMAL_PLACES = 2; const NO_ALLOWANCE_TOGGLE_SPACE_WIDTH = 56; -const ACCOUNT_PATH = `${WebsitePaths.Portal}/account`; const PLACEHOLDER_COLOR = colors.grey300; const LOADING_ROWS_COUNT = 6; @@ -189,6 +192,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> { ); } private _renderConnectedHeaderRows(): React.ReactElement<{}> { + const isMobile = this.props.screenWidth === ScreenWidths.Sm; const userAddress = this.props.userAddress; const accountState = this._getAccountState(); const main = ( @@ -199,15 +203,49 @@ export class Wallet extends React.Component<WalletProps, WalletState> { <AccountConnection accountState={accountState} injectedProviderName={this.props.injectedProviderName} /> </div> ); + const onClick = _.noop; + const accessory = ( + <DropDown + activeNode={ + // this container gives the menu button more of a hover target for the drop down + // it prevents accidentally closing the menu by moving off of the button + <Container paddingLeft="100px" paddingRight="15px"> + <Text + className="zmdi zmdi-more-horiz" + Tag="i" + fontSize="32px" + fontFamily="Material-Design-Iconic-Font" + fontColor={colors.darkGrey} + onClick={onClick} + hoverColor={colors.mediumBlue} + /> + </Container> + } + popoverContent={ + <SimpleMenu minWidth="150px"> + <CopyAddressSimpleMenuItem userAddress={this.props.userAddress} /> + <DifferentWalletSimpleMenuItem onClick={this.props.onToggleLedgerDialog} /> + <SimpleMenuItem displayText="Add Tokens..." onClick={this.props.onAddToken} /> + <SimpleMenuItem displayText="Remove Tokens..." onClick={this.props.onRemoveToken} /> + <GoToAccountManagementSimpleMenuItem /> + </SimpleMenu> + } + anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }} + targetOrigin={{ horizontal: 'right', vertical: 'top' }} + zDepth={1} + activateEvent={DropdownMouseEvent.Click} + closeEvent={isMobile ? DropdownMouseEvent.Click : DropdownMouseEvent.Hover} + /> + ); return ( - <Link key={HEADER_ITEM_KEY} to={ACCOUNT_PATH} style={{ textDecoration: 'none' }}> - <StandardIconRow - icon={<Identicon address={userAddress} diameter={ICON_DIMENSION} />} - main={main} - minHeight="60px" - backgroundColor={colors.white} - /> - </Link> + <StandardIconRow + key={HEADER_ITEM_KEY} + icon={<Identicon address={userAddress} diameter={ICON_DIMENSION} />} + main={main} + accessory={accessory} + minHeight="60px" + backgroundColor={colors.white} + /> ); } private _renderBody(): React.ReactElement<{}> { @@ -320,8 +358,24 @@ export class Wallet extends React.Component<WalletProps, WalletState> { this.state.wrappedEtherDirection === accessoryItemConfig.wrappedEtherDirection && !_.isUndefined(this.props.userEtherBalanceInWei); const etherToken = this._getEthToken(); + const wrapEtherItem = shouldShowWrapEtherItem ? ( + <WrapEtherItem + userAddress={this.props.userAddress} + networkId={this.props.networkId} + blockchain={this.props.blockchain} + dispatcher={this.props.dispatcher} + userEtherBalanceInWei={this.props.userEtherBalanceInWei} + direction={accessoryItemConfig.wrappedEtherDirection} + etherToken={etherToken} + lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch} + onConversionSuccessful={this._closeWrappedEtherActionRow.bind(this)} + // tslint:disable:jsx-no-lambda + refetchEthTokenStateAsync={async () => this.props.refetchTokenStateAsync(etherToken.address)} + /> + ) : null; return ( <div id={key} key={key} className={`flex flex-column ${className || ''}`}> + {this.state.wrappedEtherDirection === Side.Receive && wrapEtherItem} <StandardIconRow icon={icon} main={ @@ -331,23 +385,8 @@ export class Wallet extends React.Component<WalletProps, WalletState> { </div> } accessory={this._renderAccessoryItems(accessoryItemConfig)} - backgroundColor={shouldShowWrapEtherItem ? colors.walletFocusedItemBackground : undefined} /> - {shouldShowWrapEtherItem && ( - <WrapEtherItem - userAddress={this.props.userAddress} - networkId={this.props.networkId} - blockchain={this.props.blockchain} - dispatcher={this.props.dispatcher} - userEtherBalanceInWei={this.props.userEtherBalanceInWei} - direction={accessoryItemConfig.wrappedEtherDirection} - etherToken={etherToken} - lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch} - onConversionSuccessful={this._closeWrappedEtherActionRow.bind(this)} - // tslint:disable:jsx-no-lambda - refetchEthTokenStateAsync={async () => this.props.refetchTokenStateAsync(etherToken.address)} - /> - )} + {this.state.wrappedEtherDirection === Side.Deposit && wrapEtherItem} </div> ); } @@ -411,19 +450,11 @@ export class Wallet extends React.Component<WalletProps, WalletState> { price?: BigNumber, isLoading: boolean = false, ): React.ReactNode { - let result; - if (!isLoading) { - if (_.isUndefined(price)) { - result = '--'; - } else { - const unitAmount = Web3Wrapper.toUnitAmount(amount, decimals); - const value = unitAmount.mul(price); - const formattedAmount = value.toFixed(USD_DECIMAL_PLACES); - result = `$${formattedAmount}`; - } - } else { - result = '$0.00'; - } + const result = !isLoading + ? _.isUndefined(price) + ? '--' + : utils.getUsdValueFormattedAmount(amount, decimals, price) + : '$0.00'; return ( <PlaceHolder hideChildren={isLoading} fillColor={PLACEHOLDER_COLOR}> <Text fontSize="14px" fontColor={colors.darkGrey} lineHeight="1em"> diff --git a/packages/website/ts/components/wallet/wrap_ether_item.tsx b/packages/website/ts/components/wallet/wrap_ether_item.tsx index 851b35f90..2b4cf93fe 100644 --- a/packages/website/ts/components/wallet/wrap_ether_item.tsx +++ b/packages/website/ts/components/wallet/wrap_ether_item.tsx @@ -8,6 +8,7 @@ import * as React from 'react'; import { Blockchain } from 'ts/blockchain'; import { EthAmountInput } from 'ts/components/inputs/eth_amount_input'; import { TokenAmountInput } from 'ts/components/inputs/token_amount_input'; +import { Container } from 'ts/components/ui/container'; import { Dispatcher } from 'ts/redux/dispatcher'; import { colors } from 'ts/style/colors'; import { BlockchainCallErrs, Side, Token } from 'ts/types'; @@ -94,7 +95,7 @@ export class WrapEtherItem extends React.Component<WrapEtherItemProps, WrapEther const topLabelText = isWrappingEth ? 'Convert ETH into WETH 1:1' : 'Convert WETH into ETH 1:1'; return ( - <div className="flex" style={{ backgroundColor: colors.walletFocusedItemBackground }}> + <Container className="flex" backgroundColor={colors.walletFocusedItemBackground} paddingTop="25px"> <div>{this._renderIsEthConversionHappeningSpinner()} </div> <div className="flex flex-column"> <div style={styles.topLabel}>{topLabelText}</div> @@ -142,7 +143,7 @@ export class WrapEtherItem extends React.Component<WrapEtherItemProps, WrapEther {this._renderErrorMsg()} </div> - </div> + </Container> ); } private _onValueChange(_isValid: boolean, amount?: BigNumber): void { diff --git a/packages/website/ts/pages/about/about.tsx b/packages/website/ts/pages/about/about.tsx index 0259af36f..5bb5d06a9 100644 --- a/packages/website/ts/pages/about/about.tsx +++ b/packages/website/ts/pages/about/about.tsx @@ -165,16 +165,24 @@ const teamRow5: ProfileInfo[] = [ }, ]; -// const teamRow6: ProfileInfo[] = [ -// { -// name: 'Chris Kalani', -// title: 'Director of Design', -// description: `Previously founded Wake (acquired by InVision). Early Facebook product designer.`, -// image: 'images/team/chris.png', -// linkedIn: 'https://www.linkedin.com/in/chriskalani/', -// github: 'https://github.com/chriskalani', -// }, -// ]; +const teamRow6: ProfileInfo[] = [ + { + name: 'Alex Browne', + title: 'Engineer in Residence', + description: `Full-stack blockchain engineer. Previously at Plaid. Open source guru and footgun dismantler. Computer Science and Electrical Engineering at Duke.`, + image: 'images/team/alexbrowne.png', + linkedIn: 'https://www.linkedin.com/in/stephenalexbrowne/', + github: 'http://github.com/albrow', + }, + // { + // name: 'Chris Kalani', + // title: 'Director of Design', + // description: `Previously founded Wake (acquired by InVision). Early Facebook product designer.`, + // image: 'images/team/chris.png', + // linkedIn: 'https://www.linkedin.com/in/chriskalani/', + // github: 'https://github.com/chriskalani', + // }, +]; const advisors: ProfileInfo[] = [ { @@ -270,6 +278,7 @@ export class About extends React.Component<AboutProps, AboutState> { <div className="clearfix">{this._renderProfiles(teamRow3)}</div> <div className="clearfix">{this._renderProfiles(teamRow4)}</div> <div className="clearfix">{this._renderProfiles(teamRow5)}</div> + <div className="clearfix">{this._renderProfiles(teamRow6)}</div> </div> <div className="pt3 pb2"> <div diff --git a/packages/website/ts/types.ts b/packages/website/ts/types.ts index 498a0a5b8..2e4cf84d0 100644 --- a/packages/website/ts/types.ts +++ b/packages/website/ts/types.ts @@ -487,6 +487,8 @@ export enum Providers { Parity = 'PARITY', Metamask = 'METAMASK', Mist = 'MIST', + Toshi = 'TOSHI', + Cipher = 'CIPHER', } export interface InjectedProviderUpdate { diff --git a/packages/website/ts/utils/constants.ts b/packages/website/ts/utils/constants.ts index d3a2aa107..0c4b88780 100644 --- a/packages/website/ts/utils/constants.ts +++ b/packages/website/ts/utils/constants.ts @@ -6,7 +6,7 @@ export const constants = { ETHER_TOKEN_SYMBOL: 'WETH', ZRX_TOKEN_SYMBOL: 'ZRX', ETHER_SYMBOL: 'ETH', - TOKEN_AMOUNT_DISPLAY_PRECISION: 5, + TOKEN_AMOUNT_DISPLAY_PRECISION: 4, GENESIS_ORDER_BLOCK_BY_NETWORK_ID: { 1: 4145578, 42: 3117574, @@ -29,6 +29,8 @@ export const constants = { PROVIDER_NAME_METAMASK: 'MetaMask', PROVIDER_NAME_PARITY_SIGNER: 'Parity Signer', PROVIDER_NAME_MIST: 'Mist', + PROVIDER_NAME_CIPHER: 'Cipher Browser', + PROVIDER_NAME_TOSHI: 'Toshi', PROVIDER_NAME_GENERIC: 'Injected Web3', PROVIDER_NAME_PUBLIC: '0x Public', ROLLBAR_ACCESS_TOKEN: 'a6619002b51c4464928201e6ea94de65', @@ -38,6 +40,7 @@ export const constants = { UNAVAILABLE_STATUS: 503, TAKER_FEE: new BigNumber(0), TESTNET_NAME: 'Kovan', + NUMERAL_USD_FORMAT: '$0,0.00', PROJECT_URL_ETHFINEX: 'https://www.ethfinex.com/', PROJECT_URL_AMADEUS: 'http://amadeusrelay.org', PROJECT_URL_DDEX: 'https://ddex.io', diff --git a/packages/website/ts/utils/utils.ts b/packages/website/ts/utils/utils.ts index 726e1815f..fc7901463 100644 --- a/packages/website/ts/utils/utils.ts +++ b/packages/website/ts/utils/utils.ts @@ -8,6 +8,8 @@ import * as bowser from 'bowser'; import deepEqual = require('deep-equal'); import * as _ from 'lodash'; import * as moment from 'moment'; +import * as numeral from 'numeral'; + import { AccountState, BlockchainCallErrs, @@ -324,6 +326,7 @@ export const utils = { getProviderType(provider: Provider): Providers | string { const constructorName = provider.constructor.name; let parsedProviderName = constructorName; + // https://ethereum.stackexchange.com/questions/24266/elegant-way-to-detect-current-provider-int-web3-js switch (constructorName) { case 'EthereumProvider': parsedProviderName = Providers.Mist; @@ -337,6 +340,10 @@ export const utils = { parsedProviderName = Providers.Parity; } else if ((provider as any).isMetaMask) { parsedProviderName = Providers.Metamask; + } else if (!_.isUndefined(_.get(window, 'SOFA'))) { + parsedProviderName = Providers.Toshi; + } else if (!_.isUndefined(_.get(window, '__CIPHER__'))) { + parsedProviderName = Providers.Cipher; } return parsedProviderName; }, @@ -380,10 +387,20 @@ export const utils = { }, getFormattedAmount(amount: BigNumber, decimals: number, symbol: string): string { const unitAmount = Web3Wrapper.toUnitAmount(amount, decimals); - const precision = Math.min(constants.TOKEN_AMOUNT_DISPLAY_PRECISION, unitAmount.decimalPlaces()); - const formattedAmount = unitAmount.toFixed(precision); + // if the unit amount is less than 1, show the natural number of decimal places with a max of 4 + // if the unit amount is greater than or equal to 1, show only 2 decimal places + const precision = unitAmount.lt(1) + ? Math.min(constants.TOKEN_AMOUNT_DISPLAY_PRECISION, unitAmount.decimalPlaces()) + : 2; + const format = `0,0.${_.repeat('0', precision)}`; + const formattedAmount = numeral(unitAmount).format(format); return `${formattedAmount} ${symbol}`; }, + getUsdValueFormattedAmount(amount: BigNumber, decimals: number, price: BigNumber): string { + const unitAmount = Web3Wrapper.toUnitAmount(amount, decimals); + const value = unitAmount.mul(price); + return numeral(value).format(constants.NUMERAL_USD_FORMAT); + }, openUrl(url: string): void { window.open(url, '_blank'); }, @@ -350,6 +350,10 @@ version "8.10.8" resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.8.tgz#794cba23cc9f8d9715f6543fa8827433b5f5cd3b" +"@types/numeral@^0.0.22": + version "0.0.22" + resolved "https://registry.yarnpkg.com/@types/numeral/-/numeral-0.0.22.tgz#86bef1f0a2d743afdc2ef3168d45f2905e1a0b93" + "@types/opn@^5.1.0": version "5.1.0" resolved "https://registry.yarnpkg.com/@types/opn/-/opn-5.1.0.tgz#bff7bc371677f4bdbb37884400e03fd81f743927" @@ -3954,7 +3958,7 @@ etag@~1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" -eth-block-tracker@^2.2.2, eth-block-tracker@^2.3.0: +eth-block-tracker@^2.2.2: version "2.3.0" resolved "https://registry.yarnpkg.com/eth-block-tracker/-/eth-block-tracker-2.3.0.tgz#4cb782c8ef8fde2f5dc894921ae1f5c1077c35a4" dependencies: @@ -4105,13 +4109,13 @@ ethereumjs-block@~1.2.2: ethereumjs-util "^4.0.1" merkle-patricia-tree "^2.1.2" -ethereumjs-blockstream@^2.0.6: - version "2.0.7" - resolved "https://registry.yarnpkg.com/ethereumjs-blockstream/-/ethereumjs-blockstream-2.0.7.tgz#8e791d18d517f13e0ba928892dda545dc930936b" +ethereumjs-blockstream@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ethereumjs-blockstream/-/ethereumjs-blockstream-5.0.0.tgz#63bfe9185757329a32822d5815562eb1dcd75d71" dependencies: - immutable "3.8.1" - source-map-support "0.4.14" - uuid "3.0.1" + immutable "3.8.2" + source-map-support "0.5.6" + uuid "3.2.1" ethereumjs-tx@0xProject/ethereumjs-tx#fake-tx-include-signature-by-default: version "1.3.4" @@ -5796,9 +5800,9 @@ immediate@^3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.2.3.tgz#d140fa8f614659bd6541233097ddaac25cdd991c" -immutable@3.8.1: - version "3.8.1" - resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.1.tgz#200807f11ab0f72710ea485542de088075f68cd2" +immutable@3.8.2: + version "3.8.2" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.2.tgz#c2439951455bb39913daf281376f1530e104adf3" import-lazy@^2.1.0: version "2.1.0" @@ -8119,6 +8123,10 @@ number-to-bn@1.7.0: bn.js "4.11.6" strip-hex-prefix "1.0.0" +numeral@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/numeral/-/numeral-2.0.6.tgz#4ad080936d443c2561aed9f2197efffe25f4e506" + nyc@^11.0.1: version "11.7.1" resolved "https://registry.yarnpkg.com/nyc/-/nyc-11.7.1.tgz#7cb0a422e501b88ff2c1634341dec2560299d67b" @@ -10749,11 +10757,12 @@ source-map-resolve@^0.5.0: source-map-url "^0.4.0" urix "^0.1.0" -source-map-support@0.4.14: - version "0.4.14" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.14.tgz#9d4463772598b86271b4f523f6c1f4e02a7d6aef" +source-map-support@0.5.6, source-map-support@^0.5.6: + version "0.5.6" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.6.tgz#4435cee46b1aab62b8e8610ce60f788091c51c13" dependencies: - source-map "^0.5.6" + buffer-from "^1.0.0" + source-map "^0.6.0" source-map-support@^0.4.15: version "0.4.18" @@ -10767,13 +10776,6 @@ source-map-support@^0.5.0, source-map-support@^0.5.3: dependencies: source-map "^0.6.0" -source-map-support@^0.5.6: - version "0.5.6" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.6.tgz#4435cee46b1aab62b8e8610ce60f788091c51c13" - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - source-map-url@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" @@ -12180,14 +12182,14 @@ uuid@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" +uuid@3.2.1, uuid@^3.0.0, uuid@^3.0.1, uuid@^3.1.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14" + uuid@^2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a" -uuid@^3.0.0, uuid@^3.0.1, uuid@^3.1.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14" - uvm@1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/uvm/-/uvm-1.7.0.tgz#685d3a149ec7118fb73a73dfdc158ab46b0f0634" @@ -12528,39 +12530,15 @@ web3-net@1.0.0-beta.34: web3-core-method "1.0.0-beta.34" web3-utils "1.0.0-beta.34" -web3-provider-engine@^13.3.2: - version "13.8.0" - resolved "https://registry.yarnpkg.com/web3-provider-engine/-/web3-provider-engine-13.8.0.tgz#4c7c1ad2af5f1fe10343b8a65495879a2f9c00df" - dependencies: - async "^2.5.0" - clone "^2.0.0" - eth-block-tracker "^2.2.2" - eth-sig-util "^1.4.2" - ethereumjs-block "^1.2.2" - ethereumjs-tx "^1.2.0" - ethereumjs-util "^5.1.1" - ethereumjs-vm "^2.0.2" - fetch-ponyfill "^4.0.0" - json-rpc-error "^2.0.0" - json-stable-stringify "^1.0.1" - promise-to-callback "^1.0.0" - readable-stream "^2.2.9" - request "^2.67.0" - semaphore "^1.0.3" - solc "^0.4.2" - tape "^4.4.0" - xhr "^2.2.0" - xtend "^4.0.1" - -web3-provider-engine@^14.0.4: - version "14.0.4" - resolved "https://registry.yarnpkg.com/web3-provider-engine/-/web3-provider-engine-14.0.4.tgz#6f96b71ea1b3a76cc67cd52007116c8d4b64465b" +web3-provider-engine@14.0.6, web3-provider-engine@^14.0.6: + version "14.0.6" + resolved "https://registry.yarnpkg.com/web3-provider-engine/-/web3-provider-engine-14.0.6.tgz#cbdd66fe20c0136a3a495cbe40d18b6c4160d5f0" dependencies: async "^2.5.0" backoff "^2.5.0" clone "^2.0.0" cross-fetch "^2.1.0" - eth-block-tracker "^2.3.0" + eth-block-tracker "^3.0.0" eth-json-rpc-infura "^3.1.0" eth-sig-util "^1.4.2" ethereumjs-block "^1.2.2" @@ -12578,29 +12556,27 @@ web3-provider-engine@^14.0.4: xhr "^2.2.0" xtend "^4.0.1" -web3-provider-engine@^14.0.6: - version "14.0.6" - resolved "https://registry.yarnpkg.com/web3-provider-engine/-/web3-provider-engine-14.0.6.tgz#cbdd66fe20c0136a3a495cbe40d18b6c4160d5f0" +web3-provider-engine@^13.3.2: + version "13.8.0" + resolved "https://registry.yarnpkg.com/web3-provider-engine/-/web3-provider-engine-13.8.0.tgz#4c7c1ad2af5f1fe10343b8a65495879a2f9c00df" dependencies: async "^2.5.0" - backoff "^2.5.0" clone "^2.0.0" - cross-fetch "^2.1.0" - eth-block-tracker "^3.0.0" - eth-json-rpc-infura "^3.1.0" + eth-block-tracker "^2.2.2" eth-sig-util "^1.4.2" ethereumjs-block "^1.2.2" ethereumjs-tx "^1.2.0" - ethereumjs-util "^5.1.5" - ethereumjs-vm "^2.3.4" + ethereumjs-util "^5.1.1" + ethereumjs-vm "^2.0.2" + fetch-ponyfill "^4.0.0" json-rpc-error "^2.0.0" json-stable-stringify "^1.0.1" promise-to-callback "^1.0.0" readable-stream "^2.2.9" request "^2.67.0" semaphore "^1.0.3" + solc "^0.4.2" tape "^4.4.0" - ws "^5.1.1" xhr "^2.2.0" xtend "^4.0.1" |