1
0
mirror of https://github.com/trezor/trezor-wallet synced 2024-12-01 04:38:15 +00:00

Merge pull request #179 from trezor/fix/landing-refactoring

Fix/landing refactoring
This commit is contained in:
Vladimir Volek 2018-10-15 15:14:17 +02:00 committed by GitHub
commit 44619c0a95
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 436 additions and 288 deletions

View File

@ -51,12 +51,7 @@ class Link extends PureComponent {
let LinkComponent; let LinkComponent;
if (shouldRenderRouterLink) { if (shouldRenderRouterLink) {
LinkComponent = ( LinkComponent = (
<StyledNavLink <StyledNavLink {...this.props}>{this.props.children}</StyledNavLink>);
isGreen={this.props.isGreen}
isGray={this.props.isGray}
to={this.props.to}
>{this.props.children}
</StyledNavLink>);
} else { } else {
LinkComponent = ( LinkComponent = (
<A <A
@ -81,6 +76,7 @@ Link.propTypes = {
PropTypes.string, PropTypes.string,
PropTypes.object, PropTypes.object,
PropTypes.array, PropTypes.array,
PropTypes.node,
]).isRequired, ]).isRequired,
className: PropTypes.string, className: PropTypes.string,
href: PropTypes.string, href: PropTypes.string,
@ -91,9 +87,4 @@ Link.propTypes = {
isGray: PropTypes.bool, isGray: PropTypes.bool,
}; };
Link.defaultProps = {
isGreen: false,
isGray: false,
};
export default Link; export default Link;

View File

@ -23,7 +23,7 @@ export const routes: Array<Route> = [
fields: ['import'], fields: ['import'],
}, },
{ {
name: 'wallet-setting', name: 'wallet-settings',
pattern: '/settings', pattern: '/settings',
fields: ['settings'], fields: ['settings'],
}, },

View File

@ -5,15 +5,62 @@ import styled, { keyframes } from 'styled-components';
import TrezorConnect from 'trezor-connect'; import TrezorConnect from 'trezor-connect';
import P from 'components/Paragraph'; import P from 'components/Paragraph';
import Button from 'components/Button'; import Button from 'components/Button';
import { H2 } from 'components/Heading';
import { PULSATE } from 'config/animations'; import { PULSATE } from 'config/animations';
import colors from 'config/colors'; import colors from 'config/colors';
import { FONT_SIZE, FONT_WEIGHT } from 'config/variables'; import { FONT_SIZE, FONT_WEIGHT } from 'config/variables';
import CaseImage from 'images/case.png';
import Link from 'components/Link';
type Props = { type Props = {
deviceLabel: string, deviceLabel: string,
showWebUsb: boolean, showWebUsb: boolean,
showDisconnect: boolean, showDisconnect: boolean,
}; };
const Title = styled.div`
margin-top: 60px;
`;
const Wrapper = styled.div`
display: flex;
justify-content: space-around;
align-items: center;
width: 400px;
margin: 0 auto;
padding: 36px 0;
`;
const ConnectTrezorWrapper = styled.div`
position: relative;
top: 1px;
animation: ${PULSATE} 1.3s ease-out infinite;
color: ${colors.GREEN_PRIMARY};
font-size: ${FONT_SIZE.BASE};
font-weight: ${FONT_WEIGHT.BASE};
`;
const Image = styled.img`
width: 777px;
min-height: 500px;
margin: auto;
background-repeat: no-repeat;
background-position: center 0px;
background-size: contain;
`;
const Footer = styled.div`
margin-bottom: 32px;
`;
const FooterText = styled.span`
margin-right: 4px;
`;
const StyledLink = styled(Link)`
font-size: ${FONT_SIZE.BASE};
`;
class ConnectDevice extends PureComponent<Props> { class ConnectDevice extends PureComponent<Props> {
componentDidMount() { componentDidMount() {
if (this.props.showWebUsb) { if (this.props.showWebUsb) {
@ -57,25 +104,14 @@ class ConnectDevice extends PureComponent<Props> {
} }
render() { render() {
const Wrapper = styled.div`
display: flex;
justify-content: space-around;
align-items: center;
width: 400px;
margin: 0 auto;
padding: 36px 0;
`;
const ConnectTrezorWrapper = styled.div`
position: relative;
top: 1px;
animation: ${PULSATE} 1.3s ease-out infinite;
color: ${colors.GREEN_PRIMARY};
font-size: ${FONT_SIZE.BASE};
font-weight: ${FONT_WEIGHT.BASE};
`;
return ( return (
<div>
<Title>
<H2 claim>The private bank in your hands.</H2>
<P>TREZOR Wallet is an easy-to-use interface for your TREZOR.</P>
<P>TREZOR Wallet allows you to easily control your funds, manage your balance and initiate transfers.</P>
</Title>
<Wrapper> <Wrapper>
<ConnectTrezorWrapper> <ConnectTrezorWrapper>
{this.props.showDisconnect && `Unplug "${this.props.deviceLabel}" device`} {this.props.showDisconnect && `Unplug "${this.props.deviceLabel}" device`}
@ -95,6 +131,32 @@ class ConnectDevice extends PureComponent<Props> {
</React.Fragment> </React.Fragment>
)} )}
</Wrapper> </Wrapper>
<Image src={CaseImage} />
<Footer>
{this.props.showWebUsb && (
<P>
<FooterText>Device not recognized?</FooterText>
<StyledLink
to="/bridge"
isGreen
>Try installing the TREZOR Bridge.
</StyledLink>
</P>
)}
<P>
<FooterText>
Don&apos;t have TREZOR?
</FooterText>
<StyledLink
href="https://trezor.io/"
isGreen
>Get one
</StyledLink>
</P>
</Footer>
</div>
); );
} }
} }

