pull/200/head
Vladimir Volek 6 years ago
commit 851c7f23cb

@ -43,7 +43,7 @@
"settings": { "settings": {
"import/resolver": { "import/resolver": {
"babel-module": {} "babel-module": {}
}, },
"import/ignore": [ "import/ignore": [
"\\.(scss|less|css)$" "\\.(scss|less|css)$"
] ]

@ -41,6 +41,8 @@ build beta:
script: script:
- yarn install - yarn install
- yarn run build:beta - yarn run build:beta
only:
- beta
artifacts: artifacts:
expire_in: 1 week expire_in: 1 week
paths: paths:
@ -52,6 +54,8 @@ build stable:
script: script:
- yarn install - yarn install
- yarn run build:stable - yarn run build:stable
only:
- stable
artifacts: artifacts:
expire_in: 1 week expire_in: 1 week
paths: paths:
@ -64,6 +68,9 @@ deploy review:
GIT_STRATEGY: none GIT_STRATEGY: none
dependencies: dependencies:
- build development - build development
environment:
name: $CI_BUILD_REF_NAME
url: $BASE_REVIEW_URL/$CI_BUILD_REF_NAME
script: script:
- echo "Deploy a review app" - echo "Deploy a review app"
- '[ -z "${DEPLOY_BASE_DIR}" ] && echo "Deploy base dir cannot be empty" && exit 255' - '[ -z "${DEPLOY_BASE_DIR}" ] && echo "Deploy base dir cannot be empty" && exit 255'

@ -0,0 +1,22 @@
# 1.0.2-beta
- Fiat rates from coingecko (https://github.com/trezor/trezor-wallet/pull/242)
- firmware update link to beta-wallet (https://github.com/trezor/trezor-wallet/commit/b9b7d2d076f2d4c59ae2e055dc140cda6aaa5512)
- set default gas limit button
- update list of ETH and ETC tokens
- added 1 click to select value in input (https://github.com/trezor/trezor-wallet/issues/251)
- added account loader (https://github.com/trezor/trezor-wallet/pull/225)
# 1.0.1-beta
__added__
- DigiByte in coin menu
- blocking device with seedless setup
__fixed__
- token input in "Account/Summary" (https://github.com/trezor/trezor-wallet/issues/235)
- "Go to your standard wallet" button in passphrase modal (https://github.com/trezor/trezor-wallet/issues/222)
- Double click on "show passphrase" (https://github.com/trezor/trezor-wallet/issues/221)
- images preloader for offline status (https://github.com/trezor/trezor-wallet/issues/218)
# 1.0.0-beta
- first release

@ -70,7 +70,7 @@
"styled-components": "^3.4.9", "styled-components": "^3.4.9",
"styled-media-query": "^2.0.2", "styled-media-query": "^2.0.2",
"styled-normalize": "^8.0.0", "styled-normalize": "^8.0.0",
"trezor-connect": "6.0.0", "trezor-connect": "6.0.2",
"web3": "1.0.0-beta.35", "web3": "1.0.0-beta.35",
"webpack": "^4.16.3", "webpack": "^4.16.3",
"webpack-build-notifier": "^0.1.29", "webpack-build-notifier": "^0.1.29",

@ -59,11 +59,11 @@
"fiatValueTickers": [ "fiatValueTickers": [
{ {
"network": "eth", "network": "eth",
"url": "https://api.coinmarketcap.com/v1/ticker/ethereum/" "url": "https://api.coingecko.com/api/v3/coins/ethereum"
}, },
{ {
"network": "etc", "network": "etc",
"url": "https://api.coinmarketcap.com/v1/ticker/ethereum-classic/" "url": "https://api.coingecko.com/api/v3/coins/ethereum-classic"
} }
], ],

@ -1,14 +1,14 @@
[ [
{ {
"address": "0x6F6DEb5db0C4994A8283A01D6CFeEB27Fc3bBe9C", "address": "0x085fb4f24031EAedbC2B611AA528f22343eB52Db",
"name": "SmartBillions", "decimals": 8,
"symbol": "Smart", "name": "BEC",
"decimals": 0 "symbol": "BEC"
}, },
{ {
"address": "0x5ace17f87c7391e5792a7683069a8025b83bbd85", "address": "0x6ADa6F48C815689502C43eC1a59F1b5DD3C04E1F",
"name": "SmartBillions Token", "decimals": 18,
"symbol": "PLAY", "name": "UniversalCoin",
"decimals": 0 "symbol": "UNV"
} }
] ]

File diff suppressed because it is too large Load Diff

@ -38,81 +38,106 @@ export const dispose = (): Action => ({
type: ACCOUNT.DISPOSE, type: ACCOUNT.DISPOSE,
}); });
const getAccountStatus = (state: State, selectedAccount: SelectedAccountState): ?AccountStatus => { const getAccountLoader = (state: State, selectedAccount: SelectedAccountState): ?AccountStatus => {
const device = state.wallet.selectedDevice; const device = state.wallet.selectedDevice;
if (!device || !device.state) {
return {
type: 'loader-progress',
title: 'Loading device...',
shouldRender: false,
};
}
const { const {
account, account,
discovery, discovery,
network, network,
} = selectedAccount; } = selectedAccount;
// corner case: accountState didn't finish loading state after LOCATION_CHANGE action if (!device || !device.state) {
if (!network) {
return { return {
type: 'loader-progress', type: 'progress',
title: 'Loading account state...', title: 'Loading device...',
shouldRender: false, shouldRender: false,
}; };
} }
const blockchain = state.blockchain.find(b => b.shortcut === network.shortcut); // corner case: accountState didn't finish loading state after LOCATION_CHANGE action
if (blockchain && !blockchain.connected) { if (!network) {
return { return {
type: 'backend', type: 'progress',
title: `${network.name} backend is not connected`, title: 'Loading account state...',
shouldRender: false, shouldRender: false,
}; };
} }
if (account) return null;
// account not found (yet). checking why... // account not found (yet). checking why...
if (!account) {
if (!discovery || (discovery.waitingForDevice || discovery.interrupted)) { if (!discovery || (discovery.waitingForDevice || discovery.interrupted)) {
if (device.connected) { if (device.connected) {
// case 1: device is connected but discovery not started yet (probably waiting for auth) // case 1: device is connected but discovery not started yet (probably waiting for auth)
if (device.available) { if (device.available) {
return {
type: 'loader-progress',
title: 'Authenticating device...',
shouldRender: false,
};
}
// case 2: device is unavailable (created with different passphrase settings) account cannot be accessed
return { return {
type: 'loader-info', type: 'progress',
title: `Device ${device.instanceLabel} is unavailable`, title: 'Authenticating device...',
message: 'Change passphrase settings to use this device',
shouldRender: false, shouldRender: false,
}; };
} }
// case 2: device is unavailable (created with different passphrase settings) account cannot be accessed
// case 3: device is disconnected // this is related to device instance in url, it's not used for now (device clones are disabled)
return { return {
type: 'loader-info', type: 'info',
title: `Device ${device.instanceLabel} is disconnected`, title: `Device ${device.instanceLabel} is unavailable`,
message: 'Connect device to load accounts', message: 'Change passphrase settings to use this device',
shouldRender: false, shouldRender: false,
}; };
} }
if (discovery.completed) { // case 3: device is disconnected
// case 4: account not found and discovery is completed return {
return { type: 'info',
type: 'loader-info', title: `Device ${device.instanceLabel} is disconnected`,
title: 'Account does not exist', message: 'Connect device to load accounts',
shouldRender: false, shouldRender: false,
}; };
}
} }
// Additional status: account does exists and it's visible but shouldn't be active if (discovery.completed) {
// case 4: account not found and discovery is completed
return {
type: 'info',
title: 'Account does not exist',
shouldRender: false,
};
}
// case default: account information isn't loaded yet
return {
type: 'progress',
title: 'Loading account',
shouldRender: false,
};
};
const getAccountNotification = (state: State, selectedAccount: SelectedAccountState): ?AccountStatus => {
const device = state.wallet.selectedDevice;
const { account, network, discovery } = selectedAccount;
if (!device || !network) return null;
// case 1: backend status
const blockchain = state.blockchain.find(b => b.shortcut === network.shortcut);
if (blockchain && !blockchain.connected) {
return {
type: 'backend',
title: `${network.name} backend is not connected`,
shouldRender: false,
};
}
// case 2: account does exists and it's visible but shouldn't be active
if (account && discovery && !discovery.completed && !discovery.waitingForDevice) {
return {
type: 'info',
title: 'Loading other accounts...',
shouldRender: true,
};
}
// case 3: account does exists and device is disconnected
if (!device.connected) { if (!device.connected) {
return { return {
type: 'info', type: 'info',
@ -121,6 +146,8 @@ const getAccountStatus = (state: State, selectedAccount: SelectedAccountState):
}; };
} }
// case 4: account does exists and device is unavailable (created with different passphrase settings) account cannot be accessed
// this is related to device instance in url, it's not used for now (device clones are disabled)
if (!device.available) { if (!device.available) {
return { return {
type: 'info', type: 'info',
@ -130,6 +157,7 @@ const getAccountStatus = (state: State, selectedAccount: SelectedAccountState):
}; };
} }
// case default
return null; return null;
}; };
@ -153,13 +181,18 @@ const actions = [
export const observe = (prevState: State, action: Action): PayloadAction<boolean> => (dispatch: Dispatch, getState: GetState): boolean => { export const observe = (prevState: State, action: Action): PayloadAction<boolean> => (dispatch: Dispatch, getState: GetState): boolean => {
// ignore not listed actions // ignore not listed actions
if (actions.indexOf(action.type) < 0) return false; if (actions.indexOf(action.type) < 0) return false;
const state: State = getState(); const state: State = getState();
const notification = { const notification = {
type: null, type: null,
message: null, message: null,
title: null, title: null,
}; };
const loader = {
type: null,
message: null,
title: null,
};
const { location } = state.router; const { location } = state.router;
// displayed route is not an account route // displayed route is not an account route
if (!location.state.account) return false; if (!location.state.account) return false;
@ -180,13 +213,18 @@ export const observe = (prevState: State, action: Action): PayloadAction<boolean
tokens, tokens,
pending, pending,
notification, notification,
loader,
shouldRender: false, shouldRender: false,
}; };
// get "selectedAccount" status from newState // get "selectedAccount" status from newState
const status = getAccountStatus(state, newState); const statusNotification = getAccountNotification(state, newState);
newState.notification = status || notification; const statusLoader = getAccountLoader(state, newState);
newState.shouldRender = status ? status.shouldRender : true; const shouldRender = (statusNotification && statusLoader) ? (statusNotification.shouldRender || statusLoader.shouldRender) : true;
newState.notification = statusNotification || notification;
newState.shouldRender = shouldRender;
newState.loader = statusLoader || loader;
// check if newState is different than previous state // check if newState is different than previous state
const stateChanged = reducerUtils.observeChanges(prevState.selectedAccount, newState, { const stateChanged = reducerUtils.observeChanges(prevState.selectedAccount, newState, {
account: ['balance', 'nonce'], account: ['balance', 'nonce'],

@ -90,8 +90,20 @@ export const observe = (prevState: ReducersState, action: Action): ThunkAction =
let shouldUpdate: boolean = false; let shouldUpdate: boolean = false;
// check if "selectedAccount" reducer changed // check if "selectedAccount" reducer changed
shouldUpdate = reducerUtils.observeChanges(prevState.selectedAccount, currentState.selectedAccount, { shouldUpdate = reducerUtils.observeChanges(prevState.selectedAccount, currentState.selectedAccount, {
account: ['balance', 'nonce'], account: ['balance', 'nonce', 'tokens'],
}); });
if (shouldUpdate && currentState.sendForm.currency !== currentState.sendForm.networkSymbol) {
// make sure that this token is added into account
const { account, tokens } = getState().selectedAccount;
if (!account) return;
const token = findToken(tokens, account.address, currentState.sendForm.currency, account.deviceState);
if (!token) {
// token not found, re-init form
dispatch(init());
return;
}
}
// check if "sendForm" reducer changed // check if "sendForm" reducer changed
if (!shouldUpdate) { if (!shouldUpdate) {
@ -384,6 +396,26 @@ export const onDataChange = (data: string): ThunkAction => (dispatch: Dispatch,
dispatch(estimateGasPrice()); dispatch(estimateGasPrice());
}; };
export const setDefaultGasLimit = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
const state: State = getState().sendForm;
const { network } = getState().selectedAccount;
if (!network) return;
const isToken = state.currency !== state.networkSymbol;
const gasLimit = isToken ? network.defaultGasLimitTokens.toString() : network.defaultGasLimit.toString();
dispatch({
type: SEND.CHANGE,
state: {
...state,
calculatingGasLimit: false,
untouched: false,
touched: { ...state.touched, gasLimit: false },
gasLimit,
},
});
};
/* /*
* Internal method * Internal method
* Called from "onDataChange" action * Called from "onDataChange" action
@ -548,6 +580,7 @@ export default {
updateFeeLevels, updateFeeLevels,
onGasPriceChange, onGasPriceChange,
onGasLimitChange, onGasLimitChange,
setDefaultGasLimit,
onNonceChange, onNonceChange,
onDataChange, onDataChange,
onSend, onSend,

@ -1,5 +1,6 @@
/* @flow */ /* @flow */
import * as storageUtils from 'utils/storage'; import * as storageUtils from 'utils/storage';
import { findToken } from 'reducers/TokensReducer';
import type { State as SendFormState } from 'reducers/SendFormReducer'; import type { State as SendFormState } from 'reducers/SendFormReducer';
import type { import type {
@ -38,6 +39,16 @@ export const loadDraftTransaction = (): PayloadAction<?SendFormState> => (dispat
storageUtils.remove(TYPE, key); storageUtils.remove(TYPE, key);
return null; return null;
} }
// check if selected currency is token and make sure that this token is added into account
if (state.currency !== state.networkSymbol) {
const { account, tokens } = getState().selectedAccount;
if (!account) return null;
const token = findToken(tokens, account.address, state.currency, account.deviceState);
if (!token) {
storageUtils.remove(TYPE, key);
return null;
}
}
return state; return state;
}; };

@ -15,6 +15,7 @@ declare var COMMITHASH: string;
type Props = { type Props = {
opened: boolean, opened: boolean,
isLanding: boolean,
toggle: () => any, toggle: () => any,
} }
@ -25,29 +26,48 @@ const Wrapper = styled.div`
color: ${colors.TEXT_SECONDARY}; color: ${colors.TEXT_SECONDARY};
padding: 22px 48px; padding: 22px 48px;
display: flex; display: flex;
justify-content: space-between;
border-top: 1px solid ${colors.BACKGROUND}; border-top: 1px solid ${colors.BACKGROUND};
`; `;
const StyledLink = styled(Link)` const StyledLink = styled(Link)`
margin: 0 6px; margin: 0 6px;
margin-right: 20px; margin-right: 20px;
white-space: nowrap;
`; `;
const Copy = styled.div` const Copy = styled.div`
white-space: nowrap;
margin-right: 20px; margin-right: 20px;
`; `;
const Footer = ({ opened, toggle }: Props) => ( const Left = styled.div`
display: flex;
`;
const Right = styled.div`
white-space: nowrap;
`;
const Footer = ({ opened, toggle, isLanding }: Props) => (
<Wrapper> <Wrapper>
<Copy title={COMMITHASH}>&copy; {getYear(new Date())}</Copy> <Left>
<StyledLink href="http://satoshilabs.com" isGreen>SatoshiLabs</StyledLink> <Copy title={COMMITHASH}>&copy; {getYear(new Date())}</Copy>
<StyledLink href="/assets/tos.pdf" isGreen>Terms</StyledLink> <StyledLink href="http://satoshilabs.com" isGreen>SatoshiLabs</StyledLink>
<StyledLink onClick={toggle} isGreen>{ opened ? 'Hide Log' : 'Show Log' }</StyledLink> <StyledLink href="/assets/tos.pdf" isGreen>Terms</StyledLink>
<StyledLink onClick={toggle} isGreen>{ opened ? 'Hide Log' : 'Show Log' }</StyledLink>
</Left>
{!isLanding && (
<Right>
Exchange rates by<StyledLink href="https://www.coingecko.com" isGreen>Coingecko</StyledLink>
</Right>
)}
</Wrapper> </Wrapper>
); );
Footer.propTypes = { Footer.propTypes = {
opened: PropTypes.bool.isRequired, opened: PropTypes.bool.isRequired,
isLanding: PropTypes.bool,
toggle: PropTypes.func.isRequired, toggle: PropTypes.func.isRequired,
}; };

@ -55,13 +55,10 @@ class Link extends PureComponent {
} else { } else {
LinkComponent = ( LinkComponent = (
<A <A
className={this.props.className}
href={this.props.href} href={this.props.href}
target={this.props.target || '_blank'} target={this.props.target || '_blank'}
rel="noreferrer noopener" rel="noreferrer noopener"
onClick={this.props.onClick} {...this.props}
isGreen={this.props.isGreen}
isGray={this.props.isGray}
>{this.props.children} >{this.props.children}
</A> </A>
); );
@ -87,4 +84,4 @@ Link.propTypes = {
isGray: PropTypes.bool, isGray: PropTypes.bool,
}; };
export default Link; export default Link;

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

@ -185,7 +185,9 @@ class Input extends PureComponent {
autocapitalize={this.props.autocapitalize} autocapitalize={this.props.autocapitalize}
spellCheck={this.props.spellCheck} spellCheck={this.props.spellCheck}
value={this.props.value} value={this.props.value}
readOnly={this.props.readOnly}
onChange={this.props.onChange} onChange={this.props.onChange}
onClick={this.props.autoSelect ? event => event.target.select() : null}
borderColor={this.getColor(this.props.state)} borderColor={this.getColor(this.props.state)}
disabled={this.props.isDisabled} disabled={this.props.isDisabled}
name={this.props.name} name={this.props.name}
@ -220,6 +222,8 @@ Input.propTypes = {
icon: PropTypes.node, icon: PropTypes.node,
spellCheck: PropTypes.string, spellCheck: PropTypes.string,
value: PropTypes.string, value: PropTypes.string,
readOnly: PropTypes.bool,
autoSelect: PropTypes.bool,
onChange: PropTypes.func, onChange: PropTypes.func,
state: PropTypes.string, state: PropTypes.string,
bottomText: PropTypes.string, bottomText: PropTypes.string,
@ -234,6 +238,7 @@ Input.propTypes = {
Input.defaultProps = { Input.defaultProps = {
type: 'text', type: 'text',
autoSelect: false,
}; };
export default Input; export default Input;

@ -7,7 +7,7 @@ import type { Props } from '../../index';
// There could be only one account notification // There could be only one account notification
export default (props: Props) => { export default (props: Props) => {
const { network, notification } = props.selectedAccount; const { network, notification } = props.selectedAccount;
if (network && notification.type && notification.title && notification.type !== 'loader-progress' && notification.type !== 'loader-info') { if (network && notification.type && notification.title) {
if (notification.type === 'backend') { if (notification.type === 'backend') {
// special case: backend is down // special case: backend is down
// TODO: this is a different component with "auto resolve" button // TODO: this is a different component with "auto resolve" button

@ -19,6 +19,11 @@ export default [
coinName: 'Dash', coinName: 'Dash',
url: '../?coin=dash', url: '../?coin=dash',
}, },
{
id: 'dgb',
coinName: 'DigiByte',
url: '../?coin=dgb',
},
{ {
id: 'doge', id: 'doge',
coinName: 'Dogecoin', coinName: 'Dogecoin',

@ -32,7 +32,7 @@ import type { TokenAction } from 'actions/TokenActions';
import type { TrezorConnectAction } from 'actions/TrezorConnectActions'; import type { TrezorConnectAction } from 'actions/TrezorConnectActions';
import type { WalletAction } from 'actions/WalletActions'; import type { WalletAction } from 'actions/WalletActions';
import type { Web3Action } from 'actions/Web3Actions'; import type { Web3Action } from 'actions/Web3Actions';
import type { FiatRateAction } from 'services/CoinmarketcapService'; // this service has no action file, all is written inside one file import type { FiatRateAction } from 'services/CoingeckoService'; // this service has no action file, all is written inside one file
import type { import type {
Device, Device,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 408 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 KiB

@ -1,29 +1,26 @@
/* @flow */ /* @flow */
import { RATE_UPDATE } from 'services/CoinmarketcapService'; import { RATE_UPDATE } from 'services/CoingeckoService';
import type { Action } from 'flowtype'; import type { Action } from 'flowtype';
import type { FiatRateAction } from 'services/CoinmarketcapService'; import type { FiatRateAction } from 'services/CoingeckoService';
export type Fiat = { export type Fiat = {
+network: string; +network: string;
value: string; value: string;
} };
export const initialState: Array<Fiat> = []; export const initialState: Array<Fiat> = [];
const update = (state: Array<Fiat>, action: FiatRateAction): Array<Fiat> => { const update = (state: Array<Fiat>, action: FiatRateAction): Array<Fiat> => {
const newState: Array<Fiat> = [...state]; const affected = state.find(f => f.network === action.network);
const exists: ?Fiat = newState.find(f => f.network === action.network); const otherRates = state.filter(d => d !== affected);
if (exists) { const { network, rate } = action;
exists.value = action.rate.price_usd;
} else { return otherRates.concat([{
newState.push({ network,
network: action.network, value: rate.toFixed(2),
value: action.rate.price_usd, }]);
});
}
return newState;
}; };
export default (state: Array<Fiat> = initialState, action: Action): Array<Fiat> => { export default (state: Array<Fiat> = initialState, action: Action): Array<Fiat> => {

@ -22,6 +22,11 @@ export type State = {
title: ?string, title: ?string,
message: ?string, message: ?string,
}, },
loader: {
type: ?string,
title: ?string,
message: ?string,
},
shouldRender: boolean, shouldRender: boolean,
}; };
@ -37,6 +42,14 @@ export const initialState: State = {
title: null, title: null,
message: null, message: null,
}, },
<<<<<<< HEAD
=======
loader: {
type: null,
title: null,
message: null,
},
>>>>>>> master
shouldRender: false, shouldRender: false,
}; };

@ -19,7 +19,7 @@ export const RATE_UPDATE: 'rate__update' = 'rate__update';
export type FiatRateAction = { export type FiatRateAction = {
type: typeof RATE_UPDATE; type: typeof RATE_UPDATE;
network: string; network: string;
rate: any; rate: number;
} }
const loadRateAction = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => { const loadRateAction = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
@ -28,13 +28,12 @@ const loadRateAction = (): AsyncAction => async (dispatch: Dispatch, getState: G
try { try {
config.fiatValueTickers.forEach(async (ticker) => { config.fiatValueTickers.forEach(async (ticker) => {
// const rate: ?Array<any> = await JSONRequest(`${ticker.url}?convert=USD`, 'json'); const response = await httpRequest(`${ticker.url}?tickers=false&market_data=true&community_data=false&developer_data=false&sparkline=false`, 'json');
const rate: ?Array<any> = await httpRequest(`${ticker.url}?convert=USD`, 'json'); if (response) {
if (rate) {
dispatch({ dispatch({
type: RATE_UPDATE, type: RATE_UPDATE,
network: ticker.network, network: response.symbol,
rate: rate[0], rate: response.market_data.current_price.usd,
}); });
} }
}); });
@ -48,7 +47,7 @@ const loadRateAction = (): AsyncAction => async (dispatch: Dispatch, getState: G
/** /**
* Middleware * Middleware
*/ */
const CoinmarketcapService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispatch) => (action: Action): Action => { const CoingeckoService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispatch) => (action: Action): Action => {
next(action); next(action);
if (action.type === READY) { if (action.type === READY) {
@ -58,4 +57,4 @@ const CoinmarketcapService: Middleware = (api: MiddlewareAPI) => (next: Middlewa
return action; return action;
}; };
export default CoinmarketcapService; export default CoingeckoService;

@ -2,7 +2,7 @@ import WalletService from './WalletService';
import LogService from './LogService'; import LogService from './LogService';
import RouterService from './RouterService'; import RouterService from './RouterService';
import LocalStorageService from './LocalStorageService'; import LocalStorageService from './LocalStorageService';
import CoinmarketcapService from './CoinmarketcapService'; import CoingeckoService from './CoingeckoService';
import TrezorConnectService from './TrezorConnectService'; import TrezorConnectService from './TrezorConnectService';
export default [ export default [
@ -11,5 +11,5 @@ export default [
RouterService, RouterService,
LocalStorageService, LocalStorageService,
TrezorConnectService, TrezorConnectService,
CoinmarketcapService, CoingeckoService,
]; ];

@ -0,0 +1,25 @@
import React, { Component } from 'react';
import styled from 'styled-components';
const Wrapper = styled.div`
display: none;
`;
const Img = styled.img``;
class ImagesPreloader extends Component {
importAll(r) {
return r.keys().map(r);
}
render() {
const images = this.importAll(require.context('../images', false, /\.(png|jpe?g)$/));
return (
<Wrapper>
{images.map(image => <Img key={image} src={image} />)}
</Wrapper>
);
}
}
export default ImagesPreloader;

@ -12,7 +12,7 @@ describe('device utils', () => {
]; ];
types.forEach((type) => { types.forEach((type) => {
expect(nUtils.getColor(type)).toMatchSnapshot(); expect(nUtils.getPrimaryColor(type)).toMatchSnapshot();
}); });
}); });
it('get icon', () => { it('get icon', () => {

@ -9,7 +9,7 @@ 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 CaseImage from 'images/macbook.png';
import Link from 'components/Link'; import Link from 'components/Link';
type Props = { type Props = {

@ -53,7 +53,7 @@ const LandingWrapper = (props: Props) => (
{ props.children } { props.children }
</LandingContent> </LandingContent>
)} )}
<Footer /> <Footer isLanding />
</React.Fragment> </React.Fragment>
)} )}
</Wrapper> </Wrapper>

@ -45,10 +45,10 @@ const Content = ({
}) => ( }) => (
<Wrapper> <Wrapper>
{(!isLoading) && children} {(!isLoading) && children}
{isLoading && (type === 'loader-progress' || type === 'loader-info') && ( {isLoading && (type === 'progress' || type === 'info') && (
<Loading> <Loading>
<Row> <Row>
{type === 'loader-progress' && <Loader size={30} />} {type === 'progress' && <Loader size={30} />}
<Text>{title || 'Initializing accounts'}</Text> <Text>{title || 'Initializing accounts'}</Text>
</Row> </Row>
{message && <Message>{message}</Message>} {message && <Message>{message}</Message>}
@ -58,7 +58,7 @@ const Content = ({
); );
Content.propTypes = { Content.propTypes = {
children: PropTypes.oneOfType([PropTypes.element, PropTypes.array]), children: PropTypes.element,
isLoading: PropTypes.bool, isLoading: PropTypes.bool,
title: PropTypes.string, title: PropTypes.string,
message: PropTypes.string, message: PropTypes.string,

@ -93,9 +93,9 @@ const AccountReceive = (props: Props) => {
account, account,
discovery, discovery,
shouldRender, shouldRender,
notification, loader,
} = props.selectedAccount; } = props.selectedAccount;
const { type, title, message } = notification; const { type, title, message } = loader;
if (!device || !account || !discovery || !shouldRender) return <Content type={type} title={title} message={message} isLoading />; if (!device || !account || !discovery || !shouldRender) return <Content type={type} title={title} message={message} isLoading />;
const { const {
@ -120,6 +120,8 @@ const AccountReceive = (props: Props) => {
<Row> <Row>
<Input <Input
type="text" type="text"
readOnly
autoSelect
value={address} value={address}
isPartiallyHidden={isAddressHidden} isPartiallyHidden={isAddressHidden}
trezorAction={isAddressVerifying ? ( trezorAction={isAddressVerifying ? (

@ -9,6 +9,7 @@ import Textarea from 'components/Textarea';
import Tooltip from 'components/Tooltip'; import Tooltip from 'components/Tooltip';
import Icon from 'components/Icon'; import Icon from 'components/Icon';
import ICONS from 'config/icons'; import ICONS from 'config/icons';
import { FONT_SIZE } from 'config/variables';
import type { Props as BaseProps } from '../../Container'; import type { Props as BaseProps } from '../../Container';
@ -28,6 +29,7 @@ const InputRow = styled.div`
const InputLabelWrapper = styled.div` const InputLabelWrapper = styled.div`
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between;
`; `;
const GreenSpan = styled.span` const GreenSpan = styled.span`
@ -110,6 +112,18 @@ const getDataTextareaState = (dataError: string, dataWarning: string): string =>
return state; return state;
}; };
const Left = styled.div`
display: flex;
align-items: center;
`;
const Right = styled.div`
display: flex;
flex-direction: row;
font-size: ${FONT_SIZE.SMALLER};
cursor: pointer;
`;
// stateless component // stateless component
const AdvancedForm = (props: Props) => { const AdvancedForm = (props: Props) => {
const { const {
@ -124,16 +138,17 @@ const AdvancedForm = (props: Props) => {
errors, errors,
warnings, warnings,
infos, infos,
touched,
data, data,
gasLimit, gasLimit,
gasPrice, gasPrice,
} = props.sendForm; } = props.sendForm;
const { const {
setDefaultGasLimit,
onGasLimitChange, onGasLimitChange,
onGasPriceChange, onGasPriceChange,
onDataChange, onDataChange,
} = props.sendFormActions; } = props.sendFormActions;
let gasLimitTooltipCurrency: string; let gasLimitTooltipCurrency: string;
let gasLimitTooltipValue: string; let gasLimitTooltipValue: string;
if (networkSymbol !== currency) { if (networkSymbol !== currency) {
@ -144,6 +159,8 @@ const AdvancedForm = (props: Props) => {
gasLimitTooltipValue = network.defaultGasLimit.toString(10); gasLimitTooltipValue = network.defaultGasLimit.toString(10);
} }
const showDefaultGasLimitButton = data.length === 0 && touched.gasLimit;
return ( return (
<AdvancedSettingsWrapper> <AdvancedSettingsWrapper>
<GasInputRow> <GasInputRow>
@ -155,29 +172,36 @@ const AdvancedForm = (props: Props) => {
spellCheck="false" spellCheck="false"
topLabel={( topLabel={(
<InputLabelWrapper> <InputLabelWrapper>
<Left>
Gas limit Gas limit
<Tooltip <Tooltip
content={( content={(
<React.Fragment> <React.Fragment>
Gas limit refers to the maximum amount of gas user is willing to spendon a particular transaction.{' '} Gas limit refers to the maximum amount of gas user is willing to spendon a particular transaction.{' '}
<GreenSpan>Transaction fee = gas limit * gas price</GreenSpan>.{' '}Increasing the gas limit will not get the transaction confirmed sooner. <GreenSpan>Transaction fee = gas limit * gas price</GreenSpan>.{' '}Increasing the gas limit will not get the transaction confirmed sooner.
Default value for sending {gasLimitTooltipCurrency} is <GreenSpan>{gasLimitTooltipValue}</GreenSpan>. Default value for sending {gasLimitTooltipCurrency} is <GreenSpan>{gasLimitTooltipValue}</GreenSpan>.
</React.Fragment> </React.Fragment>
)} )}
maxWidth={410} maxWidth={410}
readMoreLink="https://wiki.trezor.io/Ethereum_Wallet#Gas_limit" readMoreLink="https://wiki.trezor.io/Ethereum_Wallet#Gas_limit"
placement="top" placement="top"
> >
<Icon <Icon
icon={ICONS.HELP} icon={ICONS.HELP}
color={colors.TEXT_SECONDARY} color={colors.TEXT_SECONDARY}
size={24} size={24}
/> />
</Tooltip> </Tooltip>
</Left>
{ showDefaultGasLimitButton && (
<Right onClick={() => setDefaultGasLimit()}>
Set default
</Right>)
}
</InputLabelWrapper> </InputLabelWrapper>
)} )}
bottomText={errors.gasLimit || warnings.gasLimit || infos.gasLimit || (calculatingGasLimit ? 'Calculating...' : '')} bottomText={errors.gasLimit || warnings.gasLimit || infos.gasLimit}
value={gasLimit} value={calculatingGasLimit ? 'Calculating...' : gasLimit}
isDisabled={networkSymbol === currency && data.length > 0} isDisabled={networkSymbol === currency && data.length > 0}
onChange={event => onGasLimitChange(event.target.value)} onChange={event => onGasLimitChange(event.target.value)}
/> />

@ -185,7 +185,11 @@ const AccountSend = (props: Props) => {
discovery, discovery,
tokens, tokens,
shouldRender, shouldRender,
<<<<<<< HEAD
notification, notification,
=======
loader,
>>>>>>> master
} = props.selectedAccount; } = props.selectedAccount;
const { const {
address, address,
@ -214,7 +218,7 @@ const AccountSend = (props: Props) => {
updateFeeLevels, updateFeeLevels,
onSend, onSend,
} = props.sendFormActions; } = props.sendFormActions;
const { type, title, message } = notification; const { type, title, message } = loader;
if (!device || !account || !discovery || !network || !shouldRender) return <Content type={type} title={title} message={message} isLoading />; if (!device || !account || !discovery || !network || !shouldRender) return <Content type={type} title={title} message={message} isLoading />;
const isCurrentCurrencyToken = networkSymbol !== currency; const isCurrentCurrencyToken = networkSymbol !== currency;

@ -106,8 +106,7 @@ class AccountBalance extends PureComponent<Props, State> {
render() { render() {
const { network } = this.props; const { network } = this.props;
const fiatRate: any = this.props.fiat.find(f => f.network === network.shortcut); const fiatRate = this.props.fiat.find(f => f.network === network.shortcut);
let accountBalance = ''; let accountBalance = '';
let fiatRateValue = ''; let fiatRateValue = '';
let fiat = ''; let fiat = '';
@ -117,7 +116,6 @@ class AccountBalance extends PureComponent<Props, State> {
fiat = accountBalance.times(fiatRateValue).toFixed(2); fiat = accountBalance.times(fiatRateValue).toFixed(2);
} }
return ( return (
<Wrapper> <Wrapper>
<HideBalanceIconWrapper <HideBalanceIconWrapper

@ -75,11 +75,12 @@ const AccountSummary = (props: Props) => {
network, network,
tokens, tokens,
pending, pending,
notification, loader,
shouldRender, shouldRender,
} = props.selectedAccount; } = props.selectedAccount;
const { type, title, message } = notification; const { type, title, message } = loader;
if (!device || !account || !network || !shouldRender) return <Content type={type} title={title} message={message} isLoading />; if (!device || !account || !network || !shouldRender) return <Content type={type} title={title} message={message} isLoading />;
const explorerLink: string = `${network.explorer.address}${account.address}`; const explorerLink: string = `${network.explorer.address}${account.address}`;
@ -125,9 +126,11 @@ const AccountSummary = (props: Props) => {
loadingMessage={() => 'Loading...'} loadingMessage={() => 'Loading...'}
noOptionsMessage={() => 'Token not found'} noOptionsMessage={() => 'Token not found'}
onChange={(token) => { onChange={(token) => {
const isAdded = tokens.find(t => t.symbol === token.symbol); if (token.name) {
if (!isAdded) { const isAdded = tokens.find(t => t.symbol === token.symbol);
props.addToken(token, account); if (!isAdded) {
props.addToken(token, account);
}
} }
}} }}
loadOptions={input => props.loadTokens(input, account.network)} loadOptions={input => props.loadTokens(input, account.network)}
@ -142,7 +145,6 @@ const AccountSummary = (props: Props) => {
getOptionValue={option => option.symbol} getOptionValue={option => option.symbol}
/> />
</AsyncSelectWrapper> </AsyncSelectWrapper>
<AddedTokensWrapper> <AddedTokensWrapper>
{tokens.map(token => ( {tokens.map(token => (
<AddedToken <AddedToken

@ -41,7 +41,7 @@ const DeviceSettings = () => (
/> />
<H2>Device settings is under construction</H2> <H2>Device settings is under construction</H2>
<StyledP isSmaller>Please use Bitcoin wallet interface to change your device settings</StyledP> <StyledP isSmaller>Please use Bitcoin wallet interface to change your device settings</StyledP>
<Link href="https://wallet.trezor.io/"> <Link href="https://beta-wallet.trezor.io/">
<Button>Take me to the Bitcoin wallet</Button> <Button>Take me to the Bitcoin wallet</Button>
</Link> </Link>
</Row> </Row>

@ -127,9 +127,9 @@ const FirmwareUpdate = (props: Props) => (
</svg> </svg>
</Image> </Image>
<H1>Its time to update your firmware</H1> <H1>Its time to update your firmware</H1>
<StyledP>Please use the old wallet to do that.</StyledP> <StyledP>Please use Bitcoin wallet interface to update your firmware.</StyledP>
<Link href="https://wallet.trezor.io"> <Link href="https://beta-wallet.trezor.io">
<Button>Take me to the old wallet</Button> <Button>Take me to the Bitcoin wallet</Button>
</Link> </Link>
{deviceUtils.isDeviceAccessible(props.device) && ( {deviceUtils.isDeviceAccessible(props.device) && (
<StyledNavLink to="/">Ill do that later.</StyledNavLink> <StyledNavLink to="/">Ill do that later.</StyledNavLink>

@ -32,7 +32,7 @@ const Initialize = () => (
<Row> <Row>
<H2>Your device is in not initialized</H2> <H2>Your device is in not initialized</H2>
<StyledParagraph>Please use Bitcoin wallet interface to start initialization process</StyledParagraph> <StyledParagraph>Please use Bitcoin wallet interface to start initialization process</StyledParagraph>
<A href="https://wallet.trezor.io/"> <A href="https://beta-wallet.trezor.io/">
<Button>Take me to the Bitcoin wallet</Button> <Button>Take me to the Bitcoin wallet</Button>
</A> </A>
</Row> </Row>

@ -6,6 +6,7 @@ import { ConnectedRouter } from 'react-router-redux';
// general // general
import ErrorBoundary from 'support/ErrorBoundary'; import ErrorBoundary from 'support/ErrorBoundary';
import ImagesPreloader from 'support/ImagesPreloader';
import { getPattern } from 'support/routes'; import { getPattern } from 'support/routes';
// landing views // landing views
@ -41,6 +42,7 @@ const App = () => (
<Route exact path={getPattern('landing-import')} component={ImportView} /> <Route exact path={getPattern('landing-import')} component={ImportView} />
<Route> <Route>
<ErrorBoundary> <ErrorBoundary>
<ImagesPreloader />
<WalletContainer> <WalletContainer>
<Route exact path={getPattern('wallet-settings')} 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} />

@ -10274,9 +10274,9 @@ tr46@^1.0.1:
dependencies: dependencies:
punycode "^2.1.0" punycode "^2.1.0"
trezor-connect@6.0.0: trezor-connect@6.0.2:
version "6.0.0" version "6.0.2"
resolved "https://registry.yarnpkg.com/trezor-connect/-/trezor-connect-6.0.0.tgz#2a45336f29a4a3f2a8ad2d121363b0e7a1b767ef" resolved "https://registry.yarnpkg.com/trezor-connect/-/trezor-connect-6.0.2.tgz#a4ca892cc4a167b34b97644e1404a56f6a110379"
dependencies: dependencies:
babel-runtime "^6.26.0" babel-runtime "^6.26.0"
events "^1.1.1" events "^1.1.1"

Loading…
Cancel
Save