Implement ETH/WETH conversion and allowance toggle styling
This commit is contained in:
parent
bed7d87b7f
commit
dc3be992a3
@ -4,3 +4,4 @@
|
||||
|
||||
* Added new colors (#468)
|
||||
* Fix section and menuItem text display to replace dashes with spaces.
|
||||
* Reorganized colors and added new ones
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { colors as materialUiColors } from 'material-ui/styles';
|
||||
|
||||
export const colors = {
|
||||
...materialUiColors,
|
||||
const baseColors = {
|
||||
gray40: '#F8F8F8',
|
||||
grey50: '#FAFAFA',
|
||||
grey100: '#F5F5F5',
|
||||
@ -27,6 +26,7 @@ export const colors = {
|
||||
lightBlue: '#60A4F4',
|
||||
lightBlueA700: '#0091EA',
|
||||
linkBlue: '#1D5CDE',
|
||||
mediumBlue: '#488AEA',
|
||||
darkBlue: '#4D5481',
|
||||
turquois: '#058789',
|
||||
lightPurple: '#A81CA6',
|
||||
@ -45,7 +45,22 @@ export const colors = {
|
||||
orange: '#E69D00',
|
||||
amber800: '#FF8F00',
|
||||
darkYellow: '#caca03',
|
||||
walletBoxShadow: 'rgba(56, 59, 137, 0.2)',
|
||||
walletBorder: '#f5f5f6',
|
||||
walletDefaultItemBackground: '#fbfbfc',
|
||||
};
|
||||
|
||||
const appColors = {
|
||||
// wallet specific colors
|
||||
walletBoxShadow: 'rgba(56, 59, 137, 0.2)',
|
||||
walletBorder: '#ededee',
|
||||
walletDefaultItemBackground: '#fbfbfc',
|
||||
walletFocusedItemBackground: '#f0f1f4',
|
||||
allowanceToggleShadow: 'rgba(0, 0, 0, 0)',
|
||||
allowanceToggleOffTrack: '#adadad',
|
||||
allowanceToggleOnTrack: baseColors.mediumBlue,
|
||||
wrapEtherConfirmationButton: baseColors.mediumBlue,
|
||||
};
|
||||
|
||||
export const colors = {
|
||||
...materialUiColors,
|
||||
...baseColors,
|
||||
...appColors,
|
||||
};
|
||||
|
@ -104,6 +104,8 @@ export class EthWethConversionDialog extends React.Component<
|
||||
onChange={this._onValueChange.bind(this)}
|
||||
amount={this.state.value}
|
||||
onVisitBalancesPageClick={this.props.onCancelled}
|
||||
shouldShowErrs={true}
|
||||
shouldShowUnderline={true}
|
||||
/>
|
||||
) : (
|
||||
<EthAmountInput
|
||||
|
@ -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 && (
|
||||
|
@ -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}
|
||||
/>
|
||||
);
|
||||
|
@ -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>
|
||||
|
@ -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 {
|
||||
@ -30,6 +34,10 @@ interface TokenAmountInputState {
|
||||
}
|
||||
|
||||
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 +72,9 @@ 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 ? 84 : 62 };
|
||||
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 +85,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>
|
||||
|
@ -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';
|
||||
|
@ -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 (
|
||||
<div>
|
||||
<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 }}
|
||||
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 (
|
||||
<div>
|
||||
<ListItem
|
||||
primaryText={amount}
|
||||
leftIcon={this._renderTokenIcon(token, tokenLink)}
|
||||
rightAvatar={this._renderAccessoryItems(accessoryItemConfig)}
|
||||
style={{ ...styles.tokenItem, ...styles.borderedItem }}
|
||||
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:
|
||||
if (isWrappedEtherDirectionOpen) {
|
||||
buttonLabel = 'cancel';
|
||||
buttonIcon = <Close />;
|
||||
} else {
|
||||
switch (wrappedEtherDirection) {
|
||||
case Side.Deposit:
|
||||
buttonLabel = 'wrap';
|
||||
buttonIcon = <NavigationArrowDownward />;
|
||||
break;
|
||||
case WrappedEtherAction.Unwrap:
|
||||
case Side.Receive:
|
||||
buttonLabel = 'unwrap';
|
||||
buttonIcon = <NavigationArrowUpward />;
|
||||
break;
|
||||
default:
|
||||
throw utils.spawnSwitchErr('wrappedEtherAction', action);
|
||||
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;
|
||||
}
|
||||
}
|
185
packages/website/ts/components/wallet/wrap_ether_item.tsx
Normal file
185
packages/website/ts/components/wallet/wrap_ether_item.tsx
Normal file
@ -0,0 +1,185 @@
|
||||
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'}
|
||||
onVisitBalancesPageClick={_.noop}
|
||||
shouldShowErrs={false}
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
@ -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,
|
||||
},
|
||||
});
|
||||
|
7
packages/website/ts/utils/wallet_item_styles.ts
Normal file
7
packages/website/ts/utils/wallet_item_styles.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { colors, Styles } from '@0xproject/react-shared';
|
||||
|
||||
export const styles: Styles = {
|
||||
focusedItem: {
|
||||
backgroundColor: colors.walletFocusedItemBackground,
|
||||
},
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user