View File

@ -9,11 +9,11 @@ const Wrapper = styled.div`
width: 100%; width: 100%;
`; `;
const InitializationError = (props: { error: ?string }) => ( const InitializationError = (props: { error: string }) => (
<Wrapper> <Wrapper>
<Notification <Notification
title="Initialization error" title="Initialization error"
message={props.error || ''} message={props.error}
type="error" type="error"
cancelable={false} cancelable={false}
/> />

View File

@ -0,0 +1,73 @@
/* @flow */
import * as React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import Header from 'components/Header';
import Footer from 'components/Footer';
import Log from 'components/Log';
import Loader from 'components/Loader';
import ContextNotifications from 'components/notifications/Context';
import colors from 'config/colors';
import InitializationError from '../InitializationError';
type Props = {
loading?: boolean;
error?: ?string;
children?: React.Node;
}
const Wrapper = styled.div`
min-height: 100%;
min-width: 720px;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
background: ${colors.LANDING};
`;
const LandingContent = styled.div`
flex: 1;
display: flex;
justify-content: center;
`;
const LandingLoader = styled(Loader)`
margin: auto;
`;
const LandingWrapper = (props: Props) => (
<Wrapper>
{props.loading && <LandingLoader text="Loading" size={100} />}
{!props.loading && (
<React.Fragment>
<Header />
<ContextNotifications />
{props.error && <InitializationError error={props.error} />}
<Log />
{!props.error && (
<LandingContent>
{ props.children }
</LandingContent>
)}
<Footer />
</React.Fragment>
)}
</Wrapper>
);
LandingWrapper.propTypes = {
loading: PropTypes.bool,
error: PropTypes.string,
children: PropTypes.node,
};
LandingWrapper.defaultProps = {
loading: false,
error: null,
};
export default LandingWrapper;

View File

@ -1,158 +0,0 @@
/* @flow */
import React from 'react';
import CaseImage from 'images/case.png';
import styled from 'styled-components';
import Header from 'components/Header';
import Footer from 'components/Footer';
import Log from 'components/Log';
import Link from 'components/Link';
import Loader from 'components/Loader';
import Notification from 'components/Notification';
import ContextNotifications from 'components/notifications/Context';
import colors from 'config/colors';
import P from 'components/Paragraph';
import { H2 } from 'components/Heading';
import { isWebUSB } from 'utils/device';
import { FONT_SIZE } from 'config/variables';
import InitializationError from './components/InitializationError';
import BrowserNotSupported from './components/BrowserNotSupported';
import ConnectDevice from './components/ConnectDevice';
import InstallBridge from './components/InstallBridge';
import type { Props } from './Container';
const LandingWrapper = styled.div`
min-height: 100%;
min-width: 720px;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
background: ${colors.LANDING};
`;
const LandingContent = styled.div`
flex: 1;
display: flex;
justify-content: center;
`;
const LandingImage = styled.img`
width: 777px;
min-height: 500px;
margin: auto;
background-repeat: no-repeat;
background-position: center 0px;
background-size: contain;
`;
const TitleWrapper = styled.div`
margin-top: 60px;
`;
const LandingFooterWrapper = styled.div`
margin-bottom: 32px;
`;
const LandingFooterTextWrapper = styled.span`
margin-right: 4px;
`;
const LandingLoader = styled(Loader)`
margin: auto;
`;
const StyledLink = styled(Link)`
font-size: ${FONT_SIZE.BASE};
`;
export default (props: Props) => {
const { devices } = props;
const { browserState, transport } = props.connect;
const localStorageError = props.localStorage.error;
const connectError = props.connect.error;
const bridgeRoute: boolean = props.router.location.state.hasOwnProperty('bridge');
const deviceLabel = props.wallet.disconnectRequest ? props.wallet.disconnectRequest.label : '';
const shouldShowInitializationError = connectError && !props.connect.initialized;
const shouldShowInstallBridge = props.connect.initialized && (connectError || bridgeRoute);
const shouldShowConnectDevice = props.wallet.ready && devices.length < 1;
const shouldShowDisconnectDevice = !!props.wallet.disconnectRequest;
const shouldShowUnsupportedBrowser = browserState.supported === false;
const isLoading = !shouldShowInitializationError && !shouldShowInstallBridge && !shouldShowConnectDevice && !shouldShowUnsupportedBrowser && !localStorageError;
return (
<LandingWrapper>
{isLoading && <LandingLoader text="Loading" size={100} />}
{!isLoading && (
<React.Fragment>
<Header />
{localStorageError && (
<Notification
title="Initialization error"
message="Config files are missing"
type="error"
/>
)}
<ContextNotifications />
{shouldShowInitializationError && <InitializationError error={connectError} />}
<Log />
<LandingContent>
{shouldShowUnsupportedBrowser && <BrowserNotSupported />}
{shouldShowInstallBridge && <InstallBridge selectFirstAvailableDevice={props.selectFirstAvailableDevice} transport={transport} />}
{!shouldShowInstallBridge && (shouldShowConnectDevice || shouldShowDisconnectDevice) && (
<div>
<TitleWrapper>
<H2 claim>The private bank in your hands.</H2>
<P>TREZOR Wallet is an easy-to-use interface for your TREZOR.</P>
<P>TREZOR Wallet allows you to easily control your funds, manage your balance and initiate transfers.</P>
</TitleWrapper>
<ConnectDevice
deviceLabel={deviceLabel}
showWebUsb={isWebUSB(transport)}
showDisconnect={shouldShowDisconnectDevice}
/>
<LandingImage src={CaseImage} />
{shouldShowConnectDevice && (
<LandingFooterWrapper>
{isWebUSB(transport) && (
<P>
<LandingFooterTextWrapper>Device not recognized?</LandingFooterTextWrapper>
<StyledLink
to="/bridge"
isGreen
>Try installing the TREZOR Bridge.
</StyledLink>
</P>
)}
<P>
<LandingFooterTextWrapper>
Don&apos;t have TREZOR?
</LandingFooterTextWrapper>
<StyledLink
href="https://trezor.io/"
isGreen
>Get one
</StyledLink>
</P>
</LandingFooterWrapper>
)}
</div>
)}
</LandingContent>
<Footer />
</React.Fragment>
)}
</LandingWrapper>
);
};

View File

@ -1,21 +1,16 @@
/* @flow */ /* @flow */
import * as React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import * as RouterActions from 'actions/RouterActions'; import * as RouterActions from 'actions/RouterActions';
import type { MapStateToProps, MapDispatchToProps } from 'react-redux'; import type { MapStateToProps, MapDispatchToProps } from 'react-redux';
import type { State, Dispatch } from 'flowtype'; import type { State, Dispatch } from 'flowtype';
import LandingPage from './index'; import ImportView from './index';
export type StateProps = { export type StateProps = {
localStorage: $ElementType<State, 'localStorage'>, transport: $ElementType<$ElementType<State, 'connect'>, 'transport'>,
modal: $ElementType<State, 'modal'>, children?: React.Node,
wallet: $ElementType<State, 'wallet'>,
connect: $ElementType<State, 'connect'>,
router: $ElementType<State, 'router'>,
wallet: $ElementType<State, 'wallet'>,
devices: $ElementType<State, 'devices'>,
} }
type DispatchProps = { type DispatchProps = {
@ -29,16 +24,11 @@ type OwnProps = {
export type Props = StateProps & DispatchProps; export type Props = StateProps & DispatchProps;
const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: State): StateProps => ({ const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: State): StateProps => ({
localStorage: state.localStorage, transport: state.connect.transport,
modal: state.modal,
wallet: state.wallet,
connect: state.connect,
router: state.router,
devices: state.devices,
}); });
const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => ({ const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => ({
selectFirstAvailableDevice: bindActionCreators(RouterActions.selectFirstAvailableDevice, dispatch), selectFirstAvailableDevice: bindActionCreators(RouterActions.selectFirstAvailableDevice, dispatch),
}); });
export default connect(mapStateToProps, mapDispatchToProps)(LandingPage); export default connect(mapStateToProps, mapDispatchToProps)(ImportView);

View File

@ -0,0 +1,40 @@
/* @flow */
import React from 'react';
import styled from 'styled-components';
import colors from 'config/colors';
import icons from 'config/icons';
import { H2 } from 'components/Heading';
import Icon from 'components/Icon';
import Link from 'components/Link';
import Button from 'components/Button';
import LandingWrapper from 'views/Landing/components/LandingWrapper';
const Wrapper = styled.div`
min-width: 720px;
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
`;
const Import = () => (
<LandingWrapper>
<Wrapper>
<Icon
size={60}
color={colors.WARNING_PRIMARY}
icon={icons.WARNING}
/>
<H2>Import tool is under construction</H2>
<Link to="/">
<Button>Take me back</Button>
</Link>
</Wrapper>
</LandingWrapper>
);
export default Import;

View File

@ -0,0 +1,34 @@
/* @flow */
import * as React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as RouterActions from 'actions/RouterActions';
import type { MapStateToProps, MapDispatchToProps } from 'react-redux';
import type { State, Dispatch } from 'flowtype';
import InstallBridge from './index';
export type StateProps = {
transport: $ElementType<$ElementType<State, 'connect'>, 'transport'>,
children?: React.Node,
}
type DispatchProps = {
selectFirstAvailableDevice: typeof RouterActions.selectFirstAvailableDevice,
}
type OwnProps = {
}
export type Props = StateProps & DispatchProps;
const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: State): StateProps => ({
transport: state.connect.transport,
});
const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => ({
selectFirstAvailableDevice: bindActionCreators(RouterActions.selectFirstAvailableDevice, dispatch),
});
export default connect(mapStateToProps, mapDispatchToProps)(InstallBridge);

View File

@ -8,10 +8,10 @@ import { Select } from 'components/Select';
import Link from 'components/Link'; import Link from 'components/Link';
import { H1 } from 'components/Heading'; import { H1 } from 'components/Heading';
import Button from 'components/Button'; import Button from 'components/Button';
import Loader from 'components/Loader';
import P from 'components/Paragraph'; import P from 'components/Paragraph';
import Icon from 'components/Icon'; import Icon from 'components/Icon';
import ICONS from 'config/icons'; import ICONS from 'config/icons';
import LandingWrapper from 'views/Landing/components/LandingWrapper';
import * as RouterActions from 'actions/RouterActions'; import * as RouterActions from 'actions/RouterActions';
import type { State as TrezorConnectState } from 'reducers/TrezorConnectReducer'; import type { State as TrezorConnectState } from 'reducers/TrezorConnectReducer';
@ -148,9 +148,10 @@ class InstallBridge extends PureComponent<Props, State> {
render() { render() {
const { target } = this.state; const { target } = this.state;
if (!target) { if (!target) {
return <Loader text="Loading" size={100} />; return <LandingWrapper />;
} }
return ( return (
<LandingWrapper loading={!target}>
<Wrapper> <Wrapper>
<Top> <Top>
<TitleHeader>TREZOR Bridge<Version>{this.state.currentVersion}</Version></TitleHeader> <TitleHeader>TREZOR Bridge<Version>{this.state.currentVersion}</Version></TitleHeader>
@ -206,6 +207,7 @@ class InstallBridge extends PureComponent<Props, State> {
)} )}
</Bottom> </Bottom>
</Wrapper> </Wrapper>
</LandingWrapper>
); );
} }
} }

View File

@ -0,0 +1,34 @@
/* @flow */
import * as React from 'react';
import { connect } from 'react-redux';
import type { MapStateToProps } from 'react-redux';
import type { State } from 'flowtype';
import RootView from './index';
export type StateProps = {
localStorage: $ElementType<State, 'localStorage'>,
modal: $ElementType<State, 'modal'>,
wallet: $ElementType<State, 'wallet'>,
connect: $ElementType<State, 'connect'>,
router: $ElementType<State, 'router'>,
wallet: $ElementType<State, 'wallet'>,
devices: $ElementType<State, 'devices'>,
children?: React.Node,
}
type DispatchProps = {};
type OwnProps = {};
export type Props = StateProps & DispatchProps;
const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: State): StateProps => ({
localStorage: state.localStorage,
modal: state.modal,
wallet: state.wallet,
connect: state.connect,
router: state.router,
devices: state.devices,
});
export default connect(mapStateToProps, null)(RootView);

View File

@ -0,0 +1,45 @@
/* @flow */
import React from 'react';
import { isWebUSB } from 'utils/device';
import LandingWrapper from 'views/Landing/components/LandingWrapper';
import BrowserNotSupported from 'views/Landing/components/BrowserNotSupported';
import ConnectDevice from 'views/Landing/components/ConnectDevice';
import InstallBridge from 'views/Landing/views/InstallBridge/Container';
import type { Props } from './Container';
const Root = (props: Props) => {
const { initialized, browserState, transport } = props.connect;
const { disconnectRequest } = props.wallet;
const localStorageError = props.localStorage.error;
const connectError = props.connect.error;
const error = !initialized ? (localStorageError || connectError) : null;
const shouldShowUnsupportedBrowser = browserState.supported === false;
const shouldShowInstallBridge = initialized && connectError;
const shouldShowConnectDevice = props.wallet.ready && props.devices.length < 1;
const shouldShowDisconnectDevice = !!disconnectRequest;
const isLoading = !error && !shouldShowUnsupportedBrowser && !shouldShowConnectDevice && !shouldShowUnsupportedBrowser;
const deviceLabel = disconnectRequest ? disconnectRequest.label : '';
// corner case: display InstallBridge view on "/" route
// it has it's own Container and props
if (shouldShowInstallBridge) return <InstallBridge />;
return (
<LandingWrapper loading={isLoading} error={error}>
{shouldShowUnsupportedBrowser && <BrowserNotSupported />}
{(shouldShowConnectDevice || shouldShowDisconnectDevice) && (
<ConnectDevice
deviceLabel={deviceLabel}
showWebUsb={isWebUSB(transport)}
showDisconnect={shouldShowDisconnectDevice}
/>
)}
</LandingWrapper>
);
};
export default Root;

View File

@ -1,15 +1,46 @@
import styled from 'styled-components'; import styled from 'styled-components';
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import Content from 'views/Wallet/components/Content';
const Wrapper = styled.div``; import colors from 'config/colors';
import icons from 'config/icons';
import Content from 'views/Wallet/components/Content';
import { H2 } from 'components/Heading';
import Icon from 'components/Icon';
import Link from 'components/Link';
import Button from 'components/Button';
const Section = styled.section`
display: flex;
justify-content: center;
align-items: center;
height: 100%;
`;
const Row = styled.div`
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
`;
const WalletSettings = () => ( const WalletSettings = () => (
<Content> <Content>
<Wrapper> <Section>
Wallet settings <Row>
</Wrapper> <Icon
size={60}
color={colors.WARNING_PRIMARY}
icon={icons.WARNING}
/>
<H2>Wallet settings is under construction</H2>
<Link to="/">
<Button>Take me back</Button>
</Link>
</Row>
</Section>
</Content> </Content>
); );

View File

@ -7,7 +7,11 @@ import { ConnectedRouter } from 'react-router-redux';
// general // general
import ErrorBoundary from 'support/ErrorBoundary'; import ErrorBoundary from 'support/ErrorBoundary';
import { getPattern } from 'support/routes'; import { getPattern } from 'support/routes';
import LandingContainer from 'views/Landing/Container';
// landing views
import RootView from 'views/Landing/views/Root/Container';
import InstallBridge from 'views/Landing/views/InstallBridge/Container';
import ImportView from 'views/Landing/views/Import/Container';
// wallet views // wallet views
import WalletContainer from 'views/Wallet'; import WalletContainer from 'views/Wallet';
@ -31,13 +35,13 @@ const App = () => (
<Provider store={store}> <Provider store={store}>
<ConnectedRouter history={history}> <ConnectedRouter history={history}>
<Switch> <Switch>
<Route exact path={getPattern('landing-home')} component={LandingContainer} /> <Route exact path={getPattern('landing-home')} component={RootView} />
<Route exact path={getPattern('landing-bridge')} component={LandingContainer} /> <Route exact path={getPattern('landing-bridge')} component={InstallBridge} />
<Route exact path={getPattern('landing-import')} component={LandingContainer} /> <Route exact path={getPattern('landing-import')} component={ImportView} />
<Route> <Route>
<ErrorBoundary> <ErrorBoundary>
<WalletContainer> <WalletContainer>
<Route exact path={getPattern('wallet-setting')} component={WalletSettings} /> <Route exact path={getPattern('wallet-settings')} component={WalletSettings} />
<Route exact path={getPattern('wallet-dashboard')} component={WalletDashboard} /> <Route exact path={getPattern('wallet-dashboard')} component={WalletDashboard} />
<Route exact path={getPattern('wallet-acquire')} component={WalletAcquire} /> <Route exact path={getPattern('wallet-acquire')} component={WalletAcquire} />
<Route exact path={getPattern('wallet-unreadable')} component={WalletUnreadableDevice} /> <Route exact path={getPattern('wallet-unreadable')} component={WalletUnreadableDevice} />