Merge pull request #816 from 0xProject/feature/website/portal-mobile-improvements
Make onboarding and wallet copy dynamic based on OS
This commit is contained in:
BIN
packages/website/public/images/toshi_logo.jpg
Normal file
BIN
packages/website/public/images/toshi_logo.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.5 KiB |
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -318,9 +318,9 @@ 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">
|
||||
@@ -364,7 +364,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">
|
||||
@@ -530,7 +530,7 @@ export class Portal extends React.Component<PortalProps, PortalState> {
|
||||
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>}
|
||||
|
||||
@@ -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;
|
||||
`};
|
||||
`;
|
||||
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)};
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -569,6 +569,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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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];
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user