mirror of
https://github.com/trezor/trezor-wallet
synced 2025-02-05 04:41:25 +00:00
merge
This commit is contained in:
commit
851c7f23cb
@ -43,7 +43,7 @@
|
||||
"settings": {
|
||||
"import/resolver": {
|
||||
"babel-module": {}
|
||||
},
|
||||
},
|
||||
"import/ignore": [
|
||||
"\\.(scss|less|css)$"
|
||||
]
|
||||
|
@ -41,6 +41,8 @@ build beta:
|
||||
script:
|
||||
- yarn install
|
||||
- yarn run build:beta
|
||||
only:
|
||||
- beta
|
||||
artifacts:
|
||||
expire_in: 1 week
|
||||
paths:
|
||||
@ -52,6 +54,8 @@ build stable:
|
||||
script:
|
||||
- yarn install
|
||||
- yarn run build:stable
|
||||
only:
|
||||
- stable
|
||||
artifacts:
|
||||
expire_in: 1 week
|
||||
paths:
|
||||
@ -64,6 +68,9 @@ deploy review:
|
||||
GIT_STRATEGY: none
|
||||
dependencies:
|
||||
- build development
|
||||
environment:
|
||||
name: $CI_BUILD_REF_NAME
|
||||
url: $BASE_REVIEW_URL/$CI_BUILD_REF_NAME
|
||||
script:
|
||||
- echo "Deploy a review app"
|
||||
- '[ -z "${DEPLOY_BASE_DIR}" ] && echo "Deploy base dir cannot be empty" && exit 255'
|
||||
|
22
CHANGELOG.md
Normal file
22
CHANGELOG.md
Normal file
@ -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-media-query": "^2.0.2",
|
||||
"styled-normalize": "^8.0.0",
|
||||
"trezor-connect": "6.0.0",
|
||||
"trezor-connect": "6.0.2",
|
||||
"web3": "1.0.0-beta.35",
|
||||
"webpack": "^4.16.3",
|
||||
"webpack-build-notifier": "^0.1.29",
|
||||
|
@ -59,11 +59,11 @@
|
||||
"fiatValueTickers": [
|
||||
{
|
||||
"network": "eth",
|
||||
"url": "https://api.coinmarketcap.com/v1/ticker/ethereum/"
|
||||
"url": "https://api.coingecko.com/api/v3/coins/ethereum"
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"name": "SmartBillions",
|
||||
"symbol": "Smart",
|
||||
"decimals": 0
|
||||
"address": "0x085fb4f24031EAedbC2B611AA528f22343eB52Db",
|
||||
"decimals": 8,
|
||||
"name": "BEC",
|
||||
"symbol": "BEC"
|
||||
},
|
||||
{
|
||||
"address": "0x5ace17f87c7391e5792a7683069a8025b83bbd85",
|
||||
"name": "SmartBillions Token",
|
||||
"symbol": "PLAY",
|
||||
"decimals": 0
|
||||
"address": "0x6ADa6F48C815689502C43eC1a59F1b5DD3C04E1F",
|
||||
"decimals": 18,
|
||||
"name": "UniversalCoin",
|
||||
"symbol": "UNV"
|
||||
}
|
||||
]
|
File diff suppressed because it is too large
Load Diff
@ -38,31 +38,87 @@ export const dispose = (): Action => ({
|
||||
type: ACCOUNT.DISPOSE,
|
||||
});
|
||||
|
||||
const getAccountStatus = (state: State, selectedAccount: SelectedAccountState): ?AccountStatus => {
|
||||
const getAccountLoader = (state: State, selectedAccount: SelectedAccountState): ?AccountStatus => {
|
||||
const device = state.wallet.selectedDevice;
|
||||
if (!device || !device.state) {
|
||||
return {
|
||||
type: 'loader-progress',
|
||||
title: 'Loading device...',
|
||||
shouldRender: false,
|
||||
};
|
||||
}
|
||||
|
||||
const {
|
||||
account,
|
||||
discovery,
|
||||
network,
|
||||
} = selectedAccount;
|
||||
|
||||
if (!device || !device.state) {
|
||||
return {
|
||||
type: 'progress',
|
||||
title: 'Loading device...',
|
||||
shouldRender: false,
|
||||
};
|
||||
}
|
||||
|
||||
// corner case: accountState didn't finish loading state after LOCATION_CHANGE action
|
||||
if (!network) {
|
||||
return {
|
||||
type: 'loader-progress',
|
||||
type: 'progress',
|
||||
title: 'Loading account state...',
|
||||
shouldRender: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
if (account) return null;
|
||||
// account not found (yet). checking why...
|
||||
|
||||
if (!discovery || (discovery.waitingForDevice || discovery.interrupted)) {
|
||||
if (device.connected) {
|
||||
// case 1: device is connected but discovery not started yet (probably waiting for auth)
|
||||
if (device.available) {
|
||||
return {
|
||||
type: 'progress',
|
||||
title: 'Authenticating device...',
|
||||
shouldRender: false,
|
||||
};
|
||||
}
|
||||
// case 2: 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)
|
||||
return {
|
||||
type: 'info',
|
||||
title: `Device ${device.instanceLabel} is unavailable`,
|
||||
message: 'Change passphrase settings to use this device',
|
||||
shouldRender: false,
|
||||
};
|
||||
}
|
||||
|
||||
// case 3: device is disconnected
|
||||
return {
|
||||
type: 'info',
|
||||
title: `Device ${device.instanceLabel} is disconnected`,
|
||||
message: 'Connect device to load accounts',
|
||||
shouldRender: false,
|
||||
};
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -72,47 +128,16 @@ const getAccountStatus = (state: State, selectedAccount: SelectedAccountState):
|
||||
};
|
||||
}
|
||||
|
||||
// account not found (yet). checking why...
|
||||
if (!account) {
|
||||
if (!discovery || (discovery.waitingForDevice || discovery.interrupted)) {
|
||||
if (device.connected) {
|
||||
// case 1: device is connected but discovery not started yet (probably waiting for auth)
|
||||
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 {
|
||||
type: 'loader-info',
|
||||
title: `Device ${device.instanceLabel} is unavailable`,
|
||||
message: 'Change passphrase settings to use this device',
|
||||
shouldRender: false,
|
||||
};
|
||||
}
|
||||
|
||||
// case 3: device is disconnected
|
||||
return {
|
||||
type: 'loader-info',
|
||||
title: `Device ${device.instanceLabel} is disconnected`,
|
||||
message: 'Connect device to load accounts',
|
||||
shouldRender: false,
|
||||
};
|
||||
}
|
||||
|
||||
if (discovery.completed) {
|
||||
// case 4: account not found and discovery is completed
|
||||
return {
|
||||
type: 'loader-info',
|
||||
title: 'Account does not exist',
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
// Additional status: account does exists and it's visible but shouldn't be active
|
||||
// case 3: account does exists and device is disconnected
|
||||
if (!device.connected) {
|
||||
return {
|
||||
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) {
|
||||
return {
|
||||
type: 'info',
|
||||
@ -130,6 +157,7 @@ const getAccountStatus = (state: State, selectedAccount: SelectedAccountState):
|
||||
};
|
||||
}
|
||||
|
||||
// case default
|
||||
return null;
|
||||
};
|
||||
|
||||
@ -153,13 +181,18 @@ const actions = [
|
||||
export const observe = (prevState: State, action: Action): PayloadAction<boolean> => (dispatch: Dispatch, getState: GetState): boolean => {
|
||||
// ignore not listed actions
|
||||
if (actions.indexOf(action.type) < 0) return false;
|
||||
|
||||
const state: State = getState();
|
||||
const notification = {
|
||||
type: null,
|
||||
message: null,
|
||||
title: null,
|
||||
};
|
||||
const loader = {
|
||||
type: null,
|
||||
message: null,
|
||||
title: null,
|
||||
};
|
||||
|
||||
const { location } = state.router;
|
||||
// displayed route is not an account route
|
||||
if (!location.state.account) return false;
|
||||
@ -180,13 +213,18 @@ export const observe = (prevState: State, action: Action): PayloadAction<boolean
|
||||
tokens,
|
||||
pending,
|
||||
notification,
|
||||
loader,
|
||||
shouldRender: false,
|
||||
};
|
||||
|
||||
// get "selectedAccount" status from newState
|
||||
const status = getAccountStatus(state, newState);
|
||||
newState.notification = status || notification;
|
||||
newState.shouldRender = status ? status.shouldRender : true;
|
||||
const statusNotification = getAccountNotification(state, newState);
|
||||
const statusLoader = getAccountLoader(state, newState);
|
||||
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
|
||||
const stateChanged = reducerUtils.observeChanges(prevState.selectedAccount, newState, {
|
||||
account: ['balance', 'nonce'],
|
||||
|
@ -90,8 +90,20 @@ export const observe = (prevState: ReducersState, action: Action): ThunkAction =
|
||||
let shouldUpdate: boolean = false;
|
||||
// check if "selectedAccount" reducer changed
|
||||
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
|
||||
if (!shouldUpdate) {
|
||||
@ -384,6 +396,26 @@ export const onDataChange = (data: string): ThunkAction => (dispatch: Dispatch,
|
||||
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
|
||||
* Called from "onDataChange" action
|
||||
@ -548,6 +580,7 @@ export default {
|
||||
updateFeeLevels,
|
||||
onGasPriceChange,
|
||||
onGasLimitChange,
|
||||
setDefaultGasLimit,
|
||||
onNonceChange,
|
||||
onDataChange,
|
||||
onSend,
|
||||
|
@ -1,5 +1,6 @@
|
||||
/* @flow */
|
||||
import * as storageUtils from 'utils/storage';
|
||||
import { findToken } from 'reducers/TokensReducer';
|
||||
|
||||
import type { State as SendFormState } from 'reducers/SendFormReducer';
|
||||
import type {
|
||||
@ -38,6 +39,16 @@ export const loadDraftTransaction = (): PayloadAction<?SendFormState> => (dispat
|
||||
storageUtils.remove(TYPE, key);
|
||||
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;
|
||||
};
|
||||
|
||||
|
@ -15,6 +15,7 @@ declare var COMMITHASH: string;
|
||||
|
||||
type Props = {
|
||||
opened: boolean,
|
||||
isLanding: boolean,
|
||||
toggle: () => any,
|
||||
}
|
||||
|
||||
@ -25,29 +26,48 @@ const Wrapper = styled.div`
|
||||
color: ${colors.TEXT_SECONDARY};
|
||||
padding: 22px 48px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
border-top: 1px solid ${colors.BACKGROUND};
|
||||
`;
|
||||
|
||||
const StyledLink = styled(Link)`
|
||||
margin: 0 6px;
|
||||
margin-right: 20px;
|
||||
white-space: nowrap;
|
||||
`;
|
||||
|
||||
const Copy = styled.div`
|
||||
white-space: nowrap;
|
||||
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>
|
||||
<Copy title={COMMITHASH}>© {getYear(new Date())}</Copy>
|
||||
<StyledLink href="http://satoshilabs.com" isGreen>SatoshiLabs</StyledLink>
|
||||
<StyledLink href="/assets/tos.pdf" isGreen>Terms</StyledLink>
|
||||
<StyledLink onClick={toggle} isGreen>{ opened ? 'Hide Log' : 'Show Log' }</StyledLink>
|
||||
<Left>
|
||||
<Copy title={COMMITHASH}>© {getYear(new Date())}</Copy>
|
||||
<StyledLink href="http://satoshilabs.com" isGreen>SatoshiLabs</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>
|
||||
);
|
||||
|
||||
Footer.propTypes = {
|
||||
opened: PropTypes.bool.isRequired,
|
||||
isLanding: PropTypes.bool,
|
||||
toggle: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
|
@ -55,13 +55,10 @@ class Link extends PureComponent {
|
||||
} else {
|
||||
LinkComponent = (
|
||||
<A
|
||||
className={this.props.className}
|
||||
href={this.props.href}
|
||||
target={this.props.target || '_blank'}
|
||||
rel="noreferrer noopener"
|
||||
onClick={this.props.onClick}
|
||||
isGreen={this.props.isGreen}
|
||||
isGray={this.props.isGray}
|
||||
{...this.props}
|
||||
>{this.props.children}
|
||||
</A>
|
||||
);
|
||||
|
BIN
src/components/images/CoinLogo/images/dgb.png
Normal file
BIN
src/components/images/CoinLogo/images/dgb.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.6 KiB |
@ -185,7 +185,9 @@ class Input extends PureComponent {
|
||||
autocapitalize={this.props.autocapitalize}
|
||||
spellCheck={this.props.spellCheck}
|
||||
value={this.props.value}
|
||||
readOnly={this.props.readOnly}
|
||||
onChange={this.props.onChange}
|
||||
onClick={this.props.autoSelect ? event => event.target.select() : null}
|
||||
borderColor={this.getColor(this.props.state)}
|
||||
disabled={this.props.isDisabled}
|
||||
name={this.props.name}
|
||||
@ -220,6 +222,8 @@ Input.propTypes = {
|
||||
icon: PropTypes.node,
|
||||
spellCheck: PropTypes.string,
|
||||
value: PropTypes.string,
|
||||
readOnly: PropTypes.bool,
|
||||
autoSelect: PropTypes.bool,
|
||||
onChange: PropTypes.func,
|
||||
state: PropTypes.string,
|
||||
bottomText: PropTypes.string,
|
||||
@ -234,6 +238,7 @@ Input.propTypes = {
|
||||
|
||||
Input.defaultProps = {
|
||||
type: 'text',
|
||||
autoSelect: false,
|
||||
};
|
||||
|
||||
export default Input;
|
||||
|
@ -7,7 +7,7 @@ import type { Props } from '../../index';
|
||||
// There could be only one account notification
|
||||
export default (props: Props) => {
|
||||
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') {
|
||||
// special case: backend is down
|
||||
// TODO: this is a different component with "auto resolve" button
|
||||
|
@ -19,6 +19,11 @@ export default [
|
||||
coinName: 'Dash',
|
||||
url: '../?coin=dash',
|
||||
},
|
||||
{
|
||||
id: 'dgb',
|
||||
coinName: 'DigiByte',
|
||||
url: '../?coin=dgb',
|
||||
},
|
||||
{
|
||||
id: 'doge',
|
||||
coinName: 'Dogecoin',
|
||||
|
@ -32,7 +32,7 @@ import type { TokenAction } from 'actions/TokenActions';
|
||||
import type { TrezorConnectAction } from 'actions/TrezorConnectActions';
|
||||
import type { WalletAction } from 'actions/WalletActions';
|
||||
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 {
|
||||
Device,
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 408 KiB |
BIN
src/images/macbook.png
Normal file
BIN
src/images/macbook.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 509 KiB |
@ -1,29 +1,26 @@
|
||||
/* @flow */
|
||||
|
||||
import { RATE_UPDATE } from 'services/CoinmarketcapService';
|
||||
import { RATE_UPDATE } from 'services/CoingeckoService';
|
||||
|
||||
import type { Action } from 'flowtype';
|
||||
import type { FiatRateAction } from 'services/CoinmarketcapService';
|
||||
import type { FiatRateAction } from 'services/CoingeckoService';
|
||||
|
||||
export type Fiat = {
|
||||
+network: string;
|
||||
value: string;
|
||||
}
|
||||
};
|
||||
|
||||
export const initialState: Array<Fiat> = [];
|
||||
|
||||
const update = (state: Array<Fiat>, action: FiatRateAction): Array<Fiat> => {
|
||||
const newState: Array<Fiat> = [...state];
|
||||
const exists: ?Fiat = newState.find(f => f.network === action.network);
|
||||
if (exists) {
|
||||
exists.value = action.rate.price_usd;
|
||||
} else {
|
||||
newState.push({
|
||||
network: action.network,
|
||||
value: action.rate.price_usd,
|
||||
});
|
||||
}
|
||||
return newState;
|
||||
const affected = state.find(f => f.network === action.network);
|
||||
const otherRates = state.filter(d => d !== affected);
|
||||
const { network, rate } = action;
|
||||
|
||||
return otherRates.concat([{
|
||||
network,
|
||||
value: rate.toFixed(2),
|
||||
}]);
|
||||
};
|
||||
|
||||
export default (state: Array<Fiat> = initialState, action: Action): Array<Fiat> => {
|
||||
|
@ -22,6 +22,11 @@ export type State = {
|
||||
title: ?string,
|
||||
message: ?string,
|
||||
},
|
||||
loader: {
|
||||
type: ?string,
|
||||
title: ?string,
|
||||
message: ?string,
|
||||
},
|
||||
shouldRender: boolean,
|
||||
};
|
||||
|
||||
@ -37,6 +42,14 @@ export const initialState: State = {
|
||||
title: null,
|
||||
message: null,
|
||||
},
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
loader: {
|
||||
type: null,
|
||||
title: null,
|
||||
message: null,
|
||||
},
|
||||
>>>>>>> master
|
||||
shouldRender: false,
|
||||
};
|
||||
|
||||
|
@ -19,7 +19,7 @@ export const RATE_UPDATE: 'rate__update' = 'rate__update';
|
||||
export type FiatRateAction = {
|
||||
type: typeof RATE_UPDATE;
|
||||
network: string;
|
||||
rate: any;
|
||||
rate: number;
|
||||
}
|
||||
|
||||
const loadRateAction = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||
@ -28,13 +28,12 @@ const loadRateAction = (): AsyncAction => async (dispatch: Dispatch, getState: G
|
||||
|
||||
try {
|
||||
config.fiatValueTickers.forEach(async (ticker) => {
|
||||
// const rate: ?Array<any> = await JSONRequest(`${ticker.url}?convert=USD`, 'json');
|
||||
const rate: ?Array<any> = await httpRequest(`${ticker.url}?convert=USD`, 'json');
|
||||
if (rate) {
|
||||
const response = await httpRequest(`${ticker.url}?tickers=false&market_data=true&community_data=false&developer_data=false&sparkline=false`, 'json');
|
||||
if (response) {
|
||||
dispatch({
|
||||
type: RATE_UPDATE,
|
||||
network: ticker.network,
|
||||
rate: rate[0],
|
||||
network: response.symbol,
|
||||
rate: response.market_data.current_price.usd,
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -48,7 +47,7 @@ const loadRateAction = (): AsyncAction => async (dispatch: Dispatch, getState: G
|
||||
/**
|
||||
* Middleware
|
||||
*/
|
||||
const CoinmarketcapService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispatch) => (action: Action): Action => {
|
||||
const CoingeckoService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispatch) => (action: Action): Action => {
|
||||
next(action);
|
||||
|
||||
if (action.type === READY) {
|
||||
@ -58,4 +57,4 @@ const CoinmarketcapService: Middleware = (api: MiddlewareAPI) => (next: Middlewa
|
||||
return action;
|
||||
};
|
||||
|
||||
export default CoinmarketcapService;
|
||||
export default CoingeckoService;
|
@ -2,7 +2,7 @@ import WalletService from './WalletService';
|
||||
import LogService from './LogService';
|
||||
import RouterService from './RouterService';
|
||||
import LocalStorageService from './LocalStorageService';
|
||||
import CoinmarketcapService from './CoinmarketcapService';
|
||||
import CoingeckoService from './CoingeckoService';
|
||||
import TrezorConnectService from './TrezorConnectService';
|
||||
|
||||
export default [
|
||||
@ -11,5 +11,5 @@ export default [
|
||||
RouterService,
|
||||
LocalStorageService,
|
||||
TrezorConnectService,
|
||||
CoinmarketcapService,
|
||||
CoingeckoService,
|
||||
];
|
25
src/support/ImagesPreloader.js
Normal file
25
src/support/ImagesPreloader.js
Normal file
@ -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) => {
|
||||
expect(nUtils.getColor(type)).toMatchSnapshot();
|
||||
expect(nUtils.getPrimaryColor(type)).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
it('get icon', () => {
|
||||
|
@ -9,7 +9,7 @@ import { H2 } from 'components/Heading';
|
||||
import { PULSATE } from 'config/animations';
|
||||
import colors from 'config/colors';
|
||||
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';
|
||||
|
||||
type Props = {
|
||||
|
@ -53,7 +53,7 @@ const LandingWrapper = (props: Props) => (
|
||||
{ props.children }
|
||||
</LandingContent>
|
||||
)}
|
||||
<Footer />
|
||||
<Footer isLanding />
|
||||
</React.Fragment>
|
||||
)}
|
||||
</Wrapper>
|
||||
|
@ -45,10 +45,10 @@ const Content = ({
|
||||
}) => (
|
||||
<Wrapper>
|
||||
{(!isLoading) && children}
|
||||
{isLoading && (type === 'loader-progress' || type === 'loader-info') && (
|
||||
{isLoading && (type === 'progress' || type === 'info') && (
|
||||
<Loading>
|
||||
<Row>
|
||||
{type === 'loader-progress' && <Loader size={30} />}
|
||||
{type === 'progress' && <Loader size={30} />}
|
||||
<Text>{title || 'Initializing accounts'}</Text>
|
||||
</Row>
|
||||
{message && <Message>{message}</Message>}
|
||||
@ -58,7 +58,7 @@ const Content = ({
|
||||
);
|
||||
|
||||
Content.propTypes = {
|
||||
children: PropTypes.oneOfType([PropTypes.element, PropTypes.array]),
|
||||
children: PropTypes.element,
|
||||
isLoading: PropTypes.bool,
|
||||
title: PropTypes.string,
|
||||
message: PropTypes.string,
|
||||
|
@ -93,9 +93,9 @@ const AccountReceive = (props: Props) => {
|
||||
account,
|
||||
discovery,
|
||||
shouldRender,
|
||||
notification,
|
||||
loader,
|
||||
} = 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 />;
|
||||
|
||||
const {
|
||||
@ -120,6 +120,8 @@ const AccountReceive = (props: Props) => {
|
||||
<Row>
|
||||
<Input
|
||||
type="text"
|
||||
readOnly
|
||||
autoSelect
|
||||
value={address}
|
||||
isPartiallyHidden={isAddressHidden}
|
||||
trezorAction={isAddressVerifying ? (
|
||||
|
@ -9,6 +9,7 @@ import Textarea from 'components/Textarea';
|
||||
import Tooltip from 'components/Tooltip';
|
||||
import Icon from 'components/Icon';
|
||||
import ICONS from 'config/icons';
|
||||
import { FONT_SIZE } from 'config/variables';
|
||||
|
||||
import type { Props as BaseProps } from '../../Container';
|
||||
|
||||
@ -28,6 +29,7 @@ const InputRow = styled.div`
|
||||
const InputLabelWrapper = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
`;
|
||||
|
||||
const GreenSpan = styled.span`
|
||||
@ -110,6 +112,18 @@ const getDataTextareaState = (dataError: string, dataWarning: string): string =>
|
||||
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
|
||||
const AdvancedForm = (props: Props) => {
|
||||
const {
|
||||
@ -124,16 +138,17 @@ const AdvancedForm = (props: Props) => {
|
||||
errors,
|
||||
warnings,
|
||||
infos,
|
||||
touched,
|
||||
data,
|
||||
gasLimit,
|
||||
gasPrice,
|
||||
} = props.sendForm;
|
||||
const {
|
||||
setDefaultGasLimit,
|
||||
onGasLimitChange,
|
||||
onGasPriceChange,
|
||||
onDataChange,
|
||||
} = props.sendFormActions;
|
||||
|
||||
let gasLimitTooltipCurrency: string;
|
||||
let gasLimitTooltipValue: string;
|
||||
if (networkSymbol !== currency) {
|
||||
@ -144,6 +159,8 @@ const AdvancedForm = (props: Props) => {
|
||||
gasLimitTooltipValue = network.defaultGasLimit.toString(10);
|
||||
}
|
||||
|
||||
const showDefaultGasLimitButton = data.length === 0 && touched.gasLimit;
|
||||
|
||||
return (
|
||||
<AdvancedSettingsWrapper>
|
||||
<GasInputRow>
|
||||
@ -155,29 +172,36 @@ const AdvancedForm = (props: Props) => {
|
||||
spellCheck="false"
|
||||
topLabel={(
|
||||
<InputLabelWrapper>
|
||||
<Left>
|
||||
Gas limit
|
||||
<Tooltip
|
||||
content={(
|
||||
<React.Fragment>
|
||||
<Tooltip
|
||||
content={(
|
||||
<React.Fragment>
|
||||
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>.
|
||||
</React.Fragment>
|
||||
)}
|
||||
maxWidth={410}
|
||||
readMoreLink="https://wiki.trezor.io/Ethereum_Wallet#Gas_limit"
|
||||
placement="top"
|
||||
>
|
||||
<Icon
|
||||
icon={ICONS.HELP}
|
||||
color={colors.TEXT_SECONDARY}
|
||||
size={24}
|
||||
/>
|
||||
</Tooltip>
|
||||
</React.Fragment>
|
||||
)}
|
||||
maxWidth={410}
|
||||
readMoreLink="https://wiki.trezor.io/Ethereum_Wallet#Gas_limit"
|
||||
placement="top"
|
||||
>
|
||||
<Icon
|
||||
icon={ICONS.HELP}
|
||||
color={colors.TEXT_SECONDARY}
|
||||
size={24}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Left>
|
||||
{ showDefaultGasLimitButton && (
|
||||
<Right onClick={() => setDefaultGasLimit()}>
|
||||
Set default
|
||||
</Right>)
|
||||
}
|
||||
</InputLabelWrapper>
|
||||
)}
|
||||
bottomText={errors.gasLimit || warnings.gasLimit || infos.gasLimit || (calculatingGasLimit ? 'Calculating...' : '')}
|
||||
value={gasLimit}
|
||||
bottomText={errors.gasLimit || warnings.gasLimit || infos.gasLimit}
|
||||
value={calculatingGasLimit ? 'Calculating...' : gasLimit}
|
||||
isDisabled={networkSymbol === currency && data.length > 0}
|
||||
onChange={event => onGasLimitChange(event.target.value)}
|
||||
/>
|
||||
|
@ -185,7 +185,11 @@ const AccountSend = (props: Props) => {
|
||||
discovery,
|
||||
tokens,
|
||||
shouldRender,
|
||||
<<<<<<< HEAD
|
||||
notification,
|
||||
=======
|
||||
loader,
|
||||
>>>>>>> master
|
||||
} = props.selectedAccount;
|
||||
const {
|
||||
address,
|
||||
@ -214,7 +218,7 @@ const AccountSend = (props: Props) => {
|
||||
updateFeeLevels,
|
||||
onSend,
|
||||
} = 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 />;
|
||||
|
||||
const isCurrentCurrencyToken = networkSymbol !== currency;
|
||||
|
@ -106,8 +106,7 @@ class AccountBalance extends PureComponent<Props, State> {
|
||||
|
||||
render() {
|
||||
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 fiatRateValue = '';
|
||||
let fiat = '';
|
||||
@ -117,7 +116,6 @@ class AccountBalance extends PureComponent<Props, State> {
|
||||
fiat = accountBalance.times(fiatRateValue).toFixed(2);
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<HideBalanceIconWrapper
|
||||
|
@ -75,11 +75,12 @@ const AccountSummary = (props: Props) => {
|
||||
network,
|
||||
tokens,
|
||||
pending,
|
||||
notification,
|
||||
loader,
|
||||
shouldRender,
|
||||
} = 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 />;
|
||||
|
||||
const explorerLink: string = `${network.explorer.address}${account.address}`;
|
||||
@ -125,9 +126,11 @@ const AccountSummary = (props: Props) => {
|
||||
loadingMessage={() => 'Loading...'}
|
||||
noOptionsMessage={() => 'Token not found'}
|
||||
onChange={(token) => {
|
||||
const isAdded = tokens.find(t => t.symbol === token.symbol);
|
||||
if (!isAdded) {
|
||||
props.addToken(token, account);
|
||||
if (token.name) {
|
||||
const isAdded = tokens.find(t => t.symbol === token.symbol);
|
||||
if (!isAdded) {
|
||||
props.addToken(token, account);
|
||||
}
|
||||
}
|
||||
}}
|
||||
loadOptions={input => props.loadTokens(input, account.network)}
|
||||
@ -142,7 +145,6 @@ const AccountSummary = (props: Props) => {
|
||||
getOptionValue={option => option.symbol}
|
||||
/>
|
||||
</AsyncSelectWrapper>
|
||||
|
||||
<AddedTokensWrapper>
|
||||
{tokens.map(token => (
|
||||
<AddedToken
|
||||
|
@ -41,7 +41,7 @@ const DeviceSettings = () => (
|
||||
/>
|
||||
<H2>Device settings is under construction</H2>
|
||||
<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>
|
||||
</Link>
|
||||
</Row>
|
||||
|
@ -127,9 +127,9 @@ const FirmwareUpdate = (props: Props) => (
|
||||
</svg>
|
||||
</Image>
|
||||
<H1>It’s time to update your firmware</H1>
|
||||
<StyledP>Please use the old wallet to do that.</StyledP>
|
||||
<Link href="https://wallet.trezor.io">
|
||||
<Button>Take me to the old wallet</Button>
|
||||
<StyledP>Please use Bitcoin wallet interface to update your firmware.</StyledP>
|
||||
<Link href="https://beta-wallet.trezor.io">
|
||||
<Button>Take me to the Bitcoin wallet</Button>
|
||||
</Link>
|
||||
{deviceUtils.isDeviceAccessible(props.device) && (
|
||||
<StyledNavLink to="/">I’ll do that later.</StyledNavLink>
|
||||
|
@ -32,7 +32,7 @@ const Initialize = () => (
|
||||
<Row>
|
||||
<H2>Your device is in not initialized</H2>
|
||||
<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>
|
||||
</A>
|
||||
</Row>
|
||||
|
@ -6,6 +6,7 @@ import { ConnectedRouter } from 'react-router-redux';
|
||||
|
||||
// general
|
||||
import ErrorBoundary from 'support/ErrorBoundary';
|
||||
import ImagesPreloader from 'support/ImagesPreloader';
|
||||
import { getPattern } from 'support/routes';
|
||||
|
||||
// landing views
|
||||
@ -41,6 +42,7 @@ const App = () => (
|
||||
<Route exact path={getPattern('landing-import')} component={ImportView} />
|
||||
<Route>
|
||||
<ErrorBoundary>
|
||||
<ImagesPreloader />
|
||||
<WalletContainer>
|
||||
<Route exact path={getPattern('wallet-settings')} component={WalletSettings} />
|
||||
<Route exact path={getPattern('wallet-dashboard')} component={WalletDashboard} />
|
||||
|
@ -10274,9 +10274,9 @@ tr46@^1.0.1:
|
||||
dependencies:
|
||||
punycode "^2.1.0"
|
||||
|
||||
trezor-connect@6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/trezor-connect/-/trezor-connect-6.0.0.tgz#2a45336f29a4a3f2a8ad2d121363b0e7a1b767ef"
|
||||
trezor-connect@6.0.2:
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/trezor-connect/-/trezor-connect-6.0.2.tgz#a4ca892cc4a167b34b97644e1404a56f6a110379"
|
||||
dependencies:
|
||||
babel-runtime "^6.26.0"
|
||||
events "^1.1.1"
|
||||
|
Loading…
Reference in New Issue
Block a user