diff options
Diffstat (limited to 'packages/website/ts/components/inputs/allowance_state_toggle.tsx')
-rw-r--r-- | packages/website/ts/components/inputs/allowance_state_toggle.tsx | 160 |
1 files changed, 160 insertions, 0 deletions
diff --git a/packages/website/ts/components/inputs/allowance_state_toggle.tsx b/packages/website/ts/components/inputs/allowance_state_toggle.tsx new file mode 100644 index 000000000..39d2e3030 --- /dev/null +++ b/packages/website/ts/components/inputs/allowance_state_toggle.tsx @@ -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); + } +} |