diff options
Diffstat (limited to 'packages/website')
18 files changed, 245 insertions, 125 deletions
diff --git a/packages/website/public/images/toshi_logo.jpg b/packages/website/public/images/toshi_logo.jpg Binary files differnew file mode 100644 index 000000000..3cf451d24 --- /dev/null +++ b/packages/website/public/images/toshi_logo.jpg diff --git a/packages/website/translations/english.json b/packages/website/translations/english.json index 04fb0507a..d94dbb29e 100644 --- a/packages/website/translations/english.json +++ b/packages/website/translations/english.json @@ -76,5 +76,6 @@ "WEBSITE": "website", "DEVELOPERS": "developers", "HOME": "home", - "ROCKETCHAT": "rocket.chat" + "ROCKETCHAT": "rocket.chat", + "TRADE_CALL_TO_ACTION": "trade on 0x" } diff --git a/packages/website/ts/components/onboarding/install_wallet_onboarding_step.tsx b/packages/website/ts/components/onboarding/install_wallet_onboarding_step.tsx index a95c464af..d618c8318 100644 --- a/packages/website/ts/components/onboarding/install_wallet_onboarding_step.tsx +++ b/packages/website/ts/components/onboarding/install_wallet_onboarding_step.tsx @@ -1,19 +1,42 @@ import { colors } from '@0xproject/react-shared'; -import ActionAccountBalanceWallet from 'material-ui/svg-icons/action/account-balance-wallet'; import * as React from 'react'; import { Container } from 'ts/components/ui/container'; +import { Image } from 'ts/components/ui/image'; import { Text } from 'ts/components/ui/text'; +import { utils } from 'ts/utils/utils'; export interface InstallWalletOnboardingStepProps {} -export const InstallWalletOnboardingStep: React.StatelessComponent<InstallWalletOnboardingStepProps> = () => ( - <div className="flex items-center flex-column"> - <Text> - Before you begin, you need to connect to a wallet. This will be used across all 0x relayers and dApps. - </Text> - <Container marginTop="15px" marginBottom="15px"> - <ActionAccountBalanceWallet style={{ width: '50px', height: '50px' }} color={colors.orange} /> - </Container> - <Text>Please refresh the page once you've done this to continue!</Text> - </div> -); +export const InstallWalletOnboardingStep: React.StatelessComponent<InstallWalletOnboardingStepProps> = () => { + const [downloadLink, isOnMobile] = utils.getBestWalletDownloadLinkAndIsMobile(); + const followupText = isOnMobile + ? `Please revisit this site in your mobile dApp browser to continue!` + : `Please refresh the page once you've done this to continue!`; + const downloadText = isOnMobile ? 'Get the Toshi Wallet' : 'Get the MetaMask extension'; + return ( + <div className="flex items-center flex-column"> + <Text>First, you need to connect to a wallet. This will be used across all 0x relayers and dApps.</Text> + <Container className="flex items-center" marginTop="15px" marginBottom="15px"> + <Image + height="50px" + width="50px" + borderRadius="22%" + src={isOnMobile ? '/images/toshi_logo.jpg' : '/images/metamask_icon.png'} + /> + <Container marginLeft="10px"> + <a href={downloadLink} target="_blank"> + <Text + fontWeight={700} + fontSize="18px" + fontColor={colors.mediumBlue} + textDecorationLine="underline" + > + {downloadText} + </Text> + </a> + </Container> + </Container> + <Text>{followupText}</Text> + </div> + ); +}; diff --git a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx index b7c5a9f64..1c2c92fd1 100644 --- a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx +++ b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx @@ -57,13 +57,12 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp this._unlisten(); } public componentDidUpdate(prevProps: PortalOnboardingFlowProps): void { - this._adjustStepIfShould(); - if (!prevProps.isRunning && this.props.isRunning) { + // Any one of steps 0-3 could be the starting step, and we only want to reset the scroll on the starting step. + if (this.props.isRunning && utils.isMobileWidth(this.props.screenWidth) && this.props.stepIndex < 3) { // On mobile, make sure the wallet is completely visible. - if (this.props.screenWidth === ScreenWidths.Sm) { - document.querySelector('.wallet').scrollIntoView(); - } + document.querySelector('.wallet').scrollIntoView(); } + this._adjustStepIfShould(); if (!prevProps.blockchainIsLoaded && this.props.blockchainIsLoaded) { this._autoStartOnboardingIfShould(); } @@ -275,7 +274,7 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp ); } private _handleFinalStepContinueClick(): void { - if (utils.isMobile(this.props.screenWidth)) { + if (utils.isMobileWidth(this.props.screenWidth)) { window.scrollTo(0, 0); this.props.history.push('/portal'); } diff --git a/packages/website/ts/components/portal/portal.tsx b/packages/website/ts/components/portal/portal.tsx index 8c3b5cfd7..f983241fa 100644 --- a/packages/website/ts/components/portal/portal.tsx +++ b/packages/website/ts/components/portal/portal.tsx @@ -91,7 +91,7 @@ interface PortalState { interface AccountManagementItem { pathName: string; - headerText: string; + headerText?: string; render: () => React.ReactNode; } @@ -106,7 +106,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 LARGE_LAYOUT_MARGIN = 30; +const SIDE_PADDING = 20; export class Portal extends React.Component<PortalProps, PortalState> { private _blockchain: Blockchain; @@ -225,7 +225,7 @@ export class Portal extends React.Component<PortalProps, PortalState> { : TokenVisibility.TRACKED; return ( <Container> - <DocumentTitle title="0x Portal DApp" /> + <DocumentTitle title="0x Portal" /> <TopBar userAddress={this.props.userAddress} networkId={this.props.networkId} @@ -318,13 +318,17 @@ export class Portal extends React.Component<PortalProps, PortalState> { ); } private _renderWallet(): React.ReactNode { - const isMobile = utils.isMobile(this.props.screenWidth); + const isMobile = utils.isMobileWidth(this.props.screenWidth); // We need room to scroll down for mobile onboarding - const marginBottom = isMobile ? '200px' : '15px'; + const marginBottom = isMobile ? '250px' : '15px'; return ( <div> <Container className="flex flex-column items-center"> - {isMobile && <Container marginBottom="20px">{this._renderStartOnboarding()}</Container>} + {isMobile && ( + <Container marginTop="20px" marginBottom="20px"> + {this._renderStartOnboarding()} + </Container> + )} <Container marginBottom={marginBottom} width="100%"> <Wallet style={ @@ -364,7 +368,7 @@ export class Portal extends React.Component<PortalProps, PortalState> { ); } private _renderStartOnboarding(): React.ReactNode { - const isMobile = utils.isMobile(this.props.screenWidth); + const isMobile = utils.isMobileWidth(this.props.screenWidth); const shouldStartOnboarding = !isMobile || this.props.location.pathname === `${WebsitePaths.Portal}/account`; const startOnboarding = ( <Container className="flex items-center center"> @@ -402,7 +406,7 @@ export class Portal extends React.Component<PortalProps, PortalState> { }, { pathName: `${WebsitePaths.Portal}/account`, - headerText: 'Your Account', + headerText: this._isSmallScreen() ? undefined : 'Your Account', render: this._isSmallScreen() ? this._renderWallet.bind(this) : this._renderTokenBalances.bind(this), }, { @@ -445,7 +449,7 @@ export class Portal extends React.Component<PortalProps, PortalState> { private _renderAccountManagementItem(item: AccountManagementItem): React.ReactNode { return ( <Section - header={<TextHeader labelText={item.headerText} />} + header={!_.isUndefined(item.headerText) && <TextHeader labelText={item.headerText} />} body={<Loading isLoading={!this.props.blockchainIsLoaded} content={item.render()} />} /> ); @@ -527,15 +531,21 @@ export class Portal extends React.Component<PortalProps, PortalState> { ); } private _renderRelayerIndexSection(): React.ReactNode { - return <Section header={<TextHeader labelText="0x Relayers" />} body={this._renderRelayerIndex()} />; - } - private _renderRelayerIndex(): React.ReactNode { - const isMobile = utils.isMobile(this.props.screenWidth); + const isMobile = utils.isMobileWidth(this.props.screenWidth); return ( - <Container className="flex flex-column items-center"> - {isMobile && <Container marginBottom="20px">{this._renderStartOnboarding()}</Container>} - <RelayerIndex networkId={this.props.networkId} screenWidth={this.props.screenWidth} /> - </Container> + <Section + header={!isMobile && <TextHeader labelText="0x Relayers" />} + body={ + <Container className="flex flex-column items-center"> + {isMobile && ( + <Container marginTop="20px" marginBottom="20px"> + {this._renderStartOnboarding()} + </Container> + )} + <RelayerIndex networkId={this.props.networkId} screenWidth={this.props.screenWidth} /> + </Container> + } + /> ); } private _renderNotFoundMessage(): React.ReactNode { @@ -685,19 +695,19 @@ interface LargeLayoutProps { } const LargeLayout = (props: LargeLayoutProps) => { return ( - <Container className="mx-auto flex flex-center" maxWidth={LARGE_LAYOUT_MAX_WIDTH}> + <Container + className="mx-auto flex flex-center" + maxWidth={LARGE_LAYOUT_MAX_WIDTH} + paddingLeft={SIDE_PADDING} + paddingRight={SIDE_PADDING} + > <div className="flex-last"> - <Container - width={LEFT_COLUMN_WIDTH} - position="fixed" - zIndex={zIndex.aboveTopBar} - marginLeft={LARGE_LAYOUT_MARGIN} - > + <Container width={LEFT_COLUMN_WIDTH} position="fixed" zIndex={zIndex.aboveTopBar}> {props.left} </Container> </div> - <Container className="flex-auto" marginLeft={LEFT_COLUMN_WIDTH + LARGE_LAYOUT_MARGIN}> - <Container className="flex-auto" marginLeft={LARGE_LAYOUT_MARGIN} marginRight={LARGE_LAYOUT_MARGIN}> + <Container className="flex-auto" marginLeft={LEFT_COLUMN_WIDTH}> + <Container className="flex-auto" marginLeft={SIDE_PADDING}> {props.right} </Container> </Container> @@ -711,7 +721,9 @@ interface SmallLayoutProps { const SmallLayout = (props: SmallLayoutProps) => { return ( <div className="flex flex-center"> - <div className="flex-auto px3">{props.content}</div> + <Container className="flex-auto" paddingLeft={SIDE_PADDING} paddingRight={SIDE_PADDING}> + {props.content} + </Container> </div> ); }; // tslint:disable:max-file-line-count 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 02bc1b014..431cf145b 100644 --- a/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx +++ b/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx @@ -108,12 +108,13 @@ export const RelayerGridTile: React.StatelessComponent<RelayerGridTileProps> = ( const GridTile = styled(PlainGridTile)` cursor: pointer; - transition: transform 0.2s ease; &:hover { + transition: transform 0.2s ease; transform: translate(0px, -3px); } ${media.small` - transform: none; + transform: none !important; + transition: none !important; `}; `; diff --git a/packages/website/ts/components/top_bar/top_bar.tsx b/packages/website/ts/components/top_bar/top_bar.tsx index 778536663..960e5a824 100644 --- a/packages/website/ts/components/top_bar/top_bar.tsx +++ b/packages/website/ts/components/top_bar/top_bar.tsx @@ -11,6 +11,7 @@ import { LegacyPortalMenu } from 'ts/components/legacy_portal/legacy_portal_menu import { DrawerMenu } from 'ts/components/portal/drawer_menu'; import { ProviderDisplay } from 'ts/components/top_bar/provider_display'; import { TopBarMenuItem } from 'ts/components/top_bar/top_bar_menu_item'; +import { Container } from 'ts/components/ui/container'; import { DropDown } from 'ts/components/ui/drop_down'; import { Dispatcher } from 'ts/redux/dispatcher'; import { Deco, Key, ProviderType, WebsiteLegacyPaths, WebsitePaths } from 'ts/types'; @@ -45,6 +46,8 @@ export interface TopBarProps { onVersionSelected?: (semver: string) => void; sidebarHeader?: React.ReactNode; maxWidth?: number; + paddingLeft?: number; + paddingRight?: number; } interface TopBarState { @@ -67,13 +70,12 @@ const styles: Styles = { color: colors.darkestGrey, paddingTop: 6, paddingBottom: 6, - marginTop: 17, cursor: 'pointer', fontWeight: 400, }, }; -const DEFAULT_HEIGHT = 59; +const DEFAULT_HEIGHT = 68; const EXPANDED_HEIGHT = 75; export class TopBar extends React.Component<TopBarProps, TopBarState> { @@ -81,6 +83,8 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { displayType: TopBarDisplayType.Default, style: {}, isNightVersion: false, + paddingLeft: 20, + paddingRight: 20, }; public static heightForDisplayType(displayType: TopBarDisplayType): number { const result = displayType === TopBarDisplayType.Expanded ? EXPANDED_HEIGHT : DEFAULT_HEIGHT; @@ -102,7 +106,9 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { public render(): React.ReactNode { const isNightVersion = this.props.isNightVersion; const isExpandedDisplayType = this.props.displayType === TopBarDisplayType.Expanded; - const parentClassNames = `flex mx-auto ${isExpandedDisplayType ? 'pl3 py1' : 'max-width-4'}`; + const parentClassNames = !isExpandedDisplayType + ? 'flex mx-auto items-center max-width-4' + : 'flex mx-auto items-center'; const height = isExpandedDisplayType ? EXPANDED_HEIGHT : DEFAULT_HEIGHT; const developerSectionMenuItems = [ <Link key="subMenuItem-zeroEx" to={WebsitePaths.ZeroExJs} className="text-decoration-none"> @@ -197,7 +203,6 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { fontSize: 25, color: isNightVersion ? 'white' : 'black', cursor: 'pointer', - paddingTop: 16, }; const activeNode = ( <div className="flex relative" style={{ color: menuIconStyle.color }}> @@ -211,18 +216,24 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { // TODO : Remove this once we ship portal v2 const shouldShowPortalV2Drawer = this._isViewingPortal() && utils.shouldShowPortalV2(); return ( - <div style={{ ...styles.topBar, ...bottomBorderStyle, ...this.props.style, ...{ height } }} className="pb1"> - <div className={parentClassNames} style={{ maxWidth: this.props.maxWidth }}> - <div className="col col-2 sm-pl1 md-pl2 lg-pl0" style={{ paddingTop: 15 }}> - <Link to={`${WebsitePaths.Home}`} className="text-decoration-none"> - <img src={logoUrl} height="30" /> - </Link> - </div> - <div className={`col col-${isExpandedDisplayType ? '8' : '9'} lg-hide md-hide`} /> - <div className={`col col-${isExpandedDisplayType ? '6' : '5'} sm-hide xs-hide`} /> + <div + style={{ ...styles.topBar, ...bottomBorderStyle, ...this.props.style, ...{ height } }} + className="pb1 flex items-center" + > + <Container + className={parentClassNames} + width="100%" + maxWidth={this.props.maxWidth} + paddingLeft={this.props.paddingLeft} + paddingRight={this.props.paddingRight} + > + <Link to={`${WebsitePaths.Home}`} className="text-decoration-none"> + <img src={logoUrl} height="30" /> + </Link> + <div className="flex-auto" /> {!this._isViewingPortal() && ( <div className={menuClasses}> - <div className="flex justify-between"> + <div className="flex items-center justify-between"> <DropDown activeNode={activeNode} popoverContent={popoverContent} @@ -252,7 +263,7 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { isExternal={false} /> <TopBarMenuItem - title={this.props.translate.get(Key.PortalDApp, Deco.CapWords)} + title={this.props.translate.get(Key.TradeCallToAction, Deco.Cap)} path={`${WebsitePaths.Portal}`} isPrimary={true} style={styles.menuItem} @@ -264,7 +275,7 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { </div> )} {this._isViewingPortal() && ( - <div className="sm-hide xs-hide col col-5" style={{ paddingTop: 8, marginRight: 36 }}> + <div className="sm-hide xs-hide"> <ProviderDisplay dispatcher={this.props.dispatcher} userAddress={this.props.userAddress} @@ -277,12 +288,12 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { /> </div> )} - <div className={`col ${isExpandedDisplayType ? 'col-2 pl2' : 'col-1'} md-hide lg-hide`}> + <div className={'md-hide lg-hide'}> <div style={menuIconStyle}> <i className="zmdi zmdi-menu" onClick={this._onMenuButtonClick.bind(this)} /> </div> </div> - </div> + </Container> {shouldShowPortalV2Drawer ? this._renderPortalV2Drawer() : this._renderDrawer()} </div> ); diff --git a/packages/website/ts/components/top_bar/top_bar_menu_item.tsx b/packages/website/ts/components/top_bar/top_bar_menu_item.tsx index 2e4254cfa..25fab2868 100644 --- a/packages/website/ts/components/top_bar/top_bar_menu_item.tsx +++ b/packages/website/ts/components/top_bar/top_bar_menu_item.tsx @@ -3,6 +3,8 @@ import * as _ from 'lodash'; import * as React from 'react'; import { Link } from 'react-router-dom'; +import { CallToAction } from 'ts/components/ui/button'; + const DEFAULT_STYLE = { color: colors.darkestGrey, }; @@ -27,23 +29,15 @@ export class TopBarMenuItem extends React.Component<TopBarMenuItemProps, TopBarM isNightVersion: false, }; public render(): React.ReactNode { - const primaryStyles = this.props.isPrimary - ? { - borderRadius: 4, - border: `1px solid ${this.props.isNightVersion ? colors.grey : colors.greyishPink}`, - marginTop: 15, - paddingLeft: 9, - paddingRight: 9, - minWidth: 77, - } - : {}; const menuItemColor = this.props.isNightVersion ? 'white' : this.props.style.color; const linkColor = _.isUndefined(menuItemColor) ? colors.darkestGrey : menuItemColor; + const itemContent = this.props.isPrimary ? ( + <CallToAction padding="0.8em 1.5em">{this.props.title}</CallToAction> + ) : ( + this.props.title + ); return ( - <div - className={`center ${this.props.className}`} - style={{ ...this.props.style, ...primaryStyles, color: menuItemColor }} - > + <div className={`center ${this.props.className}`} style={{ ...this.props.style, color: menuItemColor }}> {this.props.isExternal ? ( <a className="text-decoration-none" @@ -51,11 +45,11 @@ export class TopBarMenuItem extends React.Component<TopBarMenuItemProps, TopBarM target="_blank" href={this.props.path} > - {this.props.title} + {itemContent} </a> ) : ( <Link to={this.props.path} className="text-decoration-none" style={{ color: linkColor }}> - {this.props.title} + {itemContent} </Link> )} </div> diff --git a/packages/website/ts/components/ui/button.tsx b/packages/website/ts/components/ui/button.tsx index 1489a74a6..2952c8859 100644 --- a/packages/website/ts/components/ui/button.tsx +++ b/packages/website/ts/components/ui/button.tsx @@ -11,6 +11,7 @@ export interface ButtonProps { backgroundColor?: string; borderColor?: string; width?: string; + padding?: string; type?: string; isDisabled?: boolean; onClick?: (event: React.MouseEvent<HTMLElement>) => void; @@ -27,9 +28,8 @@ export const Button = styled(PlainButton)` font-size: ${props => props.fontSize}; color: ${props => props.fontColor}; transition: background-color, opacity 0.5s ease; - padding: 0.8em 2.2em; + padding: ${props => props.padding}; border-radius: 6px; - box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.25); font-weight: 500; outline: none; font-family: ${props => props.fontFamily}; @@ -44,7 +44,6 @@ export const Button = styled(PlainButton)` } &:disabled { opacity: 0.5; - box-shadow: none; } &:focus { background-color: ${props => saturate(0.2, props.backgroundColor)}; @@ -57,6 +56,7 @@ Button.defaultProps = { width: 'auto', fontFamily: 'Roboto', isDisabled: false, + padding: '0.8em 2.2em', }; Button.displayName = 'Button'; @@ -67,20 +67,26 @@ export interface CallToActionProps { type?: CallToActionType; fontSize?: string; width?: string; + padding?: string; } -export const CallToAction: React.StatelessComponent<CallToActionProps> = ({ children, type, fontSize, width }) => { +export const CallToAction: React.StatelessComponent<CallToActionProps> = ({ + children, + type, + fontSize, + padding, + width, +}) => { const isLight = type === 'light'; - const backgroundColor = isLight ? colors.white : colors.heroGrey; + const backgroundColor = isLight ? colors.white : colors.mediumBlue; const fontColor = isLight ? colors.heroGrey : colors.white; - const borderColor = isLight ? undefined : colors.white; return ( <Button fontSize={fontSize} + padding={padding} backgroundColor={backgroundColor} fontColor={fontColor} width={width} - borderColor={borderColor} > {children} </Button> @@ -89,4 +95,5 @@ export const CallToAction: React.StatelessComponent<CallToActionProps> = ({ chil CallToAction.defaultProps = { type: 'dark', + fontSize: '14px', }; diff --git a/packages/website/ts/components/ui/image.tsx b/packages/website/ts/components/ui/image.tsx index 369dc8b7e..c4ff93531 100644 --- a/packages/website/ts/components/ui/image.tsx +++ b/packages/website/ts/components/ui/image.tsx @@ -6,6 +6,7 @@ export interface ImageProps { src?: string; fallbackSrc?: string; height?: string | number; + borderRadius?: string; width?: string | number; } interface ImageState { @@ -26,6 +27,9 @@ export class Image extends React.Component<ImageProps, ImageState> { className={this.props.className} onError={this._onError.bind(this)} src={src} + style={{ + borderRadius: this.props.borderRadius, + }} height={this.props.height} width={this.props.width} /> diff --git a/packages/website/ts/components/wallet/body_overlay.tsx b/packages/website/ts/components/wallet/body_overlay.tsx index 5ced704f9..26359d0d2 100644 --- a/packages/website/ts/components/wallet/body_overlay.tsx +++ b/packages/website/ts/components/wallet/body_overlay.tsx @@ -9,11 +9,11 @@ import { Text } from 'ts/components/ui/text'; import { Dispatcher } from 'ts/redux/dispatcher'; import { colors } from 'ts/style/colors'; import { styled } from 'ts/style/theme'; -import { AccountState, BrowserType, ProviderType } from 'ts/types'; -import { constants } from 'ts/utils/constants'; +import { AccountState, ProviderType } from 'ts/types'; import { utils } from 'ts/utils/utils'; const METAMASK_IMG_SRC = '/images/metamask_icon.png'; +const TOSHI_IMG_SRC = '/images/toshi_logo.jpg'; export interface BodyOverlayProps { dispatcher: Dispatcher; @@ -92,8 +92,10 @@ interface DisconnectedOverlayProps { const DisconnectedOverlay = (props: DisconnectedOverlayProps) => { return ( <div className="flex flex-column items-center"> - <GetMetaMask /> - <UseDifferentWallet fontColor={colors.mediumBlue} onClick={props.onUseDifferentWalletClicked} /> + <GetWalletCallToAction /> + {!utils.isMobileOperatingSystem() && ( + <UseDifferentWallet fontColor={colors.mediumBlue} onClick={props.onUseDifferentWalletClicked} /> + )} </div> ); }; @@ -112,32 +114,20 @@ const UseDifferentWallet = (props: UseDifferentWallet) => { ); }; -const GetMetaMask = () => { - const browserType = utils.getBrowserType(); - let extensionLink; - switch (browserType) { - case BrowserType.Chrome: - extensionLink = constants.URL_METAMASK_CHROME_STORE; - break; - case BrowserType.Firefox: - extensionLink = constants.URL_METAMASK_FIREFOX_STORE; - break; - case BrowserType.Opera: - extensionLink = constants.URL_METAMASK_OPERA_STORE; - break; - default: - extensionLink = constants.URL_METAMASK_HOMEPAGE; - } +const GetWalletCallToAction = () => { + const [downloadLink, isOnMobile] = utils.getBestWalletDownloadLinkAndIsMobile(); + const imageUrl = isOnMobile ? TOSHI_IMG_SRC : METAMASK_IMG_SRC; + const text = isOnMobile ? 'Get Toshi Wallet' : 'Get MetaMask Wallet'; return ( - <a href={extensionLink} target="_blank" style={{ textDecoration: 'none' }}> + <a href={downloadLink} target="_blank" style={{ textDecoration: 'none' }}> <Island className="flex items-center py1 px2" style={{ height: 28, borderRadius: 28, backgroundColor: colors.mediumBlue }} > - <Image src={METAMASK_IMG_SRC} width="28px" /> + <Image src={imageUrl} width="28px" borderRadius="22%" /> <Container marginLeft="8px" marginRight="12px"> <Text fontColor={colors.white} fontSize="16px" fontWeight={500}> - Get MetaMask Wallet + {text} </Text> </Container> </Island> diff --git a/packages/website/ts/components/wallet/wallet.tsx b/packages/website/ts/components/wallet/wallet.tsx index 1e0b9ec48..de3b91ad0 100644 --- a/packages/website/ts/components/wallet/wallet.tsx +++ b/packages/website/ts/components/wallet/wallet.tsx @@ -224,17 +224,17 @@ export class Wallet extends React.Component<WalletProps, WalletState> { popoverContent={ <SimpleMenu minWidth="150px"> <CopyAddressSimpleMenuItem userAddress={this.props.userAddress} /> - <DifferentWalletSimpleMenuItem onClick={this.props.onToggleLedgerDialog} /> + {!isMobile && <DifferentWalletSimpleMenuItem onClick={this.props.onToggleLedgerDialog} />} <SimpleMenuItem displayText="Add Tokens..." onClick={this.props.onAddToken} /> <SimpleMenuItem displayText="Remove Tokens..." onClick={this.props.onRemoveToken} /> - <GoToAccountManagementSimpleMenuItem /> + {!isMobile && <GoToAccountManagementSimpleMenuItem />} </SimpleMenu> } anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }} targetOrigin={{ horizontal: 'right', vertical: 'top' }} zDepth={1} activateEvent={DropdownMouseEvent.Click} - closeEvent={isMobile ? DropdownMouseEvent.Click : DropdownMouseEvent.Hover} + closeEvent={DropdownMouseEvent.Click} /> ); return ( diff --git a/packages/website/ts/pages/landing/landing.tsx b/packages/website/ts/pages/landing/landing.tsx index f091778f4..b2cf4d979 100644 --- a/packages/website/ts/pages/landing/landing.tsx +++ b/packages/website/ts/pages/landing/landing.tsx @@ -268,15 +268,11 @@ export class Landing extends React.Component<LandingProps, LandingState> { </Link> </div> <div className="lg-col lg-col-6 sm-center sm-col sm-col-12"> - <a - href={constants.URL_ZEROEX_CHAT} - target="_blank" - className="text-decoration-none" - > + <Link to={WebsitePaths.Portal} className="text-decoration-none"> <CallToAction width="175px"> - {this.props.translate.get(Key.CommunityCallToAction, Deco.Cap)} + {this.props.translate.get(Key.TradeCallToAction, Deco.Cap)} </CallToAction> - </a> + </Link> </div> </Container> </div> @@ -295,7 +291,7 @@ export class Landing extends React.Component<LandingProps, LandingState> { <div className="mr1 px1" style={{ - backgroundColor: colors.lightTurquois, + backgroundColor: colors.white, borderRadius: 3, color: colors.heroGrey, height: 23, diff --git a/packages/website/ts/style/media.ts b/packages/website/ts/style/media.ts index 3c992eb9f..870d9a277 100644 --- a/packages/website/ts/style/media.ts +++ b/packages/website/ts/style/media.ts @@ -2,7 +2,7 @@ import { css } from 'ts/style/theme'; import { ScreenWidths } from 'ts/types'; const generateMediaWrapper = (screenWidth: ScreenWidths) => (...args: any[]) => css` - @media (max-width: ${screenWidth}) { + @media (max-width: ${screenWidth}em) { ${css.apply(css, args)}; } `; diff --git a/packages/website/ts/types.ts b/packages/website/ts/types.ts index d9b2ef618..f7324e87a 100644 --- a/packages/website/ts/types.ts +++ b/packages/website/ts/types.ts @@ -456,6 +456,7 @@ export enum Key { Developers = 'DEVELOPERS', Home = 'HOME', RocketChat = 'ROCKETCHAT', + TradeCallToAction = 'TRADE_CALL_TO_ACTION', } export enum SmartContractDocSections { @@ -569,6 +570,16 @@ export enum BrowserType { Other = 'Other', } +export enum OperatingSystemType { + Android = 'Android', + iOS = 'iOS', + Mac = 'Mac', + Windows = 'Windows', + WindowsPhone = 'WindowsPhone', + Linux = 'Linux', + Other = 'Other', +} + export enum AccountState { Disconnected = 'Disconnected', Ready = 'Ready', diff --git a/packages/website/ts/utils/constants.ts b/packages/website/ts/utils/constants.ts index 0c4b88780..4b3443d21 100644 --- a/packages/website/ts/utils/constants.ts +++ b/packages/website/ts/utils/constants.ts @@ -74,6 +74,8 @@ export const constants = { URL_GITHUB_WIKI: 'https://github.com/0xProject/wiki', URL_METAMASK_CHROME_STORE: 'https://chrome.google.com/webstore/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn', URL_METAMASK_FIREFOX_STORE: 'https://addons.mozilla.org/en-US/firefox/addon/ether-metamask/', + URL_TOSHI_IOS_APP_STORE: 'https://itunes.apple.com/us/app/toshi-ethereum-wallet/id1278383455?mt=8', + URL_TOSHI_ANDROID_APP_STORE: 'https://play.google.com/store/apps/details?id=org.toshi&hl=en_US', URL_METAMASK_HOMEPAGE: 'https://metamask.io/', URL_METAMASK_OPERA_STORE: 'https://addons.opera.com/en/extensions/details/metamask/', URL_MIST_DOWNLOAD: 'https://github.com/ethereum/mist/releases', diff --git a/packages/website/ts/utils/translate.ts b/packages/website/ts/utils/translate.ts index 39924b6f7..1ee1a59c5 100644 --- a/packages/website/ts/utils/translate.ts +++ b/packages/website/ts/utils/translate.ts @@ -55,6 +55,19 @@ export class Translate { } public get(key: Key, decoration?: Deco): string { let text = this._translation[key]; + // if a translation does not exist for a certain language, fallback to english + // if it still doesn't exist in english, throw an error + if (_.isUndefined(text)) { + const englishTranslation: Translation = languageToTranslations[Language.English]; + const englishText = englishTranslation[key]; + if (!_.isUndefined(englishText)) { + text = englishText; + } else { + throw new Error( + `Translation key not available in ${this._selectedLanguage} or ${Language.English}: ${key}`, + ); + } + } if (!_.isUndefined(decoration) && !_.includes(languagesWithoutCaps, this._selectedLanguage)) { switch (decoration) { case Deco.Cap: diff --git a/packages/website/ts/utils/utils.ts b/packages/website/ts/utils/utils.ts index 2cda41545..623819fc9 100644 --- a/packages/website/ts/utils/utils.ts +++ b/packages/website/ts/utils/utils.ts @@ -15,6 +15,7 @@ import { BlockchainCallErrs, BrowserType, Environments, + OperatingSystemType, Order, Providers, ProviderType, @@ -401,9 +402,12 @@ export const utils = { openUrl(url: string): void { window.open(url, '_blank'); }, - isMobile(screenWidth: ScreenWidths): boolean { + isMobileWidth(screenWidth: ScreenWidths): boolean { return screenWidth === ScreenWidths.Sm; }, + isMobileOperatingSystem(): boolean { + return bowser.mobile; + }, getBrowserType(): BrowserType { if (bowser.chrome) { return BrowserType.Chrome; @@ -415,7 +419,59 @@ export const utils = { return BrowserType.Other; } }, + getOperatingSystem(): OperatingSystemType { + if (bowser.android) { + return OperatingSystemType.Android; + } else if (bowser.ios) { + return OperatingSystemType.iOS; + } else if (bowser.mac) { + return OperatingSystemType.Mac; + } else if (bowser.windows) { + return OperatingSystemType.Windows; + } else if (bowser.windowsphone) { + return OperatingSystemType.WindowsPhone; + } else if (bowser.linux) { + return OperatingSystemType.Linux; + } else { + return OperatingSystemType.Other; + } + }, isTokenTracked(token: Token): boolean { return !_.isUndefined(token.trackedTimestamp); }, + // Returns a [downloadLink, isOnMobile] tuple. + getBestWalletDownloadLinkAndIsMobile(): [string, boolean] { + const browserType = utils.getBrowserType(); + const isOnMobile = utils.isMobileOperatingSystem(); + const operatingSystem = utils.getOperatingSystem(); + let downloadLink; + if (isOnMobile) { + switch (operatingSystem) { + case OperatingSystemType.Android: + downloadLink = constants.URL_TOSHI_ANDROID_APP_STORE; + break; + case OperatingSystemType.iOS: + downloadLink = constants.URL_TOSHI_IOS_APP_STORE; + break; + default: + // Toshi is only supported on these mobile OSes - just default to iOS + downloadLink = constants.URL_TOSHI_IOS_APP_STORE; + } + } else { + switch (browserType) { + case BrowserType.Chrome: + downloadLink = constants.URL_METAMASK_CHROME_STORE; + break; + case BrowserType.Firefox: + downloadLink = constants.URL_METAMASK_FIREFOX_STORE; + break; + case BrowserType.Opera: + downloadLink = constants.URL_METAMASK_OPERA_STORE; + break; + default: + downloadLink = constants.URL_METAMASK_HOMEPAGE; + } + } + return [downloadLink, isOnMobile]; + }, }; |