Skip to content
This repository has been archived by the owner on Jul 9, 2021. It is now read-only.

Commit

Permalink
Merge pull request #910 from 0xProject/feature/website/upgrade-allowa…
Browse files Browse the repository at this point in the history
…nce-toggles-to-locks-and-checks

[website] Use new designs with tooltips for allowance toggles
  • Loading branch information
fragosti authored Jul 27, 2018
2 parents 44d1be2 + c851c37 commit 48e538f
Show file tree
Hide file tree
Showing 14 changed files with 346 additions and 180 deletions.
160 changes: 160 additions & 0 deletions packages/website/ts/components/inputs/allowance_state_toggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import { colors } from '@0xproject/react-shared';
import { BigNumber, logUtils } from '@0xproject/utils';
import * as _ from 'lodash';
import * as React from 'react';
import ReactTooltip = require('react-tooltip');
import { Blockchain } from 'ts/blockchain';
import { AllowanceState, AllowanceStateView } from 'ts/components/ui/allowance_state_view';
import { Container } from 'ts/components/ui/container';
import { PointerDirection } from 'ts/components/ui/pointer';
import { Text } from 'ts/components/ui/text';
import { Dispatcher } from 'ts/redux/dispatcher';
import { BalanceErrs, Token, TokenState } from 'ts/types';
import { analytics } from 'ts/utils/analytics';
import { errorReporter } from 'ts/utils/error_reporter';
import { utils } from 'ts/utils/utils';

export interface AllowanceStateToggleProps {
networkId: number;
blockchain: Blockchain;
dispatcher: Dispatcher;
token: Token;
tokenState: TokenState;
userAddress: string;
onErrorOccurred?: (errType: BalanceErrs) => void;
refetchTokenStateAsync: () => Promise<void>;
tooltipDirection?: PointerDirection;
}

export interface AllowanceStateToggleState {
allowanceState: AllowanceState;
prevTokenState: TokenState;
loadingMessage?: string;
}

const DEFAULT_ALLOWANCE_AMOUNT_IN_BASE_UNITS = new BigNumber(2).pow(256).minus(1);

export class AllowanceStateToggle extends React.Component<AllowanceStateToggleProps, AllowanceStateToggleState> {
public static defaultProps = {
onErrorOccurred: _.noop.bind(_),
tooltipDirection: PointerDirection.Right,
};
private static _getAllowanceState(tokenState: TokenState): AllowanceState {
if (!tokenState.isLoaded) {
return AllowanceState.Loading;
}
if (tokenState.allowance.gt(0)) {
return AllowanceState.Unlocked;
}
return AllowanceState.Locked;
}
constructor(props: AllowanceStateToggleProps) {
super(props);
const tokenState = props.tokenState;
this.state = {
allowanceState: AllowanceStateToggle._getAllowanceState(tokenState),
prevTokenState: tokenState,
};
}

public render(): React.ReactNode {
const tooltipId = `tooltip-id-${this.props.token.symbol}`;
return (
<Container cursor="pointer">
<ReactTooltip id={tooltipId} effect="solid" offset={{ top: 3 }}>
{this._getTooltipContent()}
</ReactTooltip>
<div
data-tip={true}
data-for={tooltipId}
data-place={this.props.tooltipDirection}
onClick={this._onToggleAllowanceAsync.bind(this)}
>
<AllowanceStateView allowanceState={this.state.allowanceState} />
</div>
</Container>
);
}
public componentWillReceiveProps(nextProps: AllowanceStateToggleProps): void {
const nextTokenState = nextProps.tokenState;
const prevTokenState = this.state.prevTokenState;
if (
!nextTokenState.allowance.eq(prevTokenState.allowance) ||
nextTokenState.isLoaded !== prevTokenState.isLoaded
) {
const tokenState = nextProps.tokenState;
this.setState({
prevTokenState: tokenState,
allowanceState: AllowanceStateToggle._getAllowanceState(nextTokenState),
});
}
}
private _getTooltipContent(): React.ReactNode {
const symbol = this.props.token.symbol;
switch (this.state.allowanceState) {
case AllowanceState.Loading:
return (
<Text noWrap={true} fontColor={colors.white}>
{this.state.loadingMessage || 'Loading...'}
</Text>
);
case AllowanceState.Locked:
return (
<Text noWrap={true} fontColor={colors.white}>
Click to enable <b>{symbol}</b> for trading
</Text>
);
case AllowanceState.Unlocked:
return (
<Text noWrap={true} fontColor={colors.white}>
<b>{symbol}</b> is available for trading
</Text>
);
default:
return null;
}
}
private async _onToggleAllowanceAsync(): Promise<void> {
// Close all tooltips
ReactTooltip.hide();
if (this.props.userAddress === '') {
this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
return;
}

let newAllowanceAmountInBaseUnits = new BigNumber(0);
if (!this._isAllowanceSet()) {
newAllowanceAmountInBaseUnits = DEFAULT_ALLOWANCE_AMOUNT_IN_BASE_UNITS;
}
const isUnlockingToken = newAllowanceAmountInBaseUnits.gt(0);
this.setState({
allowanceState: AllowanceState.Loading,
loadingMessage: `${isUnlockingToken ? 'Unlocking' : 'Locking'} ${this.props.token.symbol}`,
});
const logData = {
tokenSymbol: this.props.token.symbol,
newAllowance: newAllowanceAmountInBaseUnits.toNumber(),
};
try {
await this.props.blockchain.setProxyAllowanceAsync(this.props.token, newAllowanceAmountInBaseUnits);
analytics.track('Set Allowances Success', logData);
await this.props.refetchTokenStateAsync();
} catch (err) {
analytics.track('Set Allowance Failure', logData);
this.setState({
allowanceState: AllowanceStateToggle._getAllowanceState(this.state.prevTokenState),
});
const errMsg = `${err}`;
if (utils.didUserDenyWeb3Request(errMsg)) {
return;
}
logUtils.log(`Unexpected error encountered: ${err}`);
logUtils.log(err.stack);
this.props.onErrorOccurred(BalanceErrs.allowanceSettingFailed);
errorReporter.report(err);
}
}
private _isAllowanceSet(): boolean {
return !this.props.tokenState.allowance.eq(0);
}
}
140 changes: 0 additions & 140 deletions packages/website/ts/components/inputs/allowance_toggle.tsx

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const OnboardingTooltip: React.StatelessComponent<OnboardingTooltipProps>
);
};
OnboardingTooltip.defaultProps = {
pointerDisplay: 'left',
pointerDisplay: PointerDirection.Left,
};

