diff options
author | Francesco Agosti <francesco.agosti93@gmail.com> | 2018-06-15 05:41:42 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-06-15 05:41:42 +0800 |
commit | e7eb220c503118631a6b23071c71b4b55df5b5cf (patch) | |
tree | 3eef31cad2d42ac8c5c2ec644503aa03e018d1fe | |
parent | 0e354e5ea1f9951088331a310999bf87c8f8f4b3 (diff) | |
parent | 7d6700582006d201f8757107b4cf286bb0ee5661 (diff) | |
download | dexon-0x-contracts-e7eb220c503118631a6b23071c71b4b55df5b5cf.tar.gz dexon-0x-contracts-e7eb220c503118631a6b23071c71b4b55df5b5cf.tar.zst dexon-0x-contracts-e7eb220c503118631a6b23071c71b4b55df5b5cf.zip |
Merge pull request #695 from 0xProject/feature/website/onboarding-flow-allowances
Implement allowances and final "congrats" onboarding flow step
8 files changed, 132 insertions, 23 deletions
diff --git a/packages/website/ts/components/inputs/allowance_toggle.tsx b/packages/website/ts/components/inputs/allowance_toggle.tsx index 09791f125..0dd2a5aa5 100644 --- a/packages/website/ts/components/inputs/allowance_toggle.tsx +++ b/packages/website/ts/components/inputs/allowance_toggle.tsx @@ -1,5 +1,6 @@ import { constants as sharedConstants, Styles } from '@0xproject/react-shared'; import { BigNumber, logUtils } from '@0xproject/utils'; +import * as _ from 'lodash'; import Toggle from 'material-ui/Toggle'; import * as React from 'react'; import { Blockchain } from 'ts/blockchain'; @@ -16,11 +17,11 @@ interface AllowanceToggleProps { networkId: number; blockchain: Blockchain; dispatcher: Dispatcher; - onErrorOccurred: (errType: BalanceErrs) => void; token: Token; tokenState: TokenState; userAddress: string; - isDisabled: boolean; + isDisabled?: boolean; + onErrorOccurred?: (errType: BalanceErrs) => void; refetchTokenStateAsync: () => Promise<void>; } @@ -55,6 +56,10 @@ const styles: Styles = { }; export class AllowanceToggle extends React.Component<AllowanceToggleProps, AllowanceToggleState> { + public static defaultProps = { + onErrorOccurred: _.noop, + isDisabled: false, + }; constructor(props: AllowanceToggleProps) { super(props); this.state = { diff --git a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx index bf52684d7..efb844cb5 100644 --- a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx +++ b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx @@ -2,11 +2,14 @@ import * as _ from 'lodash'; import * as React from 'react'; import { BigNumber } from '@0xproject/utils'; +import { Blockchain } from 'ts/blockchain'; import { OnboardingFlow, Step } from 'ts/components/onboarding/onboarding_flow'; -import { ProviderType, TokenByAddress, TokenStateByAddress } from 'ts/types'; +import { AllowanceToggle } from 'ts/containers/inputs/allowance_toggle'; +import { ProviderType, Token, TokenByAddress, TokenStateByAddress } from 'ts/types'; import { utils } from 'ts/utils/utils'; export interface PortalOnboardingFlowProps { + blockchain: Blockchain; stepIndex: number; isRunning: boolean; userAddress: string; @@ -19,6 +22,7 @@ export interface PortalOnboardingFlowProps { trackedTokenStateByAddress: TokenStateByAddress; updateIsRunning: (isRunning: boolean) => void; updateOnboardingStep: (stepIndex: number) => void; + refetchTokenStateAsync: (tokenAddress: string) => Promise<void>; } export class PortalOnboardingFlow extends React.Component<PortalOnboardingFlowProps> { @@ -39,7 +43,6 @@ export class PortalOnboardingFlow extends React.Component<PortalOnboardingFlowPr /> ); } - private _getSteps(): Step[] { const steps: Step[] = [ { @@ -77,18 +80,33 @@ export class PortalOnboardingFlow extends React.Component<PortalOnboardingFlowPr placement: 'right', continueButtonDisplay: this._userHasVisibleWeth() ? 'enabled' : 'disabled', }, + { + target: '.weth-row', + content: ( + <div> + Unlock your tokens for trading. You only need to do this once for each token. + <div> ETH: {this._renderEthAllowanceToggle()}</div> + <div> ZRX: {this._renderZrxAllowanceToggle()}</div> + </div> + ), + placement: 'right', + continueButtonDisplay: this._userHasAllowancesForWethAndZrx() ? 'enabled' : 'disabled', + }, + { + target: '.wallet', + content: 'Congrats! Your wallet is now set up for trading. Use it on any relayer in the 0x ecosystem.', + placement: 'right', + continueButtonDisplay: 'enabled', + }, ]; return steps; } - private _isAddressAvailable(): boolean { return !_.isEmpty(this.props.userAddress); } - private _userHasVisibleEth(): boolean { return this.props.userEtherBalanceInWei > new BigNumber(0); } - private _userHasVisibleWeth(): boolean { const ethToken = utils.getEthToken(this.props.tokenByAddress); if (!ethToken) { @@ -97,15 +115,25 @@ export class PortalOnboardingFlow extends React.Component<PortalOnboardingFlowPr const wethTokenState = this.props.trackedTokenStateByAddress[ethToken.address]; return wethTokenState.balance > new BigNumber(0); } - + private _userHasAllowancesForWethAndZrx(): boolean { + const ethToken = utils.getEthToken(this.props.tokenByAddress); + const zrxToken = utils.getZrxToken(this.props.tokenByAddress); + if (ethToken && zrxToken) { + const ethTokenAllowance = this.props.trackedTokenStateByAddress[ethToken.address].allowance; + const zrxTokenAllowance = this.props.trackedTokenStateByAddress[zrxToken.address].allowance; + return ethTokenAllowance > new BigNumber(0) && zrxTokenAllowance > new BigNumber(0); + } + return false; + } private _overrideOnboardingStateIfShould(): void { this._autoStartOnboardingIfShould(); this._adjustStepIfShould(); } private _adjustStepIfShould(): void { + const stepIndex = this.props.stepIndex; if (this._isAddressAvailable()) { - if (this.props.stepIndex < 2) { + if (stepIndex < 2) { this.props.updateOnboardingStep(2); } return; @@ -115,14 +143,42 @@ export class PortalOnboardingFlow extends React.Component<PortalOnboardingFlowPr this.props.injectedProviderName, ); if (isExternallyInjected) { - this.props.updateOnboardingStep(1); + if (stepIndex !== 1) { + this.props.updateOnboardingStep(1); + } return; } - this.props.updateOnboardingStep(0); + if (stepIndex !== 0) { + this.props.updateOnboardingStep(0); + } } private _autoStartOnboardingIfShould(): void { if (!this.props.isRunning && !this.props.hasBeenSeen && this.props.blockchainIsLoaded) { this.props.updateIsRunning(true); } } + private _renderZrxAllowanceToggle(): React.ReactNode { + const zrxToken = utils.getZrxToken(this.props.tokenByAddress); + return this._renderAllowanceToggle(zrxToken); + } + private _renderEthAllowanceToggle(): React.ReactNode { + const ethToken = utils.getEthToken(this.props.tokenByAddress); + return this._renderAllowanceToggle(ethToken); + } + private _renderAllowanceToggle(token: Token): React.ReactNode { + if (!token) { + return null; + } + const tokenState = this.props.trackedTokenStateByAddress[token.address]; + return ( + <AllowanceToggle + token={token} + tokenState={tokenState} + isDisabled={!tokenState.isLoaded} + blockchain={this.props.blockchain} + // tslint:disable-next-line:jsx-no-lambda + refetchTokenStateAsync={async () => this.props.refetchTokenStateAsync(token.address)} + /> + ); + } } diff --git a/packages/website/ts/components/portal/portal.tsx b/packages/website/ts/components/portal/portal.tsx index 1b3bf3dae..48486939b 100644 --- a/packages/website/ts/components/portal/portal.tsx +++ b/packages/website/ts/components/portal/portal.tsx @@ -235,7 +235,11 @@ export class Portal extends React.Component<PortalProps, PortalState> { : TokenVisibility.TRACKED; return ( <div style={styles.root}> - <PortalOnboardingFlow trackedTokenStateByAddress={this.state.trackedTokenStateByAddress} /> + <PortalOnboardingFlow + blockchain={this._blockchain} + trackedTokenStateByAddress={this.state.trackedTokenStateByAddress} + refetchTokenStateAsync={this._refetchTokenStateAsync.bind(this)} + /> <DocumentTitle title="0x Portal DApp" /> <TopBar userAddress={this.props.userAddress} diff --git a/packages/website/ts/components/token_balances.tsx b/packages/website/ts/components/token_balances.tsx index 2a051651d..7af80745c 100644 --- a/packages/website/ts/components/token_balances.tsx +++ b/packages/website/ts/components/token_balances.tsx @@ -20,11 +20,11 @@ import ReactTooltip = require('react-tooltip'); import firstBy = require('thenby'); import { Blockchain } from 'ts/blockchain'; import { AssetPicker } from 'ts/components/generate_order/asset_picker'; -import { AllowanceToggle } from 'ts/components/inputs/allowance_toggle'; import { SendButton } from 'ts/components/send_button'; import { HelpTooltip } from 'ts/components/ui/help_tooltip'; import { LifeCycleRaisedButton } from 'ts/components/ui/lifecycle_raised_button'; import { TokenIcon } from 'ts/components/ui/token_icon'; +import { AllowanceToggle } from 'ts/containers/inputs/allowance_toggle'; import { trackedTokenStorage } from 'ts/local_storage/tracked_token_storage'; import { Dispatcher } from 'ts/redux/dispatcher'; import { @@ -362,13 +362,10 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala </TableRowColumn> <TableRowColumn> <AllowanceToggle - networkId={this.props.networkId} blockchain={this.props.blockchain} - dispatcher={this.props.dispatcher} token={token} tokenState={tokenState} onErrorOccurred={this._onErrorOccurred.bind(this)} - userAddress={this.props.userAddress} isDisabled={!tokenState.isLoaded} refetchTokenStateAsync={this._refetchTokenStateAsync.bind(this, token.address)} /> diff --git a/packages/website/ts/components/wallet/wallet.tsx b/packages/website/ts/components/wallet/wallet.tsx index f80be6313..4523b0ac9 100644 --- a/packages/website/ts/components/wallet/wallet.tsx +++ b/packages/website/ts/components/wallet/wallet.tsx @@ -13,7 +13,6 @@ import { Link } from 'react-router-dom'; import firstBy = require('thenby'); import { Blockchain } from 'ts/blockchain'; -import { AllowanceToggle } from 'ts/components/inputs/allowance_toggle'; import { Container } from 'ts/components/ui/container'; import { IconButton } from 'ts/components/ui/icon_button'; import { Identicon } from 'ts/components/ui/identicon'; @@ -21,6 +20,7 @@ import { Island } from 'ts/components/ui/island'; 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 { AllowanceToggle } from 'ts/containers/inputs/allowance_toggle'; import { Dispatcher } from 'ts/redux/dispatcher'; import { colors } from 'ts/style/colors'; import { zIndex } from 'ts/style/z_index'; @@ -414,15 +414,12 @@ export class Wallet extends React.Component<WalletProps, WalletState> { ); } private _renderAllowanceToggle(config: AllowanceToggleConfig): React.ReactNode { + // TODO: Error handling return ( <AllowanceToggle - networkId={this.props.networkId} blockchain={this.props.blockchain} - dispatcher={this.props.dispatcher} token={config.token} tokenState={config.tokenState} - onErrorOccurred={_.noop} // TODO: Error handling - userAddress={this.props.userAddress} isDisabled={!config.tokenState.isLoaded} refetchTokenStateAsync={async () => this.props.refetchTokenStateAsync(config.token.address)} /> diff --git a/packages/website/ts/containers/inputs/allowance_toggle.ts b/packages/website/ts/containers/inputs/allowance_toggle.ts new file mode 100644 index 000000000..545708f92 --- /dev/null +++ b/packages/website/ts/containers/inputs/allowance_toggle.ts @@ -0,0 +1,41 @@ +import * as React from 'react'; +import { connect } from 'react-redux'; +import { Dispatch } from 'redux'; +import { Blockchain } from 'ts/blockchain'; +import { State } from 'ts/redux/reducer'; +import { BalanceErrs, Token, TokenState } from 'ts/types'; + +import { AllowanceToggle as AllowanceToggleComponent } from 'ts/components/inputs/allowance_toggle'; +import { Dispatcher } from 'ts/redux/dispatcher'; + +interface AllowanceToggleProps { + blockchain: Blockchain; + onErrorOccurred?: (errType: BalanceErrs) => void; + token: Token; + tokenState: TokenState; + isDisabled?: boolean; + refetchTokenStateAsync: () => Promise<void>; +} + +interface ConnectedState { + networkId: number; + userAddress: string; +} + +interface ConnectedDispatch { + dispatcher: Dispatcher; +} + +const mapStateToProps = (state: State, _ownProps: AllowanceToggleProps): ConnectedState => ({ + networkId: state.networkId, + userAddress: state.userAddress, +}); + +const mapDispatchTopProps = (dispatch: Dispatch<State>): ConnectedDispatch => ({ + dispatcher: new Dispatcher(dispatch), +}); + +export const AllowanceToggle: React.ComponentClass<AllowanceToggleProps> = connect( + mapStateToProps, + mapDispatchTopProps, +)(AllowanceToggleComponent); diff --git a/packages/website/ts/containers/portal_onboarding_flow.ts b/packages/website/ts/containers/portal_onboarding_flow.ts index 4298ab275..746adf0ba 100644 --- a/packages/website/ts/containers/portal_onboarding_flow.ts +++ b/packages/website/ts/containers/portal_onboarding_flow.ts @@ -2,6 +2,7 @@ import { BigNumber } from '@0xproject/utils'; import * as React from 'react'; import { connect } from 'react-redux'; import { Dispatch } from 'redux'; +import { Blockchain } from 'ts/blockchain'; import { ActionTypes, ProviderType, TokenByAddress, TokenStateByAddress } from 'ts/types'; import { PortalOnboardingFlow as PortalOnboardingFlowComponent } from 'ts/components/onboarding/portal_onboarding_flow'; @@ -9,6 +10,8 @@ import { State } from 'ts/redux/reducer'; interface PortalOnboardingFlowProps { trackedTokenStateByAddress: TokenStateByAddress; + blockchain: Blockchain; + refetchTokenStateAsync: (tokenAddress: string) => Promise<void>; } interface ConnectedState { diff --git a/packages/website/ts/utils/utils.ts b/packages/website/ts/utils/utils.ts index eb384fbb4..fdee264b2 100644 --- a/packages/website/ts/utils/utils.ts +++ b/packages/website/ts/utils/utils.ts @@ -322,8 +322,14 @@ export const utils = { return this.isDevelopment() || this.isStaging() || this.isDogfood(); }, getEthToken(tokenByAddress: TokenByAddress): Token { + return utils.getTokenBySymbol(constants.ETHER_TOKEN_SYMBOL, tokenByAddress); + }, + getZrxToken(tokenByAddress: TokenByAddress): Token { + return utils.getTokenBySymbol(constants.ZRX_TOKEN_SYMBOL, tokenByAddress); + }, + getTokenBySymbol(symbol: string, tokenByAddress: TokenByAddress): Token { const tokens = _.values(tokenByAddress); - const etherToken = _.find(tokens, { symbol: constants.ETHER_TOKEN_SYMBOL }); - return etherToken; + const token = _.find(tokens, { symbol }); + return token; }, }; |