diff options
Diffstat (limited to 'packages/website')
-rw-r--r-- | packages/website/public/images/metamask_icon.png | bin | 0 -> 5728 bytes | |||
-rw-r--r-- | packages/website/ts/components/inputs/allowance_toggle.tsx | 31 | ||||
-rw-r--r-- | packages/website/ts/components/inputs/balance_bounded_input.tsx | 18 | ||||
-rw-r--r-- | packages/website/ts/components/inputs/eth_amount_input.tsx | 14 | ||||
-rw-r--r-- | packages/website/ts/components/inputs/token_amount_input.tsx | 19 | ||||
-rw-r--r-- | packages/website/ts/components/portal.tsx | 5 | ||||
-rw-r--r-- | packages/website/ts/components/top_bar/provider_display.tsx | 3 | ||||
-rw-r--r-- | packages/website/ts/components/wallet/wallet.tsx (renamed from packages/website/ts/components/wallet.tsx) | 230 | ||||
-rw-r--r-- | packages/website/ts/components/wallet/wallet_disconnected_item.tsx | 81 | ||||
-rw-r--r-- | packages/website/ts/components/wallet/wrap_ether_item.tsx | 184 | ||||
-rw-r--r-- | packages/website/ts/utils/mui_theme.ts | 10 | ||||
-rw-r--r-- | packages/website/ts/utils/wallet_item_styles.ts | 7 |
12 files changed, 525 insertions, 77 deletions
diff --git a/packages/website/public/images/metamask_icon.png b/packages/website/public/images/metamask_icon.png Binary files differnew file mode 100644 index 000000000..ab497ecb7 --- /dev/null +++ b/packages/website/public/images/metamask_icon.png diff --git a/packages/website/ts/components/inputs/allowance_toggle.tsx b/packages/website/ts/components/inputs/allowance_toggle.tsx index da6f900e6..cfe75b751 100644 --- a/packages/website/ts/components/inputs/allowance_toggle.tsx +++ b/packages/website/ts/components/inputs/allowance_toggle.tsx @@ -1,4 +1,4 @@ -import { constants as sharedConstants } from '@0xproject/react-shared'; +import { colors, constants as sharedConstants, Styles } from '@0xproject/react-shared'; import { BigNumber, logUtils } from '@0xproject/utils'; import * as _ from 'lodash'; import Toggle from 'material-ui/Toggle'; @@ -30,6 +30,31 @@ interface AllowanceToggleState { prevAllowance: BigNumber; } +const styles: Styles = { + baseThumbStyle: { + height: 10, + width: 10, + top: 6, + backgroundColor: colors.white, + boxShadow: `0px 0px 0px ${colors.allowanceToggleShadow}`, + }, + offThumbStyle: { + left: 4, + }, + onThumbStyle: { + left: 25, + }, + baseTrackStyle: { + width: 25, + }, + offTrackStyle: { + backgroundColor: colors.allowanceToggleOffTrack, + }, + onTrackStyle: { + backgroundColor: colors.allowanceToggleOnTrack, + }, +}; + export class AllowanceToggle extends React.Component<AllowanceToggleProps, AllowanceToggleState> { constructor(props: AllowanceToggleProps) { super(props); @@ -54,6 +79,10 @@ export class AllowanceToggle extends React.Component<AllowanceToggleProps, Allow disabled={this.state.isSpinnerVisible || this.props.isDisabled} toggled={this._isAllowanceSet()} onToggle={this._onToggleAllowanceAsync.bind(this)} + thumbStyle={{ ...styles.baseThumbStyle, ...styles.offThumbStyle }} + thumbSwitchedStyle={{ ...styles.baseThumbStyle, ...styles.onThumbStyle }} + trackStyle={{ ...styles.baseTrackStyle, ...styles.offTrackStyle }} + trackSwitchedStyle={{ ...styles.baseTrackStyle, ...styles.onTrackStyle }} /> </div> {this.state.isSpinnerVisible && ( diff --git a/packages/website/ts/components/inputs/balance_bounded_input.tsx b/packages/website/ts/components/inputs/balance_bounded_input.tsx index 253b01871..e9b8dd369 100644 --- a/packages/website/ts/components/inputs/balance_bounded_input.tsx +++ b/packages/website/ts/components/inputs/balance_bounded_input.tsx @@ -12,6 +12,7 @@ interface BalanceBoundedInputProps { label?: string; balance: BigNumber; amount?: BigNumber; + hintText?: string; onChange: ValidatedBigNumberCallback; shouldShowIncompleteErrs?: boolean; shouldCheckBalance: boolean; @@ -19,6 +20,8 @@ interface BalanceBoundedInputProps { onVisitBalancesPageClick?: () => void; shouldHideVisitBalancesLink?: boolean; isDisabled?: boolean; + shouldShowErrs?: boolean; + shouldShowUnderline?: boolean; } interface BalanceBoundedInputState { @@ -31,6 +34,9 @@ export class BalanceBoundedInput extends React.Component<BalanceBoundedInputProp shouldShowIncompleteErrs: false, shouldHideVisitBalancesLink: false, isDisabled: false, + shouldShowErrs: true, + hintText: 'amount', + shouldShowUnderline: true, }; constructor(props: BalanceBoundedInputProps) { super(props); @@ -71,9 +77,12 @@ export class BalanceBoundedInput extends React.Component<BalanceBoundedInputProp } } public render() { - let errorText = this.state.errMsg; - if (this.props.shouldShowIncompleteErrs && this.state.amountString === '') { - errorText = 'This field is required'; + let errorText; + if (this.props.shouldShowErrs) { + errorText = + this.props.shouldShowIncompleteErrs && this.state.amountString === '' + ? 'This field is required' + : this.state.errMsg; } let label: React.ReactNode | string = ''; if (!_.isUndefined(this.props.label)) { @@ -87,9 +96,10 @@ export class BalanceBoundedInput extends React.Component<BalanceBoundedInputProp floatingLabelStyle={{ color: colors.grey, width: 206 }} errorText={errorText} value={this.state.amountString} - hintText={<span style={{ textTransform: 'capitalize' }}>amount</span>} + hintText={<span style={{ textTransform: 'capitalize' }}>{this.props.hintText}</span>} onChange={this._onValueChange.bind(this)} underlineStyle={{ width: 'calc(100% + 50px)' }} + underlineShow={this.props.shouldShowUnderline} disabled={this.props.isDisabled} /> ); diff --git a/packages/website/ts/components/inputs/eth_amount_input.tsx b/packages/website/ts/components/inputs/eth_amount_input.tsx index a66f92c8c..f3a879065 100644 --- a/packages/website/ts/components/inputs/eth_amount_input.tsx +++ b/packages/website/ts/components/inputs/eth_amount_input.tsx @@ -10,22 +10,31 @@ interface EthAmountInputProps { label?: string; balance: BigNumber; amount?: BigNumber; + hintText?: string; onChange: ValidatedBigNumberCallback; shouldShowIncompleteErrs: boolean; onVisitBalancesPageClick?: () => void; shouldCheckBalance: boolean; shouldHideVisitBalancesLink?: boolean; + shouldShowErrs?: boolean; + shouldShowUnderline?: boolean; + style?: React.CSSProperties; } interface EthAmountInputState {} export class EthAmountInput extends React.Component<EthAmountInputProps, EthAmountInputState> { + public static defaultProps: Partial<EthAmountInputProps> = { + shouldShowErrs: true, + shouldShowUnderline: true, + style: { height: 63 }, + }; public render() { const amount = this.props.amount ? ZeroEx.toUnitAmount(this.props.amount, constants.DECIMAL_PLACES_ETH) : undefined; return ( - <div className="flex overflow-hidden" style={{ height: 63 }}> + <div className="flex overflow-hidden" style={this.props.style}> <BalanceBoundedInput label={this.props.label} balance={this.props.balance} @@ -35,6 +44,9 @@ export class EthAmountInput extends React.Component<EthAmountInputProps, EthAmou shouldShowIncompleteErrs={this.props.shouldShowIncompleteErrs} onVisitBalancesPageClick={this.props.onVisitBalancesPageClick} shouldHideVisitBalancesLink={this.props.shouldHideVisitBalancesLink} + hintText={this.props.hintText} + shouldShowErrs={this.props.shouldShowErrs} + shouldShowUnderline={this.props.shouldShowUnderline} /> <div style={{ paddingTop: _.isUndefined(this.props.label) ? 15 : 40 }}>ETH</div> </div> diff --git a/packages/website/ts/components/inputs/token_amount_input.tsx b/packages/website/ts/components/inputs/token_amount_input.tsx index b55840fc4..9e638b67b 100644 --- a/packages/website/ts/components/inputs/token_amount_input.tsx +++ b/packages/website/ts/components/inputs/token_amount_input.tsx @@ -15,12 +15,16 @@ interface TokenAmountInputProps { token: Token; label?: string; amount?: BigNumber; + hintText?: string; shouldShowIncompleteErrs: boolean; shouldCheckBalance: boolean; shouldCheckAllowance: boolean; onChange: ValidatedBigNumberCallback; onVisitBalancesPageClick?: () => void; lastForceTokenStateRefetch: number; + shouldShowErrs?: boolean; + shouldShowUnderline?: boolean; + style?: React.CSSProperties; } interface TokenAmountInputState { @@ -29,7 +33,14 @@ interface TokenAmountInputState { isBalanceAndAllowanceLoaded: boolean; } +const HEIGHT_WITH_LABEL = 84; +const HEIGHT_WITHOUT_LABEL = 62; + export class TokenAmountInput extends React.Component<TokenAmountInputProps, TokenAmountInputState> { + public static defaultProps: Partial<TokenAmountInputProps> = { + shouldShowErrs: true, + shouldShowUnderline: true, + }; private _isUnmounted: boolean; constructor(props: TokenAmountInputProps) { super(props); @@ -64,8 +75,11 @@ export class TokenAmountInput extends React.Component<TokenAmountInputProps, Tok ? 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={{ height: hasLabel ? 84 : 62 }}> + <div className="flex overflow-hidden" style={style}> <BalanceBoundedInput label={this.props.label} amount={amount} @@ -76,6 +90,9 @@ export class TokenAmountInput extends React.Component<TokenAmountInputProps, Tok shouldShowIncompleteErrs={this.props.shouldShowIncompleteErrs} onVisitBalancesPageClick={this.props.onVisitBalancesPageClick} isDisabled={!this.state.isBalanceAndAllowanceLoaded} + hintText={this.props.hintText} + shouldShowErrs={this.props.shouldShowErrs} + shouldShowUnderline={this.props.shouldShowUnderline} /> <div style={{ paddingTop: hasLabel ? 39 : 14 }}>{this.props.token.symbol}</div> </div> diff --git a/packages/website/ts/components/portal.tsx b/packages/website/ts/components/portal.tsx index 59eaca67e..ceb0ecc72 100644 --- a/packages/website/ts/components/portal.tsx +++ b/packages/website/ts/components/portal.tsx @@ -19,7 +19,7 @@ import { TokenBalances } from 'ts/components/token_balances'; import { TopBar } 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'; +import { Wallet } from 'ts/components/wallet/wallet'; import { GenerateOrderForm } from 'ts/containers/generate_order_form'; import { localStorage } from 'ts/local_storage/local_storage'; import { Dispatcher } from 'ts/redux/dispatcher'; @@ -305,6 +305,9 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> { 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> diff --git a/packages/website/ts/components/top_bar/provider_display.tsx b/packages/website/ts/components/top_bar/provider_display.tsx index 89c506d0e..221c34f8c 100644 --- a/packages/website/ts/components/top_bar/provider_display.tsx +++ b/packages/website/ts/components/top_bar/provider_display.tsx @@ -28,7 +28,8 @@ interface ProviderDisplayState {} export class ProviderDisplay extends React.Component<ProviderDisplayProps, ProviderDisplayState> { public render() { const isAddressAvailable = !_.isEmpty(this.props.userAddress); - const isExternallyInjectedProvider = ProviderType.Injected && this.props.injectedProviderName !== '0x Public'; + const isExternallyInjectedProvider = + this.props.providerType === ProviderType.Injected && this.props.injectedProviderName !== '0x Public'; const displayAddress = isAddressAvailable ? utils.getAddressBeginAndEnd(this.props.userAddress) : isExternallyInjectedProvider ? 'Account locked' : '0x0000...0000'; diff --git a/packages/website/ts/components/wallet.tsx b/packages/website/ts/components/wallet/wallet.tsx index 8c6ef9cad..39c95d31c 100644 --- a/packages/website/ts/components/wallet.tsx +++ b/packages/website/ts/components/wallet/wallet.tsx @@ -10,8 +10,10 @@ import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import FlatButton from 'material-ui/FlatButton'; import { List, ListItem } from 'material-ui/List'; +import ActionAccountBalanceWallet from 'material-ui/svg-icons/action/account-balance-wallet'; 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 ReactTooltip = require('react-tooltip'); import firstBy = require('thenby'); @@ -20,14 +22,26 @@ import { Blockchain } from 'ts/blockchain'; import { AllowanceToggle } from 'ts/components/inputs/allowance_toggle'; import { Identicon } from 'ts/components/ui/identicon'; import { TokenIcon } from 'ts/components/ui/token_icon'; +import { WalletDisconnectedItem } from 'ts/components/wallet/wallet_disconnected_item'; +import { WrapEtherItem } from 'ts/components/wallet/wrap_ether_item'; import { Dispatcher } from 'ts/redux/dispatcher'; -import { BalanceErrs, BlockchainErrs, Token, TokenByAddress, TokenState, TokenStateByAddress } from 'ts/types'; +import { + BalanceErrs, + BlockchainErrs, + ProviderType, + Side, + Token, + TokenByAddress, + TokenState, + TokenStateByAddress, +} from 'ts/types'; import { constants } from 'ts/utils/constants'; import { utils } from 'ts/utils/utils'; +import { styles as walletItemStyles } from 'ts/utils/wallet_item_styles'; export interface WalletProps { - userAddress?: string; - networkId?: number; + userAddress: string; + networkId: number; blockchain: Blockchain; blockchainIsLoaded: boolean; blockchainErr: BlockchainErrs; @@ -36,15 +50,14 @@ export interface WalletProps { trackedTokens: Token[]; userEtherBalanceInWei: BigNumber; lastForceTokenStateRefetch: number; + injectedProviderName: string; + providerType: ProviderType; + onToggleLedgerDialog: () => void; } interface WalletState { trackedTokenStateByAddress: TokenStateByAddress; -} - -enum WrappedEtherAction { - Wrap, - Unwrap, + wrappedEtherDirection?: Side; } interface AllowanceToggleConfig { @@ -53,7 +66,7 @@ interface AllowanceToggleConfig { } interface AccessoryItemConfig { - wrappedEtherAction?: WrappedEtherAction; + wrappedEtherDirection?: Side; allowanceToggleConfig?: AllowanceToggleConfig; } @@ -87,20 +100,19 @@ const styles: Styles = { }, tokenItem: { backgroundColor: colors.walletDefaultItemBackground, - paddingTop: 8, - paddingBottom: 8, }, - headerItem: { - paddingTop: 8, - paddingBottom: 8, - }, - wrappedEtherButtonLabel: { - fontSize: 12, + wrappedEtherOpenButtonLabel: { + fontSize: 10, }, amountLabel: { fontWeight: 'bold', color: colors.black, }, + paddedItem: { + paddingTop: 8, + paddingBottom: 8, + }, + accessoryItemsContainer: { width: 150, right: 8 }, }; const ETHER_ICON_PATH = '/images/ether.png'; @@ -118,6 +130,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> { const initialTrackedTokenStateByAddress = this._getInitialTrackedTokenStateByAddress(props.trackedTokens); this.state = { trackedTokenStateByAddress: initialTrackedTokenStateByAddress, + wrappedEtherDirection: undefined, }; } public componentWillMount() { @@ -164,34 +177,56 @@ export class Wallet extends React.Component<WalletProps, WalletState> { return <div style={styles.wallet}>{isReadyToRender && this._renderRows()}</div>; } private _renderRows() { + const isAddressAvailable = !_.isEmpty(this.props.userAddress); return ( <List style={styles.list}> - {_.concat( - this._renderHeaderRows(), - this._renderEthRows(), - this._renderTokenRows(), - this._renderFooterRows(), - )} + {isAddressAvailable + ? _.concat( + this._renderConnectedHeaderRows(), + this._renderEthRows(), + this._renderTokenRows(), + this._renderFooterRows(), + ) + : _.concat(this._renderDisconnectedHeaderRows(), this._renderDisconnectedRows())} </List> ); } - private _renderHeaderRows() { + private _renderDisconnectedHeaderRows() { + const userAddress = this.props.userAddress; + const primaryText = 'wallet'; + return ( + <ListItem + primaryText={primaryText.toUpperCase()} + leftIcon={<ActionAccountBalanceWallet color={colors.mediumBlue} />} + style={styles.paddedItem} + innerDivStyle={styles.headerItemInnerDiv} + /> + ); + } + private _renderDisconnectedRows() { + return ( + <WalletDisconnectedItem + providerType={this.props.providerType} + injectedProviderName={this.props.injectedProviderName} + onToggleLedgerDialog={this.props.onToggleLedgerDialog} + /> + ); + } + private _renderConnectedHeaderRows() { const userAddress = this.props.userAddress; const primaryText = utils.getAddressBeginAndEnd(userAddress); return ( <ListItem primaryText={primaryText} leftIcon={<Identicon address={userAddress} diameter={ICON_DIMENSION} />} - style={{ ...styles.headerItem, ...styles.borderedItem }} + style={{ ...styles.paddedItem, ...styles.borderedItem }} innerDivStyle={styles.headerItemInnerDiv} /> ); } private _renderFooterRows() { const primaryText = '+ other tokens'; - return ( - <ListItem primaryText={primaryText} style={styles.borderedItem} innerDivStyle={styles.footerItemInnerDiv} /> - ); + return <ListItem primaryText={primaryText} innerDivStyle={styles.footerItemInnerDiv} />; } private _renderEthRows() { const primaryText = this._renderAmount( @@ -200,16 +235,40 @@ export class Wallet extends React.Component<WalletProps, WalletState> { ETHER_SYMBOL, ); const accessoryItemConfig = { - wrappedEtherAction: WrappedEtherAction.Wrap, + wrappedEtherDirection: Side.Deposit, }; + const isInWrappedEtherState = + !_.isUndefined(this.state.wrappedEtherDirection) && + this.state.wrappedEtherDirection === accessoryItemConfig.wrappedEtherDirection; + const style = isInWrappedEtherState + ? { ...walletItemStyles.focusedItem, ...styles.paddedItem } + : { ...styles.tokenItem, ...styles.borderedItem, ...styles.paddedItem }; + const etherToken = this._getEthToken(); return ( - <ListItem - primaryText={primaryText} - leftIcon={<img style={{ width: ICON_DIMENSION, height: ICON_DIMENSION }} src={ETHER_ICON_PATH} />} - rightAvatar={this._renderAccessoryItems(accessoryItemConfig)} - style={{ ...styles.tokenItem, ...styles.borderedItem }} - innerDivStyle={styles.tokenItemInnerDiv} - /> + <div> + <ListItem + primaryText={primaryText} + leftIcon={<img style={{ width: ICON_DIMENSION, height: ICON_DIMENSION }} src={ETHER_ICON_PATH} />} + rightAvatar={this._renderAccessoryItems(accessoryItemConfig)} + disableTouchRipple={true} + style={style} + innerDivStyle={styles.tokenItemInnerDiv} + /> + {isInWrappedEtherState && ( + <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)} + refetchEthTokenStateAsync={this._refetchTokenStateAsync.bind(this, etherToken.address)} + /> + )} + </div> ); } private _renderTokenRows() { @@ -229,32 +288,56 @@ export class Wallet extends React.Component<WalletProps, WalletState> { EtherscanLinkSuffixes.Address, ); const amount = this._renderAmount(tokenState.balance, token.decimals, token.symbol); - const wrappedEtherAction = token.symbol === ETHER_TOKEN_SYMBOL ? WrappedEtherAction.Unwrap : undefined; + const wrappedEtherDirection = token.symbol === ETHER_TOKEN_SYMBOL ? Side.Receive : undefined; const accessoryItemConfig: AccessoryItemConfig = { - wrappedEtherAction, + wrappedEtherDirection, allowanceToggleConfig: { token, tokenState, }, }; + const shouldShowWrapEtherItem = + !_.isUndefined(this.state.wrappedEtherDirection) && + this.state.wrappedEtherDirection === accessoryItemConfig.wrappedEtherDirection; + const style = shouldShowWrapEtherItem + ? { ...walletItemStyles.focusedItem, ...styles.paddedItem } + : { ...styles.tokenItem, ...styles.borderedItem, ...styles.paddedItem }; + const etherToken = this._getEthToken(); return ( - <ListItem - primaryText={amount} - leftIcon={this._renderTokenIcon(token, tokenLink)} - rightAvatar={this._renderAccessoryItems(accessoryItemConfig)} - style={{ ...styles.tokenItem, ...styles.borderedItem }} - innerDivStyle={styles.tokenItemInnerDiv} - /> + <div> + <ListItem + primaryText={amount} + leftIcon={this._renderTokenIcon(token, tokenLink)} + rightAvatar={this._renderAccessoryItems(accessoryItemConfig)} + disableTouchRipple={true} + style={style} + innerDivStyle={styles.tokenItemInnerDiv} + /> + {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)} + refetchEthTokenStateAsync={this._refetchTokenStateAsync.bind(this, etherToken.address)} + /> + )} + </div> ); } private _renderAccessoryItems(config: AccessoryItemConfig) { - const shouldShowWrappedEtherAction = !_.isUndefined(config.wrappedEtherAction); + const shouldShowWrappedEtherAction = !_.isUndefined(config.wrappedEtherDirection); const shouldShowToggle = !_.isUndefined(config.allowanceToggleConfig); return ( - <div style={{ width: 160 }}> + <div style={styles.accessoryItemsContainer}> <div className="flex"> <div className="flex-auto"> - {shouldShowWrappedEtherAction && this._renderWrappedEtherButton(config.wrappedEtherAction)} + {shouldShowWrappedEtherAction && this._renderWrappedEtherButton(config.wrappedEtherDirection)} </div> <div className="flex-last py1"> {shouldShowToggle && this._renderAllowanceToggle(config.allowanceToggleConfig)} @@ -297,28 +380,38 @@ export class Wallet extends React.Component<WalletProps, WalletState> { ); } } - private _renderWrappedEtherButton(action: WrappedEtherAction) { + private _renderWrappedEtherButton(wrappedEtherDirection: Side) { + const isWrappedEtherDirectionOpen = this.state.wrappedEtherDirection === wrappedEtherDirection; let buttonLabel; let buttonIcon; - switch (action) { - case WrappedEtherAction.Wrap: - buttonLabel = 'wrap'; - buttonIcon = <NavigationArrowDownward />; - break; - case WrappedEtherAction.Unwrap: - buttonLabel = 'unwrap'; - buttonIcon = <NavigationArrowUpward />; - break; - default: - throw utils.spawnSwitchErr('wrappedEtherAction', action); + if (isWrappedEtherDirectionOpen) { + buttonLabel = 'cancel'; + buttonIcon = <Close />; + } else { + switch (wrappedEtherDirection) { + case Side.Deposit: + buttonLabel = 'wrap'; + buttonIcon = <NavigationArrowDownward />; + break; + case Side.Receive: + buttonLabel = 'unwrap'; + buttonIcon = <NavigationArrowUpward />; + break; + default: + throw utils.spawnSwitchErr('wrappedEtherDirection', wrappedEtherDirection); + } } + const onClick = isWrappedEtherDirectionOpen + ? this._closeWrappedEtherActionRow.bind(this) + : this._openWrappedEtherActionRow.bind(this, wrappedEtherDirection); return ( <FlatButton label={buttonLabel} labelPosition="after" primary={true} icon={buttonIcon} - labelStyle={styles.wrappedEtherButtonLabel} + labelStyle={styles.wrappedEtherOpenButtonLabel} + onClick={onClick} /> ); } @@ -370,4 +463,19 @@ export class Wallet extends React.Component<WalletProps, WalletState> { }, }); } + private _openWrappedEtherActionRow(wrappedEtherDirection: Side) { + this.setState({ + wrappedEtherDirection, + }); + } + private _closeWrappedEtherActionRow() { + this.setState({ + wrappedEtherDirection: undefined, + }); + } + private _getEthToken() { + const tokens = _.values(this.props.tokenByAddress); + const etherToken = _.find(tokens, { symbol: ETHER_TOKEN_SYMBOL }); + return etherToken; + } } diff --git a/packages/website/ts/components/wallet/wallet_disconnected_item.tsx b/packages/website/ts/components/wallet/wallet_disconnected_item.tsx new file mode 100644 index 000000000..89e32f7be --- /dev/null +++ b/packages/website/ts/components/wallet/wallet_disconnected_item.tsx @@ -0,0 +1,81 @@ +import { colors, Styles } from '@0xproject/react-shared'; +import FlatButton from 'material-ui/FlatButton'; +import ActionAccountBalanceWallet from 'material-ui/svg-icons/action/account-balance-wallet'; +import * as React from 'react'; + +import { ProviderType } from 'ts/types'; +import { constants } from 'ts/utils/constants'; + +export interface WalletDisconnectedItemProps { + providerType: ProviderType; + injectedProviderName: string; + onToggleLedgerDialog: () => void; +} + +const styles: Styles = { + button: { + border: colors.walletBorder, + borderStyle: 'solid', + borderWidth: 1, + height: 80, + }, + hrefAdjustment: { + paddingTop: 20, // HACK: For some reason when we set the href prop of a FlatButton material-ui reduces the top padding + }, + otherWalletText: { + fontSize: 14, + color: colors.grey500, + textDecoration: 'underline', + }, +}; + +const ITEM_HEIGHT = 292; +const METAMASK_ICON_WIDTH = 35; +const LEDGER_ICON_WIDTH = 30; +const BUTTON_BOTTOM_PADDING = 80; + +export const WalletDisconnectedItem: React.StatelessComponent<WalletDisconnectedItemProps> = ( + props: WalletDisconnectedItemProps, +) => { + const isExternallyInjectedProvider = + props.providerType === ProviderType.Injected && props.injectedProviderName !== '0x Public'; + return ( + <div className="flex flex-center"> + <div className="mx-auto"> + <div className="table" style={{ height: ITEM_HEIGHT }}> + <div className="table-cell align-middle"> + <ProviderButton isExternallyInjectedProvider={isExternallyInjectedProvider} /> + <div className="flex flex-center py2" style={{ paddingBottom: BUTTON_BOTTOM_PADDING }}> + <div className="mx-auto"> + <div onClick={props.onToggleLedgerDialog} style={{ cursor: 'pointer' }}> + <img src="/images/ledger_icon.png" style={{ width: LEDGER_ICON_WIDTH }} /> + <span className="px1" style={styles.otherWalletText}> + user other wallet + </span> + </div> + </div> + </div> + </div> + </div> + </div> + </div> + ); +}; + +interface ProviderButtonProps { + isExternallyInjectedProvider: boolean; +} + +const ProviderButton: React.StatelessComponent<ProviderButtonProps> = (props: ProviderButtonProps) => ( + <FlatButton + label={props.isExternallyInjectedProvider ? 'Please unlock account' : 'Get Metamask Wallet Extension'} + labelStyle={{ color: colors.black }} + labelPosition="after" + primary={true} + icon={<img src="/images/metamask_icon.png" width={METAMASK_ICON_WIDTH.toString()} />} + style={props.isExternallyInjectedProvider ? styles.button : { ...styles.button, ...styles.hrefAdjustment }} + href={props.isExternallyInjectedProvider ? undefined : constants.URL_METAMASK_CHROME_STORE} + target={props.isExternallyInjectedProvider ? undefined : '_blank'} + disabled={props.isExternallyInjectedProvider} + /> +); diff --git a/packages/website/ts/components/wallet/wrap_ether_item.tsx b/packages/website/ts/components/wallet/wrap_ether_item.tsx new file mode 100644 index 000000000..3a876721a --- /dev/null +++ b/packages/website/ts/components/wallet/wrap_ether_item.tsx @@ -0,0 +1,184 @@ +import { ZeroEx } from '0x.js'; +import { colors, Styles } from '@0xproject/react-shared'; +import { BigNumber, logUtils } from '@0xproject/utils'; +import * as _ from 'lodash'; +import FlatButton from 'material-ui/FlatButton'; +import { ListItem } from 'material-ui/List'; +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 { Dispatcher } from 'ts/redux/dispatcher'; +import { BlockchainCallErrs, Side, Token } from 'ts/types'; +import { constants } from 'ts/utils/constants'; +import { errorReporter } from 'ts/utils/error_reporter'; +import { utils } from 'ts/utils/utils'; +import { styles as walletItemStyles } from 'ts/utils/wallet_item_styles'; + +export interface WrapEtherItemProps { + userAddress: string; + networkId: number; + blockchain: Blockchain; + dispatcher: Dispatcher; + userEtherBalanceInWei: BigNumber; + direction: Side; + etherToken: Token; + lastForceTokenStateRefetch: number; + onConversionSuccessful?: () => void; + refetchEthTokenStateAsync: () => Promise<void>; +} + +interface WrapEtherItemState { + currentInputAmount?: BigNumber; + currentInputHasErrors: boolean; + isEthConversionHappening: boolean; +} + +const styles: Styles = { + topLabel: { color: colors.black, fontSize: 11 }, + inputContainer: { + backgroundColor: colors.white, + borderBottomRightRadius: 3, + borderBottomLeftRadius: 3, + borderTopRightRadius: 3, + borderTopLeftRadius: 3, + padding: 4, + width: 125, + }, + ethAmountInput: { height: 32 }, + innerDiv: { paddingLeft: 60, paddingTop: 0 }, + wrapEtherConfirmationButtonContainer: { width: 128, top: 16 }, + wrapEtherConfirmationButtonLabel: { + fontSize: 10, + color: colors.white, + }, +}; + +export class WrapEtherItem extends React.Component<WrapEtherItemProps, WrapEtherItemState> { + constructor(props: WrapEtherItemProps) { + super(props); + this.state = { + currentInputAmount: undefined, + currentInputHasErrors: false, + isEthConversionHappening: false, + }; + } + public render() { + 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'; + return ( + <ListItem + primaryText={ + <div> + <div style={styles.topLabel}>{topLabelText}</div> + <div style={styles.inputContainer}> + {isWrappingEth ? ( + <EthAmountInput + balance={etherBalanceInEth} + amount={this.state.currentInputAmount} + hintText="0.00" + onChange={this._onValueChange.bind(this)} + shouldCheckBalance={true} + shouldShowIncompleteErrs={false} + shouldShowErrs={false} + shouldShowUnderline={false} + style={styles.ethAmountInput} + /> + ) : ( + <TokenAmountInput + lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch} + blockchain={this.props.blockchain} + userAddress={this.props.userAddress} + networkId={this.props.networkId} + token={this.props.etherToken} + shouldShowIncompleteErrs={false} + shouldCheckBalance={true} + shouldCheckAllowance={false} + onChange={this._onValueChange.bind(this)} + amount={this.state.currentInputAmount} + hintText="0.00" + shouldShowErrs={false} // TODO: error handling + shouldShowUnderline={false} + style={styles.ethAmountInput} + /> + )} + </div> + </div> + } + secondaryTextLines={2} + disableTouchRipple={true} + style={walletItemStyles.focusedItem} + innerDivStyle={styles.innerDiv} + leftIcon={this.state.isEthConversionHappening && this._renderIsEthConversionHappeningSpinner()} + rightAvatar={this._renderWrapEtherConfirmationButton()} + /> + ); + } + private _onValueChange(isValid: boolean, amount?: BigNumber) { + this.setState({ + currentInputAmount: amount, + currentInputHasErrors: !isValid, + }); + } + private _renderIsEthConversionHappeningSpinner() { + return ( + <div className="pl1" style={{ paddingTop: 10 }}> + <i className="zmdi zmdi-spinner zmdi-hc-spin" /> + </div> + ); + } + private _renderWrapEtherConfirmationButton() { + const isWrappingEth = this.props.direction === Side.Deposit; + const labelText = isWrappingEth ? 'wrap' : 'unwrap'; + return ( + <div style={styles.wrapEtherConfirmationButtonContainer}> + <FlatButton + backgroundColor={colors.wrapEtherConfirmationButton} + label={labelText} + labelStyle={styles.wrapEtherConfirmationButtonLabel} + onClick={this._wrapEtherConfirmationAction.bind(this)} + disabled={this.state.isEthConversionHappening} + /> + </div> + ); + } + private async _wrapEtherConfirmationAction() { + this.setState({ + isEthConversionHappening: true, + }); + try { + const etherToken = this.props.etherToken; + const amountToConvert = this.state.currentInputAmount; + if (this.props.direction === Side.Deposit) { + await this.props.blockchain.convertEthToWrappedEthTokensAsync(etherToken.address, amountToConvert); + const ethAmount = ZeroEx.toUnitAmount(amountToConvert, constants.DECIMAL_PLACES_ETH); + this.props.dispatcher.showFlashMessage(`Successfully wrapped ${ethAmount.toString()} ETH to WETH`); + } else { + await this.props.blockchain.convertWrappedEthTokensToEthAsync(etherToken.address, amountToConvert); + const tokenAmount = ZeroEx.toUnitAmount(amountToConvert, etherToken.decimals); + this.props.dispatcher.showFlashMessage(`Successfully unwrapped ${tokenAmount.toString()} WETH to ETH`); + } + await this.props.refetchEthTokenStateAsync(); + this.props.onConversionSuccessful(); + } catch (err) { + const errMsg = `${err}`; + if (_.includes(errMsg, BlockchainCallErrs.UserHasNoAssociatedAddresses)) { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + } else if (!utils.didUserDenyWeb3Request(errMsg)) { + logUtils.log(`Unexpected error encountered: ${err}`); + logUtils.log(err.stack); + const errorMsg = + this.props.direction === Side.Deposit + ? 'Failed to wrap your ETH. Please try again.' + : 'Failed to unwrap your WETH. Please try again.'; + this.props.dispatcher.showFlashMessage(errorMsg); + await errorReporter.reportAsync(err); + } + } + this.setState({ + isEthConversionHappening: false, + }); + } +} diff --git a/packages/website/ts/utils/mui_theme.ts b/packages/website/ts/utils/mui_theme.ts index 41bc2844b..d611f0895 100644 --- a/packages/website/ts/utils/mui_theme.ts +++ b/packages/website/ts/utils/mui_theme.ts @@ -9,9 +9,9 @@ export const muiTheme = getMuiTheme({ }, palette: { accent1Color: colors.lightBlueA700, - pickerHeaderColor: colors.lightBlue, - primary1Color: colors.lightBlue, - primary2Color: colors.lightBlue, + pickerHeaderColor: colors.mediumBlue, + primary1Color: colors.mediumBlue, + primary2Color: colors.mediumBlue, textColor: colors.grey700, }, datePicker: { @@ -29,8 +29,4 @@ export const muiTheme = getMuiTheme({ selectColor: colors.darkestGrey, selectTextColor: colors.darkestGrey, }, - toggle: { - thumbOnColor: colors.limeGreen, - trackOnColor: colors.lightGreen, - }, }); diff --git a/packages/website/ts/utils/wallet_item_styles.ts b/packages/website/ts/utils/wallet_item_styles.ts new file mode 100644 index 000000000..1ad304ce1 --- /dev/null +++ b/packages/website/ts/utils/wallet_item_styles.ts @@ -0,0 +1,7 @@ +import { colors, Styles } from '@0xproject/react-shared'; + +export const styles: Styles = { + focusedItem: { + backgroundColor: colors.walletFocusedItemBackground, + }, +}; |