diff options
author | Francesco Agosti <francesco.agosti93@gmail.com> | 2018-05-31 02:49:04 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-05-31 02:49:04 +0800 |
commit | e18d61b31a22519cd7d85ecffa62925ef7adc63d (patch) | |
tree | 166746953c94bdfd62cca909553f30d96d682bd8 /packages/website/ts/components/onboarding | |
parent | b20e40dd6fd9964876a0006efe8b879a9a1d2118 (diff) | |
parent | 61cd1ae5259c03e3ae1d1711d585e2222d8cfc34 (diff) | |
download | dexon-sol-tools-e18d61b31a22519cd7d85ecffa62925ef7adc63d.tar dexon-sol-tools-e18d61b31a22519cd7d85ecffa62925ef7adc63d.tar.gz dexon-sol-tools-e18d61b31a22519cd7d85ecffa62925ef7adc63d.tar.bz2 dexon-sol-tools-e18d61b31a22519cd7d85ecffa62925ef7adc63d.tar.lz dexon-sol-tools-e18d61b31a22519cd7d85ecffa62925ef7adc63d.tar.xz dexon-sol-tools-e18d61b31a22519cd7d85ecffa62925ef7adc63d.tar.zst dexon-sol-tools-e18d61b31a22519cd7d85ecffa62925ef7adc63d.zip |
Merge pull request #635 from 0xProject/feature/website/custom-onboarding-tooltip
Remove react-joyride and some more refactoring
Diffstat (limited to 'packages/website/ts/components/onboarding')
3 files changed, 236 insertions, 32 deletions
diff --git a/packages/website/ts/components/onboarding/onboarding_flow.tsx b/packages/website/ts/components/onboarding/onboarding_flow.tsx index 621d14260..4066babaf 100644 --- a/packages/website/ts/components/onboarding/onboarding_flow.tsx +++ b/packages/website/ts/components/onboarding/onboarding_flow.tsx @@ -1,39 +1,96 @@ import * as _ from 'lodash'; import * as React from 'react'; -import Joyride, { CallbackData, Step, StyleOptions } from 'react-joyride'; +import { Placement, Popper, PopperChildrenProps } from 'react-popper'; +import { ContinueButtonDisplay, OnboardingTooltip } from 'ts/components/onboarding/onboarding_tooltip'; +import { Container } from 'ts/components/ui/container'; +import { Overlay } from 'ts/components/ui/overlay'; import { zIndex } from 'ts/utils/style'; +export interface Step { + target: string; + title?: string; + content: React.ReactNode; + placement?: Placement; + hideBackButton?: boolean; + hideNextButton?: boolean; + continueButtonDisplay?: ContinueButtonDisplay; +} + export interface OnboardingFlowProps { steps: Step[]; stepIndex: number; isRunning: boolean; onClose: () => void; + updateOnboardingStep: (stepIndex: number) => void; } -const joyrideStyleOptions: StyleOptions = { - zIndex: zIndex.overlay, -}; - -// Wrapper around Joyride with defaults and styles set export class OnboardingFlow extends React.Component<OnboardingFlowProps> { public render(): React.ReactNode { + if (!this.props.isRunning) { + return null; + } + return ( + <Overlay> + <Popper referenceElement={this._getElementForStep()} placement={this._getCurrentStep().placement}> + {this._renderPopperChildren.bind(this)} + </Popper> + </Overlay> + ); + } + + private _getElementForStep(): Element { + return document.querySelector(this._getCurrentStep().target); + } + + private _renderPopperChildren(props: PopperChildrenProps): React.ReactNode { return ( - <Joyride - run={this.props.isRunning} - debug={true} - steps={this.props.steps} - stepIndex={this.props.stepIndex} - styles={{ options: joyrideStyleOptions }} - callback={this._handleChange.bind(this)} - /> + <div ref={props.ref} style={props.style} data-placement={props.placement}> + {this._renderToolTip()} + </div> ); } - private _handleChange(data: CallbackData): void { - switch (data.action) { - case 'close': - this.props.onClose(); + private _renderToolTip(): React.ReactNode { + const { steps, stepIndex } = this.props; + const step = steps[stepIndex]; + const isLastStep = steps.length - 1 === stepIndex; + return ( + <Container marginLeft="15px"> + <OnboardingTooltip + title={step.title} + content={step.content} + isLastStep={isLastStep} + hideBackButton={step.hideBackButton} + hideNextButton={step.hideNextButton} + onClose={this.props.onClose} + onClickNext={this._goToNextStep.bind(this)} + onClickBack={this._goToPrevStep.bind(this)} + continueButtonDisplay={step.continueButtonDisplay} + /> + </Container> + ); + } + + private _getCurrentStep(): Step { + return this.props.steps[this.props.stepIndex]; + } + + private _goToNextStep(): void { + const nextStep = this.props.stepIndex + 1; + if (nextStep < this.props.steps.length) { + this.props.updateOnboardingStep(nextStep); + } else { + this.props.onClose(); + } + } + + private _goToPrevStep(): void { + const nextStep = this.props.stepIndex - 1; + if (nextStep >= 0) { + this.props.updateOnboardingStep(nextStep); + } else { + this.props.onClose(); } } } diff --git a/packages/website/ts/components/onboarding/onboarding_tooltip.tsx b/packages/website/ts/components/onboarding/onboarding_tooltip.tsx new file mode 100644 index 000000000..eb34a87f2 --- /dev/null +++ b/packages/website/ts/components/onboarding/onboarding_tooltip.tsx @@ -0,0 +1,56 @@ +import * as React from 'react'; + +import { colors } from '@0xproject/react-shared'; +import { Container } from 'ts/components/ui/container'; +import { Island } from 'ts/components/ui/island'; + +export type ContinueButtonDisplay = 'enabled' | 'disabled'; + +export interface OnboardingTooltipProps { + title?: string; + content: React.ReactNode; + isLastStep: boolean; + onClose: () => void; + onClickNext: () => void; + onClickBack: () => void; + continueButtonDisplay?: ContinueButtonDisplay; + hideBackButton?: boolean; + hideNextButton?: boolean; +} + +// TODO: Make this more general button. +export interface ContinueButtonProps { + display: ContinueButtonDisplay; + children?: string; + onClick: () => void; +} + +export const ContinueButton: React.StatelessComponent<ContinueButtonProps> = (props: ContinueButtonProps) => { + const isDisabled = props.display === 'disabled'; + return ( + <button disabled={isDisabled} onClick={isDisabled ? undefined : props.onClick}> + {props.children} + </button> + ); +}; + +export const OnboardingTooltip: React.StatelessComponent<OnboardingTooltipProps> = (props: OnboardingTooltipProps) => ( + <Island> + <Container paddingRight="30px" paddingLeft="30px" maxWidth={350} paddingTop="15px" paddingBottom="15px"> + <div className="flex flex-column"> + {props.title} + {props.content} + {props.continueButtonDisplay && ( + <ContinueButton onClick={props.onClickNext} display={props.continueButtonDisplay}> + Continue + </ContinueButton> + )} + {!props.hideBackButton && <button onClick={props.onClickBack}>Back</button>} + {!props.hideNextButton && <button onClick={props.onClickNext}>Skip</button>} + <button onClick={props.onClose}>Close</button> + </div> + </Container> + </Island> +); + +OnboardingTooltip.displayName = 'OnboardingTooltip'; diff --git a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx index 11684aaee..edaeb3736 100644 --- a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx +++ b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx @@ -1,32 +1,123 @@ +import * as _ from 'lodash'; import * as React from 'react'; -import { Step } from 'react-joyride'; -import { OnboardingFlow } from 'ts/components/onboarding/onboarding_flow'; +import { BigNumber } from '@0xproject/utils'; +import { OnboardingFlow, Step } from 'ts/components/onboarding/onboarding_flow'; +import { ProviderType, TokenByAddress } from 'ts/types'; +import { utils } from 'ts/utils/utils'; export interface PortalOnboardingFlowProps { stepIndex: number; isRunning: boolean; - onClose: () => void; + userAddress: string; + hasBeenSeen: boolean; + providerType: ProviderType; + injectedProviderName: string; + blockchainIsLoaded: boolean; + userEthBalanceInWei: BigNumber; + tokenByAddress: TokenByAddress; + updateIsRunning: (isRunning: boolean) => void; + updateOnboardingStep: (stepIndex: number) => void; } -const steps: Step[] = [ - { - target: '.wallet', - content: 'You are onboarding right now!', - placement: 'right', - disableBeacon: true, - }, -]; - export class PortalOnboardingFlow extends React.Component<PortalOnboardingFlowProps> { + public componentDidMount(): void { + this._overrideOnboardingStateIfShould(); + } + public componentDidUpdate(): void { + this._overrideOnboardingStateIfShould(); + } public render(): React.ReactNode { return ( <OnboardingFlow - steps={steps} + steps={this._getSteps()} stepIndex={this.props.stepIndex} isRunning={this.props.isRunning} - onClose={this.props.onClose} + onClose={this.props.updateIsRunning.bind(this, false)} + updateOnboardingStep={this.props.updateOnboardingStep} /> ); } + + private _getSteps(): Step[] { + const steps: Step[] = [ + { + target: '.wallet', + content: + 'Before you begin, you need to connect to a wallet. This will be used across all 0x relayers and dApps', + placement: 'right', + hideBackButton: true, + hideNextButton: true, + }, + { + target: '.wallet', + content: 'Unlock your metamask extension to begin', + placement: 'right', + hideBackButton: true, + hideNextButton: true, + }, + { + target: '.wallet', + content: + 'In order to start trading on any 0x relayer in the 0x ecosystem, you need to complete two simple steps', + placement: 'right', + hideBackButton: true, + continueButtonDisplay: 'enabled', + }, + { + target: '.eth-row', + content: 'Before you begin you will need to send some ETH to your metamask wallet', + placement: 'right', + continueButtonDisplay: this._userHasEth() ? 'enabled' : 'disabled', + }, + { + target: '.weth-row', + content: 'You need to convert some of your ETH into tradeable Wrapped ETH (WETH)', + placement: 'right', + continueButtonDisplay: this._userHasWeth() ? 'enabled' : 'disabled', + }, + ]; + return steps; + } + + private _isAddressAvailable(): boolean { + return !_.isEmpty(this.props.userAddress); + } + + private _userHasEth(): boolean { + return this.props.userEthBalanceInWei > new BigNumber(0); + } + + private _userHasWeth(): boolean { + // TODO: https://app.asana.com/0/681385331277907/690722374136933 + return false; + } + + private _overrideOnboardingStateIfShould(): void { + this._autoStartOnboardingIfShould(); + this._adjustStepIfShould(); + } + + private _adjustStepIfShould(): void { + if (this._isAddressAvailable()) { + if (this.props.stepIndex < 2) { + this.props.updateOnboardingStep(2); + } + return; + } + const isExternallyInjected = utils.isExternallyInjected( + this.props.providerType, + this.props.injectedProviderName, + ); + if (isExternallyInjected) { + this.props.updateOnboardingStep(1); + return; + } + this.props.updateOnboardingStep(0); + } + private _autoStartOnboardingIfShould(): void { + if (!this.props.isRunning && !this.props.hasBeenSeen && this.props.blockchainIsLoaded) { + this.props.updateIsRunning(true); + } + } } |