Merge pull request #838 from 0xProject/feature/website/portal-final-touches

Some final polish touches for portal!
This commit is contained in:
Francesco Agosti 2018-07-06 15:38:51 -07:00 committed by GitHub
commit 269b56b907
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 165 additions and 107 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -1,10 +1,10 @@
import { BigNumber } from '@0xproject/utils';
import * as React from 'react';
import { Balance } from 'ts/components/ui/balance';
import { Container } from 'ts/components/ui/container';
import { Image } from 'ts/components/ui/image';
import { Text } from 'ts/components/ui/text';
import { constants } from 'ts/utils/constants';
import { utils } from 'ts/utils/utils';
export interface AddEthOnboardingStepProps {
userEthBalanceInWei: BigNumber;
@ -15,13 +15,11 @@ export const AddEthOnboardingStep: React.StatelessComponent<AddEthOnboardingStep
<div className="flex items-center flex-column">
<Text>
Great! Looks like you already have{' '}
<b>
{utils.getFormattedAmount(
props.userEthBalanceInWei,
constants.DECIMAL_PLACES_ETH,
constants.ETHER_SYMBOL,
)}{' '}
</b>
<Balance
amount={props.userEthBalanceInWei}
decimals={constants.DECIMAL_PLACES_ETH}
symbol={constants.ETHER_SYMBOL}
/>{' '}
in your wallet.
</Text>
<Container marginTop="15px" marginBottom="15px">

View File

@ -12,6 +12,7 @@ export type ContinueButtonDisplay = 'enabled' | 'disabled';
export interface OnboardingCardProps {
title?: string;
shouldCenterTitle?: boolean;
content: React.ReactNode;
isLastStep: boolean;
onClose: () => void;
@ -23,10 +24,13 @@ export interface OnboardingCardProps {
shouldHideNextButton?: boolean;
continueButtonText?: string;
borderRadius?: string;
// Used for super-custom content.
shouldRemoveExtraSpacing?: boolean;
}
export const OnboardingCard: React.StatelessComponent<OnboardingCardProps> = ({
title,
shouldCenterTitle,
content,
continueButtonDisplay,
continueButtonText,
@ -37,55 +41,75 @@ export const OnboardingCard: React.StatelessComponent<OnboardingCardProps> = ({
shouldHideBackButton,
shouldHideNextButton,
borderRadius,
}) => (
<Island borderRadius={borderRadius}>
<Container paddingRight="30px" paddingLeft="30px" paddingTop="15px" paddingBottom="15px">
<div className="flex flex-column">
<div className="flex justify-between">
<Title>{title}</Title>
<Container position="relative" bottom="20px" left="15px">
<IconButton color={colors.grey} iconName="zmdi-close" onClick={onClose}>
Close
</IconButton>
shouldRemoveExtraSpacing,
}) => {
const padding = shouldRemoveExtraSpacing
? {}
: {
paddingRight: '30px',
paddingLeft: '30px',
paddingTop: '15px',
paddingBottom: '15px',
};
const closeIconPositioning = shouldRemoveExtraSpacing
? { right: '15px', bottom: '3px' }
: { bottom: '20px', left: '15px' };
return (
<Island borderRadius={borderRadius}>
<Container {...padding}>
<div className="flex flex-column">
<Container className="flex justify-between">
<Container width="100%">
<Title center={shouldCenterTitle}>{title}</Title>
</Container>
<Container position="relative" {...closeIconPositioning}>
<IconButton color={colors.grey} iconName="zmdi-close" onClick={onClose}>
Close
</IconButton>
</Container>
</Container>
<Container marginBottom={shouldRemoveExtraSpacing ? undefined : '15px'}>
<Text>{content}</Text>
</Container>
{continueButtonDisplay && (
<Button
isDisabled={continueButtonDisplay === 'disabled'}
onClick={!_.isUndefined(onContinueButtonClick) ? onContinueButtonClick : onClickNext}
fontColor={colors.white}
fontSize="15px"
backgroundColor={colors.mediumBlue}
>
{continueButtonText}
</Button>
)}
{!(shouldHideBackButton && shouldHideNextButton) && (
<Container className="clearfix" marginTop="15px">
<div className="left">
{!shouldHideBackButton && (
<Text fontColor={colors.grey} onClick={onClickBack}>
Back
</Text>
)}
</div>
<div className="right">
{!shouldHideNextButton && (
<Text fontColor={colors.grey} onClick={onClickNext}>
Skip
</Text>
)}
</div>
</Container>
)}
</div>
<Container marginBottom="15px">
<Text>{content}</Text>
</Container>
{continueButtonDisplay && (
<Button
isDisabled={continueButtonDisplay === 'disabled'}
onClick={!_.isUndefined(onContinueButtonClick) ? onContinueButtonClick : onClickNext}
fontColor={colors.white}
fontSize="15px"
backgroundColor={colors.mediumBlue}
>
{continueButtonText}
</Button>
)}
<Container className="clearfix" marginTop="15px">
<div className="left">
{!shouldHideBackButton && (
<Text fontColor={colors.grey} onClick={onClickBack}>
Back
</Text>
)}
</div>
<div className="right">
{!shouldHideNextButton && (
<Text fontColor={colors.grey} onClick={onClickNext}>
Skip
</Text>
)}
</div>
</Container>
</div>
</Container>
</Island>
);
</Container>
</Island>
);
};
OnboardingCard.defaultProps = {
continueButtonText: 'Continue',
shouldCenterTitle: false,
shouldRemoveExtraSpacing: false,
};
OnboardingCard.displayName = 'OnboardingCard';

View File

@ -2,11 +2,14 @@ import * as React from 'react';
import { Placement, Popper, PopperChildrenProps } from 'react-popper';
import { OnboardingCard } from 'ts/components/onboarding/onboarding_card';
import { ContinueButtonDisplay, OnboardingTooltip } from 'ts/components/onboarding/onboarding_tooltip';
import {
ContinueButtonDisplay,
OnboardingTooltip,
TooltipPointerDisplay,
} from 'ts/components/onboarding/onboarding_tooltip';
import { Animation } from 'ts/components/ui/animation';
import { Container } from 'ts/components/ui/container';
import { Overlay } from 'ts/components/ui/overlay';
import { PointerDirection } from 'ts/components/ui/pointer';
import { zIndex } from 'ts/style/z_index';
export interface FixedPositionSettings {
@ -15,7 +18,7 @@ export interface FixedPositionSettings {
bottom?: string;
left?: string;
right?: string;
pointerDirection?: PointerDirection;
tooltipPointerDisplay?: TooltipPointerDisplay;
}
export interface TargetPositionSettings {
@ -28,12 +31,15 @@ export interface Step {
// Provide either a CSS selector, or fixed position settings. Only applies to desktop.
position: TargetPositionSettings | FixedPositionSettings;
title?: string;
shouldCenterTitle?: boolean;
content: React.ReactNode;
shouldHideBackButton?: boolean;
shouldHideNextButton?: boolean;
continueButtonDisplay?: ContinueButtonDisplay;
continueButtonText?: string;
onContinueButtonClick?: () => void;
// Only used for very custom steps.
shouldRemoveExtraSpacing?: boolean;
}
export interface OnboardingFlowProps {
@ -69,7 +75,7 @@ export class OnboardingFlow extends React.Component<OnboardingFlowProps> {
</Popper>
);
} else if (currentStep.position.type === 'fixed') {
const { top, right, bottom, left, pointerDirection } = currentStep.position;
const { top, right, bottom, left, tooltipPointerDisplay } = currentStep.position;
onboardingElement = (
<Container
position="fixed"
@ -79,7 +85,7 @@ export class OnboardingFlow extends React.Component<OnboardingFlowProps> {
bottom={bottom}
left={left}
>
{this._renderToolTip(pointerDirection)}
{this._renderToolTip(tooltipPointerDisplay)}
</Container>
);
}
@ -103,7 +109,7 @@ export class OnboardingFlow extends React.Component<OnboardingFlowProps> {
</div>
);
}
private _renderToolTip(pointerDirection?: PointerDirection): React.ReactNode {
private _renderToolTip(tooltipPointerDisplay?: TooltipPointerDisplay): React.ReactNode {
const { steps, stepIndex } = this.props;
const step = steps[stepIndex];
const isLastStep = steps.length - 1 === stepIndex;
@ -111,6 +117,7 @@ export class OnboardingFlow extends React.Component<OnboardingFlowProps> {
<Container marginLeft="30px" width="400px">
<OnboardingTooltip
title={step.title}
shouldCenterTitle={step.shouldCenterTitle}
content={step.content}
isLastStep={isLastStep}
shouldHideBackButton={step.shouldHideBackButton}
@ -121,7 +128,8 @@ export class OnboardingFlow extends React.Component<OnboardingFlowProps> {
continueButtonDisplay={step.continueButtonDisplay}
continueButtonText={step.continueButtonText}
onContinueButtonClick={step.onContinueButtonClick}
pointerDirection={pointerDirection}
pointerDisplay={tooltipPointerDisplay}
shouldRemoveExtraSpacing={step.shouldRemoveExtraSpacing}
/>
</Container>
);
@ -135,6 +143,7 @@ export class OnboardingFlow extends React.Component<OnboardingFlowProps> {
<Container position="relative" zIndex={1}>
<OnboardingCard
title={step.title}
shouldCenterTitle={step.shouldCenterTitle}
content={step.content}
isLastStep={isLastStep}
shouldHideBackButton={step.shouldHideBackButton}
@ -146,6 +155,7 @@ export class OnboardingFlow extends React.Component<OnboardingFlowProps> {
continueButtonText={step.continueButtonText}
onContinueButtonClick={step.onContinueButtonClick}
borderRadius="10px 10px 0px 0px"
shouldRemoveExtraSpacing={step.shouldRemoveExtraSpacing}
/>
</Container>
);

View File

@ -4,22 +4,27 @@ import { OnboardingCard, OnboardingCardProps } from 'ts/components/onboarding/on
import { Pointer, PointerDirection } from 'ts/components/ui/pointer';
export type ContinueButtonDisplay = 'enabled' | 'disabled';
export type TooltipPointerDisplay = PointerDirection | 'none';
export interface OnboardingTooltipProps extends OnboardingCardProps {
className?: string;
pointerDirection?: PointerDirection;
pointerDisplay?: TooltipPointerDisplay;
}
export const OnboardingTooltip: React.StatelessComponent<OnboardingTooltipProps> = props => {
const { pointerDirection, className, ...cardProps } = props;
const { pointerDisplay, className, ...cardProps } = props;
const card = <OnboardingCard {...cardProps} />;
if (pointerDisplay === 'none') {
return card;
}
return (
<Pointer className={className} direction={pointerDirection}>
<Pointer className={className} direction={pointerDisplay}>
<OnboardingCard {...cardProps} />
</Pointer>
);
};
OnboardingTooltip.defaultProps = {
pointerDirection: 'left',
pointerDisplay: 'left',
};
OnboardingTooltip.displayName = 'OnboardingTooltip';

View File

@ -91,9 +91,9 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp
};
const underMetamaskExtension: FixedPositionSettings = {
type: 'fixed',
top: '30px',
top: '10px',
right: '10px',
pointerDirection: 'top',
tooltipPointerDisplay: 'none',
};
const steps: Step[] = [
{
@ -105,10 +105,12 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp
},
{
position: underMetamaskExtension,
title: '0x Ecosystem Setup',
title: 'Please Unlock Metamask...',
content: <UnlockWalletOnboardingStep />,
shouldHideBackButton: true,
shouldHideNextButton: true,
shouldCenterTitle: true,
shouldRemoveExtraSpacing: true,
},
{
position: nextToWalletPosition,
@ -140,13 +142,7 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp
{
position: nextToWalletPosition,
title: 'Step 2: Wrap ETH',
content: (
<WrapEthOnboardingStep3
formattedWethBalanceIfExists={
this._userHasVisibleWeth() ? this._getFormattedWethBalance() : undefined
}
/>
),
content: <WrapEthOnboardingStep3 wethAmount={this._getWethBalance()} />,
continueButtonDisplay: this._userHasVisibleWeth() ? 'enabled' : 'disabled',
},
{
@ -187,11 +183,6 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp
const ethTokenState = this.props.trackedTokenStateByAddress[ethToken.address];
return ethTokenState.balance;
}
private _getFormattedWethBalance(): string {
const ethToken = utils.getEthToken(this.props.tokenByAddress);
const ethTokenState = this.props.trackedTokenStateByAddress[ethToken.address];
return utils.getFormattedAmountFromToken(ethToken, ethTokenState);
}
private _userHasVisibleWeth(): boolean {
return this._getWethBalance() > new BigNumber(0);
}

View File

@ -1,16 +1,8 @@
import * as React from 'react';
import { Container } from 'ts/components/ui/container';
import { Text } from 'ts/components/ui/text';
import { Image } from 'ts/components/ui/image';
export interface UnlockWalletOnboardingStepProps {}
export const UnlockWalletOnboardingStep: React.StatelessComponent<UnlockWalletOnboardingStepProps> = () => (
<div className="flex items-center flex-column">
<div className="flex items-center flex-column">
<Container marginTop="15px" marginBottom="15px">
<img src="/images/metamask_icon.png" height="50px" width="50px" />
</Container>
<Text center={true}>Unlock your MetaMask extension to get started.</Text>
</div>
</div>
<Image src="/images/unlock-mm.png" />
);

View File

@ -1,8 +1,11 @@
import { colors } from '@0xproject/react-shared';
import { BigNumber } from '@0xproject/utils';
import * as React from 'react';
import { Balance } from 'ts/components/ui/balance';
import { Container } from 'ts/components/ui/container';
import { IconButton } from 'ts/components/ui/icon_button';
import { Text } from 'ts/components/ui/text';
import { constants } from 'ts/utils/constants';
export interface WrapEthOnboardingStep1Props {}
@ -51,16 +54,20 @@ export const WrapEthOnboardingStep2: React.StatelessComponent<WrapEthOnboardingS
);
export interface WrapEthOnboardingStep3Props {
formattedWethBalanceIfExists?: string;
wethAmount: BigNumber;
}
export const WrapEthOnboardingStep3: React.StatelessComponent<WrapEthOnboardingStep3Props> = ({
formattedWethBalanceIfExists,
}) => (
export const WrapEthOnboardingStep3: React.StatelessComponent<WrapEthOnboardingStep3Props> = ({ wethAmount }) => (
<div className="flex items-center flex-column">
<Text>
You have <b>{formattedWethBalanceIfExists || '0 WETH'}</b> in your wallet.
{formattedWethBalanceIfExists && ' Great!'}
You have{' '}
<Balance
amount={wethAmount}
decimals={constants.DECIMAL_PLACES_ETH}
symbol={constants.ETHER_TOKEN_SYMBOL}
/>{' '}
in your wallet.
{wethAmount.gt(0) && ' Great!'}
</Text>
<Container width="100%" marginTop="25px" marginBottom="15px" className="flex justify-center">
<div className="flex flex-column items-center">

View File

@ -0,0 +1,27 @@
import { BigNumber } from '@0xproject/utils';
import * as React from 'react';
import { Container } from 'ts/components/ui/container';
import { Text } from 'ts/components/ui/text';
import { utils } from 'ts/utils/utils';
export interface BalanceProps {
amount: BigNumber;
decimals: number;
symbol: string;
}
export const Balance: React.StatelessComponent<BalanceProps> = ({ amount, decimals, symbol }) => {
const formattedAmout = utils.getFormattedAmount(amount, decimals);
return (
<span>
<Text Tag="span" fontSize="16px" fontWeight="700" lineHeight="1em">
{formattedAmout}
</Text>
<Container marginLeft="0.3em" Tag="span">
<Text Tag="span" fontSize="12px" fontWeight="700" lineHeight="1em">
{symbol}
</Text>
</Container>
</span>
);
};

View File

@ -2,6 +2,8 @@ import * as React from 'react';
type StringOrNum = string | number;
export type ContainerTag = 'div' | 'span';
export interface ContainerProps {
marginTop?: StringOrNum;
marginBottom?: StringOrNum;
@ -28,15 +30,21 @@ export interface ContainerProps {
right?: string;
bottom?: string;
zIndex?: number;
Tag?: ContainerTag;
}
export const Container: React.StatelessComponent<ContainerProps> = ({ children, className, isHidden, ...style }) => {
export const Container: React.StatelessComponent<ContainerProps> = props => {
const { children, className, Tag, isHidden, ...style } = props;
const visibility = isHidden ? 'hidden' : undefined;
return (
<div style={{ ...style, visibility }} className={className}>
<Tag style={{ ...style, visibility }} className={className}>
{children}
</div>
</Tag>
);
};
Container.defaultProps = {
Tag: 'div',
};
Container.displayName = 'Container';

View File

@ -8,6 +8,7 @@ import firstBy = require('thenby');
import { Blockchain } from 'ts/blockchain';
import { AccountConnection } from 'ts/components/ui/account_connection';
import { Balance } from 'ts/components/ui/balance';
import { Container } from 'ts/components/ui/container';
import { DropDown, DropdownMouseEvent } from 'ts/components/ui/drop_down';
import { IconButton } from 'ts/components/ui/icon_button';
@ -269,7 +270,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
position: 'relative',
overflowY: this.state.isHoveringSidebar ? 'scroll' : 'hidden',
marginRight: this.state.isHoveringSidebar ? 0 : 4,
// TODO: make this completely responsive
minHeight: '250px',
maxHeight: !utils.isMobileWidth(this.props.screenWidth) ? 'calc(90vh - 300px)' : undefined,
};
}
@ -436,12 +437,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
</PlaceHolder>
);
} else {
const result = utils.getFormattedAmount(amount, decimals, symbol);
return (
<Text fontSize="16px" fontWeight="bold" lineHeight="1em">
{result}
</Text>
);
return <Balance amount={amount} decimals={decimals} symbol={symbol} />;
}
}
private _renderValue(

View File

@ -381,9 +381,9 @@ export const utils = {
return trackedTokens;
},
getFormattedAmountFromToken(token: Token, tokenState: TokenState): string {
return utils.getFormattedAmount(tokenState.balance, token.decimals, token.symbol);
return utils.getFormattedAmount(tokenState.balance, token.decimals);
},
getFormattedAmount(amount: BigNumber, decimals: number, symbol: string): string {
getFormattedAmount(amount: BigNumber, decimals: number): string {
const unitAmount = Web3Wrapper.toUnitAmount(amount, decimals);
// if the unit amount is less than 1, show the natural number of decimal places with a max of 4
// if the unit amount is greater than or equal to 1, show only 2 decimal places
@ -392,7 +392,7 @@ export const utils = {
: 2;
const format = `0,0.${_.repeat('0', precision)}`;
const formattedAmount = numeral(unitAmount).format(format);
return `${formattedAmount} ${symbol}`;
return formattedAmount;
},
getUsdValueFormattedAmount(amount: BigNumber, decimals: number, price: BigNumber): string {
const unitAmount = Web3Wrapper.toUnitAmount(amount, decimals);