1
0
mirror of https://github.com/trezor/trezor-wallet synced 2024-12-28 09:58:23 +00:00
This commit is contained in:
Vladimir Volek 2018-11-05 14:57:23 +01:00
commit 53f7a8ecef
31 changed files with 496 additions and 295 deletions

View File

@ -1,4 +1,4 @@
image: node:8
image: node:9.3
cache:
paths:

View File

@ -10,19 +10,34 @@ build-%:
# usage:
# make stage-beta
# make stage-stable
stage-%:
sync-stage-%:
./scripts/s3sync.sh stage $*
# s3sync with beta.mytrezor.com
# Upload build/beta only
# usage:
# make beta
beta:
sync-beta:
./scripts/s3sync.sh beta beta
# s3sync with wallet.mytrezor.com
# Upload build/stable only
# usage:
# make stable
stable:
./scripts/s3sync.sh stable stable
sync-stable:
./scripts/s3sync.sh stable stable
.DEFAULT_GOAL:= default
default:
@echo "Build:"
@echo "git checkout to desired branch (beta|stable)"
@echo " make build-beta"
@echo " make build-stable"
@echo "Sync:"
@echo "s3 sync desired build to server (beta.mytrezor.com|wallet.mytrezor.com)"
@echo " make sync-beta"
@echo " make sync-stable"
@echo "Staging:"
@echo "s3 sync desired build to stage server (stage.mytrezor.com)"
@echo " make sync-stage-beta"
@echo " make sync-stage-stable"

View File

@ -52,6 +52,7 @@
"react": "^16.4.2",
"react-dom": "^16.2.0",
"react-hot-loader": "^4.3.4",
"react-json-view": "^1.19.1",
"react-qr-svg": "^2.1.0",
"react-redux": "^5.0.7",
"react-router-dom": "^4.2.2",

View File

