aboutsummaryrefslogtreecommitdiffstats
path: root/packages/website
diff options
context:
space:
mode:
authorBrandon Millman <brandon@0xproject.com>2018-03-30 05:00:37 +0800
committerGitHub <noreply@github.com>2018-03-30 05:00:37 +0800
commit6122840241a47119b046b639e326cfead1ea3e10 (patch)
treef7a4790c5dbd2757da01468463699064f121a465 /packages/website
parentc4dd9658e791a9f821ea3b6eb4326bcba53b081a (diff)
parent03b00ef8da0bafc464e14e5d8225b9a9514b19bd (diff)
downloaddexon-sol-tools-6122840241a47119b046b639e326cfead1ea3e10.tar
dexon-sol-tools-6122840241a47119b046b639e326cfead1ea3e10.tar.gz
dexon-sol-tools-6122840241a47119b046b639e326cfead1ea3e10.tar.bz2
dexon-sol-tools-6122840241a47119b046b639e326cfead1ea3e10.tar.lz
dexon-sol-tools-6122840241a47119b046b639e326cfead1ea3e10.tar.xz
dexon-sol-tools-6122840241a47119b046b639e326cfead1ea3e10.tar.zst
dexon-sol-tools-6122840241a47119b046b639e326cfead1ea3e10.zip
Merge pull request #476 from 0xProject/feature/website/wallet-wrap
Implement ETH/WETH conversion and allowance toggle styling
Diffstat (limited to 'packages/website')
-rw-r--r--packages/website/ts/components/inputs/allowance_toggle.tsx31
-rw-r--r--packages/website/ts/components/inputs/balance_bounded_input.tsx18
-rw-r--r--packages/website/ts/components/inputs/eth_amount_input.tsx14
-rw-r--r--packages/website/ts/components/inputs/token_amount_input.tsx19
-rw-r--r--packages/website/ts/components/portal.tsx2
-rw-r--r--packages/website/ts/components/wallet/wallet.tsx (renamed from packages/website/ts/components/wallet.tsx)178
-rw-r--r--packages/website/ts/components/wallet/wrap_ether_item.tsx184
-rw-r--r--packages/website/ts/utils/mui_theme.ts10
-rw-r--r--packages/website/ts/utils/wallet_item_styles.ts7
9 files changed, 394 insertions, 69 deletions
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..55013e105 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';
diff --git a/packages/website/ts/components/wallet.tsx b/packages/website/ts/components/wallet/wallet.tsx
index 8c6ef9cad..670dc07dd 100644
--- a/packages/website/ts/components/wallet.tsx
+++ b/packages/website/ts/components/wallet/wallet.tsx
@@ -12,6 +12,7 @@ import FlatButton from 'material-ui/FlatButton';
import { List, ListItem } from 'material-ui/List';
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 +21,16 @@ 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 { 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, 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;
@@ -40,11 +43,7 @@ export interface WalletProps {
interface WalletState {
trackedTokenStateByAddress: TokenStateByAddress;
-}
-
-enum WrappedEtherAction {
- Wrap,
- Unwrap,
+ wrappedEtherDirection?: Side;
}
interface AllowanceToggleConfig {
@@ -53,7 +52,7 @@ interface AllowanceToggleConfig {
}
interface AccessoryItemConfig {
- wrappedEtherAction?: WrappedEtherAction;
+ wrappedEtherDirection?: Side;
allowanceToggleConfig?: AllowanceToggleConfig;
}
@@ -87,20 +86,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 +116,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
const initialTrackedTokenStateByAddress = this._getInitialTrackedTokenStateByAddress(props.trackedTokens);
this.state = {
trackedTokenStateByAddress: initialTrackedTokenStateByAddress,
+ wrappedEtherDirection: undefined,
};
}
public componentWillMount() {
@@ -182,16 +181,14 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
<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 +197,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 +250,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 +342,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 +425,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/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,
+ },
+};