mirror of
https://github.com/trezor/trezor-wallet
synced 2025-05-25 18:28:48 +00:00
Merge branch 'master' into fix-left-navigation-bootloader
This commit is contained in:
commit
1d6e6976b6
@ -1,4 +1,6 @@
|
|||||||
public
|
public
|
||||||
|
build
|
||||||
|
build-devel
|
||||||
coverage
|
coverage
|
||||||
images
|
images
|
||||||
node_modules
|
node_modules
|
||||||
|
@ -4,11 +4,15 @@
|
|||||||
"plugin:flowtype/recommended",
|
"plugin:flowtype/recommended",
|
||||||
"plugin:jest/recommended"
|
"plugin:jest/recommended"
|
||||||
],
|
],
|
||||||
|
"globals": {
|
||||||
|
"COMMITHASH": true
|
||||||
|
},
|
||||||
"env": {
|
"env": {
|
||||||
"browser": true,
|
"browser": true,
|
||||||
"jest": true
|
"jest": true
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
|
"no-use-before-define": 0,
|
||||||
"no-plusplus": 0,
|
"no-plusplus": 0,
|
||||||
"class-methods-use-this": 0,
|
"class-methods-use-this": 0,
|
||||||
"react/require-default-props": 0,
|
"react/require-default-props": 0,
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
.*/node_modules/react-router-redux/.*
|
.*/node_modules/react-router-redux/.*
|
||||||
.*/node_modules/oboe/test/.*
|
.*/node_modules/oboe/test/.*
|
||||||
.*/_old/.*
|
.*/_old/.*
|
||||||
.*/public/.*
|
.*/public/solidity/.*
|
||||||
|
|
||||||
[libs]
|
[libs]
|
||||||
./src/flowtype/npm/redux_v3.x.x.js
|
./src/flowtype/npm/redux_v3.x.x.js
|
||||||
@ -43,4 +43,5 @@ module.name_mapper='^data' -> '<PROJECT_ROOT>/src/data'
|
|||||||
module.name_mapper='^services' -> '<PROJECT_ROOT>/src/services'
|
module.name_mapper='^services' -> '<PROJECT_ROOT>/src/services'
|
||||||
module.name_mapper='^support' -> '<PROJECT_ROOT>/src/support'
|
module.name_mapper='^support' -> '<PROJECT_ROOT>/src/support'
|
||||||
module.name_mapper='^public' -> '<PROJECT_ROOT>/public'
|
module.name_mapper='^public' -> '<PROJECT_ROOT>/public'
|
||||||
|
module.name_mapper='^images' -> '<PROJECT_ROOT>/src/images'
|
||||||
module.system=haste
|
module.system=haste
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,4 +1,5 @@
|
|||||||
build
|
build
|
||||||
|
build-devel
|
||||||
|
|
||||||
# Dependency directory
|
# Dependency directory
|
||||||
node_modules
|
node_modules
|
||||||
|
16
package.json
16
package.json
@ -10,8 +10,8 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "npx webpack-dev-server --config ./webpack/dev.babel.js",
|
"dev": "npx webpack-dev-server --config ./webpack/dev.babel.js",
|
||||||
"dev:local": "npx webpack-dev-server --config ./webpack/local.babel.js",
|
"dev:local": "npx webpack-dev-server --config ./webpack/local.babel.js",
|
||||||
"build:clean": "rm -rf build",
|
"build:prod": "rimraf build && npx webpack --config ./webpack/production.babel.js --progress --bail",
|
||||||
"build:production": "npx webpack --config ./webpack/production.babel.js --progress --bail",
|
"build:dev": "rimraf build-devel && cross-env BUILD=development npx webpack --config ./webpack/production.babel.js --output-path build-devel --progress --bail",
|
||||||
"build": "run-s build:*",
|
"build": "run-s build:*",
|
||||||
"flow": "flow check src",
|
"flow": "flow check src",
|
||||||
"lint": "run-s lint:*",
|
"lint": "run-s lint:*",
|
||||||
@ -19,7 +19,9 @@
|
|||||||
"lint:css": "npx stylelint './src/**/*.js'",
|
"lint:css": "npx stylelint './src/**/*.js'",
|
||||||
"test": "run-s test:*",
|
"test": "run-s test:*",
|
||||||
"test:unit": "npx jest",
|
"test:unit": "npx jest",
|
||||||
"test-unit:watch": "npx jest -o --watch"
|
"test-unit:watch": "npx jest -o --watch",
|
||||||
|
"prod-server": "npx http-server ./build -a localhost -S -C ./server/cert.pem -K ./server/key.pem -o",
|
||||||
|
"prod-server-dev": "npx http-server ./build-devel -a localhost -S -C ./server/cert.pem -K ./server/key.pem -o"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"babel": "^6.23.0",
|
"babel": "^6.23.0",
|
||||||
@ -27,13 +29,16 @@
|
|||||||
"bignumber.js": "2.4.0",
|
"bignumber.js": "2.4.0",
|
||||||
"color-hash": "^1.0.3",
|
"color-hash": "^1.0.3",
|
||||||
"copy-webpack-plugin": "^4.5.2",
|
"copy-webpack-plugin": "^4.5.2",
|
||||||
|
"cross-env": "^5.2.0",
|
||||||
"date-fns": "^1.29.0",
|
"date-fns": "^1.29.0",
|
||||||
"ethereumjs-tx": "^1.3.3",
|
"ethereumjs-tx": "^1.3.3",
|
||||||
"ethereumjs-units": "^0.2.0",
|
"ethereumjs-units": "^0.2.0",
|
||||||
"ethereumjs-util": "^5.1.4",
|
"ethereumjs-util": "^5.1.4",
|
||||||
"flow-webpack-plugin": "^1.2.0",
|
"flow-webpack-plugin": "^1.2.0",
|
||||||
|
"git-revision-webpack-plugin": "^3.0.3",
|
||||||
"hdkey": "^0.8.0",
|
"hdkey": "^0.8.0",
|
||||||
"html-webpack-plugin": "^3.2.0",
|
"html-webpack-plugin": "^3.2.0",
|
||||||
|
"http-server": "^0.11.1",
|
||||||
"jest-fetch-mock": "^1.6.5",
|
"jest-fetch-mock": "^1.6.5",
|
||||||
"npm-run-all": "^4.1.3",
|
"npm-run-all": "^4.1.3",
|
||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.6.2",
|
||||||
@ -49,19 +54,20 @@
|
|||||||
"react-router-redux": "next",
|
"react-router-redux": "next",
|
||||||
"react-scale-text": "^1.2.2",
|
"react-scale-text": "^1.2.2",
|
||||||
"react-select": "2.0.0",
|
"react-select": "2.0.0",
|
||||||
"react-sticky-el": "^1.0.20",
|
"react-transition-group": "^2.4.0",
|
||||||
"react-transition-group": "^2.2.1",
|
|
||||||
"redbox-react": "^1.6.0",
|
"redbox-react": "^1.6.0",
|
||||||
"redux": "4.0.0",
|
"redux": "4.0.0",
|
||||||
"redux-logger": "^3.0.6",
|
"redux-logger": "^3.0.6",
|
||||||
"redux-raven-middleware": "^1.2.0",
|
"redux-raven-middleware": "^1.2.0",
|
||||||
"redux-thunk": "^2.2.0",
|
"redux-thunk": "^2.2.0",
|
||||||
|
"rimraf": "^2.6.2",
|
||||||
"styled-components": "^3.3.3",
|
"styled-components": "^3.3.3",
|
||||||
"styled-media-query": "^2.0.2",
|
"styled-media-query": "^2.0.2",
|
||||||
"styled-normalize": "^8.0.0",
|
"styled-normalize": "^8.0.0",
|
||||||
"trezor-connect": "^5.0.32",
|
"trezor-connect": "^5.0.32",
|
||||||
"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-bundle-analyzer": "^2.13.1",
|
"webpack-bundle-analyzer": "^2.13.1",
|
||||||
"whatwg-fetch": "^2.0.4",
|
"whatwg-fetch": "^2.0.4",
|
||||||
"yarn-run-all": "^3.1.1"
|
"yarn-run-all": "^3.1.1"
|
||||||
|
20
server/cert.pem
Normal file
20
server/cert.pem
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDMjCCAhoCCQD/Ey0A0Ll1FjANBgkqhkiG9w0BAQsFADBbMQswCQYDVQQGEwJj
|
||||||
|
ejEKMAgGA1UECAwBczEKMAgGA1UEBwwBYTEKMAgGA1UECgwBczEKMAgGA1UECwwB
|
||||||
|
czEKMAgGA1UEAwwBczEQMA4GCSqGSIb3DQEJARYBczAeFw0xODA3MjUxMTUyMzla
|
||||||
|
Fw0xODA4MjQxMTUyMzlaMFsxCzAJBgNVBAYTAmN6MQowCAYDVQQIDAFzMQowCAYD
|
||||||
|
VQQHDAFhMQowCAYDVQQKDAFzMQowCAYDVQQLDAFzMQowCAYDVQQDDAFzMRAwDgYJ
|
||||||
|
KoZIhvcNAQkBFgFzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1sON
|
||||||
|
vf+ny+KASV8pCT83fJNyAV8uAvg/Ur0B/bf26onvhoUJ74w6lFfJTyw0WjOzXR/G
|
||||||
|
reXg4yO8aGHTwSuYifY0Uj1Lm4cZYz3n96X37cKyviNg8zp+QXKgESwnxJ/VyLf+
|
||||||
|
rYc/DzTQsXFs7irDXsGCq+8z56ljZ788axd5wip2AGBAnTzH9OeHD8sRIYNqFKNm
|
||||||
|
S2Rx1Ev3QIIwqng3e6vX4/kk7Zz380z1MpB2wbEsUQxx27hT4Z2tZRyn0L/AXQBL
|
||||||
|
H3zxA5FNVxgQaUAFWoqS6cv8nJtIRbnvoiwNygDMyxhrB+CbUCtCVDm8W94TE+Qn
|
||||||
|
+BdZzfPg/fQbv4B6owIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQC/g7yIDluDPOKP
|
||||||
|
mo37+hI4vQT90HnSzccMhelBoqHSw2OFN3O+eantCy9eNr9UNHeZP7KyTsvRW5nW
|
||||||
|
L48Ps1eAPC66QCNvkLmpJ3YRHa157722cPdgV0jYm5njV+9pdjNFFD/yF43nNm/r
|
||||||
|
IdiOwXak4qqajDeug9oeZh/sGPZ888gnZMaYhON8zzL+CR+xKv45ORVpHY+fQp4f
|
||||||
|
xv8195pqcehFy2RYniDzcU/cy3IfOJaxYYbV5mlQtFvY8plhC3QtcJebS0nei4J+
|
||||||
|
oX2Wh1dxKUNGxqPUjwfyYxPtxPTUEsOJAtTtwcrQ2ShKCzFSP9xdZ9i4WviV84pU
|
||||||
|
9RGGV7yC
|
||||||
|
-----END CERTIFICATE-----
|
28
server/key.pem
Normal file
28
server/key.pem
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDWw429/6fL4oBJ
|
||||||
|
XykJPzd8k3IBXy4C+D9SvQH9t/bqie+GhQnvjDqUV8lPLDRaM7NdH8at5eDjI7xo
|
||||||
|
YdPBK5iJ9jRSPUubhxljPef3pfftwrK+I2DzOn5BcqARLCfEn9XIt/6thz8PNNCx
|
||||||
|
cWzuKsNewYKr7zPnqWNnvzxrF3nCKnYAYECdPMf054cPyxEhg2oUo2ZLZHHUS/dA
|
||||||
|
gjCqeDd7q9fj+STtnPfzTPUykHbBsSxRDHHbuFPhna1lHKfQv8BdAEsffPEDkU1X
|
||||||
|
GBBpQAVaipLpy/ycm0hFue+iLA3KAMzLGGsH4JtQK0JUObxb3hMT5Cf4F1nN8+D9
|
||||||
|
9Bu/gHqjAgMBAAECggEBAIbMgXgjMn/vcBQdjZVHP52KsoEX67pjdOOKzOgigvHd
|
||||||
|
mCE3+e+IdfBMVYfDOCzxzIAEBOF7qzcGZCikVpQlt/3IMjj4Ti+VkaLP5Xx0iPSM
|
||||||
|
Q0LC1AR2z25m8v80VtW8eSQeENV8UWFLBj6J8hRfdPdRwKIIZuzeTg19Y//X4U2z
|
||||||
|
0AQHc1askJc2Hu+FEm5GajurR4CEawrgHXtBaY8F96lGPRWXJThZhB8GemKkN8Ws
|
||||||
|
i8uMgdB4dcO7iGa1GpiSErCbJxC74/FA2XKf9eVYL7tRm1Bc/hojl1FxbWVnlC+W
|
||||||
|
RXqhdcKTHQ6LODYUovBCp5A5p5S/+zB24tnMgfIhYikCgYEA+1bzT4P2XuP4/A2N
|
||||||
|
yH3Da/fWlsoVo7/c9vy0AQhWo/ZH9mHiP1A7qC6b98T2DL0qsv0viGKBKPw0J1Cj
|
||||||
|
R4Tn3aKwPxsceHOEZ9zh1qApvaQH2kVMYrxbGCYGMIcj2Ps6HjPJGy3wH5T1+sco
|
||||||
|
u0qhIKs07TtoQe767HV+XWohxacCgYEA2r78nPhKpAQDiJ1uuqNPHybttnHX2OTv
|
||||||
|
f/LCM3B4pmORCSVostjqN6yUAUYBjvEczCkcyW3mTim7H9C92bePZmRrC8/7csYm
|
||||||
|
2ymJAzRMFlbnjIzF3EkBTcWermcrXwBchDQ6LlaKATT3qbxlewplFTE7h/3wa7da
|
||||||
|
kUPwHbJx+qUCgYAZCwbfS2TG+6wZYThZW76XCXDGQYh6cmmP6on8+Fm5qJZvBD3I
|
||||||
|
1TO8hDhiLavehRK2FugfjMEV1ltT94LtY16/BLDO+OKTVd9Bgg62lerSzH9DzlfY
|
||||||
|
FrB07YT8XNrDifS2ga5uGNuuKeeAf0udrcf0O1rgsGSo/SjfWq2mnSaUTQKBgHZl
|
||||||
|
iTUs7rl3srHvBE/gtKKX33IwjDPJNhh6vMI6zhLBMW9R4CltXthjgHhv+8fymTOn
|
||||||
|
zPz5jv4feDjwMtH0mJlDIO1z1RV6Su20vYQOemBdCVb5mt5wZVRC8nBTRxZUi77C
|
||||||
|
xfruvCOLF8G3RvYh2jRuQVqKB+dFhq+5pe1s+GRBAoGBAKX8oBMbm3e2eC3FzCKE
|
||||||
|
sB4BKLzGnQmoU4E/xUYLU+h5TTht4orsCil4Y9kkIE+jPP4Z84xqpfwN4QHw4Ls+
|
||||||
|
SwBi+3C0HIo2Evss1D4ELs58N9nNnJRsGkqBqQkJIygpUVgYOTsCBOmjjm/5+Cd3
|
||||||
|
RUE7p2ym8WCZmDkVkhFmZk1J
|
||||||
|
-----END PRIVATE KEY-----
|
@ -21,7 +21,7 @@ export type StorageAction = {
|
|||||||
type: typeof STORAGE.READY,
|
type: typeof STORAGE.READY,
|
||||||
config: Config,
|
config: Config,
|
||||||
tokens: TokensCollection,
|
tokens: TokensCollection,
|
||||||
ERC20Abi: Array<Object>
|
ERC20Abi: Array<TokensCollection>
|
||||||
} | {
|
} | {
|
||||||
type: typeof STORAGE.SAVE,
|
type: typeof STORAGE.SAVE,
|
||||||
network: string,
|
network: string,
|
||||||
@ -148,7 +148,6 @@ export function loadTokensFromJSON(): AsyncAction {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: STORAGE.READY,
|
type: STORAGE.READY,
|
||||||
config,
|
config,
|
||||||
|
304
src/actions/RouterActions.js
Normal file
304
src/actions/RouterActions.js
Normal file
@ -0,0 +1,304 @@
|
|||||||
|
/* @flow */
|
||||||
|
|
||||||
|
import { push, LOCATION_CHANGE } from 'react-router-redux';
|
||||||
|
import { routes } from 'support/routes';
|
||||||
|
|
||||||
|
import type {
|
||||||
|
RouterLocationState,
|
||||||
|
Device,
|
||||||
|
TrezorDevice,
|
||||||
|
ThunkAction,
|
||||||
|
PayloadAction,
|
||||||
|
Dispatch,
|
||||||
|
GetState,
|
||||||
|
} from 'flowtype';
|
||||||
|
import type { RouterAction } from 'react-router-redux';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Parse url string to RouterLocationState object (key/value)
|
||||||
|
*/
|
||||||
|
export const pathToParams = (path: string): PayloadAction<RouterLocationState> => (): RouterLocationState => {
|
||||||
|
// split url into parts
|
||||||
|
const parts: Array<string> = path.split('/').slice(1);
|
||||||
|
const params: RouterLocationState = {};
|
||||||
|
// return empty params
|
||||||
|
if (parts.length < 1 || path === '/') return params;
|
||||||
|
|
||||||
|
// map parts to params by key/value
|
||||||
|
// assuming that url is in format: "/key/value"
|
||||||
|
for (let i = 0, len = parts.length; i < len; i += 2) {
|
||||||
|
params[parts[i]] = parts[i + 1] || parts[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for special case: /device/device-id:instance-id
|
||||||
|
if (params.hasOwnProperty('device')) {
|
||||||
|
const isClonedDevice: Array<string> = params.device.split(':');
|
||||||
|
if (isClonedDevice.length > 1) {
|
||||||
|
const [device, instance] = isClonedDevice;
|
||||||
|
params.device = device;
|
||||||
|
params.deviceInstance = instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return params;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* RouterLocationState validation
|
||||||
|
* Check if requested device or network exists in reducers
|
||||||
|
*/
|
||||||
|
export const paramsValidation = (params: RouterLocationState): PayloadAction<boolean> => (dispatch: Dispatch, getState: GetState): boolean => {
|
||||||
|
// validate requested device
|
||||||
|
if (params.hasOwnProperty('device')) {
|
||||||
|
const { devices } = getState();
|
||||||
|
|
||||||
|
let device: ?TrezorDevice;
|
||||||
|
if (params.hasOwnProperty('deviceInstance')) {
|
||||||
|
device = devices.find(d => d.features && d.features.device_id === params.device && d.instance === parseInt(params.deviceInstance, 10));
|
||||||
|
} else {
|
||||||
|
device = devices.find(d => d.path === params.device || (d.features && d.features.device_id === params.device));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!device) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate requested network
|
||||||
|
if (params.hasOwnProperty('network')) {
|
||||||
|
const { config } = getState().localStorage;
|
||||||
|
const coin = config.coins.find(c => c.network === params.network);
|
||||||
|
if (!coin) return false;
|
||||||
|
if (!params.account) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate requested account
|
||||||
|
// TODO: only if discovery on this network is completed
|
||||||
|
// if (params.hasOwnProperty('account')) {
|
||||||
|
|
||||||
|
// }
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Composing url string from given RouterLocationState object
|
||||||
|
* Filters unrecognized fields and sorting in correct order
|
||||||
|
*/
|
||||||
|
export const paramsToPath = (params: RouterLocationState): PayloadAction<?string> => (): ?string => {
|
||||||
|
// get patterns (fields) from routes and sort them by complexity
|
||||||
|
const patterns: Array<Array<string>> = routes.map(r => r.fields).sort((a, b) => (a.length > b.length ? -1 : 1));
|
||||||
|
|
||||||
|
// find pattern
|
||||||
|
const keys: Array<string> = Object.keys(params);
|
||||||
|
let patternToUse: ?Array<string>;
|
||||||
|
let i: number;
|
||||||
|
for (i = 0; i < patterns.length; i++) {
|
||||||
|
const pattern = patterns[i];
|
||||||
|
const match: Array<string> = keys.filter(key => pattern.indexOf(key) >= 0);
|
||||||
|
if (match.length === pattern.length) {
|
||||||
|
patternToUse = pattern;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pattern not found, redirect back
|
||||||
|
if (!patternToUse) return null;
|
||||||
|
|
||||||
|
// compose url string from pattern
|
||||||
|
let url: string = '';
|
||||||
|
patternToUse.forEach((field) => {
|
||||||
|
if (field === params[field]) {
|
||||||
|
// standalone (odd) fields
|
||||||
|
url += `/${field}`;
|
||||||
|
} else {
|
||||||
|
url += `/${field}/${params[field]}`;
|
||||||
|
if (field === 'device') {
|
||||||
|
if (params.hasOwnProperty('deviceInstance')) {
|
||||||
|
url += `:${params.deviceInstance}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return url;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getValidUrl = (action: RouterAction): PayloadAction<string> => (dispatch: Dispatch, getState: GetState): string => {
|
||||||
|
const { location } = getState().router;
|
||||||
|
|
||||||
|
// redirect to landing page (loading screen)
|
||||||
|
// and wait until application is ready
|
||||||
|
if (!location) return '/';
|
||||||
|
|
||||||
|
const requestedUrl = action.payload.pathname;
|
||||||
|
// Corner case: LOCATION_CHANGE was called but pathname didn't changed (redirect action from RouterService)
|
||||||
|
if (requestedUrl === location.pathname) return requestedUrl;
|
||||||
|
|
||||||
|
// Modal is opened
|
||||||
|
// redirect to previous url
|
||||||
|
if (getState().modal.opened) {
|
||||||
|
// Corner case: modal is opened and currentParams are still valid
|
||||||
|
// example 1 (valid blocking): url changed while passphrase modal opened but device is still connected (we want user to finish this action)
|
||||||
|
// example 2 (invalid blocking): url changes while passphrase modal opened because device disconnect
|
||||||
|
const currentParams = dispatch(pathToParams(location.pathname));
|
||||||
|
const currentParamsAreValid = dispatch(paramsValidation(currentParams));
|
||||||
|
if (currentParamsAreValid) { return location.pathname; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// there are no connected devices or application isn't ready or initialization error occurred
|
||||||
|
// redirect to landing page
|
||||||
|
const shouldBeLandingPage = getState().devices.length < 1 || !getState().wallet.ready || getState().connect.error !== null;
|
||||||
|
const landingPageUrl = dispatch(isLandingPageUrl(requestedUrl));
|
||||||
|
if (shouldBeLandingPage) {
|
||||||
|
return !landingPageUrl ? '/' : requestedUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disallow displaying landing page
|
||||||
|
// redirect to previous url
|
||||||
|
if (!shouldBeLandingPage && landingPageUrl) {
|
||||||
|
return location.pathname;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regular url change during application live cycle
|
||||||
|
const requestedParams = dispatch(pathToParams(requestedUrl));
|
||||||
|
const requestedParamsAreValid: boolean = dispatch(paramsValidation(requestedParams));
|
||||||
|
|
||||||
|
// Requested params are not valid
|
||||||
|
// Neither device or network doesn't exists
|
||||||
|
if (!requestedParamsAreValid) {
|
||||||
|
return location.pathname;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compose valid url from requested params
|
||||||
|
const composedUrl = dispatch(paramsToPath(requestedParams));
|
||||||
|
return composedUrl || location.pathname;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Utility used in "selectDevice" and "selectFirstAvailableDevice"
|
||||||
|
* sorting device array by "ts" (timestamp) field
|
||||||
|
*/
|
||||||
|
const sortDevices = (devices: Array<TrezorDevice>): Array<TrezorDevice> => devices.sort((a, b) => {
|
||||||
|
if (!a.ts || !b.ts) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return a.ts > b.ts ? -1 : 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Compose url from given device object and redirect
|
||||||
|
*/
|
||||||
|
export const selectDevice = (device: TrezorDevice | Device): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
||||||
|
let url: ?string;
|
||||||
|
if (!device.features) {
|
||||||
|
url = `/device/${device.path}/${device.type === 'unreadable' ? 'unreadable' : 'acquire'}`;
|
||||||
|
} else if (device.features.bootloader_mode) {
|
||||||
|
url = `/device/${device.path}/bootloader`;
|
||||||
|
} else if (!device.features.initialized) {
|
||||||
|
url = `/device/${device.features.device_id}/initialize`;
|
||||||
|
} else if (typeof device.instance === 'number') {
|
||||||
|
url = `/device/${device.features.device_id}:${device.instance}`;
|
||||||
|
} else {
|
||||||
|
url = `/device/${device.features.device_id}`;
|
||||||
|
// make sure that device is not TrezorDevice type
|
||||||
|
if (!device.hasOwnProperty('ts')) {
|
||||||
|
// it is device from trezor-connect triggered by DEVICE.CONNECT event
|
||||||
|
// need to lookup if there are unavailable instances
|
||||||
|
const available: Array<TrezorDevice> = getState().devices.filter(d => d.path === device.path);
|
||||||
|
const latest: Array<TrezorDevice> = sortDevices(available);
|
||||||
|
if (latest.length > 0 && latest[0].instance) {
|
||||||
|
url += `:${latest[0].instance}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentParams: RouterLocationState = getState().router.location.state;
|
||||||
|
const requestedParams = dispatch(pathToParams(url));
|
||||||
|
if (currentParams.device !== requestedParams.device || currentParams.deviceInstance !== requestedParams.deviceInstance) {
|
||||||
|
dispatch(goto(url));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Try to find first available device using order:
|
||||||
|
* 1. First unacquired
|
||||||
|
* 2. First connected
|
||||||
|
* 3. Saved with latest timestamp
|
||||||
|
* OR redirect to landing page
|
||||||
|
*/
|
||||||
|
export const selectFirstAvailableDevice = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
||||||
|
const { devices } = getState();
|
||||||
|
if (devices.length > 0) {
|
||||||
|
const unacquired = devices.find(d => !d.features);
|
||||||
|
if (unacquired) {
|
||||||
|
dispatch(selectDevice(unacquired));
|
||||||
|
} else {
|
||||||
|
const latest: Array<TrezorDevice> = sortDevices(devices);
|
||||||
|
const firstConnected: ?TrezorDevice = latest.find(d => d.connected);
|
||||||
|
dispatch(selectDevice(firstConnected || latest[0]));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dispatch(gotoLandingPage());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Internal method. redirect to given url
|
||||||
|
*/
|
||||||
|
const goto = (url: string): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
||||||
|
if (getState().router.location.pathname !== url) {
|
||||||
|
dispatch(push(url));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check if requested OR current url is landing page
|
||||||
|
*/
|
||||||
|
export const isLandingPageUrl = ($url?: string): PayloadAction<boolean> => (dispatch: Dispatch, getState: GetState): boolean => {
|
||||||
|
let url: ?string = $url;
|
||||||
|
if (typeof url !== 'string') {
|
||||||
|
url = getState().router.location.pathname;
|
||||||
|
}
|
||||||
|
// TODO: add more landing page cases/urls to config.json (like /tools etc)
|
||||||
|
return (url === '/' || url === '/bridge');
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Try to redirect to landing page
|
||||||
|
*/
|
||||||
|
export const gotoLandingPage = (): ThunkAction => (dispatch: Dispatch): void => {
|
||||||
|
const isLandingPage = dispatch(isLandingPageUrl());
|
||||||
|
if (!isLandingPage) {
|
||||||
|
dispatch(goto('/'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Go to given device settings page
|
||||||
|
*/
|
||||||
|
export const gotoDeviceSettings = (device: TrezorDevice): ThunkAction => (dispatch: Dispatch): void => {
|
||||||
|
if (device.features) {
|
||||||
|
const devUrl: string = `${device.features.device_id}${device.instance ? `:${device.instance}` : ''}`;
|
||||||
|
dispatch(goto(`/device/${devUrl}/settings`));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Try to redirect to initial url
|
||||||
|
*/
|
||||||
|
export const setInitialUrl = (): PayloadAction<boolean> => (dispatch: Dispatch, getState: GetState): boolean => {
|
||||||
|
const { initialPathname } = getState().wallet;
|
||||||
|
if (typeof initialPathname === 'string' && !dispatch(isLandingPageUrl(initialPathname))) {
|
||||||
|
const valid = dispatch(getValidUrl({
|
||||||
|
type: LOCATION_CHANGE,
|
||||||
|
payload: {
|
||||||
|
pathname: initialPathname,
|
||||||
|
hash: '',
|
||||||
|
search: '',
|
||||||
|
state: {},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
if (valid === initialPathname) {
|
||||||
|
dispatch(goto(valid));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
@ -7,6 +7,9 @@ import * as SEND from 'actions/constants/send';
|
|||||||
import * as NOTIFICATION from 'actions/constants/notification';
|
import * as NOTIFICATION from 'actions/constants/notification';
|
||||||
import * as PENDING from 'actions/constants/pendingTx';
|
import * as PENDING from 'actions/constants/pendingTx';
|
||||||
|
|
||||||
|
import * as SendFormActions from 'actions/SendFormActions';
|
||||||
|
import * as SessionStorageActions from 'actions/SessionStorageActions';
|
||||||
|
|
||||||
import * as stateUtils from 'reducers/utils';
|
import * as stateUtils from 'reducers/utils';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@ -16,8 +19,6 @@ import type {
|
|||||||
Dispatch,
|
Dispatch,
|
||||||
State,
|
State,
|
||||||
} from 'flowtype';
|
} from 'flowtype';
|
||||||
import * as SendFormActions from './SendFormActions';
|
|
||||||
|
|
||||||
|
|
||||||
export type SelectedAccountAction = {
|
export type SelectedAccountAction = {
|
||||||
type: typeof ACCOUNT.DISPOSE,
|
type: typeof ACCOUNT.DISPOSE,
|
||||||
@ -42,17 +43,16 @@ export const updateSelectedValues = (prevState: State, action: Action): AsyncAct
|
|||||||
// SessionStorageActions.clear(location.pathname);
|
// SessionStorageActions.clear(location.pathname);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (prevState.sendForm !== state.sendForm) {
|
||||||
|
dispatch(SessionStorageActions.save());
|
||||||
|
}
|
||||||
|
|
||||||
// handle devices state change (from trezor-connect events or location change)
|
// handle devices state change (from trezor-connect events or location change)
|
||||||
if (locationChange
|
if (locationChange
|
||||||
|| prevState.accounts !== state.accounts
|
|| prevState.accounts !== state.accounts
|
||||||
|| prevState.discovery !== state.discovery
|
|| prevState.discovery !== state.discovery
|
||||||
|| prevState.tokens !== state.tokens
|
|| prevState.tokens !== state.tokens
|
||||||
|| prevState.pending !== state.pending) {
|
|| prevState.pending !== state.pending) {
|
||||||
if (locationChange) {
|
|
||||||
// dispose current account view
|
|
||||||
dispatch(dispose());
|
|
||||||
}
|
|
||||||
|
|
||||||
const account = stateUtils.getSelectedAccount(state);
|
const account = stateUtils.getSelectedAccount(state);
|
||||||
const network = stateUtils.getSelectedNetwork(state);
|
const network = stateUtils.getSelectedNetwork(state);
|
||||||
const discovery = stateUtils.getDiscoveryProcess(state);
|
const discovery = stateUtils.getDiscoveryProcess(state);
|
||||||
|
@ -13,6 +13,7 @@ import { initialState } from 'reducers/SendFormReducer';
|
|||||||
import { findToken } from 'reducers/TokensReducer';
|
import { findToken } from 'reducers/TokensReducer';
|
||||||
import { findDevice, getPendingAmount, getPendingNonce } from 'reducers/utils';
|
import { findDevice, getPendingAmount, getPendingNonce } from 'reducers/utils';
|
||||||
import * as stateUtils from 'reducers/utils';
|
import * as stateUtils from 'reducers/utils';
|
||||||
|
import { validateAddress } from 'utils/ethUtils';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
Dispatch,
|
Dispatch,
|
||||||
@ -246,7 +247,7 @@ export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetS
|
|||||||
|
|
||||||
|
|
||||||
// const gasPrice: BigNumber = new BigNumber(EthereumjsUnits.convert(web3.gasPrice, 'wei', 'gwei')) || new BigNumber(network.defaultGasPrice);
|
// const gasPrice: BigNumber = new BigNumber(EthereumjsUnits.convert(web3.gasPrice, 'wei', 'gwei')) || new BigNumber(network.defaultGasPrice);
|
||||||
const gasPrice: BigNumber = await dispatch( BlockchainActions.getGasPrice(network.network, network.defaultGasPrice) );
|
const gasPrice: BigNumber = await dispatch(BlockchainActions.getGasPrice(network.network, network.defaultGasPrice));
|
||||||
// const gasPrice: BigNumber = new BigNumber(network.defaultGasPrice);
|
// const gasPrice: BigNumber = new BigNumber(network.defaultGasPrice);
|
||||||
const gasLimit: string = network.defaultGasLimit.toString();
|
const gasLimit: string = network.defaultGasLimit.toString();
|
||||||
const feeLevels: Array<FeeLevel> = getFeeLevels(network.symbol, gasPrice, gasLimit);
|
const feeLevels: Array<FeeLevel> = getFeeLevels(network.symbol, gasPrice, gasLimit);
|
||||||
@ -345,34 +346,21 @@ export const validation = (props: Props): void => {
|
|||||||
if (state.untouched) return;
|
if (state.untouched) return;
|
||||||
// valid address
|
// valid address
|
||||||
if (state.touched.address) {
|
if (state.touched.address) {
|
||||||
/* if (state.address.length < 1) {
|
const addressError = validateAddress(state.address);
|
||||||
errors.address = 'Address is not set';
|
if (addressError) {
|
||||||
} else if (!EthereumjsUtil.isValidAddress(state.address)) {
|
errors.address = addressError;
|
||||||
errors.address = 'Address is not valid';
|
}
|
||||||
} else {
|
|
||||||
// address warning or info are set in addressValidation ThunkAction
|
// address warning or info may be set in addressValidation ThunkAction
|
||||||
// do not override this
|
// do not override them
|
||||||
if (state.warnings.address) {
|
if (state.warnings.address) {
|
||||||
warnings.address = state.warnings.address;
|
warnings.address = state.warnings.address;
|
||||||
} else if (state.infos.address) {
|
|
||||||
infos.address = state.infos.address;
|
|
||||||
}
|
}
|
||||||
} */
|
|
||||||
|
|
||||||
/* eslint (no-lonely-if) */
|
|
||||||
if (state.address.length < 1) {
|
|
||||||
errors.address = 'Address is not set';
|
|
||||||
} else if (!EthereumjsUtil.isValidAddress(state.address)) {
|
|
||||||
errors.address = 'Address is not valid';
|
|
||||||
} else if (state.warnings.address) {
|
|
||||||
// address warning or info are set in addressValidation ThunkAction
|
|
||||||
// do not override this
|
|
||||||
warnings.address = state.warnings.address;
|
|
||||||
if (state.infos.address) {
|
if (state.infos.address) {
|
||||||
infos.address = state.infos.address;
|
infos.address = state.infos.address;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// valid amount
|
// valid amount
|
||||||
// https://stackoverflow.com/a/42701461
|
// https://stackoverflow.com/a/42701461
|
||||||
@ -733,7 +721,7 @@ const estimateGasPrice = (): AsyncAction => async (dispatch: Dispatch, getState:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const gasLimit: number = await dispatch( BlockchainActions.estimateGasLimit(network.network, state.data, state.amount, state.gasPrice) );
|
const gasLimit: number = await dispatch(BlockchainActions.estimateGasLimit(network.network, state.data, state.amount, state.gasPrice));
|
||||||
|
|
||||||
if (getState().sendForm.data === requestedData) {
|
if (getState().sendForm.data === requestedData) {
|
||||||
dispatch(onGasLimitChange(gasLimit.toString()));
|
dispatch(onGasLimitChange(gasLimit.toString()));
|
||||||
@ -783,7 +771,7 @@ export const onSend = (): AsyncAction => async (dispatch: Dispatch, getState: Ge
|
|||||||
const pendingNonce: number = stateUtils.getPendingNonce(pending);
|
const pendingNonce: number = stateUtils.getPendingNonce(pending);
|
||||||
const nonce = pendingNonce > 0 && pendingNonce >= account.nonce ? pendingNonce : account.nonce;
|
const nonce = pendingNonce > 0 && pendingNonce >= account.nonce ? pendingNonce : account.nonce;
|
||||||
|
|
||||||
const txData = await dispatch( prepareEthereumTx({
|
const txData = await dispatch(prepareEthereumTx({
|
||||||
network: network.network,
|
network: network.network,
|
||||||
token: isToken ? findToken(getState().tokens, account.address, currentState.currency, account.deviceState) : null,
|
token: isToken ? findToken(getState().tokens, account.address, currentState.currency, account.deviceState) : null,
|
||||||
from: account.address,
|
from: account.address,
|
||||||
@ -792,8 +780,8 @@ export const onSend = (): AsyncAction => async (dispatch: Dispatch, getState: Ge
|
|||||||
data: currentState.data,
|
data: currentState.data,
|
||||||
gasLimit: currentState.gasLimit,
|
gasLimit: currentState.gasLimit,
|
||||||
gasPrice: currentState.gasPrice,
|
gasPrice: currentState.gasPrice,
|
||||||
nonce
|
nonce,
|
||||||
}) );
|
}));
|
||||||
|
|
||||||
const selected: ?TrezorDevice = getState().wallet.selectedDevice;
|
const selected: ?TrezorDevice = getState().wallet.selectedDevice;
|
||||||
if (!selected) return;
|
if (!selected) return;
|
||||||
@ -828,14 +816,14 @@ export const onSend = (): AsyncAction => async (dispatch: Dispatch, getState: Ge
|
|||||||
txData.v = signedTransaction.payload.v;
|
txData.v = signedTransaction.payload.v;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const serializedTx: string = await dispatch( serializeEthereumTx(txData) );
|
const serializedTx: string = await dispatch(serializeEthereumTx(txData));
|
||||||
const push = await TrezorConnect.pushTransaction({
|
const push = await TrezorConnect.pushTransaction({
|
||||||
tx: serializedTx,
|
tx: serializedTx,
|
||||||
coin: network.network
|
coin: network.network,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!push.success) {
|
if (!push.success) {
|
||||||
throw new Error( push.payload.error );
|
throw new Error(push.payload.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
const txid = push.payload.txid;
|
const txid = push.payload.txid;
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
import TrezorConnect, {
|
import TrezorConnect, {
|
||||||
UI, DEVICE, DEVICE_EVENT, UI_EVENT, TRANSPORT_EVENT, BLOCKCHAIN_EVENT
|
DEVICE, DEVICE_EVENT, UI_EVENT, TRANSPORT_EVENT, BLOCKCHAIN_EVENT,
|
||||||
} from 'trezor-connect';
|
} from 'trezor-connect';
|
||||||
import * as CONNECT from 'actions/constants/TrezorConnect';
|
import * as CONNECT from 'actions/constants/TrezorConnect';
|
||||||
import * as NOTIFICATION from 'actions/constants/notification';
|
import * as NOTIFICATION from 'actions/constants/notification';
|
||||||
import * as WALLET from 'actions/constants/wallet';
|
|
||||||
import { getDuplicateInstanceNumber } from 'reducers/utils';
|
import { getDuplicateInstanceNumber } from 'reducers/utils';
|
||||||
|
import * as RouterActions from 'actions/RouterActions';
|
||||||
import { push } from 'react-router-redux';
|
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
DeviceMessage,
|
DeviceMessage,
|
||||||
@ -28,7 +26,6 @@ import type {
|
|||||||
AsyncAction,
|
AsyncAction,
|
||||||
Device,
|
Device,
|
||||||
TrezorDevice,
|
TrezorDevice,
|
||||||
RouterLocationState,
|
|
||||||
} from 'flowtype';
|
} from 'flowtype';
|
||||||
import * as DiscoveryActions from './DiscoveryActions';
|
import * as DiscoveryActions from './DiscoveryActions';
|
||||||
|
|
||||||
@ -79,19 +76,11 @@ export type TrezorConnectAction = {
|
|||||||
type: typeof CONNECT.STOP_ACQUIRING,
|
type: typeof CONNECT.STOP_ACQUIRING,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const sortDevices = (devices: Array<TrezorDevice>): Array<TrezorDevice> => devices.sort((a, b) => {
|
|
||||||
if (!a.ts || !b.ts) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return a.ts > b.ts ? -1 : 1;
|
|
||||||
});
|
|
||||||
|
|
||||||
export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||||
// set listeners
|
// set listeners
|
||||||
TrezorConnect.on(DEVICE_EVENT, (event: DeviceMessage): void => {
|
TrezorConnect.on(DEVICE_EVENT, (event: DeviceMessage): void => {
|
||||||
// post event to reducers
|
// post event to reducers
|
||||||
const type: DeviceMessageType = event.type; // assert flow type
|
const type: DeviceMessageType = event.type; // eslint-disable-line prefer-destructuring
|
||||||
dispatch({
|
dispatch({
|
||||||
type,
|
type,
|
||||||
device: event.payload,
|
device: event.payload,
|
||||||
@ -100,7 +89,7 @@ export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetS
|
|||||||
|
|
||||||
TrezorConnect.on(UI_EVENT, (event: UiMessage): void => {
|
TrezorConnect.on(UI_EVENT, (event: UiMessage): void => {
|
||||||
// post event to reducers
|
// post event to reducers
|
||||||
const type: UiMessageType = event.type; // assert flow type
|
const type: UiMessageType = event.type; // eslint-disable-line prefer-destructuring
|
||||||
dispatch({
|
dispatch({
|
||||||
type,
|
type,
|
||||||
payload: event.payload,
|
payload: event.payload,
|
||||||
@ -109,7 +98,7 @@ export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetS
|
|||||||
|
|
||||||
TrezorConnect.on(TRANSPORT_EVENT, (event: TransportMessage): void => {
|
TrezorConnect.on(TRANSPORT_EVENT, (event: TransportMessage): void => {
|
||||||
// post event to reducers
|
// post event to reducers
|
||||||
const type: TransportMessageType = event.type; // assert flow type
|
const type: TransportMessageType = event.type; // eslint-disable-line prefer-destructuring
|
||||||
dispatch({
|
dispatch({
|
||||||
type,
|
type,
|
||||||
payload: event.payload,
|
payload: event.payload,
|
||||||
@ -118,21 +107,22 @@ export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetS
|
|||||||
|
|
||||||
TrezorConnect.on(BLOCKCHAIN_EVENT, (event: BlockchainMessage): void => {
|
TrezorConnect.on(BLOCKCHAIN_EVENT, (event: BlockchainMessage): void => {
|
||||||
// post event to reducers
|
// post event to reducers
|
||||||
const type: BlockchainMessageType = event.type; // assert flow type
|
const type: BlockchainMessageType = event.type; // eslint-disable-line prefer-destructuring
|
||||||
dispatch({
|
dispatch({
|
||||||
type,
|
type,
|
||||||
payload: event.payload,
|
payload: event.payload,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/* global LOCAL */
|
||||||
// $FlowIssue LOCAL not declared
|
// $FlowIssue LOCAL not declared
|
||||||
window.__TREZOR_CONNECT_SRC = typeof LOCAL === 'string' ? LOCAL : 'https://sisyfos.trezor.io/connect/';
|
window.__TREZOR_CONNECT_SRC = typeof LOCAL === 'string' ? LOCAL : 'https://sisyfos.trezor.io/connect/'; // eslint-disable-line no-underscore-dangle
|
||||||
// window.__TREZOR_CONNECT_SRC = typeof LOCAL === 'string' ? LOCAL : 'https://connect.trezor.io/5/';
|
// window.__TREZOR_CONNECT_SRC = typeof LOCAL === 'string' ? LOCAL : 'https://connect.trezor.io/5/'; // eslint-disable-line no-underscore-dangle
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await TrezorConnect.init({
|
await TrezorConnect.init({
|
||||||
transportReconnect: true,
|
transportReconnect: true,
|
||||||
debug: true,
|
debug: false,
|
||||||
popup: false,
|
popup: false,
|
||||||
webusb: true,
|
webusb: true,
|
||||||
pendingTransportEvent: (getState().devices.length < 1),
|
pendingTransportEvent: (getState().devices.length < 1),
|
||||||
@ -145,67 +135,11 @@ export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetS
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// selection from Aside dropdown button
|
|
||||||
// after device_connect event
|
|
||||||
// or after acquiring device
|
|
||||||
// device type could be local TrezorDevice or Device (from trezor-connect device_connect event)
|
|
||||||
export const onSelectDevice = (device: TrezorDevice | Device): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
|
||||||
// || device.isUsedElsewhere
|
|
||||||
|
|
||||||
// switch to initial url and reset this value
|
|
||||||
|
|
||||||
if (!device.features) {
|
|
||||||
dispatch(push(`/device/${device.path}/${device.type === 'unreadable' ? 'unreadable' : 'acquire'}`));
|
|
||||||
} else if (device.features.bootloader_mode) {
|
|
||||||
dispatch(push(`/device/${device.path}/bootloader`));
|
|
||||||
} else if (!device.features.initialized) {
|
|
||||||
dispatch(push(`/device/${device.features.device_id}/initialize`));
|
|
||||||
} else if (typeof device.instance === 'number') {
|
|
||||||
dispatch(push(`/device/${device.features.device_id}:${device.instance}`));
|
|
||||||
} else {
|
|
||||||
const deviceId: string = device.features.device_id;
|
|
||||||
const urlParams: RouterLocationState = getState().router.location.state;
|
|
||||||
// let url: string = `/device/${ device.features.device_id }/network/ethereum/account/0`;
|
|
||||||
let url: string = `/device/${deviceId}`;
|
|
||||||
let instance: ?number;
|
|
||||||
// check if device is not TrezorDevice type
|
|
||||||
if (!device.hasOwnProperty('ts')) {
|
|
||||||
// its device from trezor-connect (called in initConnectedDevice triggered by device_connect event)
|
|
||||||
// need to lookup if there are unavailable instances
|
|
||||||
const available: Array<TrezorDevice> = getState().devices.filter(d => d.path === device.path);
|
|
||||||
const latest: Array<TrezorDevice> = sortDevices(available);
|
|
||||||
|
|
||||||
if (latest.length > 0 && latest[0].instance) {
|
|
||||||
url += `:${latest[0].instance}`;
|
|
||||||
instance = latest[0].instance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// check if current location is not set to this device
|
|
||||||
//dispatch( push(`/device/${ device.features.device_id }/network/etc/account/0`) );
|
|
||||||
|
|
||||||
if (urlParams.deviceInstance !== instance || urlParams.device !== deviceId) {
|
|
||||||
dispatch(push(url));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const initConnectedDevice = (device: TrezorDevice | Device): ThunkAction => (dispatch: Dispatch/* , getState: GetState */): void => {
|
|
||||||
// const selected = getState().wallet.selectedDevice;
|
|
||||||
// if (!selected || (selected && selected.state)) {
|
|
||||||
dispatch(onSelectDevice(device));
|
|
||||||
// }
|
|
||||||
// if (device.unacquired && selected && selected.path !== device.path && !selected.connected) {
|
|
||||||
// dispatch( onSelectDevice(device) );
|
|
||||||
// } else if (!selected) {
|
|
||||||
// dispatch( onSelectDevice(device) );
|
|
||||||
// }
|
|
||||||
};
|
|
||||||
|
|
||||||
// called after backend was initialized
|
// called after backend was initialized
|
||||||
// set listeners for connect/disconnect
|
// set listeners for connect/disconnect
|
||||||
export const postInit = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
export const postInit = (): ThunkAction => (dispatch: Dispatch): void => {
|
||||||
const handleDeviceConnect = (device: Device) => {
|
const handleDeviceConnect = (device: Device) => {
|
||||||
dispatch(initConnectedDevice(device));
|
dispatch(RouterActions.selectDevice(device));
|
||||||
};
|
};
|
||||||
|
|
||||||
TrezorConnect.off(DEVICE.CONNECT, handleDeviceConnect);
|
TrezorConnect.off(DEVICE.CONNECT, handleDeviceConnect);
|
||||||
@ -214,58 +148,13 @@ export const postInit = (): ThunkAction => (dispatch: Dispatch, getState: GetSta
|
|||||||
TrezorConnect.on(DEVICE.CONNECT, handleDeviceConnect);
|
TrezorConnect.on(DEVICE.CONNECT, handleDeviceConnect);
|
||||||
TrezorConnect.on(DEVICE.CONNECT_UNACQUIRED, handleDeviceConnect);
|
TrezorConnect.on(DEVICE.CONNECT_UNACQUIRED, handleDeviceConnect);
|
||||||
|
|
||||||
const { devices } = getState();
|
// try to redirect to initial url
|
||||||
|
if (!dispatch(RouterActions.setInitialUrl())) {
|
||||||
const { initialPathname, initialParams } = getState().wallet;
|
// if initial redirection fails try to switch to first available device
|
||||||
|
dispatch(RouterActions.selectFirstAvailableDevice());
|
||||||
if (initialPathname) {
|
|
||||||
dispatch({
|
|
||||||
type: WALLET.SET_INITIAL_URL,
|
|
||||||
// pathname: null,
|
|
||||||
// params: null
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (devices.length > 0) {
|
|
||||||
const unacquired: ?TrezorDevice = devices.find(d => d.features);
|
|
||||||
if (unacquired) {
|
|
||||||
dispatch(onSelectDevice(unacquired));
|
|
||||||
} else {
|
|
||||||
const latest: Array<TrezorDevice> = sortDevices(devices);
|
|
||||||
const firstConnected: ?TrezorDevice = latest.find(d => d.connected);
|
|
||||||
dispatch(onSelectDevice(firstConnected || latest[0]));
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
if (initialParams) {
|
|
||||||
if (!initialParams.hasOwnProperty('network') && initialPathname !== getState().router.location.pathname) {
|
|
||||||
// dispatch( push(initialPathname) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const switchToFirstAvailableDevice = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
|
||||||
const { devices } = getState();
|
|
||||||
if (devices.length > 0) {
|
|
||||||
// TODO: Priority:
|
|
||||||
// 1. First Unacquired
|
|
||||||
// 2. First connected
|
|
||||||
// 3. Saved with latest timestamp
|
|
||||||
const unacquired = devices.find(d => !d.features);
|
|
||||||
if (unacquired) {
|
|
||||||
dispatch(initConnectedDevice(unacquired));
|
|
||||||
} else {
|
|
||||||
const latest: Array<TrezorDevice> = sortDevices(devices);
|
|
||||||
const firstConnected: ?TrezorDevice = latest.find(d => d.connected);
|
|
||||||
dispatch(onSelectDevice(firstConnected || latest[0]));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dispatch(push('/'));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export const getSelectedDeviceState = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
export const getSelectedDeviceState = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||||
const selected = getState().wallet.selectedDevice;
|
const selected = getState().wallet.selectedDevice;
|
||||||
if (selected
|
if (selected
|
||||||
@ -345,7 +234,7 @@ export const coinChanged = (network: ?string): ThunkAction => (dispatch: Dispatc
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function reload(): AsyncAction {
|
export function reload(): AsyncAction {
|
||||||
return async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
return async (): Promise<void> => {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -391,13 +280,6 @@ export function acquire(): AsyncAction {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const gotoDeviceSettings = (device: TrezorDevice): ThunkAction => (dispatch: Dispatch): void => {
|
|
||||||
if (device.features) {
|
|
||||||
const devUrl: string = `${device.features.device_id}${device.instance ? `:${device.instance}` : ''}`;
|
|
||||||
dispatch(push(`/device/${devUrl}/settings`));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// called from Aside - device menu (forget single instance)
|
// called from Aside - device menu (forget single instance)
|
||||||
export const forget = (device: TrezorDevice): Action => ({
|
export const forget = (device: TrezorDevice): Action => ({
|
||||||
type: CONNECT.FORGET_REQUEST,
|
type: CONNECT.FORGET_REQUEST,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import styled from 'styled-components';
|
import styled, { css } from 'styled-components';
|
||||||
import colors from 'config/colors';
|
import colors from 'config/colors';
|
||||||
import Icon from 'components/Icon';
|
import Icon from 'components/Icon';
|
||||||
import icons from 'config/icons';
|
import icons from 'config/icons';
|
||||||
@ -34,8 +34,10 @@ const IconWrapper = styled.div`
|
|||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&:focus {
|
&:focus {
|
||||||
border: 1px solid ${colors.TEXT_PRIMARY};
|
${props => !props.checked && css`
|
||||||
background: ${props => (props.checked ? colors.TEXT_PRIMARY : colors.WHITE)};
|
border: 1px solid ${colors.GREEN_PRIMARY};
|
||||||
|
`}
|
||||||
|
background: ${props => (props.checked ? colors.GREEN_PRIMARY : colors.WHITE)};
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -74,7 +76,12 @@ class Checkbox extends PureComponent {
|
|||||||
<IconWrapper checked={checked}>
|
<IconWrapper checked={checked}>
|
||||||
{checked && (
|
{checked && (
|
||||||
<Tick>
|
<Tick>
|
||||||
<Icon size={26} color={checked ? colors.WHITE : colors.GREEN_PRIMARY} icon={icons.SUCCESS} />
|
<Icon
|
||||||
|
hoverColor={colors.WHITE}
|
||||||
|
size={26}
|
||||||
|
color={checked ? colors.WHITE : colors.GREEN_PRIMARY}
|
||||||
|
icon={icons.SUCCESS}
|
||||||
|
/>
|
||||||
</Tick>
|
</Tick>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ const Copy = styled.div`
|
|||||||
|
|
||||||
const Footer = ({ toggle }) => (
|
const Footer = ({ toggle }) => (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<Copy>© {getYear(new Date())}</Copy>
|
<Copy title={COMMITHASH}>© {getYear(new Date())}</Copy>
|
||||||
<StyledLink href="http://satoshilabs.com" target="_blank" rel="noreferrer noopener" isGreen>SatoshiLabs</StyledLink>
|
<StyledLink href="http://satoshilabs.com" target="_blank" rel="noreferrer noopener" isGreen>SatoshiLabs</StyledLink>
|
||||||
<StyledLink href="/assets/tos.pdf" target="_blank" rel="noreferrer noopener" isGreen>Terms</StyledLink>
|
<StyledLink href="/assets/tos.pdf" target="_blank" rel="noreferrer noopener" isGreen>Terms</StyledLink>
|
||||||
<StyledLink onClick={toggle} isGreen>Show Log</StyledLink>
|
<StyledLink onClick={toggle} isGreen>Show Log</StyledLink>
|
||||||
|
@ -27,7 +27,7 @@ const SvgWrapper = styled.svg`
|
|||||||
|
|
||||||
:hover {
|
:hover {
|
||||||
path {
|
path {
|
||||||
fill: ${props => props.hoverColor || colors.TEXT_SECONDARY}
|
fill: ${props => props.hoverColor}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -26,10 +26,7 @@ const P = ({ children, className, isSmaller = false }) => (
|
|||||||
P.propTypes = {
|
P.propTypes = {
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
isSmaller: PropTypes.bool,
|
isSmaller: PropTypes.bool,
|
||||||
children: PropTypes.oneOfType([
|
children: PropTypes.node,
|
||||||
PropTypes.array,
|
|
||||||
PropTypes.string,
|
|
||||||
]),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default P;
|
export default P;
|
||||||
|
@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
|
|||||||
import ReactSelect from 'react-select';
|
import ReactSelect from 'react-select';
|
||||||
import ReactAsyncSelect from 'react-select/lib/Async';
|
import ReactAsyncSelect from 'react-select/lib/Async';
|
||||||
import colors from 'config/colors';
|
import colors from 'config/colors';
|
||||||
import { FONT_FAMILY } from 'config/variables';
|
|
||||||
|
|
||||||
const styles = isSearchable => ({
|
const styles = isSearchable => ({
|
||||||
singleValue: base => ({
|
singleValue: base => ({
|
||||||
@ -13,7 +12,6 @@ const styles = isSearchable => ({
|
|||||||
}),
|
}),
|
||||||
control: (base, { isDisabled }) => ({
|
control: (base, { isDisabled }) => ({
|
||||||
...base,
|
...base,
|
||||||
fontFamily: FONT_FAMILY.MONOSPACE,
|
|
||||||
minHeight: 'initial',
|
minHeight: 'initial',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
borderRadius: '2px',
|
borderRadius: '2px',
|
||||||
@ -45,17 +43,16 @@ const styles = isSearchable => ({
|
|||||||
menuList: base => ({
|
menuList: base => ({
|
||||||
...base,
|
...base,
|
||||||
padding: 0,
|
padding: 0,
|
||||||
fontFamily: FONT_FAMILY.MONOSPACE,
|
|
||||||
boxShadow: 'none',
|
boxShadow: 'none',
|
||||||
background: colors.WHITE,
|
background: colors.WHITE,
|
||||||
borderLeft: `1px solid ${colors.DIVIDER}`,
|
borderLeft: `1px solid ${colors.DIVIDER}`,
|
||||||
borderRight: `1px solid ${colors.DIVIDER}`,
|
borderRight: `1px solid ${colors.DIVIDER}`,
|
||||||
borderBottom: `1px solid ${colors.DIVIDER}`,
|
borderBottom: `1px solid ${colors.DIVIDER}`,
|
||||||
}),
|
}),
|
||||||
option: (base, { isSelected }) => ({
|
option: (base, { isFocused }) => ({
|
||||||
...base,
|
...base,
|
||||||
color: colors.TEXT_SECONDARY,
|
color: colors.TEXT_SECONDARY,
|
||||||
background: isSelected ? colors.LANDING : colors.WHITE,
|
background: isFocused ? colors.LANDING : colors.WHITE,
|
||||||
borderRadius: 0,
|
borderRadius: 0,
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
|
@ -3,19 +3,13 @@ import RcTooltip from 'rc-tooltip';
|
|||||||
import colors from 'config/colors';
|
import colors from 'config/colors';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { FONT_SIZE } from 'config/variables';
|
|
||||||
|
|
||||||
const TooltipContent = styled.div`
|
|
||||||
width: ${props => (props.isAside ? '260px' : '320px')};
|
|
||||||
font-size: ${FONT_SIZE.SMALLEST};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
.rc-tooltip {
|
.rc-tooltip {
|
||||||
|
max-width: ${props => `${props.maxWidth}px` || 'auto'};
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 1070;
|
z-index: 1070;
|
||||||
display: block;
|
display: block;
|
||||||
background: red;
|
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
border: 1px solid ${colors.DIVIDER};
|
border: 1px solid ${colors.DIVIDER};
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
@ -178,9 +172,11 @@ class Tooltip extends Component {
|
|||||||
placement,
|
placement,
|
||||||
content,
|
content,
|
||||||
children,
|
children,
|
||||||
|
maxWidth,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
return (
|
return (
|
||||||
<Wrapper
|
<Wrapper
|
||||||
|
maxWidth={maxWidth}
|
||||||
className={className}
|
className={className}
|
||||||
innerRef={(node) => { this.tooltipContainerRef = node; }}
|
innerRef={(node) => { this.tooltipContainerRef = node; }}
|
||||||
>
|
>
|
||||||
@ -188,7 +184,7 @@ class Tooltip extends Component {
|
|||||||
getTooltipContainer={() => this.tooltipContainerRef}
|
getTooltipContainer={() => this.tooltipContainerRef}
|
||||||
arrowContent={<div className="rc-tooltip-arrow-inner" />}
|
arrowContent={<div className="rc-tooltip-arrow-inner" />}
|
||||||
placement={placement}
|
placement={placement}
|
||||||
overlay={<TooltipContent>{content}</TooltipContent>}
|
overlay={content}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</RcTooltip>
|
</RcTooltip>
|
||||||
@ -204,6 +200,7 @@ Tooltip.propTypes = {
|
|||||||
PropTypes.element,
|
PropTypes.element,
|
||||||
PropTypes.string,
|
PropTypes.string,
|
||||||
]),
|
]),
|
||||||
|
maxWidth: PropTypes.number,
|
||||||
content: PropTypes.oneOfType([
|
content: PropTypes.oneOfType([
|
||||||
PropTypes.element,
|
PropTypes.element,
|
||||||
PropTypes.string,
|
PropTypes.string,
|
||||||
|
@ -8,7 +8,6 @@ import {
|
|||||||
FONT_SIZE,
|
FONT_SIZE,
|
||||||
FONT_WEIGHT,
|
FONT_WEIGHT,
|
||||||
TRANSITION,
|
TRANSITION,
|
||||||
FONT_FAMILY,
|
|
||||||
} from 'config/variables';
|
} from 'config/variables';
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
@ -35,10 +34,9 @@ const TopLabel = styled.span`
|
|||||||
|
|
||||||
const StyledInput = styled.input`
|
const StyledInput = styled.input`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 6px 12px;
|
padding: 6px ${props => (props.hasIcon ? '40px' : '12px')} 6px 12px;
|
||||||
|
|
||||||
line-height: 1.42857143;
|
line-height: 1.42857143;
|
||||||
font-family: ${FONT_FAMILY.MONOSPACE};
|
|
||||||
font-size: ${FONT_SIZE.SMALL};
|
font-size: ${FONT_SIZE.SMALL};
|
||||||
font-weight: ${FONT_WEIGHT.BASE};
|
font-weight: ${FONT_WEIGHT.BASE};
|
||||||
color: ${colors.TEXT_PRIMARY};
|
color: ${colors.TEXT_PRIMARY};
|
||||||
@ -114,6 +112,8 @@ class Input extends Component {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<StyledInput
|
<StyledInput
|
||||||
|
hasIcon={!!this.props.state}
|
||||||
|
innerRef={this.props.innerRef}
|
||||||
hasAddon={!!this.props.sideAddons}
|
hasAddon={!!this.props.sideAddons}
|
||||||
type={this.props.type}
|
type={this.props.type}
|
||||||
placeholder={this.props.placeholder}
|
placeholder={this.props.placeholder}
|
||||||
@ -143,13 +143,14 @@ class Input extends Component {
|
|||||||
|
|
||||||
Input.propTypes = {
|
Input.propTypes = {
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
|
innerRef: PropTypes.func,
|
||||||
placeholder: PropTypes.string,
|
placeholder: PropTypes.string,
|
||||||
type: PropTypes.string,
|
type: PropTypes.string,
|
||||||
autoComplete: PropTypes.string,
|
autoComplete: PropTypes.string,
|
||||||
autoCorrect: PropTypes.string,
|
autoCorrect: PropTypes.string,
|
||||||
autoCapitalize: PropTypes.string,
|
autoCapitalize: PropTypes.string,
|
||||||
spellCheck: PropTypes.string,
|
spellCheck: PropTypes.string,
|
||||||
value: PropTypes.string.isRequired,
|
value: PropTypes.string,
|
||||||
onChange: PropTypes.func,
|
onChange: PropTypes.func,
|
||||||
state: PropTypes.string,
|
state: PropTypes.string,
|
||||||
bottomText: PropTypes.string,
|
bottomText: PropTypes.string,
|
||||||
|
@ -5,6 +5,7 @@ import P from 'components/Paragraph';
|
|||||||
import Icon from 'components/Icon';
|
import Icon from 'components/Icon';
|
||||||
import icons from 'config/icons';
|
import icons from 'config/icons';
|
||||||
import { H3 } from 'components/Heading';
|
import { H3 } from 'components/Heading';
|
||||||
|
import { LINE_HEIGHT } from 'config/variables';
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
width: 390px;
|
width: 390px;
|
||||||
@ -21,6 +22,12 @@ const Content = styled.div`
|
|||||||
padding: 24px 48px;
|
padding: 24px 48px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const StyledP = styled(P)`
|
||||||
|
word-wrap: break-word;
|
||||||
|
padding: 5px 0;
|
||||||
|
line-height: ${LINE_HEIGHT.SMALL}
|
||||||
|
`;
|
||||||
|
|
||||||
const Label = styled.div`
|
const Label = styled.div`
|
||||||
padding-top: 5px;
|
padding-top: 5px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
@ -49,7 +56,7 @@ const ConfirmSignTx = (props) => {
|
|||||||
<Label>Send</Label>
|
<Label>Send</Label>
|
||||||
<P>{`${amount} ${currency}` }</P>
|
<P>{`${amount} ${currency}` }</P>
|
||||||
<Label>To</Label>
|
<Label>To</Label>
|
||||||
<P>{ address }</P>
|
<StyledP>{ address }</StyledP>
|
||||||
<Label>Fee</Label>
|
<Label>Fee</Label>
|
||||||
<P>{ selectedFeeLevel.label }</P>
|
<P>{ selectedFeeLevel.label }</P>
|
||||||
</Content>
|
</Content>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import raf from 'raf';
|
import raf from 'raf';
|
||||||
import colors from 'config/colors';
|
import colors from 'config/colors';
|
||||||
|
import { H2 } from 'components/Heading';
|
||||||
import P from 'components/Paragraph';
|
import P from 'components/Paragraph';
|
||||||
import { FONT_SIZE } from 'config/variables';
|
import { FONT_SIZE } from 'config/variables';
|
||||||
import Link from 'components/Link';
|
import Link from 'components/Link';
|
||||||
@ -23,7 +24,10 @@ const Label = styled.div`
|
|||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const PassphraseError = styled.div``;
|
const PassphraseError = styled.div`
|
||||||
|
margin-top: 8px;
|
||||||
|
color: ${colors.ERROR_PRIMARY};
|
||||||
|
`;
|
||||||
|
|
||||||
const Row = styled.div`
|
const Row = styled.div`
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -137,22 +141,25 @@ export default class PinModal extends Component<Props, State> {
|
|||||||
} = this.state;
|
} = this.state;
|
||||||
// } = this.props.modal;
|
// } = this.props.modal;
|
||||||
|
|
||||||
let passphraseInputValue: string = passphrase;
|
const passphraseInputValue: string = passphrase;
|
||||||
let passphraseRevisionInputValue: string = passphraseRevision;
|
const passphraseRevisionInputValue: string = passphraseRevision;
|
||||||
if (!visible && !passphraseFocused) {
|
// if (!visible && !passphraseFocused) {
|
||||||
passphraseInputValue = passphrase.replace(/./g, '•');
|
// passphraseInputValue = passphrase.replace(/./g, '•');
|
||||||
}
|
// }
|
||||||
if (!visible && !passphraseRevisionFocused) {
|
// if (!visible && !passphraseRevisionFocused) {
|
||||||
passphraseRevisionInputValue = passphraseRevision.replace(/./g, '•');
|
// passphraseRevisionInputValue = passphraseRevision.replace(/./g, '•');
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
|
||||||
if (this.passphraseInput) {
|
if (this.passphraseInput) {
|
||||||
this.passphraseInput.value = passphraseInputValue;
|
// this.passphraseInput.value = passphraseInputValue;
|
||||||
this.passphraseInput.setAttribute('type', visible || (!visible && !passphraseFocused) ? 'text' : 'password');
|
// this.passphraseInput.setAttribute('type', visible || (!visible && !passphraseFocused) ? 'text' : 'password');
|
||||||
|
this.passphraseInput.setAttribute('type', visible ? 'text' : 'password');
|
||||||
}
|
}
|
||||||
if (this.passphraseRevisionInput) {
|
if (this.passphraseRevisionInput) {
|
||||||
this.passphraseRevisionInput.value = passphraseRevisionInputValue;
|
// this.passphraseRevisionInput.value = passphraseRevisionInputValue;
|
||||||
this.passphraseRevisionInput.setAttribute('type', visible || (!visible && !passphraseRevisionFocused) ? 'text' : 'password');
|
// this.passphraseRevisionInput.setAttribute('type', visible || (!visible && !passphraseRevisionFocused) ? 'text' : 'password');
|
||||||
|
this.passphraseRevisionInput.setAttribute('type', visible ? 'text' : 'password');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,6 +181,14 @@ export default class PinModal extends Component<Props, State> {
|
|||||||
match: previousState.singleInput || previousState.passphraseRevision === value,
|
match: previousState.singleInput || previousState.passphraseRevision === value,
|
||||||
passphrase: value,
|
passphrase: value,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
if (this.state.visible && this.passphraseRevisionInput) {
|
||||||
|
this.setState({
|
||||||
|
match: true,
|
||||||
|
passphraseRevision: value,
|
||||||
|
});
|
||||||
|
this.passphraseRevisionInput.value = value;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.setState(previousState => ({
|
this.setState(previousState => ({
|
||||||
match: previousState.passphrase === value,
|
match: previousState.passphrase === value,
|
||||||
@ -211,12 +226,29 @@ export default class PinModal extends Component<Props, State> {
|
|||||||
this.setState({
|
this.setState({
|
||||||
visible: true,
|
visible: true,
|
||||||
});
|
});
|
||||||
|
if (this.passphraseRevisionInput) {
|
||||||
|
this.passphraseRevisionInput.disabled = true;
|
||||||
|
this.passphraseRevisionInput.value = this.state.passphrase;
|
||||||
|
this.setState(previousState => ({
|
||||||
|
passphraseRevision: previousState.passphrase,
|
||||||
|
match: true,
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onPassphraseHide = (): void => {
|
onPassphraseHide = (): void => {
|
||||||
this.setState({
|
this.setState({
|
||||||
visible: false,
|
visible: false,
|
||||||
});
|
});
|
||||||
|
if (this.passphraseRevisionInput) {
|
||||||
|
const emptyPassphraseRevisionValue = '';
|
||||||
|
this.passphraseRevisionInput.value = emptyPassphraseRevisionValue;
|
||||||
|
this.setState(previousState => ({
|
||||||
|
passphraseRevision: emptyPassphraseRevisionValue,
|
||||||
|
match: emptyPassphraseRevisionValue === previousState.passphrase,
|
||||||
|
}));
|
||||||
|
this.passphraseRevisionInput.disabled = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
submit = (empty: boolean = false): void => {
|
submit = (empty: boolean = false): void => {
|
||||||
@ -232,7 +264,7 @@ export default class PinModal extends Component<Props, State> {
|
|||||||
//this.passphraseRevisionInput.style.display = 'none';
|
//this.passphraseRevisionInput.style.display = 'none';
|
||||||
//this.passphraseRevisionInput.setAttribute('readonly', 'readonly');
|
//this.passphraseRevisionInput.setAttribute('readonly', 'readonly');
|
||||||
|
|
||||||
const p = passphrase;
|
// const p = passphrase;
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
passphrase: '',
|
passphrase: '',
|
||||||
@ -271,11 +303,10 @@ export default class PinModal extends Component<Props, State> {
|
|||||||
//let passphraseRevisionInputType: string = visible || passphraseRevisionFocused ? "text" : "password";
|
//let passphraseRevisionInputType: string = visible || passphraseRevisionFocused ? "text" : "password";
|
||||||
|
|
||||||
const showPassphraseCheckboxFn: Function = visible ? this.onPassphraseHide : this.onPassphraseShow;
|
const showPassphraseCheckboxFn: Function = visible ? this.onPassphraseHide : this.onPassphraseShow;
|
||||||
console.log('passphraseInputType', passphraseInputType);
|
|
||||||
return (
|
return (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
{/* ?<H2>Enter { deviceLabel } passphrase</H2> */}
|
<H2>Enter { deviceLabel } passphrase</H2>
|
||||||
{/* <P isSmaller>Note that passphrase is case-sensitive.</P> */}
|
<P isSmaller>Note that passphrase is case-sensitive.</P>
|
||||||
<Row>
|
<Row>
|
||||||
<Label>Passphrase</Label>
|
<Label>Passphrase</Label>
|
||||||
<Input
|
<Input
|
||||||
@ -289,7 +320,7 @@ export default class PinModal extends Component<Props, State> {
|
|||||||
data-lpignore="true"
|
data-lpignore="true"
|
||||||
onFocus={() => this.onPassphraseFocus('passphrase')}
|
onFocus={() => this.onPassphraseFocus('passphrase')}
|
||||||
onBlur={() => this.onPassphraseBlur('passphrase')}
|
onBlur={() => this.onPassphraseBlur('passphrase')}
|
||||||
tabIndex="1"
|
tabIndex="0"
|
||||||
/>
|
/>
|
||||||
</Row>
|
</Row>
|
||||||
{!singleInput && (
|
{!singleInput && (
|
||||||
@ -306,7 +337,6 @@ export default class PinModal extends Component<Props, State> {
|
|||||||
data-lpignore="true"
|
data-lpignore="true"
|
||||||
onFocus={() => this.onPassphraseFocus('revision')}
|
onFocus={() => this.onPassphraseFocus('revision')}
|
||||||
onBlur={() => this.onPassphraseBlur('revision')}
|
onBlur={() => this.onPassphraseBlur('revision')}
|
||||||
tabIndex="2"
|
|
||||||
/>
|
/>
|
||||||
{!match && passphraseRevisionTouched && <PassphraseError>Passphrases do not match</PassphraseError> }
|
{!match && passphraseRevisionTouched && <PassphraseError>Passphrases do not match</PassphraseError> }
|
||||||
</Row>
|
</Row>
|
||||||
@ -315,7 +345,7 @@ export default class PinModal extends Component<Props, State> {
|
|||||||
<Checkbox onClick={showPassphraseCheckboxFn} checked={visible}>Show passphrase</Checkbox>
|
<Checkbox onClick={showPassphraseCheckboxFn} checked={visible}>Show passphrase</Checkbox>
|
||||||
</Row>
|
</Row>
|
||||||
<Row>
|
<Row>
|
||||||
<Button type="button" tabIndex="4" disabled={!match} onClick={event => this.submit()}>Enter</Button>
|
<Button type="button" disabled={!match} onClick={() => this.submit()}>Enter</Button>
|
||||||
</Row>
|
</Row>
|
||||||
<Footer>
|
<Footer>
|
||||||
<P isSmaller>If you want to access your default account</P>
|
<P isSmaller>If you want to access your default account</P>
|
||||||
|
@ -41,5 +41,6 @@ export const TRANSITION = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const LINE_HEIGHT = {
|
export const LINE_HEIGHT = {
|
||||||
|
SMALL: '1.4',
|
||||||
BASE: '1.8',
|
BASE: '1.8',
|
||||||
};
|
};
|
||||||
|
@ -6,9 +6,9 @@ import type {
|
|||||||
MiddlewareAPI as ReduxMiddlewareAPI,
|
MiddlewareAPI as ReduxMiddlewareAPI,
|
||||||
Middleware as ReduxMiddleware,
|
Middleware as ReduxMiddleware,
|
||||||
ThunkAction as ReduxThunkAction,
|
ThunkAction as ReduxThunkAction,
|
||||||
|
PayloadAction as ReduxPayloadAction,
|
||||||
AsyncAction as ReduxAsyncAction,
|
AsyncAction as ReduxAsyncAction,
|
||||||
PromiseAction as ReduxPromiseAction,
|
PromiseAction as ReduxPromiseAction,
|
||||||
ThunkDispatch as ReduxThunkDispatch,
|
|
||||||
PlainDispatch as ReduxPlainDispatch,
|
PlainDispatch as ReduxPlainDispatch,
|
||||||
} from 'redux';
|
} from 'redux';
|
||||||
|
|
||||||
@ -164,6 +164,7 @@ export type MiddlewareAPI = ReduxMiddlewareAPI<State, Action>;
|
|||||||
export type Middleware = ReduxMiddleware<State, Action>;
|
export type Middleware = ReduxMiddleware<State, Action>;
|
||||||
|
|
||||||
export type ThunkAction = ReduxThunkAction<State, Action>;
|
export type ThunkAction = ReduxThunkAction<State, Action>;
|
||||||
|
export type PayloadAction<R> = ReduxPayloadAction<State, Action, R>;
|
||||||
export type AsyncAction = ReduxAsyncAction<State, Action>;
|
export type AsyncAction = ReduxAsyncAction<State, Action>;
|
||||||
export type PromiseAction<R> = ReduxPromiseAction<State, Action, R>;
|
export type PromiseAction<R> = ReduxPromiseAction<State, Action, R>;
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
declare module 'redux' {
|
declare module 'redux' {
|
||||||
/*
|
/*
|
||||||
|
|
||||||
S = State
|
S = State
|
||||||
A = Action
|
A = Action
|
||||||
D = Dispatch
|
D = Dispatch
|
||||||
@ -16,28 +15,25 @@ declare module 'redux' {
|
|||||||
declare export type ThunkAction<S, A> = (dispatch: ReduxDispatch<S, A>, getState: () => S) => void;
|
declare export type ThunkAction<S, A> = (dispatch: ReduxDispatch<S, A>, getState: () => S) => void;
|
||||||
declare export type AsyncAction<S, A> = (dispatch: ReduxDispatch<S, A>, getState: () => S) => Promise<void>;
|
declare export type AsyncAction<S, A> = (dispatch: ReduxDispatch<S, A>, getState: () => S) => Promise<void>;
|
||||||
declare export type PromiseAction<S, A, R> = (dispatch: ReduxDispatch<S, A>, getState: () => S) => Promise<R>;
|
declare export type PromiseAction<S, A, R> = (dispatch: ReduxDispatch<S, A>, getState: () => S) => Promise<R>;
|
||||||
|
declare export type PayloadAction<S, A, R> = (dispatch: ReduxDispatch<S, A>, getState: () => S) => R;
|
||||||
|
|
||||||
declare export type ThunkDispatch<S, A> = (action: ThunkAction<S, A>) => void;
|
declare export type ThunkDispatch<S, A> = (action: ThunkAction<S, A>) => void;
|
||||||
declare export type AsyncDispatch<S, A> = (action: AsyncAction<S, A>) => Promise<void>;
|
declare export type AsyncDispatch<S, A> = (action: AsyncAction<S, A>) => Promise<void>;
|
||||||
declare export type PromiseDispatch<S, A> = <R>(action: PromiseAction<S, A, R>) => Promise<R>;
|
declare export type PromiseDispatch<S, A> = <R>(action: PromiseAction<S, A, R>) => Promise<R>;
|
||||||
|
declare export type PayloadDispatch<S, A> = <R>(action: PayloadAction<S, A, R>) => R;
|
||||||
declare export type PlainDispatch<A: {type: $Subtype<string>}> = DispatchAPI<A>;
|
declare export type PlainDispatch<A: {type: $Subtype<string>}> = DispatchAPI<A>;
|
||||||
/* NEW: Dispatch is now a combination of these different dispatch types */
|
/* NEW: Dispatch is now a combination of these different dispatch types */
|
||||||
declare export type ReduxDispatch<S, A> = PlainDispatch<A> & ThunkDispatch<S, A> & AsyncDispatch<S, A> & PromiseDispatch<S, A>;
|
declare export type ReduxDispatch<S, A> = PlainDispatch<A> & ThunkDispatch<S, A> & AsyncDispatch<S, A> & PromiseDispatch<S, A> & PayloadDispatch<S, A>;
|
||||||
|
|
||||||
declare export type MiddlewareAPI<S, A> = {
|
declare export type MiddlewareAPI<S, A> = {
|
||||||
// dispatch: Dispatch<S, A>;
|
|
||||||
// dispatch: (action: A | ThunkAction<S, A>) => A | void;
|
|
||||||
// dispatch: PlainDispatch<A> | () => A;
|
|
||||||
// dispatch: ( A | ThunkAction<S, A> | void ) => void;
|
|
||||||
dispatch: ReduxDispatch<S, A>;
|
dispatch: ReduxDispatch<S, A>;
|
||||||
// dispatch: Dispatch<S, A>;
|
|
||||||
// dispatch: D;
|
|
||||||
getState(): S;
|
getState(): S;
|
||||||
};
|
};
|
||||||
|
|
||||||
declare export type Middleware<S, A> =
|
declare export type Middleware<S, A> =
|
||||||
(api: MiddlewareAPI<S, A>) =>
|
(api: MiddlewareAPI<S, A>) =>
|
||||||
(next: PlainDispatch<A>) => PlainDispatch<A>;
|
(next: PlainDispatch<A>) =>
|
||||||
|
(PlainDispatch<A> | (action: A) => Promise<A>);
|
||||||
|
|
||||||
declare export type Store<S, A, D = ReduxDispatch<S, A>> = {
|
declare export type Store<S, A, D = ReduxDispatch<S, A>> = {
|
||||||
// rewrite MiddlewareAPI members in order to get nicer error messages (intersections produce long messages)
|
// rewrite MiddlewareAPI members in order to get nicer error messages (intersections produce long messages)
|
||||||
|
@ -66,7 +66,6 @@ export type CustomBackend = {
|
|||||||
url: string;
|
url: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export type State = {
|
export type State = {
|
||||||
initialized: boolean;
|
initialized: boolean;
|
||||||
error: ?string;
|
error: ?string;
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
|
|
||||||
|
|
||||||
import * as ACCOUNT from 'actions/constants/account';
|
import * as ACCOUNT from 'actions/constants/account';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@ -14,7 +12,6 @@ import type {
|
|||||||
|
|
||||||
export type State = {
|
export type State = {
|
||||||
location?: string;
|
location?: string;
|
||||||
|
|
||||||
account: ?Account;
|
account: ?Account;
|
||||||
network: ?Coin;
|
network: ?Coin;
|
||||||
tokens: Array<Token>,
|
tokens: Array<Token>,
|
||||||
|
@ -12,17 +12,18 @@ import type { Action, RouterLocationState, TrezorDevice } from 'flowtype';
|
|||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
ready: boolean;
|
ready: boolean;
|
||||||
|
unloading: boolean;
|
||||||
online: boolean;
|
online: boolean;
|
||||||
dropdownOpened: boolean;
|
dropdownOpened: boolean;
|
||||||
initialParams: ?RouterLocationState;
|
initialParams: ?RouterLocationState;
|
||||||
initialPathname: ?string;
|
initialPathname: ?string;
|
||||||
disconnectRequest: ?TrezorDevice;
|
disconnectRequest: ?TrezorDevice;
|
||||||
|
|
||||||
selectedDevice: ?TrezorDevice;
|
selectedDevice: ?TrezorDevice;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: State = {
|
const initialState: State = {
|
||||||
ready: false,
|
ready: false,
|
||||||
|
unloading: false,
|
||||||
online: navigator.onLine,
|
online: navigator.onLine,
|
||||||
dropdownOpened: false,
|
dropdownOpened: false,
|
||||||
initialParams: null,
|
initialParams: null,
|
||||||
@ -33,6 +34,12 @@ const initialState: State = {
|
|||||||
|
|
||||||
export default function wallet(state: State = initialState, action: Action): State {
|
export default function wallet(state: State = initialState, action: Action): State {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
case WALLET.ON_BEFORE_UNLOAD:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
unloading: true,
|
||||||
|
};
|
||||||
|
|
||||||
case WALLET.SET_INITIAL_URL:
|
case WALLET.SET_INITIAL_URL:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
@ -1,156 +1,46 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
import { LOCATION_CHANGE/* , replace */ } from 'react-router-redux';
|
import { LOCATION_CHANGE, replace } from 'react-router-redux';
|
||||||
import * as CONNECT from 'actions/constants/TrezorConnect';
|
import * as RouterActions from 'actions/RouterActions';
|
||||||
import * as WALLET from 'actions/constants/wallet';
|
|
||||||
import * as NotificationActions from 'actions/NotificationActions';
|
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
Middleware,
|
Middleware,
|
||||||
MiddlewareAPI,
|
MiddlewareAPI,
|
||||||
MiddlewareDispatch,
|
MiddlewareDispatch,
|
||||||
Action,
|
Action,
|
||||||
RouterLocationState,
|
|
||||||
TrezorDevice,
|
|
||||||
} from 'flowtype';
|
} from 'flowtype';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Middleware used for init application and managing router path.
|
* Redux Middleware used for managing router path
|
||||||
|
* This middleware couldn't use async/await because LOCATION_CHANGE action is also synchronized with RouterReducer (react-router-redux)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const pathToParams = (path: string): RouterLocationState => {
|
|
||||||
const urlParts: Array<string> = path.split('/').slice(1);
|
|
||||||
const params: RouterLocationState = {};
|
|
||||||
if (urlParts.length < 1 || path === '/') return params;
|
|
||||||
|
|
||||||
for (let i = 0, len = urlParts.length; i < len; i += 2) {
|
|
||||||
params[urlParts[i]] = urlParts[i + 1] || urlParts[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.hasOwnProperty('device')) {
|
|
||||||
const isClonedDevice: Array<string> = params.device.split(':');
|
|
||||||
if (isClonedDevice.length > 1) {
|
|
||||||
params.device = isClonedDevice[0];
|
|
||||||
params.deviceInstance = isClonedDevice[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return params;
|
|
||||||
};
|
|
||||||
|
|
||||||
const validation = (api: MiddlewareAPI, params: RouterLocationState): boolean => {
|
|
||||||
if (params.hasOwnProperty('device')) {
|
|
||||||
const { devices } = api.getState();
|
|
||||||
|
|
||||||
let device: ?TrezorDevice;
|
|
||||||
if (params.hasOwnProperty('deviceInstance')) {
|
|
||||||
device = devices.find(d => d.features && d.features.device_id === params.device && d.instance === parseInt(params.deviceInstance, 10));
|
|
||||||
} else {
|
|
||||||
device = devices.find(d => d.path === params.device || (d.features && d.features.device_id === params.device));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!device) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.hasOwnProperty('network')) {
|
|
||||||
const { config } = api.getState().localStorage;
|
|
||||||
const coin = config.coins.find(c => c.network === params.network);
|
|
||||||
if (!coin) return false;
|
|
||||||
if (!params.account) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if (params.account) {
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
let __unloading: boolean = false;
|
|
||||||
|
|
||||||
const RouterService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispatch) => (action: Action): Action => {
|
const RouterService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispatch) => (action: Action): Action => {
|
||||||
if (action.type === WALLET.ON_BEFORE_UNLOAD) {
|
// make sure that middleware should process this action
|
||||||
__unloading = true;
|
if (action.type !== LOCATION_CHANGE || api.getState().wallet.unloading) {
|
||||||
} else if (action.type === LOCATION_CHANGE && !__unloading) {
|
// pass action
|
||||||
const { location } = api.getState().router;
|
|
||||||
const { devices } = api.getState();
|
|
||||||
const { error } = api.getState().connect;
|
|
||||||
|
|
||||||
const requestedParams: RouterLocationState = pathToParams(action.payload.pathname);
|
|
||||||
const currentParams: RouterLocationState = pathToParams(location ? location.pathname : '/');
|
|
||||||
const postActions: Array<Action> = [];
|
|
||||||
|
|
||||||
let redirectPath: ?string;
|
|
||||||
// first event after application loads
|
|
||||||
if (!location) {
|
|
||||||
postActions.push({
|
|
||||||
type: WALLET.SET_INITIAL_URL,
|
|
||||||
pathname: action.payload.pathname,
|
|
||||||
state: requestedParams,
|
|
||||||
});
|
|
||||||
|
|
||||||
redirectPath = '/';
|
|
||||||
} else {
|
|
||||||
const isModalOpened: boolean = api.getState().modal.opened;
|
|
||||||
// there are no devices attached or initialization error occurs
|
|
||||||
const landingPage: boolean = devices.length < 1 || error !== null;
|
|
||||||
|
|
||||||
// modal is still opened and currentPath is still valid
|
|
||||||
// example 1 (valid blocking): url changes while passphrase modal opened but device is still connected (we want user to finish this action)
|
|
||||||
// example 2 (invalid blocking): url changes while passphrase modal opened because device disconnect
|
|
||||||
if (isModalOpened && action.payload.pathname !== location.pathname && validation(api, currentParams)) {
|
|
||||||
redirectPath = location.pathname;
|
|
||||||
console.warn('Modal still opened');
|
|
||||||
} else if (landingPage) {
|
|
||||||
// keep route on landing page
|
|
||||||
if (action.payload.pathname !== '/' && action.payload.pathname !== '/bridge') {
|
|
||||||
redirectPath = '/';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// PATH VALIDATION
|
|
||||||
// redirect from root view
|
|
||||||
if (action.payload.pathname === '/' || !validation(api, requestedParams)) {
|
|
||||||
// TODO redirect to first device
|
|
||||||
redirectPath = '/device/x';
|
|
||||||
} else if (requestedParams.device) {
|
|
||||||
if (requestedParams.network !== currentParams.network) {
|
|
||||||
postActions.push({
|
|
||||||
type: CONNECT.COIN_CHANGED,
|
|
||||||
payload: {
|
|
||||||
network: requestedParams.network,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (redirectPath) {
|
|
||||||
console.warn('Redirecting...', redirectPath);
|
|
||||||
// override action to keep routerReducer sync
|
|
||||||
const url: string = redirectPath;
|
|
||||||
action.payload.state = pathToParams(url);
|
|
||||||
action.payload.pathname = url;
|
|
||||||
// change url
|
|
||||||
// api.dispatch(replace(url));
|
|
||||||
} else {
|
|
||||||
action.payload.state = requestedParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
// resolve LOCATION_CHANGE action
|
|
||||||
next(action);
|
|
||||||
|
|
||||||
// resolve post actions
|
|
||||||
postActions.forEach((a) => {
|
|
||||||
api.dispatch(a);
|
|
||||||
});
|
|
||||||
|
|
||||||
api.dispatch(NotificationActions.clear(currentParams, requestedParams));
|
|
||||||
|
|
||||||
return action;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pass all actions through by default
|
|
||||||
return next(action);
|
return next(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
// compose valid url
|
||||||
|
const validUrl = api.dispatch(RouterActions.getValidUrl(action));
|
||||||
|
// override action state (to be stored in RouterReducer)
|
||||||
|
const override = action;
|
||||||
|
override.payload.state = api.dispatch(RouterActions.pathToParams(validUrl));
|
||||||
|
const redirect = action.payload.pathname !== validUrl;
|
||||||
|
if (redirect) {
|
||||||
|
// override action pathname
|
||||||
|
override.payload.pathname = validUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// pass action
|
||||||
|
next(override);
|
||||||
|
|
||||||
|
if (redirect) {
|
||||||
|
// replace invalid url
|
||||||
|
api.dispatch(replace(validUrl));
|
||||||
|
}
|
||||||
|
|
||||||
|
return override;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default RouterService;
|
export default RouterService;
|
@ -1,12 +1,12 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
import { push } from 'react-router-redux';
|
|
||||||
|
|
||||||
import TrezorConnect, {
|
import {
|
||||||
TRANSPORT, DEVICE_EVENT, UI_EVENT, UI, DEVICE, BLOCKCHAIN
|
TRANSPORT, DEVICE, BLOCKCHAIN,
|
||||||
} from 'trezor-connect';
|
} from 'trezor-connect';
|
||||||
import * as TrezorConnectActions from 'actions/TrezorConnectActions';
|
import * as TrezorConnectActions from 'actions/TrezorConnectActions';
|
||||||
import * as DiscoveryActions from 'actions/DiscoveryActions';
|
import * as DiscoveryActions from 'actions/DiscoveryActions';
|
||||||
import * as BlockchainActions from 'actions/BlockchainActions';
|
import * as BlockchainActions from 'actions/BlockchainActions';
|
||||||
|
import * as RouterActions from 'actions/RouterActions';
|
||||||
import * as ModalActions from 'actions/ModalActions';
|
import * as ModalActions from 'actions/ModalActions';
|
||||||
import * as STORAGE from 'actions/constants/localStorage';
|
import * as STORAGE from 'actions/constants/localStorage';
|
||||||
import * as CONNECT from 'actions/constants/TrezorConnect';
|
import * as CONNECT from 'actions/constants/TrezorConnect';
|
||||||
@ -31,7 +31,7 @@ const TrezorConnectService: Middleware = (api: MiddlewareAPI) => (next: Middlewa
|
|||||||
api.dispatch(TrezorConnectActions.init());
|
api.dispatch(TrezorConnectActions.init());
|
||||||
} else if (action.type === TRANSPORT.ERROR) {
|
} else if (action.type === TRANSPORT.ERROR) {
|
||||||
// TODO: check if modal is open
|
// TODO: check if modal is open
|
||||||
// api.dispatch( push('/') );
|
// api.dispatch( RouterActions.gotoLandingPage() );
|
||||||
} else if (action.type === TRANSPORT.START) {
|
} else if (action.type === TRANSPORT.START) {
|
||||||
api.dispatch(BlockchainActions.init());
|
api.dispatch(BlockchainActions.init());
|
||||||
} else if (action.type === BLOCKCHAIN_READY) {
|
} else if (action.type === BLOCKCHAIN_READY) {
|
||||||
@ -42,7 +42,7 @@ const TrezorConnectService: Middleware = (api: MiddlewareAPI) => (next: Middlewa
|
|||||||
api.dispatch(ModalActions.onRememberRequest(prevModalState));
|
api.dispatch(ModalActions.onRememberRequest(prevModalState));
|
||||||
} else if (action.type === CONNECT.FORGET) {
|
} else if (action.type === CONNECT.FORGET) {
|
||||||
//api.dispatch( TrezorConnectActions.forgetDevice(action.device) );
|
//api.dispatch( TrezorConnectActions.forgetDevice(action.device) );
|
||||||
api.dispatch(TrezorConnectActions.switchToFirstAvailableDevice());
|
api.dispatch(RouterActions.selectFirstAvailableDevice());
|
||||||
} else if (action.type === CONNECT.FORGET_SINGLE) {
|
} else if (action.type === CONNECT.FORGET_SINGLE) {
|
||||||
if (api.getState().devices.length < 1 && action.device.connected) {
|
if (api.getState().devices.length < 1 && action.device.connected) {
|
||||||
// prompt disconnect device info in LandingPage
|
// prompt disconnect device info in LandingPage
|
||||||
@ -50,9 +50,9 @@ const TrezorConnectService: Middleware = (api: MiddlewareAPI) => (next: Middlewa
|
|||||||
type: CONNECT.DISCONNECT_REQUEST,
|
type: CONNECT.DISCONNECT_REQUEST,
|
||||||
device: action.device,
|
device: action.device,
|
||||||
});
|
});
|
||||||
api.dispatch(push('/'));
|
api.dispatch(RouterActions.gotoLandingPage());
|
||||||
} else {
|
} else {
|
||||||
api.dispatch(TrezorConnectActions.switchToFirstAvailableDevice());
|
api.dispatch(RouterActions.selectFirstAvailableDevice());
|
||||||
}
|
}
|
||||||
} else if (action.type === DEVICE.CONNECT || action.type === DEVICE.CONNECT_UNACQUIRED) {
|
} else if (action.type === DEVICE.CONNECT || action.type === DEVICE.CONNECT_UNACQUIRED) {
|
||||||
api.dispatch(DiscoveryActions.restore());
|
api.dispatch(DiscoveryActions.restore());
|
||||||
@ -60,7 +60,7 @@ const TrezorConnectService: Middleware = (api: MiddlewareAPI) => (next: Middlewa
|
|||||||
} else if (action.type === CONNECT.AUTH_DEVICE) {
|
} else if (action.type === CONNECT.AUTH_DEVICE) {
|
||||||
api.dispatch(DiscoveryActions.check());
|
api.dispatch(DiscoveryActions.check());
|
||||||
} else if (action.type === CONNECT.DUPLICATE) {
|
} else if (action.type === CONNECT.DUPLICATE) {
|
||||||
api.dispatch(TrezorConnectActions.onSelectDevice(action.device));
|
api.dispatch(RouterActions.selectDevice(action.device));
|
||||||
} else if (action.type === CONNECT.COIN_CHANGED) {
|
} else if (action.type === CONNECT.COIN_CHANGED) {
|
||||||
api.dispatch(TrezorConnectActions.coinChanged(action.payload.network));
|
api.dispatch(TrezorConnectActions.coinChanged(action.payload.network));
|
||||||
} else if (action.type === BLOCKCHAIN.BLOCK) {
|
} else if (action.type === BLOCKCHAIN.BLOCK) {
|
||||||
@ -68,7 +68,7 @@ const TrezorConnectService: Middleware = (api: MiddlewareAPI) => (next: Middlewa
|
|||||||
} else if (action.type === BLOCKCHAIN.NOTIFICATION) {
|
} else if (action.type === BLOCKCHAIN.NOTIFICATION) {
|
||||||
// api.dispatch(BlockchainActions.onNotification(action.payload));
|
// api.dispatch(BlockchainActions.onNotification(action.payload));
|
||||||
} else if (action.type === BLOCKCHAIN.ERROR) {
|
} else if (action.type === BLOCKCHAIN.ERROR) {
|
||||||
api.dispatch( BlockchainActions.error(action.payload) );
|
api.dispatch(BlockchainActions.error(action.payload));
|
||||||
}
|
}
|
||||||
|
|
||||||
return action;
|
return action;
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
|
|
||||||
|
|
||||||
import { DEVICE } from 'trezor-connect';
|
import { DEVICE } from 'trezor-connect';
|
||||||
import { LOCATION_CHANGE } from 'react-router-redux';
|
import { LOCATION_CHANGE } from 'react-router-redux';
|
||||||
import * as WALLET from 'actions/constants/wallet';
|
import * as WALLET from 'actions/constants/wallet';
|
||||||
|
import * as CONNECT from 'actions/constants/TrezorConnect';
|
||||||
|
|
||||||
import * as WalletActions from 'actions/WalletActions';
|
import * as WalletActions from 'actions/WalletActions';
|
||||||
|
import * as RouterActions from 'actions/RouterActions';
|
||||||
|
import * as NotificationActions from 'actions/NotificationActions';
|
||||||
import * as LocalStorageActions from 'actions/LocalStorageActions';
|
import * as LocalStorageActions from 'actions/LocalStorageActions';
|
||||||
import * as TrezorConnectActions from 'actions/TrezorConnectActions';
|
import * as TrezorConnectActions from 'actions/TrezorConnectActions';
|
||||||
import * as SelectedAccountActions from 'actions/SelectedAccountActions';
|
import * as SelectedAccountActions from 'actions/SelectedAccountActions';
|
||||||
@ -22,24 +23,74 @@ import type {
|
|||||||
*/
|
*/
|
||||||
const WalletService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispatch) => (action: Action): Action => {
|
const WalletService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispatch) => (action: Action): Action => {
|
||||||
const prevState = api.getState();
|
const prevState = api.getState();
|
||||||
const locationChange: boolean = action.type === LOCATION_CHANGE;
|
|
||||||
|
|
||||||
// Application live cycle starts here
|
// Application live cycle starts HERE!
|
||||||
if (locationChange) {
|
// when first LOCATION_CHANGE is called router does not have "location" set yet
|
||||||
const { location } = api.getState().router;
|
if (action.type === LOCATION_CHANGE && !prevState.router.location) {
|
||||||
if (!location) {
|
// initialize wallet
|
||||||
api.dispatch(WalletActions.init());
|
api.dispatch(WalletActions.init());
|
||||||
// load data from config.json and local storage
|
// set initial url
|
||||||
api.dispatch(LocalStorageActions.loadData());
|
// TODO: validate if initial url is potentially correct
|
||||||
}
|
api.dispatch({
|
||||||
|
type: WALLET.SET_INITIAL_URL,
|
||||||
|
pathname: action.payload.pathname,
|
||||||
|
state: {},
|
||||||
|
});
|
||||||
|
// pass action and break process
|
||||||
|
return next(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
// pass action
|
// pass action
|
||||||
next(action);
|
next(action);
|
||||||
|
|
||||||
if (action.type === DEVICE.CONNECT) {
|
switch (action.type) {
|
||||||
api.dispatch(WalletActions.clearUnavailableDevicesData(prevState, action.device));
|
case WALLET.SET_INITIAL_URL:
|
||||||
|
api.dispatch(LocalStorageActions.loadData());
|
||||||
|
break;
|
||||||
|
case WALLET.SET_SELECTED_DEVICE:
|
||||||
|
if (action.device) {
|
||||||
|
// try to authorize device
|
||||||
|
api.dispatch(TrezorConnectActions.getSelectedDeviceState());
|
||||||
|
} else {
|
||||||
|
// try select different device
|
||||||
|
api.dispatch(RouterActions.selectFirstAvailableDevice());
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
case DEVICE.CONNECT:
|
||||||
|
api.dispatch(WalletActions.clearUnavailableDevicesData(prevState, action.device));
|
||||||
|
break;
|
||||||
|
default: {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update common values ONLY if application is ready
|
||||||
|
if (!api.getState().wallet.ready) return action;
|
||||||
|
|
||||||
|
// double verification needed
|
||||||
|
// Corner case: LOCATION_CHANGE was called but pathname didn't changed (redirection from RouterService)
|
||||||
|
const prevLocation = prevState.router.location;
|
||||||
|
const currentLocation = api.getState().router.location;
|
||||||
|
if (action.type === LOCATION_CHANGE && prevLocation.pathname !== currentLocation.pathname) {
|
||||||
|
// watch for coin change
|
||||||
|
if (prevLocation.state.network !== currentLocation.state.network) {
|
||||||
|
api.dispatch({
|
||||||
|
type: CONNECT.COIN_CHANGED,
|
||||||
|
payload: {
|
||||||
|
network: currentLocation.state.network,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// watch for account change
|
||||||
|
if (prevLocation.state.network !== currentLocation.state.network || prevLocation.state.account !== currentLocation.state.account) {
|
||||||
|
api.dispatch(SelectedAccountActions.dispose());
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear notifications
|
||||||
|
api.dispatch(NotificationActions.clear(prevLocation.state, currentLocation.state));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// update common values in WallerReducer
|
// update common values in WallerReducer
|
||||||
api.dispatch(WalletActions.updateSelectedValues(prevState, action));
|
api.dispatch(WalletActions.updateSelectedValues(prevState, action));
|
||||||
@ -47,15 +98,6 @@ const WalletService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispa
|
|||||||
// update common values in SelectedAccountReducer
|
// update common values in SelectedAccountReducer
|
||||||
api.dispatch(SelectedAccountActions.updateSelectedValues(prevState, action));
|
api.dispatch(SelectedAccountActions.updateSelectedValues(prevState, action));
|
||||||
|
|
||||||
// selected device changed
|
|
||||||
if (action.type === WALLET.SET_SELECTED_DEVICE) {
|
|
||||||
if (action.device) {
|
|
||||||
api.dispatch(TrezorConnectActions.getSelectedDeviceState());
|
|
||||||
} else {
|
|
||||||
api.dispatch(TrezorConnectActions.switchToFirstAvailableDevice());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return action;
|
return action;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
21
src/store.js
21
src/store.js
@ -3,8 +3,6 @@
|
|||||||
import { createStore, applyMiddleware, compose } from 'redux';
|
import { createStore, applyMiddleware, compose } from 'redux';
|
||||||
import { routerMiddleware } from 'react-router-redux';
|
import { routerMiddleware } from 'react-router-redux';
|
||||||
import thunk from 'redux-thunk';
|
import thunk from 'redux-thunk';
|
||||||
// import createHistory from 'history/createBrowserHistory';
|
|
||||||
// import { useRouterHistory } from 'react-router';
|
|
||||||
import createHistory from 'history/createHashHistory';
|
import createHistory from 'history/createHashHistory';
|
||||||
import { createLogger } from 'redux-logger';
|
import { createLogger } from 'redux-logger';
|
||||||
import reducers from 'reducers';
|
import reducers from 'reducers';
|
||||||
@ -17,17 +15,21 @@ import type { Action, GetState, Store } from 'flowtype';
|
|||||||
|
|
||||||
export const history: History = createHistory({ queryKey: false });
|
export const history: History = createHistory({ queryKey: false });
|
||||||
|
|
||||||
const RAVEN_KEY: string = 'https://497392c3ff6e46dc9e54eef123979378@sentry.io/294339';
|
|
||||||
Raven.config(RAVEN_KEY).install();
|
|
||||||
|
|
||||||
const initialState: any = {};
|
const initialState: any = {};
|
||||||
const enhancers = [];
|
const enhancers = [];
|
||||||
const middleware = [
|
|
||||||
|
const middlewares = [
|
||||||
thunk,
|
thunk,
|
||||||
RavenMiddleware(RAVEN_KEY),
|
|
||||||
routerMiddleware(history),
|
routerMiddleware(history),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// sentry io middleware only in dev build
|
||||||
|
if (process.env.BUILD === 'development') {
|
||||||
|
const RAVEN_KEY = 'https://34b8c09deb6c4cd2a4dc3f0029cd02d8@sentry.io/1279550';
|
||||||
|
const ravenMiddleware = RavenMiddleware(RAVEN_KEY);
|
||||||
|
Raven.config(RAVEN_KEY).install();
|
||||||
|
middlewares.push(ravenMiddleware);
|
||||||
|
}
|
||||||
|
|
||||||
let composedEnhancers: any;
|
let composedEnhancers: any;
|
||||||
if (process.env.NODE_ENV === 'development') {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
@ -50,13 +52,12 @@ if (process.env.NODE_ENV === 'development') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
composedEnhancers = compose(
|
composedEnhancers = compose(
|
||||||
applyMiddleware(logger, ...middleware, ...services),
|
applyMiddleware(logger, ...middlewares, ...services),
|
||||||
...enhancers,
|
...enhancers,
|
||||||
);
|
);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
composedEnhancers = compose(
|
composedEnhancers = compose(
|
||||||
applyMiddleware(...middleware, ...services),
|
applyMiddleware(...middlewares, ...services),
|
||||||
...enhancers,
|
...enhancers,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -63,6 +63,44 @@ const baseStyles = () => injectGlobal`
|
|||||||
url('./fonts/roboto/roboto-mono-v4-greek_cyrillic-ext_greek-ext_latin_cyrillic_vietnamese_latin-ext-regular.svg#RobotoMono') format('svg'); /* Legacy iOS */
|
url('./fonts/roboto/roboto-mono-v4-greek_cyrillic-ext_greek-ext_latin_cyrillic_vietnamese_latin-ext-regular.svg#RobotoMono') format('svg'); /* Legacy iOS */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.slide-left-enter {
|
||||||
|
transform: translate(100%);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-left-enter.slide-left-enter-active {
|
||||||
|
transform: translate(0%);
|
||||||
|
transition: transform 300ms ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-left-exit {
|
||||||
|
transform: translate(-100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-left-exit.slide-left-exit-active {
|
||||||
|
transform: translate(0%);
|
||||||
|
transition: transform 300ms ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-right-enter {
|
||||||
|
transform: translate(-100%);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-right-enter.slide-right-enter-active {
|
||||||
|
transform: translate(0%);
|
||||||
|
transition: transform 300ms ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-right-exit {
|
||||||
|
transform: translate(-100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-right-exit.slide-right-exit-active {
|
||||||
|
transform: translate(-200%);
|
||||||
|
transition: transform 300ms ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default baseStyles;
|
export default baseStyles;
|
94
src/support/routes.js
Normal file
94
src/support/routes.js
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
/* @flow */
|
||||||
|
|
||||||
|
export type Route = {
|
||||||
|
+name: string;
|
||||||
|
+pattern: string;
|
||||||
|
fields: Array<string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const routes: Array<Route> = [
|
||||||
|
{
|
||||||
|
name: 'landing-home',
|
||||||
|
pattern: '/',
|
||||||
|
fields: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'landing-bridge',
|
||||||
|
pattern: '/bridge',
|
||||||
|
fields: ['bridge'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'landing-import',
|
||||||
|
pattern: '/import',
|
||||||
|
fields: ['import'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'wallet-setting',
|
||||||
|
pattern: '/settings',
|
||||||
|
fields: ['settings'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'wallet-acquire',
|
||||||
|
pattern: '/device/:device/acquire',
|
||||||
|
fields: ['device', 'acquire'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'wallet-unreadable',
|
||||||
|
pattern: '/device/:device/unreadable',
|
||||||
|
fields: ['device', 'unreadable'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'wallet-bootloader',
|
||||||
|
pattern: '/device/:device/bootloader',
|
||||||
|
fields: ['device', 'bootloader'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'wallet-initialize',
|
||||||
|
pattern: '/device/:device/initialize',
|
||||||
|
fields: ['device', 'initialize'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'wallet-device-settings',
|
||||||
|
pattern: '/device/:device/settings',
|
||||||
|
fields: ['device', 'settings'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'wallet-dashboard',
|
||||||
|
pattern: '/device/:device',
|
||||||
|
fields: ['device'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'wallet-account-summary',
|
||||||
|
pattern: '/device/:device/network/:network/account/:account',
|
||||||
|
fields: ['device', 'network', 'account'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'wallet-account-send',
|
||||||
|
pattern: '/device/:device/network/:network/account/:account/send',
|
||||||
|
fields: ['device', 'network', 'account', 'send'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'wallet-account-send-override',
|
||||||
|
pattern: '/device/:device/network/:network/account/:account/send/override',
|
||||||
|
fields: ['device', 'network', 'account', 'send'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'wallet-account-receive',
|
||||||
|
pattern: '/device/:device/network/:network/account/:account/receive',
|
||||||
|
fields: ['device', 'network', 'account', 'receive'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'wallet-account-signverify',
|
||||||
|
pattern: '/device/:device/network/:network/account/:account/signverify',
|
||||||
|
fields: ['device', 'network', 'account', 'signverify'],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const getPattern = (name: string): string => {
|
||||||
|
const entry = routes.find(r => r.name === name);
|
||||||
|
if (!entry) {
|
||||||
|
console.error(`Route for ${name} not found`);
|
||||||
|
return '/';
|
||||||
|
}
|
||||||
|
return entry.pattern;
|
||||||
|
};
|
@ -1,6 +1,7 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
|
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
|
import EthereumjsUtil from 'ethereumjs-util';
|
||||||
|
|
||||||
export const decimalToHex = (dec: number): string => new BigNumber(dec).toString(16);
|
export const decimalToHex = (dec: number): string => new BigNumber(dec).toString(16);
|
||||||
|
|
||||||
@ -29,3 +30,15 @@ export const strip = (str: string): string => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const calcGasPrice = (price: BigNumber, limit: string): string => price.times(limit).toString();
|
export const calcGasPrice = (price: BigNumber, limit: string): string => price.times(limit).toString();
|
||||||
|
|
||||||
|
export const validateAddress = (address: string): ?string => {
|
||||||
|
const hasUpperCase = new RegExp('^(.*[A-Z].*)$');
|
||||||
|
if (address.length < 1) {
|
||||||
|
return 'Address is not set';
|
||||||
|
} else if (!EthereumjsUtil.isValidAddress(address)) {
|
||||||
|
return 'Address is not valid';
|
||||||
|
} else if (address.match(hasUpperCase) && !EthereumjsUtil.isValidChecksumAddress(address)) {
|
||||||
|
return 'Address is not a valid checksum';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
@ -3,7 +3,7 @@
|
|||||||
// TODO: chagne currency units
|
// TODO: chagne currency units
|
||||||
const currencyUnitsConstant: string = 'mbtc2';
|
const currencyUnitsConstant: string = 'mbtc2';
|
||||||
|
|
||||||
export const formatAmount = (n: number, coinInfo: any, currencyUnits = currencyUnitsConstant): string => {
|
export const formatAmount = (n: number, coinInfo: any, currencyUnits: string = currencyUnitsConstant): string => {
|
||||||
const amount = (n / 1e8);
|
const amount = (n / 1e8);
|
||||||
if (coinInfo.isBitcoin && currencyUnits === 'mbtc' && amount <= 0.1 && n !== 0) {
|
if (coinInfo.isBitcoin && currencyUnits === 'mbtc' && amount <= 0.1 && n !== 0) {
|
||||||
const s = (n / 1e5).toString();
|
const s = (n / 1e5).toString();
|
||||||
|
@ -86,7 +86,7 @@ class ConnectDevice extends Component<Props> {
|
|||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)}
|
)}
|
||||||
</ConnectTrezorWrapper>
|
</ConnectTrezorWrapper>
|
||||||
{this.props.showWebUsb && (
|
{this.props.showWebUsb && !this.props.showDisconnect && (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<P>and</P>
|
<P>and</P>
|
||||||
<Button isWebUsb>
|
<Button isWebUsb>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import CaseImage from 'images/case.png';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import Header from 'components/Header';
|
import Header from 'components/Header';
|
||||||
import Footer from 'components/Footer';
|
import Footer from 'components/Footer';
|
||||||
@ -39,11 +39,10 @@ const LandingContent = styled.div`
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const LandingImage = styled.div`
|
const LandingImage = styled.img`
|
||||||
width: 777px;
|
width: 777px;
|
||||||
min-height: 500px;
|
min-height: 500px;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
background-image: url('../images/case.png');
|
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-position: center 0px;
|
background-position: center 0px;
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
@ -120,7 +119,7 @@ export default (props: Props) => {
|
|||||||
showDisconnect={shouldShowDisconnectDevice}
|
showDisconnect={shouldShowDisconnectDevice}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<LandingImage />
|
<LandingImage src={CaseImage} />
|
||||||
|
|
||||||
{shouldShowConnectDevice && (
|
{shouldShowConnectDevice && (
|
||||||
<LandingFooterWrapper>
|
<LandingFooterWrapper>
|
||||||
|
@ -5,6 +5,7 @@ import { connect } from 'react-redux';
|
|||||||
import { withRouter } from 'react-router-dom';
|
import { withRouter } from 'react-router-dom';
|
||||||
|
|
||||||
import * as TrezorConnectActions from 'actions/TrezorConnectActions';
|
import * as TrezorConnectActions from 'actions/TrezorConnectActions';
|
||||||
|
import * as RouterActions from 'actions/RouterActions';
|
||||||
import type { MapStateToProps, MapDispatchToProps } from 'react-redux';
|
import type { MapStateToProps, MapDispatchToProps } from 'react-redux';
|
||||||
import type { State, Dispatch } from 'flowtype';
|
import type { State, Dispatch } from 'flowtype';
|
||||||
|
|
||||||
@ -35,8 +36,8 @@ const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps>
|
|||||||
acquireDevice: bindActionCreators(TrezorConnectActions.acquire, dispatch),
|
acquireDevice: bindActionCreators(TrezorConnectActions.acquire, dispatch),
|
||||||
forgetDevice: bindActionCreators(TrezorConnectActions.forget, dispatch),
|
forgetDevice: bindActionCreators(TrezorConnectActions.forget, dispatch),
|
||||||
duplicateDevice: bindActionCreators(TrezorConnectActions.duplicateDevice, dispatch),
|
duplicateDevice: bindActionCreators(TrezorConnectActions.duplicateDevice, dispatch),
|
||||||
gotoDeviceSettings: bindActionCreators(TrezorConnectActions.gotoDeviceSettings, dispatch),
|
gotoDeviceSettings: bindActionCreators(RouterActions.gotoDeviceSettings, dispatch),
|
||||||
onSelectDevice: bindActionCreators(TrezorConnectActions.onSelectDevice, dispatch),
|
onSelectDevice: bindActionCreators(RouterActions.selectDevice, dispatch),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default withRouter(
|
export default withRouter(
|
||||||
|
@ -20,7 +20,6 @@ import type { Props } from '../common';
|
|||||||
import Row from '../Row';
|
import Row from '../Row';
|
||||||
import RowCoin from '../RowCoin';
|
import RowCoin from '../RowCoin';
|
||||||
|
|
||||||
|
|
||||||
const Wrapper = styled.div``;
|
const Wrapper = styled.div``;
|
||||||
|
|
||||||
const Text = styled.span`
|
const Text = styled.span`
|
||||||
@ -28,6 +27,10 @@ const Text = styled.span`
|
|||||||
color: ${colors.TEXT_SECONDARY};
|
color: ${colors.TEXT_SECONDARY};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const TooltipContent = styled.div`
|
||||||
|
font-size: ${FONT_SIZE.SMALLEST};
|
||||||
|
`;
|
||||||
|
|
||||||
const RowAccountWrapper = styled.div`
|
const RowAccountWrapper = styled.div`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -120,7 +123,7 @@ const AccountMenu = (props: Props): ?React$Element<string> => {
|
|||||||
|
|
||||||
const deviceAccounts: Accounts = findDeviceAccounts(accounts, selected, location.state.network);
|
const deviceAccounts: Accounts = findDeviceAccounts(accounts, selected, location.state.network);
|
||||||
|
|
||||||
let selectedAccounts = deviceAccounts.map((account, i) => {
|
const selectedAccounts = deviceAccounts.map((account, i) => {
|
||||||
// const url: string = `${baseUrl}/network/${location.state.network}/account/${i}`;
|
// const url: string = `${baseUrl}/network/${location.state.network}/account/${i}`;
|
||||||
const url: string = location.pathname.replace(/account+\/([0-9]*)/, `account/${i}`);
|
const url: string = location.pathname.replace(/account+\/([0-9]*)/, `account/${i}`);
|
||||||
|
|
||||||
@ -158,26 +161,6 @@ const AccountMenu = (props: Props): ?React$Element<string> => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (selectedAccounts.length < 1) {
|
|
||||||
if (selected.connected) {
|
|
||||||
const url: string = location.pathname.replace(/account+\/([0-9]*)/, 'account/0');
|
|
||||||
selectedAccounts = (
|
|
||||||
<NavLink
|
|
||||||
to={url}
|
|
||||||
>
|
|
||||||
<Row column>
|
|
||||||
<RowAccountWrapper
|
|
||||||
isSelected
|
|
||||||
borderTop
|
|
||||||
>
|
|
||||||
Account #1
|
|
||||||
</RowAccountWrapper>
|
|
||||||
</Row>
|
|
||||||
</NavLink>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let discoveryStatus = null;
|
let discoveryStatus = null;
|
||||||
const discovery = props.discovery.find(d => d.deviceState === selected.state && d.network === location.state.network);
|
const discovery = props.discovery.find(d => d.deviceState === selected.state && d.network === location.state.network);
|
||||||
|
|
||||||
@ -204,7 +187,8 @@ const AccountMenu = (props: Props): ?React$Element<string> => {
|
|||||||
} else {
|
} else {
|
||||||
discoveryStatus = (
|
discoveryStatus = (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
content={<React.Fragment>To add a new account, last account must have some transactions.</React.Fragment>}
|
maxWidth={300}
|
||||||
|
content={<TooltipContent>To add a new account, last account must have some transactions.</TooltipContent>}
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
>
|
>
|
||||||
<Row>
|
<Row>
|
||||||
|
@ -20,6 +20,8 @@ const RowCoinWrapper = styled.div`
|
|||||||
display: block;
|
display: block;
|
||||||
font-size: ${FONT_SIZE.BASE};
|
font-size: ${FONT_SIZE.BASE};
|
||||||
color: ${colors.TEXT_PRIMARY};
|
color: ${colors.TEXT_PRIMARY};
|
||||||
|
transition: background-color 0.3s, color 0.3s;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: ${colors.GRAY_LIGHT};
|
background-color: ${colors.GRAY_LIGHT};
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ const AsideWrapper = styled.aside`
|
|||||||
position: relative;
|
position: relative;
|
||||||
top: 0;
|
top: 0;
|
||||||
width: 320px;
|
width: 320px;
|
||||||
|
min-width: 320px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: ${colors.MAIN};
|
background: ${colors.MAIN};
|
||||||
border-right: 1px solid ${colors.DIVIDER};
|
border-right: 1px solid ${colors.DIVIDER};
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
import * as TrezorConnectActions from 'actions/TrezorConnectActions';
|
import * as TrezorConnectActions from 'actions/TrezorConnectActions';
|
||||||
|
import * as RouterActions from 'actions/RouterActions';
|
||||||
import { toggleDeviceDropdown } from 'actions/WalletActions';
|
import { toggleDeviceDropdown } from 'actions/WalletActions';
|
||||||
import type { State } from 'flowtype';
|
import type { State } from 'flowtype';
|
||||||
|
|
||||||
@ -21,8 +22,8 @@ export type DispatchProps = {
|
|||||||
acquireDevice: typeof TrezorConnectActions.acquire,
|
acquireDevice: typeof TrezorConnectActions.acquire,
|
||||||
forgetDevice: typeof TrezorConnectActions.forget,
|
forgetDevice: typeof TrezorConnectActions.forget,
|
||||||
duplicateDevice: typeof TrezorConnectActions.duplicateDevice,
|
duplicateDevice: typeof TrezorConnectActions.duplicateDevice,
|
||||||
gotoDeviceSettings: typeof TrezorConnectActions.gotoDeviceSettings,
|
gotoDeviceSettings: typeof RouterActions.gotoDeviceSettings,
|
||||||
onSelectDevice: typeof TrezorConnectActions.onSelectDevice,
|
onSelectDevice: typeof RouterActions.selectDevice,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Props = StateProps & DispatchProps;
|
export type Props = StateProps & DispatchProps;
|
@ -36,15 +36,15 @@ const TransitionContentWrapper = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const Footer = styled.div`
|
const Footer = styled.div`
|
||||||
position: fixed;
|
position: relative;
|
||||||
|
width: 320px;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
background: ${colors.MAIN};
|
background: ${colors.MAIN};
|
||||||
border-right: 1px solid ${colors.DIVIDER};
|
border-right: 1px solid ${colors.DIVIDER};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Body = styled.div`
|
const Body = styled.div`
|
||||||
overflow: auto;
|
width: 320px;
|
||||||
background: ${colors.LANDING};
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Help = styled.div`
|
const Help = styled.div`
|
||||||
@ -70,6 +70,25 @@ const A = styled.a`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const TransitionMenu = (props: TransitionMenuProps): React$Element<TransitionGroup> => (
|
||||||
|
<TransitionGroupWrapper component="div" className="transition-container">
|
||||||
|
<CSSTransition
|
||||||
|
key={props.animationType}
|
||||||
|
onExit={() => { window.dispatchEvent(new Event('resize')); }}
|
||||||
|
onExited={() => window.dispatchEvent(new Event('resize'))}
|
||||||
|
in
|
||||||
|
out
|
||||||
|
classNames={props.animationType}
|
||||||
|
appear={false}
|
||||||
|
timeout={300}
|
||||||
|
>
|
||||||
|
<TransitionContentWrapper>
|
||||||
|
{ props.children }
|
||||||
|
</TransitionContentWrapper>
|
||||||
|
</CSSTransition>
|
||||||
|
</TransitionGroupWrapper>
|
||||||
|
);
|
||||||
|
|
||||||
class LeftNavigation extends Component {
|
class LeftNavigation extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
@ -107,29 +126,6 @@ class LeftNavigation extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: refactor to transition component for reuse of transitions
|
|
||||||
getMenuTransition(children) {
|
|
||||||
return (
|
|
||||||
<TransitionGroupWrapper component="div" className="transition-container">
|
|
||||||
<CSSTransition
|
|
||||||
key={this.state.animationType}
|
|
||||||
onExit={() => {
|
|
||||||
window.dispatchEvent(new Event('resize'));
|
|
||||||
}}
|
|
||||||
onExited={() => window.dispatchEvent(new Event('resize'))}
|
|
||||||
classNames={this.state.animationType}
|
|
||||||
appear={false}
|
|
||||||
timeout={30000}
|
|
||||||
in
|
|
||||||
out
|
|
||||||
>
|
|
||||||
<TransitionContentWrapper>
|
|
||||||
{children}
|
|
||||||
</TransitionContentWrapper>
|
|
||||||
</CSSTransition>
|
|
||||||
</TransitionGroupWrapper>);
|
|
||||||
}
|
|
||||||
|
|
||||||
shouldRenderAccounts() {
|
shouldRenderAccounts() {
|
||||||
const { selectedDevice } = this.props.wallet;
|
const { selectedDevice } = this.props.wallet;
|
||||||
return selectedDevice
|
return selectedDevice
|
||||||
@ -150,6 +146,22 @@ class LeftNavigation extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { props } = this;
|
||||||
|
let menu;
|
||||||
|
if (this.shouldRenderAccounts()) {
|
||||||
|
menu = (
|
||||||
|
<TransitionMenu animationType="slide-left">
|
||||||
|
<AccountMenu {...props} />
|
||||||
|
</TransitionMenu>
|
||||||
|
);
|
||||||
|
} else if (this.shouldRenderCoins()) {
|
||||||
|
menu = (
|
||||||
|
<TransitionMenu animationType="slide-right">
|
||||||
|
<CoinMenu {...props} />
|
||||||
|
</TransitionMenu>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StickyContainer
|
<StickyContainer
|
||||||
location={this.props.location.pathname}
|
location={this.props.location.pathname}
|
||||||
@ -180,8 +192,7 @@ class LeftNavigation extends Component {
|
|||||||
/>
|
/>
|
||||||
<Body>
|
<Body>
|
||||||
{this.state.shouldRenderDeviceSelection && <DeviceMenu {...this.props} />}
|
{this.state.shouldRenderDeviceSelection && <DeviceMenu {...this.props} />}
|
||||||
{this.shouldRenderAccounts() && this.getMenuTransition(<AccountMenu {...this.props} />)}
|
{menu}
|
||||||
{this.shouldRenderCoins() && this.getMenuTransition(<CoinMenu {...this.props} />)}
|
|
||||||
</Body>
|
</Body>
|
||||||
<Footer className="sticky-bottom">
|
<Footer className="sticky-bottom">
|
||||||
<Help>
|
<Help>
|
||||||
|
@ -30,10 +30,11 @@ const SelectedAccount = (props: Props) => {
|
|||||||
const {
|
const {
|
||||||
account,
|
account,
|
||||||
discovery,
|
discovery,
|
||||||
network
|
network,
|
||||||
} = accountState;
|
} = accountState;
|
||||||
|
|
||||||
if (!network) return; // TODO: this shouldn't happen. change accountState reducer?
|
// corner case: accountState didn't finish loading state after LOCATION_CHANGE action
|
||||||
|
if (!network) return (<Notification type="info" title="Loading account state..." />);
|
||||||
|
|
||||||
const blockchain = props.blockchain.find(b => b.name === network.network);
|
const blockchain = props.blockchain.find(b => b.name === network.network);
|
||||||
if (blockchain && !blockchain.connected) {
|
if (blockchain && !blockchain.connected) {
|
||||||
@ -43,12 +44,13 @@ const SelectedAccount = (props: Props) => {
|
|||||||
title="Backend not connected"
|
title="Backend not connected"
|
||||||
actions={
|
actions={
|
||||||
[{
|
[{
|
||||||
label: "Try again",
|
label: 'Try again',
|
||||||
callback: async () => {
|
callback: async () => {
|
||||||
await props.blockchainReconnect(network.network);
|
await props.blockchainReconnect(network.network);
|
||||||
}
|
},
|
||||||
}]
|
}]
|
||||||
} />
|
}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,6 @@ import { connect } from 'react-redux';
|
|||||||
|
|
||||||
import { reconnect } from 'actions/DiscoveryActions';
|
import { reconnect } from 'actions/DiscoveryActions';
|
||||||
import SendFormActions from 'actions/SendFormActions';
|
import SendFormActions from 'actions/SendFormActions';
|
||||||
import * as SessionStorageActions from 'actions/SessionStorageActions';
|
|
||||||
import type { MapStateToProps, MapDispatchToProps } from 'react-redux';
|
import type { MapStateToProps, MapDispatchToProps } from 'react-redux';
|
||||||
import type { State, Dispatch } from 'flowtype';
|
import type { State, Dispatch } from 'flowtype';
|
||||||
import type { StateProps as BaseStateProps, DispatchProps as BaseDispatchProps } from 'views/Wallet/components/SelectedAccount';
|
import type { StateProps as BaseStateProps, DispatchProps as BaseDispatchProps } from 'views/Wallet/components/SelectedAccount';
|
||||||
@ -24,7 +23,6 @@ export type StateProps = BaseStateProps & {
|
|||||||
|
|
||||||
export type DispatchProps = BaseDispatchProps & {
|
export type DispatchProps = BaseDispatchProps & {
|
||||||
sendFormActions: typeof SendFormActions,
|
sendFormActions: typeof SendFormActions,
|
||||||
saveSessionStorage: typeof SessionStorageActions.save
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Props = StateProps & BaseStateProps & DispatchProps & BaseDispatchProps;
|
export type Props = StateProps & BaseStateProps & DispatchProps & BaseDispatchProps;
|
||||||
@ -43,7 +41,6 @@ const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: St
|
|||||||
const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => ({
|
const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => ({
|
||||||
blockchainReconnect: bindActionCreators(reconnect, dispatch),
|
blockchainReconnect: bindActionCreators(reconnect, dispatch),
|
||||||
sendFormActions: bindActionCreators(SendFormActions, dispatch),
|
sendFormActions: bindActionCreators(SendFormActions, dispatch),
|
||||||
saveSessionStorage: bindActionCreators(SessionStorageActions.save, dispatch),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(AccountSend);
|
export default connect(mapStateToProps, mapDispatchToProps)(AccountSend);
|
@ -18,12 +18,17 @@ type Props = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
|
padding-top: 20px;
|
||||||
border-top: 1px solid ${colors.DIVIDER};
|
border-top: 1px solid ${colors.DIVIDER};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const StyledLink = styled(Link)`
|
||||||
|
text-decoration: none;
|
||||||
|
`;
|
||||||
|
|
||||||
const TransactionWrapper = styled.div`
|
const TransactionWrapper = styled.div`
|
||||||
border-bottom: 1px solid ${colors.DIVIDER};
|
border-bottom: 1px solid ${colors.DIVIDER};
|
||||||
padding: 14px 48px;
|
padding: 14px 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -39,13 +44,17 @@ const TransactionIcon = styled.div`
|
|||||||
width: 36px;
|
width: 36px;
|
||||||
height: 36px;
|
height: 36px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
line-height: 30px;
|
line-height: 25px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: ${props => props.color};
|
color: ${props => props.textColor};
|
||||||
background: ${props => props.background};
|
background: ${props => props.background};
|
||||||
border-color: ${props => props.borderColor};
|
border-color: ${props => props.borderColor};
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: ${props => props.color};
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const P = styled.p``;
|
const P = styled.p``;
|
||||||
@ -54,7 +63,9 @@ const TransactionName = styled.div`
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const TransactionAmount = styled.div``;
|
const TransactionAmount = styled.div`
|
||||||
|
color: colors.TEXT_SECONDARY;
|
||||||
|
`;
|
||||||
|
|
||||||
class PendingTransactions extends Component<Props> {
|
class PendingTransactions extends Component<Props> {
|
||||||
getPendingTransactions() {
|
getPendingTransactions() {
|
||||||
@ -62,7 +73,11 @@ class PendingTransactions extends Component<Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getTransactionIconColors(tx: any) {
|
getTransactionIconColors(tx: any) {
|
||||||
let iconColors = { };
|
let iconColors = {
|
||||||
|
textColor: '#fff',
|
||||||
|
background: '#000',
|
||||||
|
borderColor: '#000',
|
||||||
|
};
|
||||||
const bgColorFactory = new ColorHash({ lightness: 0.7 });
|
const bgColorFactory = new ColorHash({ lightness: 0.7 });
|
||||||
const textColorFactory = new ColorHash();
|
const textColorFactory = new ColorHash();
|
||||||
|
|
||||||
@ -73,26 +88,25 @@ class PendingTransactions extends Component<Props> {
|
|||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
iconColors = {
|
iconColors = {
|
||||||
color: '#ffffff',
|
textColor: '#ffffff',
|
||||||
background: '#000000',
|
background: '#000000',
|
||||||
borderColor: '#000000',
|
borderColor: '#000000',
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
const bgColor: string = bgColorFactory.hex(token.name);
|
const bgColor: string = bgColorFactory.hex(token.name);
|
||||||
iconColors = {
|
iconColors = {
|
||||||
color: textColorFactory.hex(token.name),
|
textColor: textColorFactory.hex(token.name),
|
||||||
background: bgColor,
|
background: bgColor,
|
||||||
borderColor: bgColor,
|
borderColor: bgColor,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
iconColors = {
|
iconColors = {
|
||||||
color: textColorFactory.hex(tx.network),
|
textColor: textColorFactory.hex(tx.network),
|
||||||
background: bgColorFactory.hex(tx.network),
|
background: bgColorFactory.hex(tx.network),
|
||||||
borderColor: bgColorFactory.hex(tx.network),
|
borderColor: bgColorFactory.hex(tx.network),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return iconColors;
|
return iconColors;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,9 +139,11 @@ class PendingTransactions extends Component<Props> {
|
|||||||
<Wrapper>
|
<Wrapper>
|
||||||
<H2>Pending transactions</H2>
|
<H2>Pending transactions</H2>
|
||||||
{this.getPendingTransactions().map(tx => (
|
{this.getPendingTransactions().map(tx => (
|
||||||
<TransactionWrapper>
|
<TransactionWrapper
|
||||||
|
key={tx.id}
|
||||||
|
>
|
||||||
<TransactionIcon
|
<TransactionIcon
|
||||||
color={() => this.getTransactionIconColors(tx).color}
|
textColor={() => this.getTransactionIconColors(tx).textColor}
|
||||||
background={() => this.getTransactionIconColors(tx).background}
|
background={() => this.getTransactionIconColors(tx).background}
|
||||||
borderColor={() => this.getTransactionIconColors(tx).borderColor}
|
borderColor={() => this.getTransactionIconColors(tx).borderColor}
|
||||||
>
|
>
|
||||||
@ -137,13 +153,14 @@ class PendingTransactions extends Component<Props> {
|
|||||||
</TransactionIcon>
|
</TransactionIcon>
|
||||||
|
|
||||||
<TransactionName>
|
<TransactionName>
|
||||||
<Link
|
<StyledLink
|
||||||
href={`${this.props.network.explorer.tx}${tx.id}`}
|
href={`${this.props.network.explorer.tx}${tx.id}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer noopener"
|
rel="noreferrer noopener"
|
||||||
|
isGray
|
||||||
>
|
>
|
||||||
{this.getTransactionName(tx)}
|
{this.getTransactionName(tx)}
|
||||||
</Link>
|
</StyledLink>
|
||||||
</TransactionName>
|
</TransactionName>
|
||||||
|
|
||||||
<TransactionAmount>
|
<TransactionAmount>
|
||||||
|
@ -21,6 +21,10 @@ import PendingTransactions from './components/PendingTransactions';
|
|||||||
|
|
||||||
import type { Props } from './Container';
|
import type { Props } from './Container';
|
||||||
|
|
||||||
|
// TODO: Decide on a small screen width for the whole app
|
||||||
|
// and put it inside config/variables.js
|
||||||
|
const SmallScreenWidth = '850px';
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
isAdvancedSettingsHidden: boolean,
|
isAdvancedSettingsHidden: boolean,
|
||||||
shouldAnimateAdvancedSettingsToggle: boolean,
|
shouldAnimateAdvancedSettingsToggle: boolean,
|
||||||
@ -76,6 +80,7 @@ const SetMaxAmountButton = styled(Button)`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const CurrencySelect = styled(Select)`
|
const CurrencySelect = styled(Select)`
|
||||||
|
min-width: 77px;
|
||||||
height: 34px;
|
height: 34px;
|
||||||
flex: 0.2;
|
flex: 0.2;
|
||||||
`;
|
`;
|
||||||
@ -108,12 +113,21 @@ const StyledLink = styled(Link)`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const ToggleAdvancedSettingsWrapper = styled.div`
|
const ToggleAdvancedSettingsWrapper = styled.div`
|
||||||
|
min-height: 40px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
|
@media screen and (max-width: ${SmallScreenWidth}) {
|
||||||
|
${props => (props.isAdvancedSettingsHidden && css`
|
||||||
|
flex-direction: column;
|
||||||
|
`)}
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ToggleAdvancedSettingsButton = styled(Button)`
|
const ToggleAdvancedSettingsButton = styled(Button)`
|
||||||
|
min-height: 40px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -121,7 +135,12 @@ const ToggleAdvancedSettingsButton = styled(Button)`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const SendButton = styled(Button)`
|
const SendButton = styled(Button)`
|
||||||
min-width: 50%;
|
min-width: ${props => (props.isAdvancedSettingsHidden ? '50%' : '100%')};
|
||||||
|
font-size: 13px;
|
||||||
|
|
||||||
|
@media screen and (max-width: ${SmallScreenWidth}) {
|
||||||
|
margin-top: ${props => (props.isAdvancedSettingsHidden ? '10px' : 0)};
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const AdvancedSettingsWrapper = styled.div`
|
const AdvancedSettingsWrapper = styled.div`
|
||||||
@ -180,17 +199,17 @@ class AccountSend extends Component<Props, State> {
|
|||||||
componentWillReceiveProps(newProps: Props) {
|
componentWillReceiveProps(newProps: Props) {
|
||||||
calculate(this.props, newProps);
|
calculate(this.props, newProps);
|
||||||
validation(newProps);
|
validation(newProps);
|
||||||
|
|
||||||
this.props.saveSessionStorage();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getAddressInputState(address: string, addressErrors: string, addressWarnings: string) {
|
getAddressInputState(address: string, addressErrors: string, addressWarnings: string) {
|
||||||
let state = '';
|
let state = '';
|
||||||
if (address && !addressErrors) {
|
if (address && !addressErrors) {
|
||||||
state = 'success';
|
state = 'success';
|
||||||
} else if (addressWarnings && !addressErrors) {
|
}
|
||||||
|
if (addressWarnings && !addressErrors) {
|
||||||
state = 'warning';
|
state = 'warning';
|
||||||
} else if (addressErrors) {
|
}
|
||||||
|
if (addressErrors) {
|
||||||
state = 'error';
|
state = 'error';
|
||||||
}
|
}
|
||||||
return state;
|
return state;
|
||||||
@ -200,27 +219,19 @@ class AccountSend extends Component<Props, State> {
|
|||||||
let state = '';
|
let state = '';
|
||||||
if (amountWarnings && !amountErrors) {
|
if (amountWarnings && !amountErrors) {
|
||||||
state = 'warning';
|
state = 'warning';
|
||||||
} else if (amountErrors) {
|
}
|
||||||
|
if (amountErrors) {
|
||||||
state = 'error';
|
state = 'error';
|
||||||
}
|
}
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
getAmountInputBottomText(amountErrors: string, amountWarnings: string) {
|
|
||||||
let text = '';
|
|
||||||
if (amountWarnings && !amountErrors) {
|
|
||||||
text = amountWarnings;
|
|
||||||
} else if (amountErrors) {
|
|
||||||
text = amountErrors;
|
|
||||||
}
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
getGasLimitInputState(gasLimitErrors: string, gasLimitWarnings: string) {
|
getGasLimitInputState(gasLimitErrors: string, gasLimitWarnings: string) {
|
||||||
let state = '';
|
let state = '';
|
||||||
if (gasLimitWarnings && !gasLimitErrors) {
|
if (gasLimitWarnings && !gasLimitErrors) {
|
||||||
state = 'warning';
|
state = 'warning';
|
||||||
} else if (gasLimitErrors) {
|
}
|
||||||
|
if (gasLimitErrors) {
|
||||||
state = 'error';
|
state = 'error';
|
||||||
}
|
}
|
||||||
return state;
|
return state;
|
||||||
@ -230,7 +241,8 @@ class AccountSend extends Component<Props, State> {
|
|||||||
let state = '';
|
let state = '';
|
||||||
if (gasPriceWarnings && !gasPriceErrors) {
|
if (gasPriceWarnings && !gasPriceErrors) {
|
||||||
state = 'warning';
|
state = 'warning';
|
||||||
} else if (gasPriceErrors) {
|
}
|
||||||
|
if (gasPriceErrors) {
|
||||||
state = 'error';
|
state = 'error';
|
||||||
}
|
}
|
||||||
return state;
|
return state;
|
||||||
@ -275,6 +287,7 @@ class AccountSend extends Component<Props, State> {
|
|||||||
total,
|
total,
|
||||||
errors,
|
errors,
|
||||||
warnings,
|
warnings,
|
||||||
|
infos,
|
||||||
data,
|
data,
|
||||||
sending,
|
sending,
|
||||||
gasLimit,
|
gasLimit,
|
||||||
@ -340,7 +353,7 @@ class AccountSend extends Component<Props, State> {
|
|||||||
autoCapitalize="off"
|
autoCapitalize="off"
|
||||||
spellCheck="false"
|
spellCheck="false"
|
||||||
topLabel="Address"
|
topLabel="Address"
|
||||||
bottomText={errors.address ? 'Wrong Address' : ''}
|
bottomText={errors.address || warnings.address || infos.address}
|
||||||
value={address}
|
value={address}
|
||||||
onChange={event => onAddressChange(event.target.value)}
|
onChange={event => onAddressChange(event.target.value)}
|
||||||
/>
|
/>
|
||||||
@ -356,7 +369,7 @@ class AccountSend extends Component<Props, State> {
|
|||||||
topLabel="Amount"
|
topLabel="Amount"
|
||||||
value={amount}
|
value={amount}
|
||||||
onChange={event => onAmountChange(event.target.value)}
|
onChange={event => onAmountChange(event.target.value)}
|
||||||
bottomText={this.getAmountInputBottomText(errors.amount, warnings.amount)}
|
bottomText={errors.amount || warnings.amount || infos.amount}
|
||||||
sideAddons={[
|
sideAddons={[
|
||||||
(
|
(
|
||||||
<SetMaxAmountButton
|
<SetMaxAmountButton
|
||||||
@ -430,7 +443,9 @@ class AccountSend extends Component<Props, State> {
|
|||||||
/>
|
/>
|
||||||
</InputRow>
|
</InputRow>
|
||||||
|
|
||||||
<ToggleAdvancedSettingsWrapper>
|
<ToggleAdvancedSettingsWrapper
|
||||||
|
isAdvancedSettingsHidden={this.state.isAdvancedSettingsHidden}
|
||||||
|
>
|
||||||
<ToggleAdvancedSettingsButton
|
<ToggleAdvancedSettingsButton
|
||||||
isTransparent
|
isTransparent
|
||||||
onClick={() => this.handleToggleAdvancedSettingsButton()}
|
onClick={() => this.handleToggleAdvancedSettingsButton()}
|
||||||
@ -448,6 +463,7 @@ class AccountSend extends Component<Props, State> {
|
|||||||
{this.state.isAdvancedSettingsHidden && (
|
{this.state.isAdvancedSettingsHidden && (
|
||||||
<SendButton
|
<SendButton
|
||||||
isDisabled={isSendButtonDisabled}
|
isDisabled={isSendButtonDisabled}
|
||||||
|
isAdvancedSettingsHidden={this.state.isAdvancedSettingsHidden}
|
||||||
onClick={() => onSend()}
|
onClick={() => onSend()}
|
||||||
>
|
>
|
||||||
{sendButtonText}
|
{sendButtonText}
|
||||||
@ -486,7 +502,7 @@ class AccountSend extends Component<Props, State> {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</InputLabelWrapper>
|
</InputLabelWrapper>
|
||||||
)}
|
)}
|
||||||
bottomText={errors.gasLimit ? errors.gasLimit : warnings.gasLimit}
|
bottomText={errors.gasLimit || warnings.gasLimit || infos.gasLimit}
|
||||||
value={gasLimit}
|
value={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)}
|
||||||
@ -520,7 +536,7 @@ class AccountSend extends Component<Props, State> {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</InputLabelWrapper>
|
</InputLabelWrapper>
|
||||||
)}
|
)}
|
||||||
bottomText={errors.gasPrice ? errors.gasPrice : warnings.gasPrice}
|
bottomText={errors.gasPrice || warnings.gasPrice || infos.gasPrice}
|
||||||
value={gasPrice}
|
value={gasPrice}
|
||||||
onChange={event => onGasPriceChange(event.target.value)}
|
onChange={event => onGasPriceChange(event.target.value)}
|
||||||
/>
|
/>
|
||||||
|
@ -131,7 +131,12 @@ const AccountSummary = (props: Props) => {
|
|||||||
placeholder="Search for the token"
|
placeholder="Search for the token"
|
||||||
loadingMessage={() => 'Loading...'}
|
loadingMessage={() => 'Loading...'}
|
||||||
noOptionsMessage={() => 'Token not found'}
|
noOptionsMessage={() => 'Token not found'}
|
||||||
onChange={token => props.addToken(token, account)}
|
onChange={(token) => {
|
||||||
|
const isAdded = tokens.find(t => t.symbol === token.symbol);
|
||||||
|
if (!isAdded) {
|
||||||
|
props.addToken(token, account);
|
||||||
|
}
|
||||||
|
}}
|
||||||
loadOptions={input => props.loadTokens(input, account.network)}
|
loadOptions={input => props.loadTokens(input, account.network)}
|
||||||
formatOptionLabel={(option) => {
|
formatOptionLabel={(option) => {
|
||||||
const isAdded = tokens.find(t => t.symbol === option.symbol);
|
const isAdded = tokens.find(t => t.symbol === option.symbol);
|
||||||
|
@ -6,6 +6,7 @@ import { ConnectedRouter } from 'react-router-redux';
|
|||||||
|
|
||||||
// general
|
// general
|
||||||
import ErrorBoundary from 'support/ErrorBoundary';
|
import ErrorBoundary from 'support/ErrorBoundary';
|
||||||
|
import { getPattern } from 'support/routes';
|
||||||
import LandingContainer from 'views/Landing/Container';
|
import LandingContainer from 'views/Landing/Container';
|
||||||
|
|
||||||
// wallet views
|
// wallet views
|
||||||
@ -29,25 +30,24 @@ const App = () => (
|
|||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<ConnectedRouter history={history}>
|
<ConnectedRouter history={history}>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route exact path="/" component={LandingContainer} />
|
<Route exact path={getPattern('landing-home')} component={LandingContainer} />
|
||||||
<Route exact path="/bridge" component={LandingContainer} />
|
<Route exact path={getPattern('landing-bridge')} component={LandingContainer} />
|
||||||
<Route exact path="/import" component={LandingContainer} />
|
<Route exact path={getPattern('landing-import')} component={LandingContainer} />
|
||||||
<Route>
|
<Route>
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<WalletContainer>
|
<WalletContainer>
|
||||||
<Route exact path="/settings" component={WalletSettings} />
|
<Route exact path={getPattern('wallet-setting')} component={WalletSettings} />
|
||||||
<Route exact path="/device/:device/" component={WalletDashboard} />
|
<Route exact path={getPattern('wallet-dashboard')} component={WalletDashboard} />
|
||||||
<Route exact path="/device/:device/network/:network" component={WalletDashboard} />
|
<Route exact path={getPattern('wallet-acquire')} component={WalletAcquire} />
|
||||||
<Route exact path="/device/:device/acquire" component={WalletAcquire} />
|
<Route exact path={getPattern('wallet-unreadable')} component={WalletUnreadableDevice} />
|
||||||
<Route exact path="/device/:device/unreadable" component={WalletUnreadableDevice} />
|
<Route exact path={getPattern('wallet-bootloader')} component={WalletBootloader} />
|
||||||
<Route exact path="/device/:device/bootloader" component={WalletBootloader} />
|
<Route exact path={getPattern('wallet-initialize')} component={WalletInitialize} />
|
||||||
<Route exact path="/device/:device/initialize" component={WalletInitialize} />
|
<Route exact path={getPattern('wallet-device-settings')} component={WalletDeviceSettings} />
|
||||||
<Route exact path="/device/:device/settings" component={WalletDeviceSettings} />
|
<Route exact path={getPattern('wallet-account-summary')} component={AccountSummary} />
|
||||||
<Route exact path="/device/:device/network/:network/account/:account" component={AccountSummary} />
|
<Route path={getPattern('wallet-account-send')} component={AccountSend} />
|
||||||
<Route path="/device/:device/network/:network/account/:account/send" component={AccountSend} />
|
<Route path={getPattern('wallet-account-send-override')} component={AccountSend} />
|
||||||
<Route path="/device/:device/network/:network/account/:account/send/override" component={AccountSend} />
|
<Route path={getPattern('wallet-account-receive')} component={AccountReceive} />
|
||||||
<Route path="/device/:device/network/:network/account/:account/receive" component={AccountReceive} />
|
<Route path={getPattern('wallet-account-signverify')} component={AccountSignVerify} />
|
||||||
<Route path="/device/:device/network/:network/account/:account/signverify" component={AccountSignVerify} />
|
|
||||||
</WalletContainer>
|
</WalletContainer>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</Route>
|
</Route>
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
import webpack from 'webpack';
|
import webpack from 'webpack';
|
||||||
|
import GitRevisionPlugin from 'git-revision-webpack-plugin';
|
||||||
import HtmlWebpackPlugin from 'html-webpack-plugin';
|
import HtmlWebpackPlugin from 'html-webpack-plugin';
|
||||||
import FlowWebpackPlugin from 'flow-webpack-plugin';
|
import FlowWebpackPlugin from 'flow-webpack-plugin';
|
||||||
|
import WebpackBuildNotifierPlugin from 'webpack-build-notifier';
|
||||||
|
|
||||||
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
|
// turn on for bundle analyzing
|
||||||
|
// import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
SRC, BUILD, PORT, PUBLIC,
|
SRC, BUILD, PORT, PUBLIC,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
|
|
||||||
|
const gitRevisionPlugin = new GitRevisionPlugin();
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
watch: true,
|
watch: true,
|
||||||
@ -92,7 +95,16 @@ module.exports = {
|
|||||||
hints: false,
|
hints: false,
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new FlowWebpackPlugin(),
|
new WebpackBuildNotifierPlugin({
|
||||||
|
title: 'Trezor Wallet',
|
||||||
|
suppressSuccess: true,
|
||||||
|
}),
|
||||||
|
new webpack.DefinePlugin({
|
||||||
|
COMMITHASH: JSON.stringify(gitRevisionPlugin.commithash()),
|
||||||
|
}),
|
||||||
|
new FlowWebpackPlugin({
|
||||||
|
reportingSeverity: 'warning',
|
||||||
|
}),
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
chunks: ['index'],
|
chunks: ['index'],
|
||||||
template: `${SRC}index.html`,
|
template: `${SRC}index.html`,
|
||||||
@ -100,11 +112,11 @@ module.exports = {
|
|||||||
inject: true,
|
inject: true,
|
||||||
favicon: `${SRC}images/favicon.ico`,
|
favicon: `${SRC}images/favicon.ico`,
|
||||||
}),
|
}),
|
||||||
new BundleAnalyzerPlugin({
|
// new BundleAnalyzerPlugin({
|
||||||
openAnalyzer: false,
|
// openAnalyzer: false,
|
||||||
analyzerMode: false, // turn on to generate bundle pass 'static'
|
// analyzerMode: false, // turn on to generate bundle pass 'static'
|
||||||
reportFilename: 'bundle-report.html',
|
// reportFilename: 'bundle-report.html',
|
||||||
}),
|
// }),
|
||||||
new webpack.optimize.OccurrenceOrderPlugin(),
|
new webpack.optimize.OccurrenceOrderPlugin(),
|
||||||
new webpack.NoEmitOnErrorsPlugin(),
|
new webpack.NoEmitOnErrorsPlugin(),
|
||||||
new webpack.HotModuleReplacementPlugin(),
|
new webpack.HotModuleReplacementPlugin(),
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import webpack from 'webpack';
|
import webpack from 'webpack';
|
||||||
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
|
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
|
||||||
|
import GitRevisionPlugin from 'git-revision-webpack-plugin';
|
||||||
import HtmlWebpackPlugin from 'html-webpack-plugin';
|
import HtmlWebpackPlugin from 'html-webpack-plugin';
|
||||||
import CopyWebpackPlugin from 'copy-webpack-plugin';
|
import CopyWebpackPlugin from 'copy-webpack-plugin';
|
||||||
import MiniCssExtractPlugin from '../../trezor-connect/node_modules/mini-css-extract-plugin';
|
import MiniCssExtractPlugin from '../../trezor-connect/node_modules/mini-css-extract-plugin';
|
||||||
@ -15,6 +16,8 @@ import {
|
|||||||
PORT,
|
PORT,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
|
|
||||||
|
const gitRevisionPlugin = new GitRevisionPlugin();
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
watch: true,
|
watch: true,
|
||||||
mode: 'development',
|
mode: 'development',
|
||||||
@ -163,6 +166,7 @@ module.exports = {
|
|||||||
|
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
LOCAL: JSON.stringify(`http://localhost:${PORT}/`),
|
LOCAL: JSON.stringify(`http://localhost:${PORT}/`),
|
||||||
|
COMMITHASH: JSON.stringify(gitRevisionPlugin.commithash()),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// ignore node lib from trezor-link
|
// ignore node lib from trezor-link
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
|
|
||||||
import webpack from 'webpack';
|
import webpack from 'webpack';
|
||||||
|
import GitRevisionPlugin from 'git-revision-webpack-plugin';
|
||||||
import HtmlWebpackPlugin from 'html-webpack-plugin';
|
import HtmlWebpackPlugin from 'html-webpack-plugin';
|
||||||
import CopyWebpackPlugin from 'copy-webpack-plugin';
|
import CopyWebpackPlugin from 'copy-webpack-plugin';
|
||||||
import FlowWebpackPlugin from 'flow-webpack-plugin';
|
|
||||||
import { SRC, BUILD, PUBLIC } from './constants';
|
import { SRC, BUILD, PUBLIC } from './constants';
|
||||||
|
|
||||||
|
const gitRevisionPlugin = new GitRevisionPlugin();
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
mode: 'production',
|
mode: 'production',
|
||||||
entry: {
|
entry: {
|
||||||
@ -61,7 +63,10 @@ module.exports = {
|
|||||||
hints: false,
|
hints: false,
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new FlowWebpackPlugin(),
|
new webpack.DefinePlugin({
|
||||||
|
'process.env.BUILD': JSON.stringify(process.env.BUILD),
|
||||||
|
COMMITHASH: JSON.stringify(gitRevisionPlugin.commithash()
|
||||||
|
}),
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
chunks: ['index'],
|
chunks: ['index'],
|
||||||
template: `${SRC}index.html`,
|
template: `${SRC}index.html`,
|
||||||
|
120
yarn.lock
120
yarn.lock
@ -2736,6 +2736,10 @@ cors@^2.8.1:
|
|||||||
object-assign "^4"
|
object-assign "^4"
|
||||||
vary "^1"
|
vary "^1"
|
||||||
|
|
||||||
|
corser@~2.0.0:
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/corser/-/corser-2.0.1.tgz#8eda252ecaab5840dcd975ceb90d9370c819ff87"
|
||||||
|
|
||||||
cosmiconfig@^3.1.0:
|
cosmiconfig@^3.1.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-3.1.0.tgz#640a94bf9847f321800403cd273af60665c73397"
|
resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-3.1.0.tgz#640a94bf9847f321800403cd273af60665c73397"
|
||||||
@ -2803,6 +2807,13 @@ create-hmac@^1.1.4:
|
|||||||
safe-buffer "^5.0.1"
|
safe-buffer "^5.0.1"
|
||||||
sha.js "^2.4.8"
|
sha.js "^2.4.8"
|
||||||
|
|
||||||
|
cross-env@^5.2.0:
|
||||||
|
version "5.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-5.2.0.tgz#6ecd4c015d5773e614039ee529076669b9d126f2"
|
||||||
|
dependencies:
|
||||||
|
cross-spawn "^6.0.5"
|
||||||
|
is-windows "^1.0.0"
|
||||||
|
|
||||||
cross-spawn@^4.0.0:
|
cross-spawn@^4.0.0:
|
||||||
version "4.0.2"
|
version "4.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-4.0.2.tgz#7b9247621c23adfdd3856004a823cbe397424d41"
|
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-4.0.2.tgz#7b9247621c23adfdd3856004a823cbe397424d41"
|
||||||
@ -3297,6 +3308,10 @@ dom-helpers@^3.2.0:
|
|||||||
version "3.2.1"
|
version "3.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.2.1.tgz#3203e07fed217bd1f424b019735582fc37b2825a"
|
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.2.1.tgz#3203e07fed217bd1f424b019735582fc37b2825a"
|
||||||
|
|
||||||
|
dom-helpers@^3.3.1:
|
||||||
|
version "3.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.3.1.tgz#fc1a4e15ffdf60ddde03a480a9c0fece821dd4a6"
|
||||||
|
|
||||||
dom-serializer@0:
|
dom-serializer@0:
|
||||||
version "0.1.0"
|
version "0.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82"
|
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82"
|
||||||
@ -3395,6 +3410,15 @@ ecc-jsbn@~0.1.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
jsbn "~0.1.0"
|
jsbn "~0.1.0"
|
||||||
|
|
||||||
|
ecstatic@^3.0.0:
|
||||||
|
version "3.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ecstatic/-/ecstatic-3.3.0.tgz#91cd417d152abf85b37b1ab3ebf3bd25cdc64e80"
|
||||||
|
dependencies:
|
||||||
|
he "^1.1.1"
|
||||||
|
mime "^1.6.0"
|
||||||
|
minimist "^1.1.0"
|
||||||
|
url-join "^2.0.5"
|
||||||
|
|
||||||
editions@^1.3.3:
|
editions@^1.3.3:
|
||||||
version "1.3.4"
|
version "1.3.4"
|
||||||
resolved "https://registry.yarnpkg.com/editions/-/editions-1.3.4.tgz#3662cb592347c3168eb8e498a0ff73271d67f50b"
|
resolved "https://registry.yarnpkg.com/editions/-/editions-1.3.4.tgz#3662cb592347c3168eb8e498a0ff73271d67f50b"
|
||||||
@ -4527,6 +4551,10 @@ gh-got@^6.0.0:
|
|||||||
got "^7.0.0"
|
got "^7.0.0"
|
||||||
is-plain-obj "^1.1.0"
|
is-plain-obj "^1.1.0"
|
||||||
|
|
||||||
|
git-revision-webpack-plugin@^3.0.3:
|
||||||
|
version "3.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/git-revision-webpack-plugin/-/git-revision-webpack-plugin-3.0.3.tgz#f909949d7851d1039ed530518f73f5d46594e66f"
|
||||||
|
|
||||||
github-username@^4.0.0:
|
github-username@^4.0.0:
|
||||||
version "4.1.0"
|
version "4.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/github-username/-/github-username-4.1.0.tgz#cbe280041883206da4212ae9e4b5f169c30bf417"
|
resolved "https://registry.yarnpkg.com/github-username/-/github-username-4.1.0.tgz#cbe280041883206da4212ae9e4b5f169c30bf417"
|
||||||
@ -4887,7 +4915,7 @@ hdkey@^0.8.0:
|
|||||||
safe-buffer "^5.1.1"
|
safe-buffer "^5.1.1"
|
||||||
secp256k1 "^3.0.1"
|
secp256k1 "^3.0.1"
|
||||||
|
|
||||||
he@1.1.x:
|
he@1.1.x, he@^1.1.1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
|
resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
|
||||||
|
|
||||||
@ -5052,7 +5080,7 @@ http-proxy-middleware@~0.18.0:
|
|||||||
lodash "^4.17.5"
|
lodash "^4.17.5"
|
||||||
micromatch "^3.1.9"
|
micromatch "^3.1.9"
|
||||||
|
|
||||||
http-proxy@^1.16.2:
|
http-proxy@^1.16.2, http-proxy@^1.8.1:
|
||||||
version "1.17.0"
|
version "1.17.0"
|
||||||
resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.17.0.tgz#7ad38494658f84605e2f6db4436df410f4e5be9a"
|
resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.17.0.tgz#7ad38494658f84605e2f6db4436df410f4e5be9a"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -5060,6 +5088,19 @@ http-proxy@^1.16.2:
|
|||||||
follow-redirects "^1.0.0"
|
follow-redirects "^1.0.0"
|
||||||
requires-port "^1.0.0"
|
requires-port "^1.0.0"
|
||||||
|
|
||||||
|
http-server@^0.11.1:
|
||||||
|
version "0.11.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/http-server/-/http-server-0.11.1.tgz#2302a56a6ffef7f9abea0147d838a5e9b6b6a79b"
|
||||||
|
dependencies:
|
||||||
|
colors "1.0.3"
|
||||||
|
corser "~2.0.0"
|
||||||
|
ecstatic "^3.0.0"
|
||||||
|
http-proxy "^1.8.1"
|
||||||
|
opener "~1.4.0"
|
||||||
|
optimist "0.6.x"
|
||||||
|
portfinder "^1.0.13"
|
||||||
|
union "~0.4.3"
|
||||||
|
|
||||||
http-signature@~1.1.0:
|
http-signature@~1.1.0:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf"
|
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf"
|
||||||
@ -5559,7 +5600,7 @@ is-whitespace-character@^1.0.0:
|
|||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/is-whitespace-character/-/is-whitespace-character-1.0.2.tgz#ede53b4c6f6fb3874533751ec9280d01928d03ed"
|
resolved "https://registry.yarnpkg.com/is-whitespace-character/-/is-whitespace-character-1.0.2.tgz#ede53b4c6f6fb3874533751ec9280d01928d03ed"
|
||||||
|
|
||||||
is-windows@^1.0.1, is-windows@^1.0.2:
|
is-windows@^1.0.0, is-windows@^1.0.1, is-windows@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
|
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
|
||||||
|
|
||||||
@ -6738,6 +6779,10 @@ mime@1.4.1:
|
|||||||
version "1.4.1"
|
version "1.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6"
|
resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6"
|
||||||
|
|
||||||
|
mime@^1.6.0:
|
||||||
|
version "1.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
|
||||||
|
|
||||||
mime@^2.1.0:
|
mime@^2.1.0:
|
||||||
version "2.3.1"
|
version "2.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/mime/-/mime-2.3.1.tgz#b1621c54d63b97c47d3cfe7f7215f7d64517c369"
|
resolved "https://registry.yarnpkg.com/mime/-/mime-2.3.1.tgz#b1621c54d63b97c47d3cfe7f7215f7d64517c369"
|
||||||
@ -6789,7 +6834,7 @@ minimist@^0.1.0:
|
|||||||
version "0.1.0"
|
version "0.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.1.0.tgz#99df657a52574c21c9057497df742790b2b4c0de"
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.1.0.tgz#99df657a52574c21c9057497df742790b2b4c0de"
|
||||||
|
|
||||||
minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0:
|
minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
|
||||||
|
|
||||||
@ -7013,7 +7058,7 @@ node-libs-browser@^2.0.0:
|
|||||||
util "^0.10.3"
|
util "^0.10.3"
|
||||||
vm-browserify "0.0.4"
|
vm-browserify "0.0.4"
|
||||||
|
|
||||||
node-notifier@^5.2.1:
|
node-notifier@5.2.1, node-notifier@^5.2.1:
|
||||||
version "5.2.1"
|
version "5.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.2.1.tgz#fa313dd08f5517db0e2502e5758d664ac69f9dea"
|
resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.2.1.tgz#fa313dd08f5517db0e2502e5758d664ac69f9dea"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -7290,13 +7335,17 @@ opener@^1.4.3:
|
|||||||
version "1.5.1"
|
version "1.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.1.tgz#6d2f0e77f1a0af0032aca716c2c1fbb8e7e8abed"
|
resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.1.tgz#6d2f0e77f1a0af0032aca716c2c1fbb8e7e8abed"
|
||||||
|
|
||||||
|
opener@~1.4.0:
|
||||||
|
version "1.4.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/opener/-/opener-1.4.3.tgz#5c6da2c5d7e5831e8ffa3964950f8d6674ac90b8"
|
||||||
|
|
||||||
opn@^5.1.0:
|
opn@^5.1.0:
|
||||||
version "5.3.0"
|
version "5.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/opn/-/opn-5.3.0.tgz#64871565c863875f052cfdf53d3e3cb5adb53b1c"
|
resolved "https://registry.yarnpkg.com/opn/-/opn-5.3.0.tgz#64871565c863875f052cfdf53d3e3cb5adb53b1c"
|
||||||
dependencies:
|
dependencies:
|
||||||
is-wsl "^1.1.0"
|
is-wsl "^1.1.0"
|
||||||
|
|
||||||
optimist@^0.6.1:
|
optimist@0.6.x, optimist@^0.6.1:
|
||||||
version "0.6.1"
|
version "0.6.1"
|
||||||
resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686"
|
resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -7656,6 +7705,14 @@ pn@^1.1.0:
|
|||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb"
|
resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb"
|
||||||
|
|
||||||
|
portfinder@^1.0.13:
|
||||||
|
version "1.0.17"
|
||||||
|
resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.17.tgz#a8a1691143e46c4735edefcf4fbcccedad26456a"
|
||||||
|
dependencies:
|
||||||
|
async "^1.5.2"
|
||||||
|
debug "^2.2.0"
|
||||||
|
mkdirp "0.5.x"
|
||||||
|
|
||||||
portfinder@^1.0.9:
|
portfinder@^1.0.9:
|
||||||
version "1.0.13"
|
version "1.0.13"
|
||||||
resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.13.tgz#bb32ecd87c27104ae6ee44b5a3ccbf0ebb1aede9"
|
resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.13.tgz#bb32ecd87c27104ae6ee44b5a3ccbf0ebb1aede9"
|
||||||
@ -8071,13 +8128,6 @@ prop-types@15.x, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.8, pr
|
|||||||
loose-envify "^1.3.1"
|
loose-envify "^1.3.1"
|
||||||
object-assign "^4.1.1"
|
object-assign "^4.1.1"
|
||||||
|
|
||||||
prop-types@>=15.5.10, prop-types@^15.6.2:
|
|
||||||
version "15.6.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102"
|
|
||||||
dependencies:
|
|
||||||
loose-envify "^1.3.1"
|
|
||||||
object-assign "^4.1.1"
|
|
||||||
|
|
||||||
prop-types@^15.6.1:
|
prop-types@^15.6.1:
|
||||||
version "15.6.1"
|
version "15.6.1"
|
||||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.1.tgz#36644453564255ddda391191fb3a125cbdf654ca"
|
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.1.tgz#36644453564255ddda391191fb3a125cbdf654ca"
|
||||||
@ -8086,6 +8136,13 @@ prop-types@^15.6.1:
|
|||||||
loose-envify "^1.3.1"
|
loose-envify "^1.3.1"
|
||||||
object-assign "^4.1.1"
|
object-assign "^4.1.1"
|
||||||
|
|
||||||
|
prop-types@^15.6.2:
|
||||||
|
version "15.6.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102"
|
||||||
|
dependencies:
|
||||||
|
loose-envify "^1.3.1"
|
||||||
|
object-assign "^4.1.1"
|
||||||
|
|
||||||
proxy-addr@~2.0.3:
|
proxy-addr@~2.0.3:
|
||||||
version "2.0.3"
|
version "2.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.3.tgz#355f262505a621646b3130a728eb647e22055341"
|
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.3.tgz#355f262505a621646b3130a728eb647e22055341"
|
||||||
@ -8164,6 +8221,10 @@ qs@6.5.2, qs@~6.5.1, qs@~6.5.2:
|
|||||||
version "6.5.2"
|
version "6.5.2"
|
||||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
|
||||||
|
|
||||||
|
qs@~2.3.3:
|
||||||
|
version "2.3.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/qs/-/qs-2.3.3.tgz#e9e85adbe75da0bbe4c8e0476a086290f863b404"
|
||||||
|
|
||||||
qs@~6.4.0:
|
qs@~6.4.0:
|
||||||
version "6.4.0"
|
version "6.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233"
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233"
|
||||||
@ -8418,12 +8479,6 @@ react-select@2.0.0:
|
|||||||
react-input-autosize "^2.2.1"
|
react-input-autosize "^2.2.1"
|
||||||
react-transition-group "^2.2.1"
|
react-transition-group "^2.2.1"
|
||||||
|
|
||||||
react-sticky-el@^1.0.20:
|
|
||||||
version "1.0.20"
|
|
||||||
resolved "https://registry.yarnpkg.com/react-sticky-el/-/react-sticky-el-1.0.20.tgz#b3c5e7128218633f440dc67aec239d1cd078342d"
|
|
||||||
dependencies:
|
|
||||||
prop-types ">=15.5.10"
|
|
||||||
|
|
||||||
react-transition-group@^2.2.1:
|
react-transition-group@^2.2.1:
|
||||||
version "2.2.1"
|
version "2.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.2.1.tgz#e9fb677b79e6455fd391b03823afe84849df4a10"
|
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.2.1.tgz#e9fb677b79e6455fd391b03823afe84849df4a10"
|
||||||
@ -8435,6 +8490,15 @@ react-transition-group@^2.2.1:
|
|||||||
prop-types "^15.5.8"
|
prop-types "^15.5.8"
|
||||||
warning "^3.0.0"
|
warning "^3.0.0"
|
||||||
|
|
||||||
|
react-transition-group@^2.4.0:
|
||||||
|
version "2.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.4.0.tgz#1d9391fabfd82e016f26fabd1eec329dbd922b5a"
|
||||||
|
dependencies:
|
||||||
|
dom-helpers "^3.3.1"
|
||||||
|
loose-envify "^1.3.1"
|
||||||
|
prop-types "^15.6.2"
|
||||||
|
react-lifecycles-compat "^3.0.4"
|
||||||
|
|
||||||
"react@^15.4.2 || ^16.0.0":
|
"react@^15.4.2 || ^16.0.0":
|
||||||
version "16.2.0"
|
version "16.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/react/-/react-16.2.0.tgz#a31bd2dab89bff65d42134fa187f24d054c273ba"
|
resolved "https://registry.yarnpkg.com/react/-/react-16.2.0.tgz#a31bd2dab89bff65d42134fa187f24d054c273ba"
|
||||||
@ -10360,6 +10424,12 @@ union-value@^1.0.0:
|
|||||||
is-extendable "^0.1.1"
|
is-extendable "^0.1.1"
|
||||||
set-value "^0.4.3"
|
set-value "^0.4.3"
|
||||||
|
|
||||||
|
union@~0.4.3:
|
||||||
|
version "0.4.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/union/-/union-0.4.6.tgz#198fbdaeba254e788b0efcb630bc11f24a2959e0"
|
||||||
|
dependencies:
|
||||||
|
qs "~2.3.3"
|
||||||
|
|
||||||
uniq@^1.0.1:
|
uniq@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff"
|
resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff"
|
||||||
@ -10457,6 +10527,10 @@ urix@^0.1.0:
|
|||||||
version "0.1.0"
|
version "0.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
|
resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
|
||||||
|
|
||||||
|
url-join@^2.0.5:
|
||||||
|
version "2.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/url-join/-/url-join-2.0.5.tgz#5af22f18c052a000a48d7b82c5e9c2e2feeda728"
|
||||||
|
|
||||||
url-join@^4.0.0:
|
url-join@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.0.tgz#4d3340e807d3773bda9991f8305acdcc2a665d2a"
|
resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.0.tgz#4d3340e807d3773bda9991f8305acdcc2a665d2a"
|
||||||
@ -10896,6 +10970,14 @@ webpack-addons@^1.1.5:
|
|||||||
dependencies:
|
dependencies:
|
||||||
jscodeshift "^0.4.0"
|
jscodeshift "^0.4.0"
|
||||||
|
|
||||||
|
webpack-build-notifier@^0.1.29:
|
||||||
|
version "0.1.29"
|
||||||
|
resolved "https://registry.yarnpkg.com/webpack-build-notifier/-/webpack-build-notifier-0.1.29.tgz#d71f89bb94346c6b748e07aa3d117d2beb0a151f"
|
||||||
|
dependencies:
|
||||||
|
ansi-regex "^2.0.0"
|
||||||
|
node-notifier "5.2.1"
|
||||||
|
strip-ansi "^3.0.1"
|
||||||
|
|
||||||
webpack-bundle-analyzer@^2.13.1:
|
webpack-bundle-analyzer@^2.13.1:
|
||||||
version "2.13.1"
|
version "2.13.1"
|
||||||
resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-2.13.1.tgz#07d2176c6e86c3cdce4c23e56fae2a7b6b4ad526"
|
resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-2.13.1.tgz#07d2176c6e86c3cdce4c23e56fae2a7b6b4ad526"
|
||||||
|
Loading…
Reference in New Issue
Block a user