Basic onboarding flow infrastructure set up

This commit is contained in:
fragosti
2018-05-22 10:15:58 -07:00
parent de1ff52de3
commit 7af77d3eb0
17 changed files with 350 additions and 38 deletions

View File

@@ -42,6 +42,7 @@
"react-document-title": "^2.0.3",
"react-dom": "15.6.1",
"react-ga": "^2.4.1",
"react-joyride": "^2.0.0-11",
"react-redux": "^5.0.3",
"react-router-dom": "^4.1.1",
"react-scroll": "^1.5.2",

View File

@@ -0,0 +1,31 @@
import * as _ from 'lodash';
import * as React from 'react';
import Joyride, { Step, StyleOptions } from 'react-joyride';
import { zIndex } from 'ts/utils/style';
interface OnboardingFlowProps {
steps: Step[];
stepIndex?: number;
isRunning: boolean;
}
const style: StyleOptions = {
zIndex: zIndex.overlay,
};
// Wrapper around Joyride with defaults and styles set
export class OnboardingFlow extends React.Component<OnboardingFlowProps> {
public render(): React.ReactNode {
const { steps, stepIndex, isRunning } = this.props;
return (
<Joyride
run={isRunning}
debug={true}
steps={steps}
stepIndex={stepIndex}
styles={{ options: style }}
/>
);
}
};

View File

@@ -0,0 +1,29 @@
import * as React from 'react';
import { OnboardingFlow } from 'ts/components/onboarding/onboarding_flow';
export interface PortalOnboardingFlowProps {
stepIndex: number;
isRunning: boolean;
}
const steps = [
{
target: '.wallet',
content: 'Hey!',
placement: 'right',
disableBeacon: true,
},
];
export class PortalOnboardingFlow extends React.Component<PortalOnboardingFlow> {
public render(): React.ReactNode {
return (
<OnboardingFlow
steps={steps}
stepIndex={this.props.stepIndex}
isRunning={this.props.isRunning}
/>
)
}
};

View File

