diff options
Diffstat (limited to 'packages/website/ts')
28 files changed, 441 insertions, 309 deletions
diff --git a/packages/website/ts/components/generate_order/asset_picker.tsx b/packages/website/ts/components/generate_order/asset_picker.tsx index d7cc554c4..b0dcf5678 100644 --- a/packages/website/ts/components/generate_order/asset_picker.tsx +++ b/packages/website/ts/components/generate_order/asset_picker.tsx @@ -9,6 +9,7 @@ import { TokenIcon } from 'ts/components/ui/token_icon'; import { trackedTokenStorage } from 'ts/local_storage/tracked_token_storage'; import { Dispatcher } from 'ts/redux/dispatcher'; import { DialogConfigs, Token, TokenByAddress, TokenVisibility } from 'ts/types'; +import { constants } from 'ts/utils/constants'; const TOKEN_ICON_DIMENSION = 100; const TILE_DIMENSION = 146; @@ -134,7 +135,9 @@ export class AssetPicker extends React.Component<AssetPickerProps, AssetPickerSt const gridTiles = _.map(this.props.tokenByAddress, (token: Token, address: string) => { if ( (this.props.tokenVisibility === TokenVisibility.TRACKED && !token.isTracked) || - (this.props.tokenVisibility === TokenVisibility.UNTRACKED && token.isTracked) + (this.props.tokenVisibility === TokenVisibility.UNTRACKED && token.isTracked) || + token.symbol === constants.ZRX_TOKEN_SYMBOL || + token.symbol === constants.ETHER_TOKEN_SYMBOL ) { return null; // Skip } @@ -151,12 +154,12 @@ export class AssetPicker extends React.Component<AssetPickerProps, AssetPickerSt height: TILE_DIMENSION, ...tileStyles, }} - className="p2 mx-auto" + className="p2 flex flex-column items-center" onClick={this._onChooseToken.bind(this, address)} onMouseEnter={this._onToggleHover.bind(this, address, true)} onMouseLeave={this._onToggleHover.bind(this, address, false)} > - <div className="p1 center"> + <div className="p1"> <TokenIcon token={token} diameter={TOKEN_ICON_DIMENSION} /> </div> <div className="center">{token.name}</div> @@ -232,12 +235,14 @@ export class AssetPicker extends React.Component<AssetPickerProps, AssetPickerSt this.props.onTokenChosen(newToken.address); } private async _onTrackConfirmationRespondedAsync(didUserAcceptTracking: boolean): Promise<void> { + const resetState: AssetPickerState = { + ...this.state, + isAddingTokenToTracked: false, + assetView: AssetViews.ASSET_PICKER, + chosenTrackTokenAddress: undefined, + }; if (!didUserAcceptTracking) { - this.setState({ - isAddingTokenToTracked: false, - assetView: AssetViews.ASSET_PICKER, - chosenTrackTokenAddress: undefined, - }); + this.setState(resetState); this._onCloseDialog(); return; } @@ -246,6 +251,10 @@ export class AssetPicker extends React.Component<AssetPickerProps, AssetPickerSt }); const tokenAddress = this.state.chosenTrackTokenAddress; const token = this.props.tokenByAddress[tokenAddress]; + if (_.isUndefined(tokenAddress)) { + this.setState(resetState); + return; + } const newTokenEntry = { ...token, }; @@ -254,11 +263,7 @@ export class AssetPicker extends React.Component<AssetPickerProps, AssetPickerSt trackedTokenStorage.addTrackedTokenToUser(this.props.userAddress, this.props.networkId, newTokenEntry); this.props.dispatcher.updateTokenByAddress([newTokenEntry]); - this.setState({ - isAddingTokenToTracked: false, - assetView: AssetViews.ASSET_PICKER, - chosenTrackTokenAddress: undefined, - }); + this.setState(resetState); this.props.onTokenChosen(tokenAddress); } } diff --git a/packages/website/ts/components/onboarding/onboarding_card.tsx b/packages/website/ts/components/onboarding/onboarding_card.tsx new file mode 100644 index 000000000..bc83b8034 --- /dev/null +++ b/packages/website/ts/components/onboarding/onboarding_card.tsx @@ -0,0 +1,84 @@ +import { colors } from '@0xproject/react-shared'; +import * as React from 'react'; + +import { Button } from 'ts/components/ui/button'; +import { Container } from 'ts/components/ui/container'; +import { IconButton } from 'ts/components/ui/icon_button'; +import { Island } from 'ts/components/ui/island'; +import { Text, Title } from 'ts/components/ui/text'; + +export type ContinueButtonDisplay = 'enabled' | 'disabled'; + +export interface OnboardingCardProps { + title?: string; + content: React.ReactNode; + isLastStep: boolean; + onClose: () => void; + onClickNext: () => void; + onClickBack: () => void; + continueButtonDisplay?: ContinueButtonDisplay; + shouldHideBackButton?: boolean; + shouldHideNextButton?: boolean; + continueButtonText?: string; + borderRadius?: string; +} + +export const OnboardingCard: React.StatelessComponent<OnboardingCardProps> = ({ + title, + content, + continueButtonDisplay, + continueButtonText, + onClickNext, + onClickBack, + onClose, + shouldHideBackButton, + shouldHideNextButton, + borderRadius, +}) => ( + <Island borderRadius={borderRadius}> + <Container paddingRight="30px" paddingLeft="30px" maxWidth={350} 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> + </Container> + </div> + <Container marginBottom="15px"> + <Text>{content}</Text> + </Container> + {continueButtonDisplay && ( + <Button + isDisabled={continueButtonDisplay === 'disabled'} + onClick={onClickNext} + fontColor={colors.white} + fontSize="15px" + backgroundColor={colors.mediumBlue} + > + {continueButtonText} + </Button> + )} + <Container className="flex justify-between" marginTop="15px"> + {!shouldHideBackButton && ( + <Text fontColor={colors.grey} onClick={onClickBack}> + Back + </Text> + )} + {!shouldHideNextButton && ( + <Text fontColor={colors.grey} onClick={onClickNext}> + Skip + </Text> + )} + </Container> + </div> + </Container> + </Island> +); + +OnboardingCard.defaultProps = { + continueButtonText: 'Continue', +}; + +OnboardingCard.displayName = 'OnboardingCard'; diff --git a/packages/website/ts/components/onboarding/onboarding_flow.tsx b/packages/website/ts/components/onboarding/onboarding_flow.tsx index 34aeace82..ec8d96191 100644 --- a/packages/website/ts/components/onboarding/onboarding_flow.tsx +++ b/packages/website/ts/components/onboarding/onboarding_flow.tsx @@ -1,7 +1,9 @@ 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 { Animation } from 'ts/components/ui/animation'; import { Container } from 'ts/components/ui/container'; import { Overlay } from 'ts/components/ui/overlay'; @@ -22,26 +24,41 @@ export interface OnboardingFlowProps { isRunning: boolean; onClose: () => void; updateOnboardingStep: (stepIndex: number) => void; + disableOverlay?: boolean; + isMobile: boolean; } export class OnboardingFlow extends React.Component<OnboardingFlowProps> { + public static defaultProps = { + disableOverlay: false, + isMobile: false, + }; public render(): React.ReactNode { if (!this.props.isRunning) { return null; } - return ( - <Overlay> - <Popper referenceElement={this._getElementForStep()} placement={this._getCurrentStep().placement}> + let onboardingElement = null; + if (this.props.isMobile) { + onboardingElement = <Animation type="easeUpFromBottom">{this._renderOnboardignCard()}</Animation>; + } else { + onboardingElement = ( + <Popper + referenceElement={this._getElementForStep()} + placement={this._getCurrentStep().placement} + positionFixed={true} + > {this._renderPopperChildren.bind(this)} </Popper> - </Overlay> - ); + ); + } + if (this.props.disableOverlay) { + return onboardingElement; + } + return <Overlay>{onboardingElement}</Overlay>; } - private _getElementForStep(): Element { return document.querySelector(this._getCurrentStep().target); } - private _renderPopperChildren(props: PopperChildrenProps): React.ReactNode { return ( <div ref={props.ref} style={props.style} data-placement={props.placement}> @@ -49,13 +66,12 @@ export class OnboardingFlow extends React.Component<OnboardingFlowProps> { </div> ); } - private _renderToolTip(): React.ReactNode { const { steps, stepIndex } = this.props; const step = steps[stepIndex]; const isLastStep = steps.length - 1 === stepIndex; return ( - <Container marginLeft="30px"> + <Container marginLeft="30px" maxWidth={350}> <OnboardingTooltip title={step.title} content={step.content} @@ -72,10 +88,31 @@ export class OnboardingFlow extends React.Component<OnboardingFlowProps> { ); } + private _renderOnboardignCard(): React.ReactNode { + const { steps, stepIndex } = this.props; + const step = steps[stepIndex]; + const isLastStep = steps.length - 1 === stepIndex; + return ( + <Container position="relative" zIndex={1} maxWidth="100vw"> + <OnboardingCard + title={step.title} + content={step.content} + isLastStep={isLastStep} + shouldHideBackButton={step.shouldHideBackButton} + shouldHideNextButton={step.shouldHideNextButton} + onClose={this.props.onClose} + onClickNext={this._goToNextStep.bind(this)} + onClickBack={this._goToPrevStep.bind(this)} + continueButtonDisplay={step.continueButtonDisplay} + continueButtonText={step.continueButtonText} + borderRadius="10px 10px 0px 0px" + /> + </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) { @@ -84,7 +121,6 @@ export class OnboardingFlow extends React.Component<OnboardingFlowProps> { this.props.onClose(); } } - private _goToPrevStep(): void { const nextStep = this.props.stepIndex - 1; if (nextStep >= 0) { diff --git a/packages/website/ts/components/onboarding/onboarding_tooltip.tsx b/packages/website/ts/components/onboarding/onboarding_tooltip.tsx index 45851b4de..d8065625d 100644 --- a/packages/website/ts/components/onboarding/onboarding_tooltip.tsx +++ b/packages/website/ts/components/onboarding/onboarding_tooltip.tsx @@ -1,90 +1,25 @@ -import { colors } from '@0xproject/react-shared'; import * as React from 'react'; -import { Button } from 'ts/components/ui/button'; -import { Container } from 'ts/components/ui/container'; -import { IconButton } from 'ts/components/ui/icon_button'; -import { Island } from 'ts/components/ui/island'; +import { OnboardingCard, OnboardingCardProps } from 'ts/components/onboarding/onboarding_card'; import { Pointer, PointerDirection } from 'ts/components/ui/pointer'; -import { Text, Title } from 'ts/components/ui/text'; export type ContinueButtonDisplay = 'enabled' | 'disabled'; -export interface OnboardingTooltipProps { - title?: string; - content: React.ReactNode; - isLastStep: boolean; - onClose: () => void; - onClickNext: () => void; - onClickBack: () => void; - continueButtonDisplay?: ContinueButtonDisplay; - shouldHideBackButton?: boolean; - shouldHideNextButton?: boolean; - pointerDirection?: PointerDirection; - continueButtonText?: string; +export interface OnboardingTooltipProps extends OnboardingCardProps { className?: string; + pointerDirection?: PointerDirection; } -export const OnboardingTooltip: React.StatelessComponent<OnboardingTooltipProps> = ({ - title, - content, - continueButtonDisplay, - continueButtonText, - onClickNext, - onClickBack, - onClose, - shouldHideBackButton, - shouldHideNextButton, - pointerDirection, - className, -}) => ( - <Pointer className={className} direction={pointerDirection}> - <Island> - <Container paddingRight="30px" paddingLeft="30px" maxWidth={350} 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> - </Container> - </div> - <Container marginBottom="15px"> - <Text>{content}</Text> - </Container> - {continueButtonDisplay && ( - <Button - isDisabled={continueButtonDisplay === 'disabled'} - onClick={onClickNext} - fontColor={colors.white} - fontSize="15px" - backgroundColor={colors.mediumBlue} - > - {continueButtonText} - </Button> - )} - <Container className="flex justify-between" marginTop="15px"> - {!shouldHideBackButton && ( - <Text fontColor={colors.grey} onClick={onClickBack}> - Back - </Text> - )} - {!shouldHideNextButton && ( - <Text fontColor={colors.grey} onClick={onClickNext}> - Skip - </Text> - )} - </Container> - </div> - </Container> - </Island> - </Pointer> -); - +export const OnboardingTooltip: React.StatelessComponent<OnboardingTooltipProps> = props => { + const { pointerDirection, className, ...cardProps } = props; + return ( + <Pointer className={className} direction={pointerDirection}> + <OnboardingCard {...cardProps} /> + </Pointer> + ); +}; OnboardingTooltip.defaultProps = { pointerDirection: 'left', - continueButtonText: 'Continue', }; 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 7e40192f6..10d4af30e 100644 --- a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx +++ b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx @@ -14,7 +14,7 @@ import { SetAllowancesOnboardingStep } from 'ts/components/onboarding/set_allowa import { UnlockWalletOnboardingStep } from 'ts/components/onboarding/unlock_wallet_onboarding_step'; import { WrapEthOnboardingStep } from 'ts/components/onboarding/wrap_eth_onboarding_step'; import { AllowanceToggle } from 'ts/containers/inputs/allowance_toggle'; -import { ProviderType, Token, TokenByAddress, TokenStateByAddress } from 'ts/types'; +import { ProviderType, ScreenWidths, Token, TokenByAddress, TokenStateByAddress } from 'ts/types'; import { analytics } from 'ts/utils/analytics'; import { utils } from 'ts/utils/utils'; @@ -34,6 +34,7 @@ export interface PortalOnboardingFlowProps extends RouteComponentProps<any> { updateIsRunning: (isRunning: boolean) => void; updateOnboardingStep: (stepIndex: number) => void; refetchTokenStateAsync: (tokenAddress: string) => Promise<void>; + screenWidth: ScreenWidths; } class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProps> { @@ -57,6 +58,8 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp isRunning={this.props.isRunning} onClose={this._closeOnboarding.bind(this)} updateOnboardingStep={this._updateOnboardingStep.bind(this)} + disableOverlay={this.props.screenWidth === ScreenWidths.Sm} + isMobile={this.props.screenWidth === ScreenWidths.Sm} /> ); } diff --git a/packages/website/ts/components/onboarding/unlock_wallet_onboarding_step.tsx b/packages/website/ts/components/onboarding/unlock_wallet_onboarding_step.tsx index 6e6a74a06..0039aa545 100644 --- a/packages/website/ts/components/onboarding/unlock_wallet_onboarding_step.tsx +++ b/packages/website/ts/components/onboarding/unlock_wallet_onboarding_step.tsx @@ -10,7 +10,7 @@ export const UnlockWalletOnboardingStep: React.StatelessComponent<UnlockWalletOn <Container marginTop="15px" marginBottom="15px"> <img src="/images/metamask_icon.png" height="50px" width="50px" /> </Container> - <Text>Unlock your metamask extension to begin.</Text> + <Text center={true}>Unlock your metamask extension to get started.</Text> </div> </div> ); diff --git a/packages/website/ts/components/portal/back_button.tsx b/packages/website/ts/components/portal/back_button.tsx index 2d0bbefc3..ca35abc2b 100644 --- a/packages/website/ts/components/portal/back_button.tsx +++ b/packages/website/ts/components/portal/back_button.tsx @@ -2,6 +2,7 @@ import { Styles } from '@0xproject/react-shared'; import * as React from 'react'; import { Link } from 'react-router-dom'; +import { Island } from 'ts/components/ui/island'; import { colors } from 'ts/style/colors'; export interface BackButtonProps { @@ -15,9 +16,7 @@ const styles: Styles = { backButton: { height: BACK_BUTTON_HEIGHT, paddingTop: 10, - backgroundColor: colors.white, borderRadius: BACK_BUTTON_HEIGHT, - boxShadow: `0px 4px 6px ${colors.walletBoxShadow}`, }, backButtonIcon: { color: colors.mediumBlue, @@ -29,14 +28,14 @@ export const BackButton = (props: BackButtonProps) => { return ( <div style={{ height: 65, paddingTop: 25 }}> <Link to={props.to} style={{ textDecoration: 'none' }}> - <div className="flex right" style={styles.backButton}> + <Island className="flex right" style={styles.backButton}> <div style={{ marginLeft: 12 }}> <i style={styles.backButtonIcon} className={`zmdi zmdi-arrow-left`} /> </div> <div style={{ marginLeft: 12, marginRight: 12 }}> <div style={{ fontSize: 16, color: colors.lightGrey }}>{props.labelText}</div> </div> - </div> + </Island> </Link> </div> ); diff --git a/packages/website/ts/components/portal/drawer_menu.tsx b/packages/website/ts/components/portal/drawer_menu.tsx index 4bd07769f..205a60afc 100644 --- a/packages/website/ts/components/portal/drawer_menu.tsx +++ b/packages/website/ts/components/portal/drawer_menu.tsx @@ -65,7 +65,7 @@ interface HeaderProps { const Header = (props: HeaderProps) => { return ( <div className="flex flex-center py4"> - <div className="flex flex-column mx-auto"> + <div className="flex flex-column mx-auto items-center"> <Identicon address={props.userAddress} diameter={IDENTICON_DIAMETER} style={styles.identicon} /> <Text className="pt2" fontColor={colors.white}> {props.displayMessage} diff --git a/packages/website/ts/components/portal/portal.tsx b/packages/website/ts/components/portal/portal.tsx index 67314678b..4166fde53 100644 --- a/packages/website/ts/components/portal/portal.tsx +++ b/packages/website/ts/components/portal/portal.tsx @@ -1,4 +1,4 @@ -import { colors, constants as sharedConstants, Styles } from '@0xproject/react-shared'; +import { colors, constants as sharedConstants } from '@0xproject/react-shared'; import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import ActionAccountBalanceWallet from 'material-ui/svg-icons/action/account-balance-wallet'; @@ -107,26 +107,7 @@ const TOP_BAR_HEIGHT = TopBar.heightForDisplayType(TopBarDisplayType.Expanded); const LEFT_COLUMN_WIDTH = 346; const MENU_PADDING_LEFT = 185; const LARGE_LAYOUT_MAX_WIDTH = 1200; - -const styles: Styles = { - root: { - width: '100%', - height: '100%', - backgroundColor: colors.lightestGrey, - }, - body: { - height: `calc(100vh - ${TOP_BAR_HEIGHT}px)`, - }, - leftColumn: { - width: LEFT_COLUMN_WIDTH, - height: '100%', - }, - scrollContainer: { - height: `calc(100vh - ${TOP_BAR_HEIGHT}px)`, - WebkitOverflowScrolling: 'touch', - overflow: 'auto', - }, -}; +const LARGE_LAYOUT_MARGIN = 30; export class Portal extends React.Component<PortalProps, PortalState> { private _blockchain: Blockchain; @@ -245,12 +226,7 @@ export class Portal extends React.Component<PortalProps, PortalState> { ? TokenVisibility.UNTRACKED : TokenVisibility.TRACKED; return ( - <div style={styles.root}> - <PortalOnboardingFlow - blockchain={this._blockchain} - trackedTokenStateByAddress={this.state.trackedTokenStateByAddress} - refetchTokenStateAsync={this._refetchTokenStateAsync.bind(this)} - /> + <Container> <DocumentTitle title="0x Portal DApp" /> <TopBar userAddress={this.props.userAddress} @@ -264,10 +240,14 @@ export class Portal extends React.Component<PortalProps, PortalState> { blockchain={this._blockchain} translate={this.props.translate} displayType={TopBarDisplayType.Expanded} - style={{ backgroundColor: colors.lightestGrey }} + style={{ + backgroundColor: colors.lightestGrey, + position: 'fixed', + zIndex: zIndex.topBar, + }} maxWidth={LARGE_LAYOUT_MAX_WIDTH} /> - <div id="portal" style={styles.body}> + <Container marginTop={TOP_BAR_HEIGHT} minHeight="100vh" backgroundColor={colors.lightestGrey}> <Switch> <Route path={`${WebsitePaths.Portal}/:route`} render={this._renderOtherRoutes.bind(this)} /> <Route @@ -306,8 +286,8 @@ export class Portal extends React.Component<PortalProps, PortalState> { tokenByAddress={this.props.tokenByAddress} tokenVisibility={tokenVisibility} /> - </div> - </div> + </Container> + </Container> ); } private _renderMainRoute(): React.ReactNode { @@ -340,62 +320,84 @@ export class Portal extends React.Component<PortalProps, PortalState> { ); } private _renderWallet(): React.ReactNode { + const startOnboarding = this._renderStartOnboarding(); + const isMobile = utils.isMobile(this.props.screenWidth); + // We need room to scroll down for mobile onboarding + const marginBottom = isMobile ? '200px' : '15px'; return ( <div> - <Wallet - style={this.props.isPortalOnboardingShowing ? { zIndex: zIndex.aboveOverlay } : undefined} - userAddress={this.props.userAddress} - networkId={this.props.networkId} + <Container> + {isMobile && <Container marginBottom="15px">{startOnboarding}</Container>} + <Container marginBottom={marginBottom}> + <Wallet + style={ + !isMobile && this.props.isPortalOnboardingShowing + ? { zIndex: zIndex.aboveOverlay, position: 'relative' } + : undefined + } + 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={this._getCurrentTrackedTokens()} + userEtherBalanceInWei={this.props.userEtherBalanceInWei} + lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch} + injectedProviderName={this.props.injectedProviderName} + providerType={this.props.providerType} + screenWidth={this.props.screenWidth} + location={this.props.location} + trackedTokenStateByAddress={this.state.trackedTokenStateByAddress} + onToggleLedgerDialog={this._onToggleLedgerDialog.bind(this)} + onAddToken={this._onAddToken.bind(this)} + onRemoveToken={this._onRemoveToken.bind(this)} + refetchTokenStateAsync={this._refetchTokenStateAsync.bind(this)} + /> + </Container> + {!isMobile && <Container marginTop="15px">{startOnboarding}</Container>} + </Container> + <PortalOnboardingFlow blockchain={this._blockchain} - blockchainIsLoaded={this.props.blockchainIsLoaded} - blockchainErr={this.props.blockchainErr} - dispatcher={this.props.dispatcher} - tokenByAddress={this.props.tokenByAddress} - trackedTokens={this._getCurrentTrackedTokens()} - userEtherBalanceInWei={this.props.userEtherBalanceInWei} - lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch} - injectedProviderName={this.props.injectedProviderName} - providerType={this.props.providerType} - screenWidth={this.props.screenWidth} - location={this.props.location} trackedTokenStateByAddress={this.state.trackedTokenStateByAddress} - onToggleLedgerDialog={this._onToggleLedgerDialog.bind(this)} - onAddToken={this._onAddToken.bind(this)} - onRemoveToken={this._onRemoveToken.bind(this)} refetchTokenStateAsync={this._refetchTokenStateAsync.bind(this)} /> - <Container marginTop="15px"> - <Island> - <Container - marginTop="30px" - marginBottom="30px" - marginLeft="30px" - marginRight="30px" - className="flex justify-around items-center" - > - <ActionAccountBalanceWallet - style={{ width: '30px', height: '30px' }} - color={colors.orange} - /> - <Text - fontColor={colors.grey} - fontSize="16px" - center={true} - onClick={this._startOnboarding.bind(this)} - > - Learn how to set up your account - </Text> - </Container> - </Island> - </Container> </div> ); } + private _renderStartOnboarding(): React.ReactNode { + return ( + <Island> + <Container + marginTop="30px" + marginBottom="30px" + marginLeft="30px" + marginRight="30px" + className="flex justify-around items-center" + > + <ActionAccountBalanceWallet style={{ width: '30px', height: '30px' }} color={colors.orange} /> + <Text + fontColor={colors.grey} + fontSize="16px" + center={true} + onClick={this._startOnboarding.bind(this)} + > + Learn how to set up your account + </Text> + </Container> + </Island> + ); + } private _startOnboarding(): void { const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId]; analytics.logEvent('Portal', 'Onboarding Started - Manual', networkName, this.props.portalOnboardingStep); this.props.dispatcher.updatePortalOnboardingShowing(true); + // On mobile, make sure the wallet is completely visible. + if (this.props.screenWidth === ScreenWidths.Sm) { + document.querySelector('.wallet').scrollIntoView(); + } } private _renderWalletSection(): React.ReactNode { return <Section header={<TextHeader labelText="Your Account" />} body={this._renderWallet()} />; @@ -536,7 +538,7 @@ export class Portal extends React.Component<PortalProps, PortalState> { private _renderRelayerIndexSection(): React.ReactNode { return ( <Section - header={<TextHeader labelText="Explore 0x Relayers" />} + header={<TextHeader labelText="0x Relayers" />} body={<RelayerIndex networkId={this.props.networkId} screenWidth={this.props.screenWidth} />} /> ); @@ -693,14 +695,23 @@ interface LargeLayoutProps { } const LargeLayout = (props: LargeLayoutProps) => { return ( - <div className="mx-auto flex flex-center" style={{ maxWidth: LARGE_LAYOUT_MAX_WIDTH }}> - <div className="flex-last px2"> - <div style={styles.leftColumn}>{props.left}</div> - </div> - <div className="flex-auto px2" style={styles.scrollContainer}> - {props.right} + <Container className="mx-auto flex flex-center" maxWidth={LARGE_LAYOUT_MAX_WIDTH}> + <div className="flex-last"> + <Container + width={LEFT_COLUMN_WIDTH} + position="fixed" + zIndex={zIndex.aboveTopBar} + marginLeft={LARGE_LAYOUT_MARGIN} + > + {props.left} + </Container> </div> - </div> + <Container className="flex-auto" marginLeft={LEFT_COLUMN_WIDTH + LARGE_LAYOUT_MARGIN}> + <Container className="flex-auto" marginLeft={LARGE_LAYOUT_MARGIN} marginRight={LARGE_LAYOUT_MARGIN}> + {props.right} + </Container> + </Container> + </Container> ); }; @@ -710,9 +721,7 @@ interface SmallLayoutProps { const SmallLayout = (props: SmallLayoutProps) => { return ( <div className="flex flex-center"> - <div className="flex-auto px3" style={styles.scrollContainer}> - {props.content} - </div> + <div className="flex-auto px3">{props.content}</div> </div> ); }; // tslint:disable:max-file-line-count diff --git a/packages/website/ts/components/portal/text_header.tsx b/packages/website/ts/components/portal/text_header.tsx index 4aabd47d0..853da3a29 100644 --- a/packages/website/ts/components/portal/text_header.tsx +++ b/packages/website/ts/components/portal/text_header.tsx @@ -1,21 +1,16 @@ -import { Styles } from '@0xproject/react-shared'; +import { colors } from '@0xproject/react-shared'; import * as React from 'react'; +import { Text } from 'ts/components/ui/text'; + export interface TextHeaderProps { labelText: string; } -const styles: Styles = { - title: { - fontWeight: 'bold', - fontSize: 20, - }, -}; - export const TextHeader = (props: TextHeaderProps) => { return ( - <div className="py3" style={styles.title}> + <Text className="pt3 pb2" fontWeight="bold" fontSize="16px" fontColor={colors.darkestGrey}> {props.labelText} - </div> + </Text> ); }; diff --git a/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx b/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx index 23860856b..18b069ae2 100644 --- a/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx +++ b/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx @@ -1,6 +1,6 @@ import { constants as sharedConstants, Styles } from '@0xproject/react-shared'; import * as _ from 'lodash'; -import { GridTile } from 'material-ui/GridList'; +import { GridTile as PlainGridTile } from 'material-ui/GridList'; import * as React from 'react'; import { analytics } from 'ts/utils/analytics'; @@ -9,7 +9,9 @@ import { Container } from 'ts/components/ui/container'; import { Image } from 'ts/components/ui/image'; import { Island } from 'ts/components/ui/island'; import { colors } from 'ts/style/colors'; +import { styled } from 'ts/style/theme'; import { WebsiteBackendRelayerInfo } from 'ts/types'; +import { utils } from 'ts/utils/utils'; export interface RelayerGridTileProps { relayerInfo: WebsiteBackendRelayerInfo; @@ -19,29 +21,23 @@ export interface RelayerGridTileProps { const styles: Styles = { root: { boxSizing: 'border-box', + // All material UI components have position: relative + // which creates a new stacking context and makes z-index stuff impossible. So reset. + position: 'static', }, innerDiv: { - padding: 6, height: '100%', boxSizing: 'border-box', }, header: { height: '50%', width: '100%', - borderBottomRightRadius: 4, - borderBottomLeftRadius: 4, - borderTopRightRadius: 4, - borderTopLeftRadius: 4, - borderWidth: 1, - borderStyle: 'solid', - borderColor: colors.walletBorder, }, body: { - paddingLeft: 6, - paddingRight: 6, height: '50%', width: '100%', boxSizing: 'border-box', + padding: 12, }, weeklyTradeVolumeLabel: { fontSize: 14, @@ -69,7 +65,10 @@ export const RelayerGridTile: React.StatelessComponent<RelayerGridTileProps> = ( const weeklyTxnVolume = props.relayerInfo.weeklyTxnVolume; const networkName = sharedConstants.NETWORK_NAME_BY_ID[props.networkId]; const eventLabel = `${props.relayerInfo.name}-${networkName}`; - const trackRelayerClick = () => analytics.logEvent('Portal', 'Relayer Click', eventLabel); + const onClick = () => { + analytics.logEvent('Portal', 'Relayer Click', eventLabel); + utils.openUrl(link); + }; const headerImageUrl = props.relayerInfo.logoImgUrl; const headerBackgroundColor = !_.isUndefined(headerImageUrl) && !_.isUndefined(props.relayerInfo.primaryColor) @@ -77,22 +76,17 @@ export const RelayerGridTile: React.StatelessComponent<RelayerGridTileProps> = ( : FALLBACK_PRIMARY_COLOR; return ( <Island style={styles.root} Component={GridTile}> - <div style={styles.innerDiv}> - <a href={link} target="_blank" style={{ textDecoration: 'none' }} onClick={trackRelayerClick}> - <div - className="flex items-center" - style={{ ...styles.header, backgroundColor: headerBackgroundColor }} - > - <Image - className="mx-auto" - src={props.relayerInfo.logoImgUrl} - fallbackSrc={FALLBACK_IMG_SRC} - height={RELAYER_ICON_HEIGHT} - /> - </div> - </a> + <div style={styles.innerDiv} onClick={onClick}> + <div className="flex items-center" style={{ ...styles.header, backgroundColor: headerBackgroundColor }}> + <Image + className="mx-auto" + src={props.relayerInfo.logoImgUrl} + fallbackSrc={FALLBACK_IMG_SRC} + height={RELAYER_ICON_HEIGHT} + /> + </div> <div style={styles.body}> - <div className="py1" style={styles.relayerNameLabel}> + <div className="pb1" style={styles.relayerNameLabel}> {props.relayerInfo.name} </div> <Section titleText="Weekly Trade Volume"> @@ -111,6 +105,14 @@ export const RelayerGridTile: React.StatelessComponent<RelayerGridTileProps> = ( ); }; +const GridTile = styled(PlainGridTile)` + cursor: pointer; + transition: transform 0.2s ease; + &:hover { + transform: translate(0px, -3px); + } +`; + interface SectionProps { titleText: string; children?: React.ReactNode; diff --git a/packages/website/ts/components/relayer_index/relayer_index.tsx b/packages/website/ts/components/relayer_index/relayer_index.tsx index d565eb608..4aea1bbbb 100644 --- a/packages/website/ts/components/relayer_index/relayer_index.tsx +++ b/packages/website/ts/components/relayer_index/relayer_index.tsx @@ -1,4 +1,3 @@ -import { Styles } from '@0xproject/react-shared'; import * as _ from 'lodash'; import CircularProgress from 'material-ui/CircularProgress'; import { GridList } from 'material-ui/GridList'; @@ -6,7 +5,6 @@ import * as React from 'react'; import { RelayerGridTile } from 'ts/components/relayer_index/relayer_grid_tile'; import { Retry } from 'ts/components/ui/retry'; -import { colors } from 'ts/style/colors'; import { ScreenWidths, WebsiteBackendRelayerInfo } from 'ts/types'; import { backendClient } from 'ts/utils/backend_client'; @@ -20,22 +18,6 @@ interface RelayerIndexState { error?: Error; } -const styles: Styles = { - root: { - width: '100%', - }, - item: { - backgroundColor: colors.white, - borderBottomRightRadius: 10, - borderBottomLeftRadius: 10, - borderTopRightRadius: 10, - borderTopLeftRadius: 10, - boxShadow: `0px 4px 6px ${colors.walletBoxShadow}`, - overflow: 'hidden', - padding: 4, - }, -}; - const CELL_HEIGHT = 290; const NUMBER_OF_COLUMNS_LARGE = 3; const NUMBER_OF_COLUMNS_MEDIUM = 2; @@ -76,18 +58,16 @@ export class RelayerIndex extends React.Component<RelayerIndexProps, RelayerInde } else { const numberOfColumns = this._numberOfColumnsForScreenWidth(this.props.screenWidth); return ( - <div style={styles.root}> - <GridList - cellHeight={CELL_HEIGHT} - cols={numberOfColumns} - padding={GRID_PADDING} - style={styles.gridList} - > - {this.state.relayerInfos.map((relayerInfo: WebsiteBackendRelayerInfo, index) => ( - <RelayerGridTile key={index} relayerInfo={relayerInfo} networkId={this.props.networkId} /> - ))} - </GridList> - </div> + <GridList + cellHeight={CELL_HEIGHT} + cols={numberOfColumns} + padding={GRID_PADDING} + style={{ marginTop: -10, marginBottom: 0 }} + > + {this.state.relayerInfos.map((relayerInfo: WebsiteBackendRelayerInfo, index) => ( + <RelayerGridTile key={index} relayerInfo={relayerInfo} networkId={this.props.networkId} /> + ))} + </GridList> ); } } diff --git a/packages/website/ts/components/relayer_index/relayer_top_tokens.tsx b/packages/website/ts/components/relayer_index/relayer_top_tokens.tsx index b599e7123..f544fc924 100644 --- a/packages/website/ts/components/relayer_index/relayer_top_tokens.tsx +++ b/packages/website/ts/components/relayer_index/relayer_top_tokens.tsx @@ -70,7 +70,10 @@ class TokenLink extends React.Component<TokenLinkProps, TokenLinkState> { }; const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId]; const eventLabel = `${this.props.tokenInfo.symbol}-${networkName}`; - const trackTokenClick = () => analytics.logEvent('Portal', 'Token Click', eventLabel); + const onClick = (event: React.MouseEvent<HTMLElement>) => { + event.stopPropagation(); + analytics.logEvent('Portal', 'Token Click', eventLabel); + }; return ( <a href={tokenLinkFromToken(this.props.tokenInfo, this.props.networkId)} @@ -78,7 +81,7 @@ class TokenLink extends React.Component<TokenLinkProps, TokenLinkState> { style={style} onMouseEnter={this._onToggleHover.bind(this, true)} onMouseLeave={this._onToggleHover.bind(this, false)} - onClick={trackTokenClick} + onClick={onClick} > {this.props.tokenInfo.symbol} </a> diff --git a/packages/website/ts/components/top_bar/provider_display.tsx b/packages/website/ts/components/top_bar/provider_display.tsx index 1e8855c14..496e5cae0 100644 --- a/packages/website/ts/components/top_bar/provider_display.tsx +++ b/packages/website/ts/components/top_bar/provider_display.tsx @@ -10,6 +10,7 @@ import { Container } from 'ts/components/ui/container'; import { DropDown } from 'ts/components/ui/drop_down'; import { Identicon } from 'ts/components/ui/identicon'; import { Image } from 'ts/components/ui/image'; +import { Island } from 'ts/components/ui/island'; import { Text } from 'ts/components/ui/text'; import { Dispatcher } from 'ts/redux/dispatcher'; import { colors } from 'ts/style/colors'; @@ -35,9 +36,7 @@ interface ProviderDisplayState {} const styles: Styles = { root: { height: ROOT_HEIGHT, - backgroundColor: colors.white, borderRadius: ROOT_HEIGHT, - boxShadow: `0px 4px 6px ${colors.walletBoxShadow}`, }, }; @@ -62,7 +61,7 @@ export class ProviderDisplay extends React.Component<ProviderDisplayProps, Provi this.props.providerType === ProviderType.Injected ? injectedProviderName : 'Ledger Nano S'; const isProviderMetamask = providerTitle === constants.PROVIDER_NAME_METAMASK; const hoverActiveNode = ( - <div className="flex items-center p1" style={styles.root}> + <Island className="flex items-center p1" style={styles.root}> <div> {this._isBlockchainReady() ? ( <Identicon address={this.props.userAddress} diameter={ROOT_HEIGHT} /> @@ -78,7 +77,7 @@ export class ProviderDisplay extends React.Component<ProviderDisplayProps, Provi {isProviderMetamask && ( <Image src="/images/metamask_icon.png" height={ROOT_HEIGHT} width={ROOT_HEIGHT} /> )} - </div> + </Island> ); const hasLedgerProvider = this.props.providerType === ProviderType.Ledger; const horizontalPosition = isExternallyInjectedProvider || hasLedgerProvider ? 'left' : 'middle'; diff --git a/packages/website/ts/components/ui/animation.tsx b/packages/website/ts/components/ui/animation.tsx new file mode 100644 index 000000000..136f3d005 --- /dev/null +++ b/packages/website/ts/components/ui/animation.tsx @@ -0,0 +1,34 @@ +import * as React from 'react'; +import { keyframes, styled } from 'ts/style/theme'; + +export type AnimationType = 'easeUpFromBottom'; + +export interface AnimationProps { + type: AnimationType; +} + +const PlainAnimation: React.StatelessComponent<AnimationProps> = props => <div {...props} />; + +const appearFromBottomFrames = keyframes` + from { + position: fixed; + bottom: -500px; + left: 0px; + } + + to { + position: fixed; + bottom: 0px; + left: 0px; + } +`; + +const animations: { [K in AnimationType]: string } = { + easeUpFromBottom: `${appearFromBottomFrames} 1s ease 0s 1 forwards`, +}; + +export const Animation = styled(PlainAnimation)` + animation: ${props => animations[props.type]}; +`; + +Animation.displayName = 'Animation'; diff --git a/packages/website/ts/components/ui/button.tsx b/packages/website/ts/components/ui/button.tsx index cb542a386..02fa47480 100644 --- a/packages/website/ts/components/ui/button.tsx +++ b/packages/website/ts/components/ui/button.tsx @@ -1,5 +1,5 @@ import { colors } from '@0xproject/react-shared'; -import { darken, grayscale } from 'polished'; +import { darken, saturate } from 'polished'; import * as React from 'react'; import { styled } from 'ts/style/theme'; @@ -17,7 +17,7 @@ export interface ButtonProps { } const PlainButton: React.StatelessComponent<ButtonProps> = ({ children, isDisabled, onClick, type, className }) => ( - <button type={type} className={className} onClick={isDisabled ? undefined : onClick}> + <button type={type} className={className} onClick={isDisabled ? undefined : onClick} disabled={isDisabled}> {children} </button> ); @@ -26,14 +26,15 @@ export const Button = styled(PlainButton)` cursor: ${props => (props.isDisabled ? 'default' : 'pointer')}; font-size: ${props => props.fontSize}; color: ${props => props.fontColor}; - transition: background-color 0.5s ease; + transition: background-color, opacity 0.5s ease; padding: 0.8em 2.2em; border-radius: 6px; box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.25); font-weight: 500; + outline: none; font-family: ${props => props.fontFamily}; width: ${props => props.width}; - background-color: ${props => (props.isDisabled ? grayscale(props.backgroundColor) : props.backgroundColor)}; + background-color: ${props => props.backgroundColor}; border: ${props => (props.borderColor ? `1px solid ${props.borderColor}` : 'none')}; &:hover { background-color: ${props => (!props.isDisabled ? darken(0.1, props.backgroundColor) : '')}; @@ -41,6 +42,13 @@ export const Button = styled(PlainButton)` &:active { background-color: ${props => (!props.isDisabled ? darken(0.2, props.backgroundColor) : '')}; } + &:disabled { + opacity: 0.5; + box-shadow: none; + } + &:focus { + background-color: ${props => saturate(0.2, props.backgroundColor)}; + } `; Button.defaultProps = { diff --git a/packages/website/ts/components/ui/container.tsx b/packages/website/ts/components/ui/container.tsx index 1776345da..a747ef01f 100644 --- a/packages/website/ts/components/ui/container.tsx +++ b/packages/website/ts/components/ui/container.tsx @@ -15,6 +15,7 @@ export interface ContainerProps { borderRadius?: StringOrNum; maxWidth?: StringOrNum; width?: StringOrNum; + minHeight?: StringOrNum; isHidden?: boolean; className?: string; position?: 'absolute' | 'fixed' | 'relative' | 'unset'; @@ -23,6 +24,7 @@ export interface ContainerProps { left?: string; right?: string; bottom?: string; + zIndex?: number; } export const Container: React.StatelessComponent<ContainerProps> = ({ children, className, isHidden, ...style }) => { diff --git a/packages/website/ts/components/ui/identicon.tsx b/packages/website/ts/components/ui/identicon.tsx index 30df995c8..cc1655962 100644 --- a/packages/website/ts/components/ui/identicon.tsx +++ b/packages/website/ts/components/ui/identicon.tsx @@ -23,7 +23,7 @@ export class Identicon extends React.Component<IdenticonProps, IdenticonState> { const radius = diameter / 2; return ( <div - className="circle mx-auto relative transitionFix" + className="circle relative transitionFix" style={{ width: diameter, height: diameter, diff --git a/packages/website/ts/components/ui/island.tsx b/packages/website/ts/components/ui/island.tsx index de90b664f..c8abfb7e0 100644 --- a/packages/website/ts/components/ui/island.tsx +++ b/packages/website/ts/components/ui/island.tsx @@ -1,31 +1,28 @@ import * as React from 'react'; import { colors } from 'ts/style/colors'; +import { styled } from 'ts/style/theme'; export interface IslandProps { style?: React.CSSProperties; - children?: React.ReactNode; className?: string; Component?: string | React.ComponentClass<any> | React.StatelessComponent<any>; + borderRadius?: string; } -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> +const PlainIsland: React.StatelessComponent<IslandProps> = ({ Component, style, className, children }) => ( + <Component style={style} className={className} children={children} /> ); +export const Island = styled(PlainIsland)` + background-color: ${colors.white}; + border-radius: ${props => props.borderRadius}; + box-shadow: 0px 4px 6px ${colors.walletBoxShadow}; + overflow: hidden; +`; + Island.defaultProps = { Component: 'div', + borderRadius: '10px', style: {}, }; diff --git a/packages/website/ts/components/wallet/wallet.tsx b/packages/website/ts/components/wallet/wallet.tsx index ac2fe0d31..dc48d6619 100644 --- a/packages/website/ts/components/wallet/wallet.tsx +++ b/packages/website/ts/components/wallet/wallet.tsx @@ -29,6 +29,7 @@ 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 { styled } from 'ts/style/theme'; import { BlockchainErrs, ProviderType, @@ -86,7 +87,6 @@ interface AccessoryItemConfig { const styles: Styles = { root: { width: '100%', - position: 'relative', }, footerItemInnerDiv: { paddingLeft: 24, @@ -139,6 +139,12 @@ const USD_DECIMAL_PLACES = 2; const NO_ALLOWANCE_TOGGLE_SPACE_WIDTH = 56; const ACCOUNT_PATH = `${WebsitePaths.Portal}/account`; +const ActionButton = styled(FloatingActionButton)` + button { + position: static !important; + } +`; + export class Wallet extends React.Component<WalletProps, WalletState> { public static defaultProps = { style: {}, @@ -245,17 +251,12 @@ export class Wallet extends React.Component<WalletProps, WalletState> { <ListItem primaryText={ <div className="flex"> - <FloatingActionButton mini={true} zDepth={0} onClick={this.props.onAddToken}> + <ActionButton mini={true} zDepth={0} onClick={this.props.onAddToken}> <ContentAdd /> - </FloatingActionButton> - <FloatingActionButton - mini={true} - zDepth={0} - className="px1" - onClick={this.props.onRemoveToken} - > + </ActionButton> + <ActionButton mini={true} zDepth={0} className="px1" onClick={this.props.onRemoveToken}> <ContentRemove /> - </FloatingActionButton> + </ActionButton> <div style={{ paddingLeft: 10, @@ -310,7 +311,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> { wrappedEtherDirection: Side.Deposit, }; const key = ETHER_ITEM_KEY; - return this._renderBalanceRow(key, icon, primaryText, secondaryText, accessoryItemConfig, 'eth-row'); + return this._renderBalanceRow(key, icon, primaryText, secondaryText, accessoryItemConfig, false, 'eth-row'); } private _renderTokenRows(): React.ReactNode { const trackedTokens = this.props.trackedTokens; @@ -321,8 +322,11 @@ export class Wallet extends React.Component<WalletProps, WalletState> { ); return _.map(trackedTokensStartingWithEtherToken, this._renderTokenRow.bind(this)); } - private _renderTokenRow(token: Token, _index: number): React.ReactNode { + private _renderTokenRow(token: Token, index: number): React.ReactNode { const tokenState = this.props.trackedTokenStateByAddress[token.address]; + if (_.isUndefined(tokenState)) { + return null; + } const tokenLink = sharedUtils.getEtherScanLinkIfExists( token.address, this.props.networkId, @@ -346,12 +350,14 @@ export class Wallet extends React.Component<WalletProps, WalletState> { }, }; const key = token.address; + const isLastRow = index === this.props.trackedTokens.length - 1; return this._renderBalanceRow( key, icon, primaryText, secondaryText, accessoryItemConfig, + isLastRow, isWeth ? 'weth-row' : undefined, ); } @@ -361,13 +367,19 @@ export class Wallet extends React.Component<WalletProps, WalletState> { primaryText: React.ReactNode, secondaryText: React.ReactNode, accessoryItemConfig: AccessoryItemConfig, + isLastRow: boolean, className?: string, ): React.ReactNode { const shouldShowWrapEtherItem = !_.isUndefined(this.state.wrappedEtherDirection) && this.state.wrappedEtherDirection === accessoryItemConfig.wrappedEtherDirection && !_.isUndefined(this.props.userEtherBalanceInWei); - const additionalStyle = shouldShowWrapEtherItem ? walletItemStyles.focusedItem : styles.borderedItem; + let additionalStyle; + if (shouldShowWrapEtherItem) { + additionalStyle = walletItemStyles.focusedItem; + } else if (!isLastRow) { + additionalStyle = styles.borderedItem; + } const style = { ...styles.tokenItem, ...additionalStyle }; const etherToken = this._getEthToken(); return ( diff --git a/packages/website/ts/containers/portal_onboarding_flow.ts b/packages/website/ts/containers/portal_onboarding_flow.ts index ba2b8f512..12daad021 100644 --- a/packages/website/ts/containers/portal_onboarding_flow.ts +++ b/packages/website/ts/containers/portal_onboarding_flow.ts @@ -3,7 +3,7 @@ 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 { ActionTypes, ProviderType, ScreenWidths, TokenByAddress, TokenStateByAddress } from 'ts/types'; import { PortalOnboardingFlow as PortalOnboardingFlowComponent } from 'ts/components/onboarding/portal_onboarding_flow'; import { State } from 'ts/redux/reducer'; @@ -25,6 +25,7 @@ interface ConnectedState { blockchainIsLoaded: boolean; userEtherBalanceInWei?: BigNumber; tokenByAddress: TokenByAddress; + screenWidth: ScreenWidths; } interface ConnectedDispatch { @@ -43,6 +44,7 @@ const mapStateToProps = (state: State, _ownProps: PortalOnboardingFlowProps): Co userEtherBalanceInWei: state.userEtherBalanceInWei, tokenByAddress: state.tokenByAddress, hasBeenSeen: state.hasPortalOnboardingBeenSeen, + screenWidth: state.screenWidth, }); const mapDispatchToProps = (dispatch: Dispatch<State>): ConnectedDispatch => ({ diff --git a/packages/website/ts/pages/about/about.tsx b/packages/website/ts/pages/about/about.tsx index 3136dbca3..0259af36f 100644 --- a/packages/website/ts/pages/about/about.tsx +++ b/packages/website/ts/pages/about/about.tsx @@ -150,14 +150,32 @@ const teamRow5: ProfileInfo[] = [ }, { name: 'Francesco Agosti', - title: 'Senior Frontend Engineer', + title: 'Engineer', description: `Full-stack engineer. Previously senior software engineer at Yelp. Computer Science at Duke.`, image: 'images/team/fragosti.png', linkedIn: 'https://www.linkedin.com/in/fragosti/', github: 'http://github.com/fragosti', }, + { + name: 'Mel Oberto', + title: 'Office Ops / Executive Assistant', + description: `Daily Operations. Previously People Operations Associate at Heap. Marketing and MBA at Sacred Heart University.`, + image: 'images/team/mel.png', + linkedIn: 'https://www.linkedin.com/in/melanieoberto', + }, ]; +// const teamRow6: ProfileInfo[] = [ +// { +// name: 'Chris Kalani', +// title: 'Director of Design', +// description: `Previously founded Wake (acquired by InVision). Early Facebook product designer.`, +// image: 'images/team/chris.png', +// linkedIn: 'https://www.linkedin.com/in/chriskalani/', +// github: 'https://github.com/chriskalani', +// }, +// ]; + const advisors: ProfileInfo[] = [ { name: 'Fred Ehrsam', @@ -241,9 +259,9 @@ export class About extends React.Component<AboutProps, AboutState> { lineHeight: 1.5, }} > - Our team is a diverse and globally distributed group with backgrounds in engineering, - research, business and design. We are passionate about decentralized technology and its - potential to act as an equalizing force in the world. + Our team is a globally distributed group with backgrounds in engineering, research, business + and design. We are passionate about decentralized technology and its potential to act as an + equalizing force in the world. </div> </div> <div className="pt3 md-px4 lg-px0"> diff --git a/packages/website/ts/pages/about/profile.tsx b/packages/website/ts/pages/about/profile.tsx index dd046a8cb..bcbeb0272 100644 --- a/packages/website/ts/pages/about/profile.tsx +++ b/packages/website/ts/pages/about/profile.tsx @@ -39,6 +39,7 @@ export const Profile = (props: ProfileProps) => { fontSize: 14, fontFamily: 'Roboto Mono', color: colors.darkGrey, + whiteSpace: 'nowrap', }} > {props.profileInfo.title.toUpperCase()} @@ -47,7 +48,7 @@ export const Profile = (props: ProfileProps) => { <div style={{ minHeight: 60, lineHeight: 1.4 }} className="pt1 pb2 mx-auto lg-h6 md-h6 sm-h5 sm-center"> {props.profileInfo.description} </div> - <div className="flex pb3 mx-auto sm-hide xs-hide" style={{ width: 280, opacity: 0.5 }}> + <div className="flex pb3 sm-hide xs-hide" style={{ width: 280, opacity: 0.5 }}> {renderSocialMediaIcons(props.profileInfo)} </div> </div> diff --git a/packages/website/ts/pages/jobs/open_positions.tsx b/packages/website/ts/pages/jobs/open_positions.tsx index f3d980315..e789795c1 100644 --- a/packages/website/ts/pages/jobs/open_positions.tsx +++ b/packages/website/ts/pages/jobs/open_positions.tsx @@ -11,6 +11,7 @@ import { colors } from 'ts/style/colors'; import { styled } from 'ts/style/theme'; import { ScreenWidths, WebsiteBackendJobInfo } from 'ts/types'; import { backendClient } from 'ts/utils/backend_client'; +import { utils } from 'ts/utils/utils'; const labelStyle = { fontFamily: 'Roboto Mono', fontSize: 18 }; const HEADER_TEXT = 'Open Positions'; @@ -161,7 +162,7 @@ export class OpenPositions extends React.Component<OpenPositionsProps, OpenPosit private _openJobInfoUrl(jobInfo: WebsiteBackendJobInfo): void { const url = jobInfo.url; - window.open(url, '_blank'); + utils.openUrl(url); } } diff --git a/packages/website/ts/redux/reducer.ts b/packages/website/ts/redux/reducer.ts index 9d3d8f7d9..ed6a4868e 100644 --- a/packages/website/ts/redux/reducer.ts +++ b/packages/website/ts/redux/reducer.ts @@ -156,7 +156,7 @@ export function reducer(state: State = INITIAL_STATE, action: Action): State { } case ActionTypes.AddTokenToTokenByAddress: { - const newTokenByAddress = state.tokenByAddress; + const newTokenByAddress = { ...state.tokenByAddress }; newTokenByAddress[action.data.address] = action.data; return { ...state, @@ -165,7 +165,7 @@ export function reducer(state: State = INITIAL_STATE, action: Action): State { } case ActionTypes.RemoveTokenFromTokenByAddress: { - const newTokenByAddress = state.tokenByAddress; + const newTokenByAddress = { ...state.tokenByAddress }; delete newTokenByAddress[action.data.address]; return { ...state, @@ -174,7 +174,7 @@ export function reducer(state: State = INITIAL_STATE, action: Action): State { } case ActionTypes.UpdateTokenByAddress: { - const tokenByAddress = state.tokenByAddress; + const tokenByAddress = { ...state.tokenByAddress }; const tokens = action.data; _.each(tokens, token => { const updatedToken = { @@ -253,7 +253,7 @@ export function reducer(state: State = INITIAL_STATE, action: Action): State { } case ActionTypes.UpdateChosenAssetTokenAddress: { - const newAssetToken = state.sideToAssetToken[action.data.side]; + const newAssetToken = { ...state.sideToAssetToken[action.data.side] }; newAssetToken.address = action.data.address; const newSideToAssetToken = { ...state.sideToAssetToken, diff --git a/packages/website/ts/style/colors.ts b/packages/website/ts/style/colors.ts index b15000d7a..45be4fe7f 100644 --- a/packages/website/ts/style/colors.ts +++ b/packages/website/ts/style/colors.ts @@ -1,7 +1,7 @@ import { colors as sharedColors } from '@0xproject/react-shared'; const appColors = { - walletBoxShadow: 'rgba(56, 59, 137, 0.2)', + walletBoxShadow: 'rgba(0, 0, 0, 0.05)', walletBorder: '#ededee', walletDefaultItemBackground: '#fbfbfc', walletFocusedItemBackground: '#f0f1f4', diff --git a/packages/website/ts/style/z_index.ts b/packages/website/ts/style/z_index.ts index 0411cdd91..a3998f59b 100644 --- a/packages/website/ts/style/z_index.ts +++ b/packages/website/ts/style/z_index.ts @@ -1,5 +1,6 @@ export const zIndex = { topBar: 1100, + aboveTopBar: 1101, overlay: 1105, aboveOverlay: 1106, }; diff --git a/packages/website/ts/utils/utils.ts b/packages/website/ts/utils/utils.ts index 0bd3dbcfa..0cb965f05 100644 --- a/packages/website/ts/utils/utils.ts +++ b/packages/website/ts/utils/utils.ts @@ -362,4 +362,10 @@ export const utils = { const formattedAmount = unitAmount.toFixed(precision); return `${formattedAmount} ${symbol}`; }, + openUrl(url: string): void { + window.open(url, '_blank'); + }, + isMobile(screenWidth: ScreenWidths): boolean { + return screenWidth === ScreenWidths.Sm; + }, }; |