@ -25,7 +25,19 @@ export const onPinSubmit = (value: string): Action => {
};
};
export const onPassphraseSubmit = (passphrase: string): AsyncAction => async (dispatch: Dispatch): Promise<void> => {
export const onPassphraseSubmit = (passphrase: string): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const { modal } = getState();
if (modal.context !== MODAL.CONTEXT_DEVICE) return;
if (passphrase === '') {
// set standard wallet type if passphrase is blank
dispatch({
type: CONNECT.UPDATE_WALLET_TYPE,
device: modal.device,
hidden: false,
});
}
await TrezorConnect.uiResponse({
type: UI.RECEIVE_PASSPHRASE,
payload: {
@ -106,15 +118,16 @@ export const onDeviceConnect = (device: Device): ThunkAction => (dispatch: Dispa
}
};
export const onWalletTypeRequest = (device: TrezorDevice, hidden: boolean, state: ?string): ThunkAction => (dispatch: Dispatch): void => {
export const onWalletTypeRequest = (hidden: boolean): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
const { modal } = getState();
if (modal.context !== MODAL.CONTEXT_DEVICE) return;
dispatch({
type: MODAL.CLOSE,
});
dispatch({
type: CONNECT.RECEIVE_WALLET_TYPE,
device,
device: modal.device,
hidden,
state,
});
};
@ -129,7 +142,6 @@ export const gotoExternalWallet = (id: string, url: string): ThunkAction => (dis
export default {
onPinSubmit,
onPassphraseSubmit,
// askForRemember,
onRememberDevice,
onForgetDevice,
onForgetSingleDevice,

View File

@ -64,8 +64,8 @@ export const paramsValidation = (params: RouterLocationState): PayloadAction<boo
if (!device) return false;
if (!deviceUtils.isDeviceAccessible(device)) {
// TODO: there should be no access to deep links if device has incorrect mode/firmware
// if (params.hasOwnProperty('network') || params.hasOwnProperty('account')) return false;
// no access to deep links if device has incorrect mode/firmware
if (params.hasOwnProperty('network') || params.hasOwnProperty('account')) return false;
}
}
@ -190,6 +190,8 @@ const getDeviceUrl = (device: TrezorDevice | Device): PayloadAction<?string> =>
url = `/device/${device.path}/bootloader`;
} else if (device.mode === 'initialize') {
url = `/device/${device.features.device_id}/initialize`;
} else if (device.mode === 'seedless') {
url = `/device/${device.features.device_id}/seedless`;
} else if (device.firmware === 'required') {
url = `/device/${device.features.device_id}/firmware-update`;
} else if (typeof device.instance === 'number') {

View File

@ -74,6 +74,9 @@ export const verify = (
): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const selected = getState().wallet.selectedDevice;
if (!selected) return;
dispatch({ type: SIGN_VERIFY.VERIFY_PROGRESS, isVerifyProgress: true });
const response = await TrezorConnect.ethereumVerifyMessage({
device: {
path: selected.path,
@ -87,6 +90,8 @@ export const verify = (
useEmptyPassphrase: selected.useEmptyPassphrase,
});
dispatch({ type: SIGN_VERIFY.VERIFY_PROGRESS, isVerifyProgress: false });
if (response && response.success) {
dispatch({
type: NOTIFICATION.ADD,

View File

@ -78,10 +78,9 @@ export type TrezorConnectAction = {
type: typeof CONNECT.REQUEST_WALLET_TYPE,
device: TrezorDevice
} | {
type: typeof CONNECT.RECEIVE_WALLET_TYPE,
type: typeof CONNECT.RECEIVE_WALLET_TYPE | typeof CONNECT.UPDATE_WALLET_TYPE,
device: TrezorDevice,
hidden: boolean,
state: ?string,
};
declare var LOCAL: ?string;

View File

@ -29,4 +29,5 @@ export const START_ACQUIRING: 'connect__start_acquiring' = 'connect__start_acqui
export const STOP_ACQUIRING: 'connect__stop_acquiring' = 'connect__stop_acquiring';
export const REQUEST_WALLET_TYPE: 'connect__request_wallet_type' = 'connect__request_wallet_type';
export const RECEIVE_WALLET_TYPE: 'connect__receive_wallet_type' = 'connect__receive_wallet_type';
export const RECEIVE_WALLET_TYPE: 'connect__receive_wallet_type' = 'connect__receive_wallet_type';
export const UPDATE_WALLET_TYPE: 'connect__update_wallet_type' = 'connect__update_wallet_type';

View File

@ -5,6 +5,7 @@ import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import colors from 'config/colors';
import { H2 } from 'components/Heading';
import ReactJson from 'react-json-view';
import Icon from 'components/Icon';
import P from 'components/Paragraph';
@ -44,22 +45,17 @@ const Click = styled.div`
}
`;
const Textarea = styled.textarea`
width: 100%;
height: 200px;
min-height: 200px;
resize: vertical;
font-size: 10px;
&:focus {
box-shadow: none;
}
`;
const StyledParagraph = styled(P)`
margin: 10px 0;
`;
const LogWrapper = styled.div`
background: white;
padding: 25px;
height: 300px;
overflow: scroll;
`;
const Log = (props: Props): ?React$Element<string> => {
if (!props.log.opened) return null;
return (
@ -69,7 +65,9 @@ const Log = (props: Props): ?React$Element<string> => {
</Click>
<H2>Log</H2>
<StyledParagraph isSmaller>Attention: The log contains your XPUBs. Anyone with your XPUBs can see your account history.</StyledParagraph>
<Textarea value={JSON.stringify(props.log.entries)} readOnly />
<LogWrapper>
<ReactJson src={props.log.entries} />
</LogWrapper>
</Wrapper>
);
};

View File

@ -1,10 +1,12 @@
/* @flow */
import * as React from 'react';
import styled, { css } from 'styled-components';
import styled from 'styled-components';
import PropTypes from 'prop-types';
import Icon from 'components/Icon';
import colors from 'config/colors';
import { getPrimaryColor, getSecondaryColor } from 'utils/notification';
import Loader from 'components/Loader';
import { TRANSITION } from 'config/variables';
type Props = {
@ -15,59 +17,39 @@ type Props = {
size: number;
};
onClick: () => void;
isLoading?: boolean;
children: React.Node;
};
const LoaderContent = styled.div`
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
display: flex;
justify-content: center;
align-items: center;
cursor: default;
background: ${props => getSecondaryColor(props.type)};
`;
const Wrapper = styled.button`
padding: 12px 58px;
border-radius: 3px;
background: transparent;
font-size: 14px;
position: relative;
font-weight: 300;
cursor: pointer;
color: ${colors.WHITE};
border: 0;
color: ${props => getPrimaryColor(props.type)};
border: 1px solid ${props => getPrimaryColor(props.type)};
transition: ${TRANSITION.HOVER};
${props => props.type === 'info' && css`
border: 1px solid ${colors.INFO_PRIMARY};
color: ${colors.INFO_PRIMARY};
&:hover {
color: ${colors.WHITE};
background: ${colors.INFO_PRIMARY};
}
`}
${props => props.type === 'success' && css`
border: 1px solid ${colors.SUCCESS_PRIMARY};
color: ${colors.SUCCESS_PRIMARY};
&:hover {
color: ${colors.WHITE};
background: ${colors.SUCCESS_PRIMARY};
}
`}
${props => props.type === 'error' && css`
border: 1px solid ${colors.ERROR_PRIMARY};
color: ${colors.ERROR_PRIMARY};
&:hover {
color: ${colors.WHITE};
background: ${colors.ERROR_PRIMARY};
}
`}
${props => props.type === 'warning' && css`
border: 1px solid ${colors.WARNING_PRIMARY};
color: ${colors.WARNING_PRIMARY};
&:hover {
color: ${colors.WHITE};
background: ${colors.WARNING_PRIMARY};
}
`}
&:hover {
color: ${colors.WHITE};
background: ${props => getPrimaryColor(props.type)};
}
`;
const IconWrapper = styled.span`
@ -75,13 +57,18 @@ const IconWrapper = styled.span`
`;
const NotificationButton = ({
type, icon, onClick, children,
type, icon, onClick, children, isLoading,
}: Props) => (
<Wrapper
icon={icon}
onClick={onClick}
type={type}
>
{isLoading && (
<LoaderContent type={type}>
<Loader size={30} />
</LoaderContent>
)}
{icon && (
<IconWrapper>
<Icon
@ -102,6 +89,7 @@ NotificationButton.propTypes = {
color: PropTypes.string,
size: PropTypes.number,
}),
isLoading: PropTypes.bool,
onClick: PropTypes.func,
children: PropTypes.node.isRequired,
};

View File

@ -1,9 +1,7 @@
/* @flow */
import * as React from 'react';
import styled, { css } from 'styled-components';
import colors from 'config/colors';
import { getColor, getIcon } from 'utils/notification';
import styled from 'styled-components';
import { getPrimaryColor, getSecondaryColor, getIcon } from 'utils/notification';
import Icon from 'components/Icon';
import icons from 'config/icons';
import { FONT_WEIGHT, FONT_SIZE } from 'config/variables';
@ -28,34 +26,14 @@ type Props = {
const Wrapper = styled.div`
width: 100%;
position: relative;
color: ${colors.TEXT_PRIMARY};
background: ${colors.TEXT_SECONDARY};
padding: 24px 48px 9px 24px;
display: flex;
flex-direction: row;
text-align: left;
justify-content: center;
align-items: center;
${props => props.type === 'info' && css`
color: ${colors.INFO_PRIMARY};
background: ${colors.INFO_SECONDARY};
`}
${props => props.type === 'success' && css`
color: ${colors.SUCCESS_PRIMARY};
background: ${colors.SUCCESS_SECONDARY};
`}
${props => props.type === 'warning' && css`
color: ${colors.WARNING_PRIMARY};
background: ${colors.WARNING_SECONDARY};
`}
${props => props.type === 'error' && css`
color: ${colors.ERROR_PRIMARY};
background: ${colors.ERROR_SECONDARY};
`}
color: ${props => getPrimaryColor(props.type)};
background: ${props => getSecondaryColor(props.type)};
`;
const Body = styled.div`
@ -119,7 +97,7 @@ const Notification = (props: Props): React$Element<string> => {
{props.cancelable && (
<CloseClick onClick={() => close()}>
<Icon
color={getColor(props.type)}
color={getPrimaryColor(props.type)}
icon={icons.CLOSE}
size={20}
/>
@ -128,7 +106,7 @@ const Notification = (props: Props): React$Element<string> => {
<Body>
<IconWrapper>
<StyledIcon
color={getColor(props.type)}
color={getPrimaryColor(props.type)}
icon={getIcon(props.type)}
/>
</IconWrapper>

View File

@ -25,6 +25,7 @@ const InputIconWrapper = styled.div`
flex: 1;
position: relative;
display: inline-block;
background: white;
`;
const TopLabel = styled.span`
@ -43,10 +44,12 @@ const StyledInput = styled.input`
color: ${props => (props.color ? props.color : colors.TEXT)};
border-radius: 2px;
${props => props.hasAddon && css`
border-top-right-radius: 0;
border-bottom-right-radius: 0;
`}
border: 1px solid ${colors.DIVIDER};
border-color: ${props => props.borderColor};
@ -58,6 +61,15 @@ const StyledInput = styled.input`
background: ${colors.GRAY_LIGHT};
color: ${colors.TEXT_SECONDARY};
}
${props => props.trezorAction && css`
z-index: 10001;
position: relative; /* bigger than modal container */
border-color: ${colors.WHITE};
border-width: 2px;
transform: translate(-1px, -1px);
background: ${colors.DIVIDER};
`};
`;
const StyledIcon = styled(Icon)`
@ -73,6 +85,49 @@ const BottomText = styled.span`
color: ${props => (props.color ? props.color : colors.TEXT_SECONDARY)};
`;
const Overlay = styled.div`
${props => props.isPartiallyHidden && css`
bottom: 0;
border: 1px solid ${colors.DIVIDER};
border-radius: 2px;
position: absolute;
width: 100%;
height: 100%;
background-image: linear-gradient(to right,
rgba(0,0,0, 0) 0%,
rgba(255,255,255, 1) 220px
);
`}
`;
const TrezorAction = styled.div`
display: ${props => (props.action ? 'flex' : 'none')};
align-items: center;
height: 37px;
margin: 0px 10px;
padding: 0 14px 0 5px;
position: absolute;
top: 45px;
background: black;
color: ${colors.WHITE};
border-radius: 5px;
line-height: 37px;
z-index: 10001;
transform: translate(-1px, -1px);
`;
const ArrowUp = styled.div`
position: absolute;
top: -9px;
left: 12px;
width: 0;
height: 0;
border-left: 9px solid transparent;
border-right: 9px solid transparent;
border-bottom: 9px solid black;
z-index: 10001;
`;
class Input extends PureComponent {
getIcon(inputState) {
let icon = [];
@ -114,8 +169,11 @@ class Input extends PureComponent {
color={this.getColor(this.props.state)}
/>
)}
<Overlay isPartiallyHidden={this.props.isPartiallyHidden} />
{this.props.icon}
<StyledInput
isSmallText={this.props.isSmallText}
trezorAction={this.props.trezorAction}
hasIcon={this.getIcon(this.props.state).length > 0}
innerRef={this.props.innerRef}
hasAddon={!!this.props.sideAddons}
@ -133,6 +191,9 @@ class Input extends PureComponent {
name={this.props.name}
data-lpignore="true"
/>
<TrezorAction action={this.props.trezorAction}>
<ArrowUp />{this.props.trezorAction}
</TrezorAction>
</InputIconWrapper>
{this.props.sideAddons && this.props.sideAddons.map(sideAddon => sideAddon)}
</InputWrapper>
@ -156,16 +217,19 @@ Input.propTypes = {
autocomplete: PropTypes.string,
autocorrect: PropTypes.string,
autocapitalize: PropTypes.string,
icon: PropTypes.node,
spellCheck: PropTypes.string,
value: PropTypes.string,
onChange: PropTypes.func,
state: PropTypes.string,
bottomText: PropTypes.string,
topLabel: PropTypes.node,
trezorAction: PropTypes.node,
sideAddons: PropTypes.arrayOf(PropTypes.node),
isDisabled: PropTypes.bool,
name: PropTypes.string,
isSmallText: PropTypes.string,
isPartiallyHidden: PropTypes.bool,
};
Input.defaultProps = {

View File

@ -91,18 +91,14 @@ class WalletType extends PureComponent<Props> {
keyboardHandler(event: KeyboardEvent): void {
if (event.keyCode === 13) {
event.preventDefault();
this.changeType(false);
this.props.onWalletTypeRequest(false);
}
}
keyboardHandler: (event: KeyboardEvent) => void;
changeType(hidden: boolean, state: ?string) {
this.props.onWalletTypeRequest(this.props.device, hidden, state);
}
render() {
const { device, onCancel } = this.props;
const { device, onCancel, onWalletTypeRequest } = this.props;
return (
<Wrapper>
@ -122,7 +118,7 @@ class WalletType extends PureComponent<Props> {
Standard Wallet
</Header>
<P isSmaller>Continue to access your standard wallet.</P>
<StyledButton onClick={() => this.changeType(false, device.state)}>Go to your standard wallet</StyledButton>
<StyledButton onClick={() => onWalletTypeRequest(false)}>Go to your standard wallet</StyledButton>
</Content>
<Content>
<Tooltip
@ -146,7 +142,7 @@ class WalletType extends PureComponent<Props> {
Hidden Wallet
</Header>
<P isSmaller>You will be asked to enter your passphrase to unlock your hidden wallet.</P>
<StyledButton isWhite onClick={() => this.changeType(true, device.state)}>Go to your hidden wallet</StyledButton>
<StyledButton isWhite onClick={() => onWalletTypeRequest(true)}>Go to your hidden wallet</StyledButton>
</Content>
</Wrapper>
);

View File

@ -161,24 +161,18 @@ class Passphrase extends PureComponent<Props, State> {
}
handleCheckboxClick() {
// If passphrase was visible and now shouldn't be --> delete the value of passphraseCheckInputValue
// doPassphraseInputsMatch
// - if passphrase was visible and now shouldn't be --> doPassphraseInputsMatch = false
// - because passphraseCheckInputValue will be empty string
// - if passphrase wasn't visibe and now should be --> doPassphraseInputsMatch = true
// - because passphraseCheckInputValue will be same as passphraseInputValue
let doInputsMatch = false;
let match = false;
if (this.state.shouldShowSingleInput || this.state.passphraseInputValue === this.state.passphraseCheckInputValue) {
doInputsMatch = true;
match = true;
} else {
doInputsMatch = !!this.state.isPassphraseHidden;
match = !!this.state.isPassphraseHidden;
}
this.setState(previousState => ({
isPassphraseHidden: !previousState.isPassphraseHidden,
passphraseInputValue: previousState.isPassphraseHidden ? previousState.passphraseInputValue : '',
passphraseCheckInputValue: previousState.isPassphraseHidden ? previousState.passphraseInputValue : '',
doPassphraseInputsMatch: doInputsMatch,
passphraseInputValue: previousState.passphraseInputValue,
passphraseCheckInputValue: previousState.passphraseCheckInputValue,
doPassphraseInputsMatch: match,
}));
}
@ -239,11 +233,9 @@ class Passphrase extends PureComponent<Props, State> {
/>
</Row>
)}
{!this.state.doPassphraseInputsMatch && (
<PassphraseError>Passphrases do not match</PassphraseError>
)}
<Row>
<Checkbox
isChecked={!this.state.isPassphraseHidden}
@ -259,10 +251,8 @@ class Passphrase extends PureComponent<Props, State> {
>Enter
</Button>
</Row>
<Footer>
<P isSmaller>
Changed your mind? &nbsp;
<P isSmaller>Changed your mind? &nbsp;
<LinkButton
isGreen
onClick={() => this.submitPassphrase(true)}

View File

@ -5,7 +5,7 @@ import Icon from 'components/Icon';
import ICONS from 'config/icons';
import colors from 'config/colors';
import Notification from 'components/Notification';
import { getIcon, getColor } from 'utils/notification';
import { getIcon, getPrimaryColor } from 'utils/notification';
const Wrapper = styled.div``;
@ -65,7 +65,7 @@ class Group extends PureComponent {
render() {
const { type, groupNotifications, close } = this.props;
const color = getColor(type);
const color = getPrimaryColor(type);
return (
<Wrapper>
{groupNotifications.length > 1 && (

View File

@ -54,7 +54,6 @@ class NotificationsGroup extends PureComponent {
// key: 5,
// title: 'this is a title of warning notification as',
// type: 'success',
// message: <Link href="link" isGreen>See transaction detail</Link>,
// },
// {
// key: 6,

View File

@ -0,0 +1,70 @@
import React from 'react';
import RcTooltip from 'rc-tooltip';
import colors from 'config/colors';
import Link from 'components/Link';
import styled from 'styled-components';
import PropTypes from 'prop-types';
const Wrapper = styled.div``;
const Content = styled.div`
max-width: ${props => `${props.maxWidth}px` || 'auto'};
`;
const ContentWrapper = styled.div`
display: block;
`;
const ReadMore = styled.div`
margin-top: 15px;
padding: 10px 0 5px 0;
text-align: center;
width: 100%;
color: ${colors.WHITE};
border-top: 1px solid ${colors.TEXT_SECONDARY};
`;
const Tooltip = ({
maxWidth,
className,
placement,
content,
readMoreLink,
children,
}) => (
<Wrapper className={className}>
<RcTooltip
arrowContent={<div className="rc-tooltip-arrow-inner" />}
placement={placement}
overlay={() => (
<ContentWrapper>
<Content maxWidth={maxWidth}>{content}</Content>
{readMoreLink && (
<Link href={readMoreLink}>
<ReadMore>Read more</ReadMore>
</Link>
)
}
</ContentWrapper>)}
>
{children}
</RcTooltip>
</Wrapper>
);
Tooltip.propTypes = {
className: PropTypes.string,
placement: PropTypes.string,
children: PropTypes.oneOfType([
PropTypes.element,
PropTypes.string,
]),
maxWidth: PropTypes.number,
content: PropTypes.oneOfType([
PropTypes.element,
PropTypes.string,
]),
readMoreLink: PropTypes.string,
};
export default Tooltip;

View File

@ -17,7 +17,7 @@
</head>
<body>
<!--[if lt IE 8]>
<div class="unsupported-browsers">
<div class="unsupported-browsers">
<div class="header"></div>
<h1>Your browser is not supported</h1>
<p>Please choose one of the supported browsers</p>
@ -33,6 +33,6 @@
</div>
</div>
<![endif]-->
<div id="root"></div>
<div id="trezor-wallet-root"></div>
</body>
</html>

View File

@ -4,7 +4,7 @@ import { render } from 'react-dom';
import baseStyles from 'support/styles';
import App from 'views/index';
const root: ?HTMLElement = document.getElementById('root');
const root: ?HTMLElement = document.getElementById('trezor-wallet-root');
if (root) {
baseStyles();
render(<App />, root);

View File

@ -317,6 +317,7 @@ export default function devices(state: State = initialState, action: Action): St
return onSelectedDevice(state, action.device);
case CONNECT.RECEIVE_WALLET_TYPE:
case CONNECT.UPDATE_WALLET_TYPE:
return onChangeWalletType(state, action.device, action.hidden);
default:

View File

@ -26,6 +26,12 @@ export default (state: State = initialState, action: Action): State => {
signature: action.signature,
};
case SIGN_VERIFY.VERIFY_PROGRESS:
return {
...state,
isVerifyProgress: action.isVerifyProgress,
};
case SIGN_VERIFY.CLEAR:
return {
...initialState,

View File

@ -105,22 +105,22 @@ const WalletService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispa
api.dispatch(SendFormActionActions.observe(prevState, action));
}
} else {
switch (action.type) {
case CONNECT.AUTH_DEVICE:
// selected device did changed
// try to restore discovery after device authentication
api.dispatch(DiscoveryActions.restore());
break;
case CONNECT.RECEIVE_WALLET_TYPE:
if (action.state) {
api.dispatch(RouterActions.selectFirstAvailableDevice(true));
}
api.dispatch(TrezorConnectActions.authorizeDevice());
break;
default: break;
// no changes in common values
if (action.type === CONNECT.RECEIVE_WALLET_TYPE) {
if (action.device.state) {
// redirect to root view (Dashboard) if device was authenticated before
api.dispatch(RouterActions.selectFirstAvailableDevice(true));
}
api.dispatch(TrezorConnectActions.authorizeDevice());
}
if (action.type === CONNECT.AUTH_DEVICE) {
// selected device did changed
// try to restore discovery after device authentication
api.dispatch(DiscoveryActions.restore());
}
}
// even if "selectedDevice" didn't change because it was updated on DEVICE.CHANGED before DEVICE.CONNECT action
// try to restore discovery
if (action.type === DEVICE.CONNECT) {

View File

@ -47,6 +47,11 @@ export const routes: Array<Route> = [
pattern: '/device/:device/initialize',
fields: ['device', 'initialize'],
},
{
name: 'wallet-seedless',
pattern: '/device/:device/seedless',
fields: ['device', 'seedless'],
},
{
name: 'wallet-firmware-update',
pattern: '/device/:device/firmware-update',

View File

@ -44,7 +44,7 @@ const baseStyles = () => injectGlobal`
outline: 0;
}
#root {
#trezor-wallet-root {
height: 100%;
}

View File

@ -21,6 +21,9 @@ export const getStatus = (device: TrezorDevice): string => {
if (device.mode === 'initialize') {
return 'initialize';
}
if (device.mode === 'seedless') {
return 'seedless';
}
if (device.firmware === 'required') {
return 'firmware-required';
}
@ -57,6 +60,8 @@ export const getStatusName = (deviceStatus: string): string => {
return 'Connected (bootloader mode)';
case 'initialize':
return 'Connected (not initialized)';
case 'seedless':
return 'Connected (seedless mode)';
case 'firmware-required':
return 'Connected (update required)';
case 'firmware-recommended':
@ -81,8 +86,8 @@ export const isDisabled = (selectedDevice: TrezorDevice, devices: Array<TrezorDe
if (devices.length < 1) return true; // no devices
if (devices.length === 1) {
if (!selectedDevice.features) return true; // unacquired, unreadable
if (selectedDevice.mode !== 'normal') return true; // bootloader, not initialized
if (selectedDevice.firmware === 'required') return true; // bootloader, not initialized
if (selectedDevice.mode !== 'normal') return true; // bootloader, not initialized, seedless
if (selectedDevice.firmware === 'required') return true;
}
return false; // default
};
@ -112,6 +117,7 @@ export const getStatusColor = (deviceStatus: string): string => {
return colors.ERROR_PRIMARY;
case 'bootloader':
case 'initialize':
case 'seedless':
case 'firmware-recommended':
case 'used-in-other-window':
case 'unacquired':

View File

@ -1,7 +1,7 @@
import colors from 'config/colors';
import icons from 'config/icons';
const getColor = (type) => {
const getPrimaryColor = (type) => {
let color;
switch (type) {
case 'info':
@ -23,9 +23,32 @@ const getColor = (type) => {
return color;
};
const getSecondaryColor = (type) => {
let color;
switch (type) {
case 'info':
color = colors.INFO_SECONDARY;
break;
case 'error':
color = colors.ERROR_SECONDARY;
break;
case 'warning':
color = colors.WARNING_SECONDARY;
break;
case 'success':
color = colors.SUCCESS_SECONDARY;
break;
default:
color = null;
}
return color;
};
const getIcon = type => icons[type.toUpperCase()];
export {
getColor,
getPrimaryColor,
getSecondaryColor,
getIcon,
};

View File

@ -1,14 +1,21 @@
/* @flow */
import React from 'react';
import { QRCode } from 'react-qr-svg';
<<<<<<< HEAD
import styled, { css } from 'styled-components';
=======
import styled from 'styled-components';
import media from 'styled-media-query';
import { H2 } from 'components/Heading';
>>>>>>> master
import Button from 'components/Button';
import Icon from 'components/Icon';
import Tooltip from 'components/Tooltip';
import Input from 'components/inputs/Input';
import ICONS from 'config/icons';
import colors from 'config/colors';
import { FONT_SIZE, FONT_WEIGHT, FONT_FAMILY } from 'config/variables';
import { CONTEXT_DEVICE } from 'actions/constants/modal';
import Content from 'views/Wallet/components/Content';
@ -23,10 +30,9 @@ const Label = styled.div`
`;
const AddressWrapper = styled.div`
position: relative;
display: flex;
flex-wrap: wrap;
flex-direction: ${props => (props.isShowingQrCode ? 'column' : 'row')};
flex-direction: row;
`;
const StyledQRCode = styled(QRCode)`
@ -35,87 +41,21 @@ const StyledQRCode = styled(QRCode)`
border: 1px solid ${colors.BODY};
`;
const ValueWrapper = styled.div`
font-size: ${FONT_SIZE.SMALL};
height: 40px;
font-weight: ${FONT_WEIGHT.SMALLEST};
line-height: 1.42857143;
font-family: ${FONT_FAMILY.MONOSPACE};
color: ${colors.TEXT_PRIMARY};
border: 1px solid ${colors.DIVIDER};
border-radius: 3px;
padding: 10px 12px;
padding-right: 38px;
position: relative;
flex: 1;
user-select: all;
${props => props.isHidden && css`
padding-right: 6px;
user-select: none;
border-radius: 3px 0px 0px 3px;
&:after {
content: '';
position: absolute;
left: 0;
top: 0;
height: 100%;
width: 100%;
background: linear-gradient(to right,
rgba(255,255,255, 0) 0%,
rgba(255,255,255, 1) 220px
);
pointer-events: none; /* so the text is still selectable */
}
`};
${props => props.isVerifying && css`
z-index: 10001; /* bigger than modal container */
border-color: ${colors.WHITE};
border-width: 2px;
transform: translate(-1px, -1px);
background: ${colors.DIVIDER};
`};
`;
const ArrowUp = styled.div`
position: absolute;
top: 35px;
left: 70px;
width: 0;
height: 0;
border-left: 9px solid transparent;
border-right: 9px solid transparent;
border-bottom: 9px solid black;
z-index: 10001;
`;
const AddressInfoText = styled.div`
display: flex;
align-items: center;
height: 37px;
margin: 0px 2px;
padding: 0 14px 0 5px;
position: absolute;
top: 45px;
background: black;
color: ${colors.WHITE};
border-radius: 5px;
line-height: 37px;
z-index: 10001;
transform: translate(-1px, -1px);
`;
const ShowAddressButton = styled(Button)`
padding-top: 0;
padding-bottom: 0;
padding-left: 10px;
min-width: 195px;
padding: 0;
white-space: nowrap;
display: flex;
height: 40px;
align-items: center;
justify-content: center;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
${media.lessThan('795px')`
margin-top: 10px;
`}
`;
const ShowAddressIcon = styled(Icon)`
@ -128,9 +68,28 @@ const EyeButton = styled(Button)`
z-index: 10001;
padding: 0;
width: 30px;
background: white;
top: 5px;
position: absolute;
right: 10px;
&:hover {
background: white;
}
`;
const Row = styled.div`
display: flex;
width: 100%;
${media.lessThan('795px')`
flex-direction: column;
`}
`;
const QrWrapper = styled.div`
display: flex;
flex-direction: column;
`;
const AccountReceive = (props: Props) => {
@ -159,57 +118,51 @@ const AccountReceive = (props: Props) => {
return (
<Content>
<React.Fragment>
<Title>Receive Ethereum or tokens</Title>
<AddressWrapper
isShowingQrCode={addressVerified || addressUnverified}
>
{isAddressVerifying && (
<AddressInfoText>Confirm address on Trezor</AddressInfoText>
)}
{((addressVerified || addressUnverified) && !isAddressVerifying) && (
<Tooltip
placement="left"
content={(
<VerifyAddressTooltip
isConnected={device.connected}
isAvailable={device.available}
addressUnverified={addressUnverified}
/>
)}
>
<EyeButton
isTransparent
onClick={() => props.showAddress(account.addressPath)}
>
<Icon
icon={addressUnverified ? ICONS.EYE_CROSSED : ICONS.EYE}
color={addressUnverified ? colors.ERROR_PRIMARY : colors.TEXT_PRIMARY}
/>
</EyeButton>
</Tooltip>
)}
<ValueWrapper
isHidden={isAddressHidden}
isVerifying={isAddressVerifying}
>
{address}
</ValueWrapper>
{isAddressVerifying && (
<React.Fragment>
<ArrowUp />
<AddressInfoText>
<Icon
icon={ICONS.T1}
color={colors.WHITE}
/>
<H2>Receive Ethereum or tokens</H2>
<AddressWrapper isShowingQrCode={addressVerified || addressUnverified}>
<Label>Address</Label>
<Row>
<Input
type="text"
value={address}
isPartiallyHidden={isAddressHidden}
trezorAction={isAddressVerifying ? (
<React.Fragment>
<Icon
icon={ICONS.T1}
color={colors.WHITE}
/>
Check address on your Trezor
</AddressInfoText>
</React.Fragment>
)}
{(addressVerified || addressUnverified) && (
<React.Fragment>
</React.Fragment>
) : null}
icon={((addressVerified || addressUnverified) && !isAddressVerifying) && (
<Tooltip
placement="left"
content={(
<VerifyAddressTooltip
isConnected={device.connected}
isAvailable={device.available}
addressUnverified={addressUnverified}
/>
)}
>
<EyeButton onClick={() => props.showAddress(account.addressPath)}>
<Icon
icon={addressUnverified ? ICONS.EYE_CROSSED : ICONS.EYE}
color={addressUnverified ? colors.ERROR_PRIMARY : colors.TEXT_PRIMARY}
/>
</EyeButton>
</Tooltip>
)}
/>
{!(addressVerified || addressUnverified) && (
<ShowAddressButton onClick={() => props.showAddress(account.addressPath)} isDisabled={device.connected && !discovery.completed}>
<ShowAddressIcon icon={ICONS.EYE} color={colors.WHITE} />Show full address
</ShowAddressButton>
)}
</Row>
{(addressVerified || addressUnverified) && !isAddressVerifying && (
<QrWrapper>
<Label>QR code</Label>
<StyledQRCode
bgColor="#FFFFFF"
@ -218,12 +171,7 @@ const AccountReceive = (props: Props) => {
style={{ width: 150 }}
value={account.address}
/>
</React.Fragment>
)}
{!(addressVerified || addressUnverified) && (
<ShowAddressButton onClick={() => props.showAddress(account.addressPath)} isDisabled={device.connected && !discovery.completed}>
<ShowAddressIcon icon={ICONS.EYE} color={colors.WHITE} />Show full address
</ShowAddressButton>
</QrWrapper>
)}
</AddressWrapper>
</React.Fragment>

View File

@ -13,7 +13,8 @@ type OwnProps = {}
export type StateProps = {
selectedAccount: $ElementType<State, 'selectedAccount'>,
signature: string,
isSignProgress: boolean
isSignProgress: boolean,
isVerifyProgress: boolean
}
export type DispatchProps = {
@ -26,6 +27,7 @@ const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: St
selectedAccount: state.selectedAccount,
signature: state.signVerifyReducer.signature,
isSignProgress: state.signVerifyReducer.isSignProgress,
isVerifyProgress: state.signVerifyReducer.isVerifyProgress,
});
const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => ({

View File

@ -0,0 +1,37 @@
import styled from 'styled-components';
import { H2 } from 'components/Heading';
import Button from 'components/Button';
import Paragraph from 'components/Paragraph';
import React from 'react';
import { connect } from 'react-redux';
const Wrapper = styled.div`
display: flex;
flex: 1;
justify-content: center;
align-items: center;
`;
const Row = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
`;
const StyledParagraph = styled(Paragraph)`
margin: 10px 50px;
display: block;
text-align: center;
`;
const Seedless = () => (
<Wrapper>
<Row>
<H2>Device is in seedless mode</H2>
<StyledParagraph>It&apos;s not suitable to use this service.</StyledParagraph>
</Row>
</Wrapper>
);
export default connect(null, null)(Seedless);

View File

@ -26,6 +26,7 @@ import WalletSettings from 'views/Wallet/views/WalletSettings';
import WalletBootloader from 'views/Wallet/views/Bootloader';
import WalletFirmwareUpdate from 'views/Wallet/views/FirmwareUpdate';
import WalletInitialize from 'views/Wallet/views/Initialize';
import WalletSeedless from 'views/Wallet/views/Seedless';
import WalletAcquire from 'views/Wallet/views/Acquire';
import WalletUnreadableDevice from 'views/Wallet/views/UnreadableDevice';
@ -47,6 +48,7 @@ const App = () => (
<Route exact path={getPattern('wallet-unreadable')} component={WalletUnreadableDevice} />
<Route exact path={getPattern('wallet-bootloader')} component={WalletBootloader} />
<Route exact path={getPattern('wallet-initialize')} component={WalletInitialize} />
<Route exact path={getPattern('wallet-seedless')} component={WalletSeedless} />
<Route exact path={getPattern('wallet-firmware-update')} component={WalletFirmwareUpdate} />
<Route exact path={getPattern('wallet-device-settings')} component={WalletDeviceSettings} />
<Route exact path={getPattern('wallet-account-summary')} component={AccountSummary} />

View File

@ -1690,6 +1690,10 @@ balanced-match@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
base16@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/base16/-/base16-1.0.0.tgz#e297f60d7ec1014a7a971a39ebc8a98c0b681e70"
base64-js@0.0.8:
version "0.0.8"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-0.0.8.tgz#1101e9544f4a76b1bc3b26d452ca96d7a35e7978"
@ -4149,6 +4153,24 @@ fb-watchman@^2.0.0:
dependencies:
bser "^2.0.0"
fbemitter@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/fbemitter/-/fbemitter-2.1.1.tgz#523e14fdaf5248805bb02f62efc33be703f51865"
dependencies:
fbjs "^0.8.4"
fbjs@^0.8.0, fbjs@^0.8.4, fbjs@^0.8.5:
version "0.8.17"
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd"
dependencies:
core-js "^1.0.0"
isomorphic-fetch "^2.1.1"
loose-envify "^1.0.0"
object-assign "^4.1.0"
promise "^7.1.1"
setimmediate "^1.0.5"
ua-parser-js "^0.7.18"
fbjs@^0.8.16:
version "0.8.16"
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db"
@ -4161,18 +4183,6 @@ fbjs@^0.8.16:
setimmediate "^1.0.5"
ua-parser-js "^0.7.9"
fbjs@^0.8.5:
version "0.8.17"
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd"
dependencies:
core-js "^1.0.0"
isomorphic-fetch "^2.1.1"
loose-envify "^1.0.0"
object-assign "^4.1.0"
promise "^7.1.1"
setimmediate "^1.0.5"
ua-parser-js "^0.7.18"
fd-slicer@~1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
@ -4342,6 +4352,13 @@ flush-write-stream@^1.0.0:
inherits "^2.0.1"
readable-stream "^2.0.4"
flux@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/flux/-/flux-3.1.3.tgz#d23bed515a79a22d933ab53ab4ada19d05b2f08a"
dependencies:
fbemitter "^2.0.0"
fbjs "^0.8.0"
follow-redirects@^1.0.0:
version "1.4.1"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.4.1.tgz#d8120f4518190f55aac65bb6fc7b85fcd666d6aa"
@ -6390,10 +6407,18 @@ lodash.camelcase@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
lodash.curry@^4.0.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/lodash.curry/-/lodash.curry-4.1.1.tgz#248e36072ede906501d75966200a86dab8b23170"
lodash.debounce@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
lodash.flow@^3.3.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/lodash.flow/-/lodash.flow-3.5.0.tgz#87bf40292b8cf83e4e8ce1a3ae4209e20071675a"
lodash.isarguments@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a"
@ -8183,6 +8208,10 @@ punycode@^2.1.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
pure-color@^1.2.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/pure-color/-/pure-color-1.3.0.tgz#1fe064fb0ac851f0de61320a8bf796836422f33e"
q@^1.1.2:
version "1.5.1"
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
@ -8347,6 +8376,15 @@ rc@^1.1.7:
minimist "^1.2.0"
strip-json-comments "~2.0.1"
react-base16-styling@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/react-base16-styling/-/react-base16-styling-0.6.0.tgz#ef2156d66cf4139695c8a167886cb69ea660792c"
dependencies:
base16 "^1.0.0"
lodash.curry "^4.0.1"
lodash.flow "^3.3.0"
pure-color "^1.2.0"
"react-dom@^15.4.2 || ^16.0.0", react-dom@^16.2.0:
version "16.2.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.2.0.tgz#69003178601c0ca19b709b33a83369fe6124c044"
@ -8377,6 +8415,15 @@ react-is@^16.3.1:
version "16.4.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.4.1.tgz#d624c4650d2c65dbd52c72622bbf389435d9776e"
react-json-view@^1.19.1:
version "1.19.1"
resolved "https://registry.yarnpkg.com/react-json-view/-/react-json-view-1.19.1.tgz#95d8e59e024f08a25e5dc8f076ae304eed97cf5c"
dependencies:
flux "^3.1.3"
react-base16-styling "^0.6.0"
react-lifecycles-compat "^3.0.4"
react-textarea-autosize "^6.1.0"
react-lifecycles-compat@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
@ -8453,9 +8500,15 @@ react-select@2.0.0:
react-input-autosize "^2.2.1"
react-transition-group "^2.2.1"
<<<<<<< HEAD
react-textarea-autosize@^7.0.4:
version "7.0.4"
resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-7.0.4.tgz#4e4be649b544a88713e7b5043f76950f35d3d503"
=======
react-textarea-autosize@^6.1.0:
version "6.1.0"
resolved "http://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-6.1.0.tgz#df91387f8a8f22020b77e3833c09829d706a09a5"
>>>>>>> master
dependencies:
prop-types "^15.6.0"