@@ -19,8 +19,11 @@ import { TextHeader } from 'ts/components/portal/text_header';
import { RelayerIndex } from 'ts/components/relayer_index/relayer_index';
import { TokenBalances } from 'ts/components/token_balances';
import { TopBar, TopBarDisplayType } from 'ts/components/top_bar/top_bar';
import { PortalOnboardingFlow } from 'ts/containers/portal_onboarding_flow';
import { Island } from 'ts/components/ui/island';
import { TradeHistory } from 'ts/components/trade_history/trade_history';
import { FlashMessage } from 'ts/components/ui/flash_message';
import { Container } from 'ts/components/ui/container';
import { Wallet } from 'ts/components/wallet/wallet';
import { GenerateOrderForm } from 'ts/containers/generate_order_form';
import { localStorage } from 'ts/local_storage/local_storage';
@@ -183,6 +186,7 @@ export class Portal extends React.Component<PortalProps, PortalState> {
: TokenVisibility.TRACKED;
return (
<div style={styles.root}>
<PortalOnboardingFlow />
<DocumentTitle title="0x Portal DApp" />
<TopBar
userAddress={this.props.userAddress}
@@ -278,25 +282,37 @@ export class Portal extends React.Component<PortalProps, PortalState> {
const allTokens = _.values(this.props.tokenByAddress);
const trackedTokens = _.filter(allTokens, t => t.isTracked);
return (
<Wallet
userAddress={this.props.userAddress}
networkId={this.props.networkId}
blockchain={this._blockchain}
blockchainIsLoaded={this.props.blockchainIsLoaded}
blockchainErr={this.props.blockchainErr}
dispatcher={this.props.dispatcher}
tokenByAddress={this.props.tokenByAddress}
trackedTokens={trackedTokens}
userEtherBalanceInWei={this.props.userEtherBalanceInWei}
lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
injectedProviderName={this.props.injectedProviderName}
providerType={this.props.providerType}
onToggleLedgerDialog={this._onToggleLedgerDialog.bind(this)}
onAddToken={this._onAddToken.bind(this)}
onRemoveToken={this._onRemoveToken.bind(this)}
/>
<div>
<Wallet
userAddress={this.props.userAddress}
networkId={this.props.networkId}
blockchain={this._blockchain}
blockchainIsLoaded={this.props.blockchainIsLoaded}
blockchainErr={this.props.blockchainErr}
dispatcher={this.props.dispatcher}
tokenByAddress={this.props.tokenByAddress}
trackedTokens={trackedTokens}
userEtherBalanceInWei={this.props.userEtherBalanceInWei}
lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
injectedProviderName={this.props.injectedProviderName}
providerType={this.props.providerType}
onToggleLedgerDialog={this._onToggleLedgerDialog.bind(this)}
onAddToken={this._onAddToken.bind(this)}
onRemoveToken={this._onRemoveToken.bind(this)}
/>
<Container marginTop="15px">
<Island>
{/** TODO: Implement real styles. */}
<p onClick={this._startOnboarding.bind(this)}>Start onboarding flow.</p>
</Island>
</Container>
</div>
);
}
private _startOnboarding(): void {
this.props.dispatcher.updatePortalOnboardingShowing(true);
}
private _renderWalletSection(): React.ReactNode {
return <Section header={<TextHeader labelText="Your Account" />} body={this._renderWallet()} />;
}

View File

@@ -4,6 +4,7 @@ import { GridTile } from 'material-ui/GridList';
import * as React from 'react';
import { TopTokens } from 'ts/components/relayer_index/relayer_top_tokens';
import { Island } from 'ts/components/ui/island';
import { TokenIcon } from 'ts/components/ui/token_icon';
import { Token, WebsiteBackendRelayerInfo } from 'ts/types';
import { colors } from 'ts/utils/colors';
@@ -15,13 +16,6 @@ export interface RelayerGridTileProps {
const styles: Styles = {
root: {
backgroundColor: colors.white,
borderBottomRightRadius: 10,
borderBottomLeftRadius: 10,
borderTopRightRadius: 10,
borderTopLeftRadius: 10,
boxShadow: `0px 4px 6px ${colors.walletBoxShadow}`,
overflow: 'hidden',
boxSizing: 'border-box',
},
innerDiv: {
@@ -68,7 +62,7 @@ const FALLBACK_IMG_SRC = '/images/landing/hero_chip_image.png';
export const RelayerGridTile: React.StatelessComponent<RelayerGridTileProps> = (props: RelayerGridTileProps) => {
const link = props.relayerInfo.appUrl || props.relayerInfo.url;
return (
<GridTile style={styles.root}>
<Island style={styles.root} Component={GridTile}>
<div style={styles.innerDiv}>
<a href={link} target="_blank" style={{ textDecoration: 'none' }}>
<ImgWithFallback
@@ -91,7 +85,7 @@ export const RelayerGridTile: React.StatelessComponent<RelayerGridTileProps> = (
</div>
</div>
</div>
</GridTile>
</Island>
);
};

View File

@@ -20,6 +20,7 @@ import { Deco, Key, ProviderType, WebsiteLegacyPaths, WebsitePaths } from 'ts/ty
import { constants } from 'ts/utils/constants';
import { Translate } from 'ts/utils/translate';
import { utils } from 'ts/utils/utils';
import { zIndex } from 'ts/utils/style';
export enum TopBarDisplayType {
Default,
@@ -59,7 +60,7 @@ const styles: Styles = {
width: '100%',
position: 'relative',
top: 0,
zIndex: 1100,
zIndex: zIndex.topBar,
paddingBottom: 1,
},
bottomBar: {

View File

@@ -0,0 +1,16 @@
import * as React from 'react';
interface ContainerProps {
marginTop?: string | number;
marginBottom?: string | number;
marginRight?: string | number;
marginLeft?: string | number;
children?: React.ReactNode;
}
export const Container: React.StatelessComponent<ContainerProps> = (props: ContainerProps) => {
const { children, ...style } = props;
return <div style={style}>{children}</div>;
};
Container.displayName = 'Container';

View File

@@ -0,0 +1,33 @@
import * as React from 'react';
import { Styleable } from 'ts/types';
import { colors } from 'ts/utils/colors';
export interface IslandProps {
style?: React.CSSProperties;
children?: React.ReactNode;
className?: string;
Component?: string | React.ComponentClass<any> | React.StatelessComponent<any>;
}
const defaultStyle: React.CSSProperties = {
backgroundColor: colors.white,
borderBottomRightRadius: 10,
borderBottomLeftRadius: 10,
borderTopRightRadius: 10,
borderTopLeftRadius: 10,
boxShadow: `0px 4px 6px ${colors.walletBoxShadow}`,
overflow: 'hidden',
};
export const Island: React.StatelessComponent<IslandProps> = (props: IslandProps) => (
<props.Component style={{...defaultStyle, ...props.style}} className={props.className}>
{props.children}
</props.Component>
);
Island.defaultProps = {
Component: 'div',
style: {},
};
Island.displayName = 'Island';

View File

@@ -25,6 +25,7 @@ import { Blockchain } from 'ts/blockchain';
import { AllowanceToggle } from 'ts/components/inputs/allowance_toggle';
import { IconButton } from 'ts/components/ui/icon_button';
import { Identicon } from 'ts/components/ui/identicon';
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';
@@ -84,13 +85,6 @@ interface AccessoryItemConfig {
const styles: Styles = {
root: {
width: '100%',
backgroundColor: colors.white,
borderBottomRightRadius: 10,
borderBottomLeftRadius: 10,
borderTopRightRadius: 10,
borderTopLeftRadius: 10,
boxShadow: `0px 4px 6px ${colors.walletBoxShadow}`,
overflow: 'hidden',
},
headerItemInnerDiv: {
paddingLeft: 65,
@@ -198,11 +192,11 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
const isReadyToRender = this.props.blockchainIsLoaded && this.props.blockchainErr === BlockchainErrs.NoError;
const isAddressAvailable = !_.isEmpty(this.props.userAddress);
return (
<div className="flex flex-column" style={styles.root}>
<Island className="flex flex-column" style={styles.root}>
{isReadyToRender && isAddressAvailable
? _.concat(this._renderConnectedHeaderRows(), this._renderBody(), this._renderFooterRows())
: _.concat(this._renderDisconnectedHeaderRows(), this._renderDisconnectedRows())}
</div>
</Island>
);
}
private _renderDisconnectedHeaderRows(): React.ReactElement<{}> {

View File

@@ -0,0 +1,23 @@
import * as React from 'react';
import { connect } from 'react-redux';
import { Dispatch } from 'redux';
import {
PortalOnboardingFlow as PortalOnboardingFlowComponent,
PortalOnboardingFlowProps as PortalOnboardingFlowComponentProps,
} from 'ts/components/onboarding/portal_onboarding_flow';
import { State } from 'ts/redux/reducer';
interface ConnectedState {
stepIndex: number;
isRunning: boolean;
}
const mapStateToProps = (state: State, ownProps: PortalOnboardingFlowComponentProps): ConnectedState => {
return {
stepIndex: state.portalOnboardingStep,
isRunning: state.isPortalOnboardingShowing,
};
};
export const PortalOnboardingFlow: React.ComponentClass<PortalOnboardingFlowComponentProps> = connect(mapStateToProps)(PortalOnboardingFlowComponent);

View File

@@ -174,6 +174,13 @@ export class Dispatcher {
});
}
public updatePortalOnboardingShowing(isShowing: boolean): void {
this._dispatch({
data: isShowing,
type: ActionTypes.UpdatePortalOnboardingShowing,
});
}
// Docs
public updateCurrentDocsVersion(version: string): void {
this._dispatch({

View File

@@ -40,6 +40,8 @@ export interface State {
lastForceTokenStateRefetch: number;
userAddress: string;
userEtherBalanceInWei: BigNumber;
portalOnboardingStep: number;
isPortalOnboardingShowing: boolean;
// Note: cache of supplied orderJSON in fill order step. Do not use for anything else.
userSuppliedOrderCache: Order;
@@ -80,7 +82,8 @@ const INITIAL_STATE: State = {
userAddress: '',
userEtherBalanceInWei: new BigNumber(0),
userSuppliedOrderCache: undefined,
portalOnboardingStep: 0,
isPortalOnboardingShowing: false,
// Docs
docsVersion: DEFAULT_DOCS_VERSION,
availableDocVersions: [DEFAULT_DOCS_VERSION],
@@ -293,6 +296,22 @@ export function reducer(state: State = INITIAL_STATE, action: Action): State {
};
}
case ActionTypes.UpdatePortalOnboardingStep: {
const portalOnboardingStep = action.data;
return {
...state,
portalOnboardingStep,
};
}
case ActionTypes.UpdatePortalOnboardingShowing: {
const isPortalOnboardingShowing = action.data;
return {
...state,
isPortalOnboardingShowing,
};
}
// Docs
case ActionTypes.UpdateLibraryVersion: {
return {

View File

@@ -1,6 +1,7 @@
import { ECSignature } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import * as _ from 'lodash';
import * as React from 'react';
export enum Side {
Receive = 'RECEIVE',
@@ -21,6 +22,11 @@ export interface TokenByAddress {
[address: string]: Token;
}
export interface Styleable {
style: React.CSSProperties;
children: React.ReactNode;
}
export interface AssetToken {
address?: string;
amount?: BigNumber;
@@ -125,6 +131,8 @@ export enum ActionTypes {
UpdateUserSuppliedOrderCache = 'UPDATE_USER_SUPPLIED_ORDER_CACHE',
UpdateOrderFillAmount = 'UPDATE_ORDER_FILL_AMOUNT',
UpdateShouldBlockchainErrDialogBeOpen = 'UPDATE_SHOULD_BLOCKCHAIN_ERR_DIALOG_BE_OPEN',
UpdatePortalOnboardingStep = 'UPDATE_ONBOARDING_STEP',
UpdatePortalOnboardingShowing = 'UPDATE_PORTAL_ONBOARDING_SHOWING',
// Docs
UpdateLibraryVersion = 'UPDATE_LIBRARY_VERSION',

View File

@@ -0,0 +1,4 @@
export const zIndex = {
topBar: 1100,
overlay: 1101,
};