OnboardingTooltip.displayName = 'OnboardingTooltip';
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
WrapEthOnboardingStep2,
WrapEthOnboardingStep3,
} from 'ts/components/onboarding/wrap_eth_onboarding_step';
import { AllowanceToggle } from 'ts/containers/inputs/allowance_toggle';
import { AllowanceStateToggle } from 'ts/containers/inputs/allowance_state_toggle';
import { BrowserType, ProviderType, ScreenWidths, Token, TokenByAddress, TokenStateByAddress } from 'ts/types';
import { analytics } from 'ts/utils/analytics';
import { utils } from 'ts/utils/utils';
Expand Down Expand Up @@ -149,8 +149,8 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp
title: 'Step 3: Unlock Tokens',
content: (
<SetAllowancesOnboardingStep
zrxAllowanceToggle={this._renderZrxAllowanceToggle()}
ethAllowanceToggle={this._renderEthAllowanceToggle()}
zrxAllowanceToggle={this._renderZrxAllowanceStateToggle()}
ethAllowanceToggle={this._renderEthAllowanceStateToggle()}
doesUserHaveAllowancesForWethAndZrx={this._doesUserHaveAllowancesForWethAndZrx()}
/>
),
Expand Down Expand Up @@ -243,15 +243,15 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp
stepIndex: this.props.stepIndex,
});
}
private _renderZrxAllowanceToggle(): React.ReactNode {
private _renderZrxAllowanceStateToggle(): React.ReactNode {
const zrxToken = utils.getZrxToken(this.props.tokenByAddress);
return this._renderAllowanceToggle(zrxToken);
return this._renderAllowanceStateToggle(zrxToken);
}
private _renderEthAllowanceToggle(): React.ReactNode {
private _renderEthAllowanceStateToggle(): React.ReactNode {
const ethToken = utils.getEthToken(this.props.tokenByAddress);
return this._renderAllowanceToggle(ethToken);
return this._renderAllowanceStateToggle(ethToken);
}
private _renderAllowanceToggle(token: Token): React.ReactNode {
private _renderAllowanceStateToggle(token: Token): React.ReactNode {
if (!token) {
return null;
}
Expand All @@ -260,10 +260,9 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp
return null;
}
return (
<AllowanceToggle
<AllowanceStateToggle
token={token}
tokenState={tokenStateIfExists}
isDisabled={!tokenStateIfExists.isLoaded}
blockchain={this.props.blockchain}
// tslint:disable-next-line:jsx-no-lambda
refetchTokenStateAsync={async () => this.props.refetchTokenStateAsync(token.address)}
Expand Down
4 changes: 4 additions & 0 deletions packages/website/ts/components/portal/portal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { TradeHistory } from 'ts/components/trade_history/trade_history';
import { Container } from 'ts/components/ui/container';
import { FlashMessage } from 'ts/components/ui/flash_message';
import { Image } from 'ts/components/ui/image';
import { PointerDirection } from 'ts/components/ui/pointer';
import { Text } from 'ts/components/ui/text';
import { Wallet } from 'ts/components/wallet/wallet';
import { GenerateOrderForm } from 'ts/containers/generate_order_form';
Expand Down Expand Up @@ -355,6 +356,9 @@ export class Portal extends React.Component<PortalProps, PortalState> {
onAddToken={this._onAddToken.bind(this)}
onRemoveToken={this._onRemoveToken.bind(this)}
refetchTokenStateAsync={this._refetchTokenStateAsync.bind(this)}
toggleTooltipDirection={
this.props.isPortalOnboardingShowing ? PointerDirection.Left : PointerDirection.Right
}
/>
</Container>
{!isMobile && <Container marginTop="8px">{this._renderStartOnboarding()}</Container>}
Expand Down
Loading

0 comments on commit 48e538f

Please sign in to comment.