mirror of
https://github.com/trezor/trezor-wallet
synced 2025-02-07 05:32:42 +00:00
Merge branch 'release/1.1.0-beta' into beta
This commit is contained in:
commit
7f0d3b507c
31
.babelrc
31
.babelrc
@ -1,31 +0,0 @@
|
|||||||
{
|
|
||||||
"presets": [
|
|
||||||
["env", {
|
|
||||||
"useBuiltIns": true,
|
|
||||||
"loose": true
|
|
||||||
}],
|
|
||||||
"react"
|
|
||||||
],
|
|
||||||
"plugins": [
|
|
||||||
"react-hot-loader/babel",
|
|
||||||
"transform-class-properties",
|
|
||||||
"transform-object-rest-spread",
|
|
||||||
"transform-flow-strip-types",
|
|
||||||
["transform-runtime", {
|
|
||||||
"polyfill": false,
|
|
||||||
"regenerator": true
|
|
||||||
}],
|
|
||||||
["module-resolver", {
|
|
||||||
"root": ["./src"],
|
|
||||||
"alias": {
|
|
||||||
"public": ["./public"]
|
|
||||||
}
|
|
||||||
}],
|
|
||||||
"babel-plugin-styled-components"
|
|
||||||
],
|
|
||||||
"env": {
|
|
||||||
"test": {
|
|
||||||
"presets": ["jest"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
18
.eslintrc
18
.eslintrc
@ -6,16 +6,19 @@
|
|||||||
],
|
],
|
||||||
"globals": {
|
"globals": {
|
||||||
"LOCAL": true,
|
"LOCAL": true,
|
||||||
"COMMITHASH": true
|
"COMMITHASH": true,
|
||||||
|
"VERSION": true
|
||||||
},
|
},
|
||||||
"env": {
|
"env": {
|
||||||
"browser": true,
|
"browser": true,
|
||||||
"jest": true
|
"jest": true,
|
||||||
|
"cypress/globals": true
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"import/prefer-default-export": 0,
|
"import/prefer-default-export": 0,
|
||||||
"no-use-before-define": 0,
|
"no-use-before-define": 0,
|
||||||
"no-plusplus": 0,
|
"no-plusplus": 0,
|
||||||
|
"jest/no-disabled-tests": 0,
|
||||||
"class-methods-use-this": 0,
|
"class-methods-use-this": 0,
|
||||||
"react/require-default-props": 0,
|
"react/require-default-props": 0,
|
||||||
"react/forbid-prop-types": 0,
|
"react/forbid-prop-types": 0,
|
||||||
@ -32,13 +35,17 @@
|
|||||||
"new-cap": 0,
|
"new-cap": 0,
|
||||||
"max-len": 0,
|
"max-len": 0,
|
||||||
"eol-last": 0,
|
"eol-last": 0,
|
||||||
"spaced-comment": 0
|
"spaced-comment": 0,
|
||||||
|
"no-unused-expressions": 0,
|
||||||
|
"chai-friendly/no-unused-expressions": 2
|
||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"import",
|
"import",
|
||||||
"react",
|
"react",
|
||||||
"jest",
|
"jest",
|
||||||
"flowtype"
|
"flowtype",
|
||||||
|
"cypress",
|
||||||
|
"chai-friendly"
|
||||||
],
|
],
|
||||||
"settings": {
|
"settings": {
|
||||||
"import/resolver": {
|
"import/resolver": {
|
||||||
@ -52,8 +59,7 @@
|
|||||||
"ecmaVersion": 7,
|
"ecmaVersion": 7,
|
||||||
"sourceType": "module",
|
"sourceType": "module",
|
||||||
"ecmaFeatures": {
|
"ecmaFeatures": {
|
||||||
"jsx": true,
|
"jsx": true
|
||||||
"experimentalObjectRestSpread": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -10,6 +10,7 @@
|
|||||||
.*/_old/.*
|
.*/_old/.*
|
||||||
.*/scripts/solidity/.*
|
.*/scripts/solidity/.*
|
||||||
.*/build/.*
|
.*/build/.*
|
||||||
|
.*/cache/.*
|
||||||
|
|
||||||
[libs]
|
[libs]
|
||||||
./src/flowtype/npm/redux_v3.x.x.js
|
./src/flowtype/npm/redux_v3.x.x.js
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -20,3 +20,5 @@ logs
|
|||||||
_old
|
_old
|
||||||
|
|
||||||
coverage
|
coverage
|
||||||
|
test/**/__diff_output__
|
||||||
|
test/screenshots
|
@ -1,14 +1,19 @@
|
|||||||
image: node:9.3
|
image: node:10.15.1
|
||||||
|
|
||||||
|
variables:
|
||||||
|
CYPRESS_CACHE_FOLDER: "$CI_PROJECT_DIR/cache/Cypress"
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
key: ${CI_COMMIT_REF_SLUG}
|
key: ${CI_COMMIT_REF_SLUG}
|
||||||
paths:
|
paths:
|
||||||
- node_modules/
|
- node_modules
|
||||||
|
- ${CYPRESS_CACHE_FOLDER}
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- test
|
- test
|
||||||
- build
|
- build
|
||||||
- deploy
|
- deploy
|
||||||
|
- integration tests
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
stage: test
|
stage: test
|
||||||
@ -64,6 +69,22 @@ build stable:
|
|||||||
- build/stable
|
- build/stable
|
||||||
- scripts/s3sync.sh
|
- scripts/s3sync.sh
|
||||||
|
|
||||||
|
build emulator and bridge image:
|
||||||
|
variables:
|
||||||
|
CONTAINER_NAME: "$CI_REGISTRY/emulator-bridge-tests"
|
||||||
|
image: docker:latest
|
||||||
|
services:
|
||||||
|
- docker:dind
|
||||||
|
before_script:
|
||||||
|
- docker login $CI_REGISTRY -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD
|
||||||
|
stage: build
|
||||||
|
when: manual
|
||||||
|
script:
|
||||||
|
- docker pull $CONTAINER_NAME:latest || true
|
||||||
|
- docker build --cache-from $CONTAINER_NAME:latest --tag $CONTAINER_NAME:$CI_COMMIT_SHA --tag $CONTAINER_NAME:latest .
|
||||||
|
- docker push $CONTAINER_NAME:$CI_COMMIT_SHA
|
||||||
|
- docker push $CONTAINER_NAME:latest
|
||||||
|
|
||||||
deploy review:
|
deploy review:
|
||||||
stage: deploy
|
stage: deploy
|
||||||
variables:
|
variables:
|
||||||
@ -138,3 +159,24 @@ delete review:
|
|||||||
- branches
|
- branches
|
||||||
tags:
|
tags:
|
||||||
- deploy
|
- deploy
|
||||||
|
|
||||||
|
integration tests:
|
||||||
|
image: docker:latest
|
||||||
|
services:
|
||||||
|
- docker:dind
|
||||||
|
stage: integration tests
|
||||||
|
script:
|
||||||
|
- 'export SHARED_PATH="$(dirname ${CI_PROJECT_DIR})/shared"'
|
||||||
|
- rm -r ${SHARED_PATH} || true
|
||||||
|
- docker build -t wallet-emulator-bridge-tests .
|
||||||
|
- mkdir -p ${SHARED_PATH}/trezor-wallet/screenshots
|
||||||
|
- mkdir -p ${SHARED_PATH}/trezor-wallet/videos
|
||||||
|
- docker run --volume ${SHARED_PATH}/trezor-wallet/screenshots:/trezor-wallet/test/screenshots --volume ${SHARED_PATH}/trezor-wallet/videos:/trezor-wallet/test/videos --rm wallet-emulator-bridge-tests
|
||||||
|
- find ${SHARED_PATH}
|
||||||
|
- mkdir trezor-wallet
|
||||||
|
- cp -r ${SHARED_PATH}/ trezor-wallet/
|
||||||
|
artifacts:
|
||||||
|
when: always
|
||||||
|
expire_in: 1 week
|
||||||
|
paths:
|
||||||
|
- trezor-wallet/
|
24
CHANGELOG.md
24
CHANGELOG.md
@ -1,3 +1,27 @@
|
|||||||
|
## 1.1.0-beta
|
||||||
|
__added__
|
||||||
|
- Ripple support
|
||||||
|
- responsive sidebar
|
||||||
|
- QR code scanner in send form
|
||||||
|
- clear send form button
|
||||||
|
- backup notification modal
|
||||||
|
|
||||||
|
__updated__
|
||||||
|
- connect v7
|
||||||
|
- babel v7
|
||||||
|
- ethereum tokens list
|
||||||
|
- most of dependencies
|
||||||
|
|
||||||
|
__changed__
|
||||||
|
- icons for T1 and TT
|
||||||
|
- device header styles
|
||||||
|
- input styles
|
||||||
|
- sign and verify title
|
||||||
|
|
||||||
|
__fixed__
|
||||||
|
- beta disclaimer wrapper position
|
||||||
|
- sidebar scrollbar
|
||||||
|
|
||||||
## 1.0.3-beta
|
## 1.0.3-beta
|
||||||
__added__
|
__added__
|
||||||
- Ethereum: sign & verify tab
|
- Ethereum: sign & verify tab
|
||||||
|
53
Dockerfile
53
Dockerfile
@ -1,17 +1,50 @@
|
|||||||
FROM node:9.3
|
FROM python:latest
|
||||||
|
|
||||||
ARG BUILD_TYPE=stable
|
#
|
||||||
|
# setup
|
||||||
|
#
|
||||||
|
RUN apt-get update
|
||||||
|
RUN curl -sL https://deb.nodesource.com/setup_8.x | bash -
|
||||||
|
RUN apt-get install -y chromium libappindicator3-1 xdg-utils fonts-liberation nodejs wget dpkg git python python3 python3-pip xvfb libgtk2.0-0 libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2
|
||||||
|
RUN npm install -g yarn
|
||||||
|
|
||||||
WORKDIR /trezor-wallet-app
|
RUN ln -s /usr/bin/chromium /usr/local/bin/chromium-browser
|
||||||
|
|
||||||
COPY package.json /trezor-wallet-app
|
#
|
||||||
COPY yarn.lock /trezor-wallet-app
|
# build emulator
|
||||||
|
#
|
||||||
|
RUN mkdir /trezor-emulator
|
||||||
|
WORKDIR /trezor-emulator
|
||||||
|
|
||||||
RUN yarn install
|
RUN git clone https://github.com/trezor/trezor-core
|
||||||
|
WORKDIR /trezor-emulator/trezor-core
|
||||||
|
RUN git submodule update --init --recursive
|
||||||
|
|
||||||
COPY . /trezor-wallet-app
|
RUN apt-get install libusb-1.0-0
|
||||||
|
RUN pip3 install scons trezor
|
||||||
|
RUN make build_unix_noui
|
||||||
|
|
||||||
RUN yarn run build:${BUILD_TYPE}
|
#
|
||||||
|
# install bridge
|
||||||
|
#
|
||||||
|
RUN mkdir /trezor-bridge
|
||||||
|
WORKDIR /trezor-bridge
|
||||||
|
RUN wget https://wallet.trezor.io/data/bridge/2.0.25/trezor-bridge_2.0.25_amd64.deb
|
||||||
|
RUN dpkg -x /trezor-bridge/trezor-bridge_2.0.25_amd64.deb /trezor-bridge/extracted
|
||||||
|
|
||||||
EXPOSE 8080
|
#
|
||||||
CMD [ "yarn", "run", "prod-server" ]
|
# install trezor-wallet
|
||||||
|
#
|
||||||
|
RUN mkdir /trezor-wallet
|
||||||
|
WORKDIR /trezor-wallet
|
||||||
|
COPY package.json /trezor-wallet
|
||||||
|
COPY yarn.lock /trezor-wallet
|
||||||
|
RUN yarn
|
||||||
|
COPY . /trezor-wallet
|
||||||
|
RUN yarn run build:stable
|
||||||
|
|
||||||
|
#
|
||||||
|
# run
|
||||||
|
#
|
||||||
|
ENTRYPOINT ["/trezor-wallet/test/scripts/run-all.sh"]
|
||||||
|
EXPOSE 8080 21325
|
@ -1,13 +1,11 @@
|
|||||||
# Trezor Wallet
|
# Trezor Wallet
|
||||||
|
|
||||||
|
You can try this wallet live [HERE](https://beta-wallet.trezor.io/next/)
|
||||||
|
|
||||||
To install dependencies run `npm install` or `yarn`
|
To install dependencies run `npm install` or `yarn`
|
||||||
To start locally run `npm run dev` or `yarn run dev`
|
To start locally run `npm run dev` or `yarn run dev`
|
||||||
To build the project run `npm run build` or `yarn run build`
|
To build the project run `npm run build` or `yarn run build`
|
||||||
|
|
||||||
## Docker
|
|
||||||
- Build `./scripts/docker-build.sh`
|
|
||||||
- Run `./scripts/docker-run.sh`
|
|
||||||
|
|
||||||
## Project structure
|
## Project structure
|
||||||
The project is divided into two parts - data that are used when compiling the project and data that aren't.
|
The project is divided into two parts - data that are used when compiling the project and data that aren't.
|
||||||
|
|
||||||
|
51
babel.config.js
Normal file
51
babel.config.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
module.exports = (api) => {
|
||||||
|
// api.cache.forever();
|
||||||
|
|
||||||
|
const presets = [
|
||||||
|
[
|
||||||
|
'@babel/preset-env',
|
||||||
|
{
|
||||||
|
useBuiltIns: 'entry',
|
||||||
|
loose: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'@babel/preset-react',
|
||||||
|
'@babel/preset-flow',
|
||||||
|
];
|
||||||
|
|
||||||
|
const plugins = [
|
||||||
|
'react-hot-loader/babel',
|
||||||
|
'@babel/plugin-transform-flow-strip-types',
|
||||||
|
'@babel/plugin-proposal-class-properties',
|
||||||
|
'@babel/plugin-proposal-object-rest-spread',
|
||||||
|
[
|
||||||
|
'@babel/plugin-transform-runtime',
|
||||||
|
{
|
||||||
|
regenerator: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'module-resolver',
|
||||||
|
{
|
||||||
|
root: [
|
||||||
|
'./src',
|
||||||
|
],
|
||||||
|
alias: {
|
||||||
|
public: [
|
||||||
|
'./public',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'babel-plugin-styled-components',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (api.env('test')) {
|
||||||
|
presets.push('jest');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
presets,
|
||||||
|
plugins,
|
||||||
|
};
|
||||||
|
};
|
11
cypress.json
Normal file
11
cypress.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"integrationFolder": "test/integration",
|
||||||
|
"fixturesFolder": "test/fixtures",
|
||||||
|
"pluginsFile": "test/plugins/index.js",
|
||||||
|
"supportFile": "test/support/index.js",
|
||||||
|
"defaultCommandTimeout": 10000,
|
||||||
|
"screenshotsFolder": "test/screenshots",
|
||||||
|
"video": false,
|
||||||
|
"trashAssetsBeforeRuns": true,
|
||||||
|
"chromeWebSecurity": false
|
||||||
|
}
|
@ -17,4 +17,7 @@ module.exports = {
|
|||||||
setupFiles: [
|
setupFiles: [
|
||||||
'./support/setupJest.js',
|
'./support/setupJest.js',
|
||||||
],
|
],
|
||||||
|
transform: {
|
||||||
|
'^.+\\.jsx?$': 'babel-jest',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
141
package.json
141
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "trezor-wallet",
|
"name": "trezor-wallet",
|
||||||
"version": "1.0.0",
|
"version": "1.0.3-beta",
|
||||||
"author": "TREZOR <info@trezor.io>",
|
"author": "TREZOR <info@trezor.io>",
|
||||||
"description": "",
|
"description": "",
|
||||||
"bin": {
|
"bin": {
|
||||||
@ -21,100 +21,111 @@
|
|||||||
"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",
|
||||||
|
"test-integration:dev": "npx cypress open -c baseUrl=http://localhost:8081/#/",
|
||||||
|
"test-integration:test": "npx cypress run",
|
||||||
|
"test-integration:gitlab": "npx cypress run -c baseUrl=https://localhost:8080/#/ --browser chromium",
|
||||||
"server:beta": "node ./server/index.js --buildType=beta",
|
"server:beta": "node ./server/index.js --buildType=beta",
|
||||||
"server:stable": "node ./server/index.js --buildType=stable"
|
"server:stable": "node ./server/index.js --buildType=stable"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"babel": "^6.23.0",
|
"@babel/polyfill": "^7.2.5",
|
||||||
"babel-core": "^6.26.3",
|
"bignumber.js": "8.0.2",
|
||||||
"bignumber.js": "2.4.0",
|
|
||||||
"color-hash": "^1.0.3",
|
"color-hash": "^1.0.3",
|
||||||
"commander": "^2.19.0",
|
"commander": "^2.19.0",
|
||||||
"connected-react-router": "^6.0.0",
|
"connected-react-router": "6.0.0",
|
||||||
"copy-webpack-plugin": "^4.5.2",
|
"copy-webpack-plugin": "^4.6.0",
|
||||||
"cross-env": "^5.2.0",
|
"cross-env": "^5.2.0",
|
||||||
"date-fns": "^1.29.0",
|
"date-fns": "^1.30.1",
|
||||||
"ethereumjs-tx": "^1.3.3",
|
"ethereumjs-tx": "^1.3.7",
|
||||||
"ethereumjs-units": "^0.2.0",
|
"ethereumjs-units": "^0.2.0",
|
||||||
"ethereumjs-util": "^5.1.4",
|
"ethereumjs-util": "^6.0.0",
|
||||||
"express": "^4.16.3",
|
"express": "^4.16.4",
|
||||||
"flow-webpack-plugin": "^1.2.0",
|
|
||||||
"git-revision-webpack-plugin": "^3.0.3",
|
"git-revision-webpack-plugin": "^3.0.3",
|
||||||
"hdkey": "^0.8.0",
|
"hdkey": "^1.1.0",
|
||||||
"history": "^4.7.2",
|
"history": "^4.7.2",
|
||||||
"html-webpack-plugin": "^3.2.0",
|
"html-webpack-plugin": "^3.2.0",
|
||||||
"jest-fetch-mock": "^1.6.5",
|
"jest-fetch-mock": "^2.1.0",
|
||||||
"morgan": "^1.9.1",
|
"morgan": "^1.9.1",
|
||||||
"npm-run-all": "^4.1.3",
|
"npm-run-all": "^4.1.5",
|
||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.6.2",
|
||||||
"raf": "^3.4.0",
|
"raf": "^3.4.1",
|
||||||
"raven-js": "^3.22.3",
|
"raven-js": "^3.27.0",
|
||||||
"rc-tooltip": "^3.7.0",
|
"rc-tooltip": "^3.7.3",
|
||||||
"react": "^16.6.3",
|
"react": "^16.8.1",
|
||||||
"react-dom": "^16.6.3",
|
"react-dom": "^16.8.1",
|
||||||
"react-hot-loader": "^4.6.2",
|
"react-hot-loader": "^4.6.5",
|
||||||
"react-json-view": "^1.19.1",
|
"react-json-view": "^1.19.1",
|
||||||
|
"react-qr-reader": "^2.1.2",
|
||||||
"react-qr-svg": "^2.1.0",
|
"react-qr-svg": "^2.1.0",
|
||||||
"react-redux": "^6.0.0",
|
"react-redux": "^6.0.0",
|
||||||
"react-router": "^4.3.1",
|
"react-router": "^4.3.1",
|
||||||
"react-router-dom": "^4.2.2",
|
"react-router-dom": "^4.3.1",
|
||||||
"react-scale-text": "^1.2.2",
|
"react-scale-text": "^1.2.2",
|
||||||
"react-select": "^2.2.0",
|
"react-select": "^2.3.0",
|
||||||
"react-textarea-autosize": "^7.0.4",
|
"react-textarea-autosize": "^7.1.0",
|
||||||
"react-transition-group": "^2.4.0",
|
"react-transition-group": "^2.5.3",
|
||||||
"redbox-react": "^1.6.0",
|
"redbox-react": "^1.6.0",
|
||||||
"redux": "4.0.0",
|
"redux": "4.0.1",
|
||||||
"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.3.0",
|
||||||
"rimraf": "^2.6.2",
|
"request": "^2.88.0",
|
||||||
"styled-components": "^4.1.2",
|
"rimraf": "^2.6.3",
|
||||||
"styled-normalize": "^8.0.4",
|
"styled-components": "^4.1.3",
|
||||||
"trezor-connect": "7.0.0-beta.1",
|
"styled-normalize": "^8.0.6",
|
||||||
|
"trezor-bridge-communicator": "1.0.2",
|
||||||
|
"trezor-connect": "7.0.0-beta.3",
|
||||||
"wallet-address-validator": "^0.2.4",
|
"wallet-address-validator": "^0.2.4",
|
||||||
"web3": "1.0.0-beta.35",
|
"web3": "1.0.0-beta.38",
|
||||||
"webpack": "^4.16.3",
|
"webpack": "^4.29.3",
|
||||||
"webpack-build-notifier": "^0.1.29",
|
"webpack-build-notifier": "^0.1.30",
|
||||||
"webpack-bundle-analyzer": "^2.13.1",
|
"webpack-bundle-analyzer": "^3.0.3",
|
||||||
"whatwg-fetch": "^2.0.4",
|
"whatwg-fetch": "^3.0.0",
|
||||||
"yarn-run-all": "^3.1.1"
|
"yarn-run-all": "^3.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-cli": "^6.24.1",
|
"@babel/cli": "^7.2.3",
|
||||||
"babel-eslint": "^8.2.6",
|
"@babel/core": "^7.2.2",
|
||||||
"babel-loader": "^7.1.5",
|
"@babel/plugin-proposal-class-properties": "^7.3.0",
|
||||||
"babel-plugin-module-resolver": "^3.1.1",
|
"@babel/plugin-proposal-object-rest-spread": "^7.3.2",
|
||||||
"babel-plugin-styled-components": "^1.5.1",
|
"@babel/plugin-transform-flow-strip-types": "^7.2.3",
|
||||||
"babel-plugin-transform-class-properties": "^6.24.1",
|
"@babel/plugin-transform-runtime": "^7.2.0",
|
||||||
"babel-plugin-transform-flow-strip-types": "^6.22.0",
|
"@babel/preset-env": "^7.3.1",
|
||||||
"babel-plugin-transform-object-rest-spread": "^6.23.0",
|
"@babel/preset-flow": "^7.0.0",
|
||||||
"babel-plugin-transform-runtime": "^6.23.0",
|
"@babel/preset-react": "^7.0.0",
|
||||||
"babel-preset-env": "^1.6.0",
|
"@babel/register": "^7.0.0",
|
||||||
"babel-preset-jest": "^23.2.0",
|
"babel-eslint": "^10.0.1",
|
||||||
"babel-preset-react": "^6.24.1",
|
"babel-jest": "^24.1.0",
|
||||||
"eslint": "^4",
|
"babel-loader": "^8.0.5",
|
||||||
"eslint-config-airbnb": "^17.0.0",
|
"babel-plugin-module-resolver": "^3.1.3",
|
||||||
"eslint-import-resolver-babel-module": "^4.0.0",
|
"babel-plugin-styled-components": "^1.10.0",
|
||||||
"eslint-loader": "^2.1.0",
|
"cypress": "^3.1.5",
|
||||||
"eslint-plugin-flowtype": "^2.50.0",
|
"cypress-image-snapshot": "^3.0.0",
|
||||||
"eslint-plugin-import": "^2.14.0",
|
"eslint": "^5.13.0",
|
||||||
"eslint-plugin-jest": "^21.18.0",
|
"eslint-config-airbnb": "^17.1.0",
|
||||||
"eslint-plugin-jsx-a11y": "^6.1.1",
|
"eslint-import-resolver-babel-module": "^5.0.1",
|
||||||
"eslint-plugin-react": "^7.10.0",
|
"eslint-loader": "^2.1.2",
|
||||||
"file-loader": "1.1.11",
|
"eslint-plugin-chai-friendly": "^0.4.1",
|
||||||
|
"eslint-plugin-cypress": "^2.2.0",
|
||||||
|
"eslint-plugin-flowtype": "^3.2.1",
|
||||||
|
"eslint-plugin-import": "^2.16.0",
|
||||||
|
"eslint-plugin-jest": "^22.2.2",
|
||||||
|
"eslint-plugin-jsx-a11y": "^6.2.1",
|
||||||
|
"eslint-plugin-react": "^7.12.4",
|
||||||
|
"file-loader": "3.0.1",
|
||||||
"flow-bin": "0.75.0",
|
"flow-bin": "0.75.0",
|
||||||
"jest": "^23.4.2",
|
"jest": "^24.1.0",
|
||||||
"stylelint": "^8.0.0",
|
"stylelint": "^9.10.1",
|
||||||
"stylelint-config-standard": "^18.2.0",
|
"stylelint-config-standard": "^18.2.0",
|
||||||
"stylelint-config-styled-components": "^0.1.1",
|
"stylelint-config-styled-components": "^0.1.1",
|
||||||
"stylelint-custom-processor-loader": "^0.5.0",
|
"stylelint-custom-processor-loader": "^0.6.0",
|
||||||
"stylelint-processor-styled-components": "^1.3.2",
|
"stylelint-processor-styled-components": "^1.5.2",
|
||||||
"stylelint-webpack-plugin": "^0.10.5",
|
"stylelint-webpack-plugin": "^0.10.5",
|
||||||
"webpack-cli": "^2.1.3",
|
"webpack-cli": "^3.2.3",
|
||||||
"webpack-dev-server": "^3.1.4",
|
"webpack-dev-server": "^3.1.14",
|
||||||
"yargs": "11.0.0"
|
"yargs": "12.0.5"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"fsevents": "*"
|
"fsevents": "1.2.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -19,6 +19,9 @@ export type BlockchainAction = {
|
|||||||
type: typeof BLOCKCHAIN.UPDATE_FEE,
|
type: typeof BLOCKCHAIN.UPDATE_FEE,
|
||||||
shortcut: string,
|
shortcut: string,
|
||||||
feeLevels: Array<BlockchainFeeLevel>,
|
feeLevels: Array<BlockchainFeeLevel>,
|
||||||
|
} | {
|
||||||
|
type: typeof BLOCKCHAIN.START_SUBSCRIBE,
|
||||||
|
shortcut: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Conditionally subscribe to blockchain backend
|
// Conditionally subscribe to blockchain backend
|
||||||
@ -52,6 +55,11 @@ export const subscribe = (networkName: string): PromiseAction<void> => async (di
|
|||||||
const network = config.networks.find(c => c.shortcut === networkName);
|
const network = config.networks.find(c => c.shortcut === networkName);
|
||||||
if (!network) return;
|
if (!network) return;
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: BLOCKCHAIN.START_SUBSCRIBE,
|
||||||
|
shortcut: network.shortcut,
|
||||||
|
});
|
||||||
|
|
||||||
switch (network.type) {
|
switch (network.type) {
|
||||||
case 'ethereum':
|
case 'ethereum':
|
||||||
await dispatch(EthereumBlockchainActions.subscribe(networkName));
|
await dispatch(EthereumBlockchainActions.subscribe(networkName));
|
||||||
|
@ -205,7 +205,7 @@ const discoverAccount = (device: TrezorDevice, discoveryProcess: Discovery): Asy
|
|||||||
}
|
}
|
||||||
|
|
||||||
// handle outdated firmware error
|
// handle outdated firmware error
|
||||||
if (error.message === UI.FIRMWARE) {
|
if (error.message === UI.FIRMWARE_OLD) {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: DISCOVERY.FIRMWARE_OUTDATED,
|
type: DISCOVERY.FIRMWARE_OUTDATED,
|
||||||
device,
|
device,
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable import/no-named-as-default-member */
|
||||||
/* @flow */
|
/* @flow */
|
||||||
|
|
||||||
import TrezorConnect, { UI } from 'trezor-connect';
|
import TrezorConnect, { UI } from 'trezor-connect';
|
||||||
@ -9,6 +10,10 @@ import type {
|
|||||||
ThunkAction, AsyncAction, Action, GetState, Dispatch, TrezorDevice,
|
ThunkAction, AsyncAction, Action, GetState, Dispatch, TrezorDevice,
|
||||||
} from 'flowtype';
|
} from 'flowtype';
|
||||||
import type { State } from 'reducers/ModalReducer';
|
import type { State } from 'reducers/ModalReducer';
|
||||||
|
import type { parsedURI } from 'utils/cryptoUriParser';
|
||||||
|
|
||||||
|
import sendEthereumFormActions from './ethereum/SendFormActions';
|
||||||
|
import sendRippleFormActions from './ripple/SendFormActions';
|
||||||
|
|
||||||
export type ModalAction = {
|
export type ModalAction = {
|
||||||
type: typeof MODAL.CLOSE
|
type: typeof MODAL.CLOSE
|
||||||
@ -16,8 +21,11 @@ export type ModalAction = {
|
|||||||
type: typeof MODAL.OPEN_EXTERNAL_WALLET,
|
type: typeof MODAL.OPEN_EXTERNAL_WALLET,
|
||||||
id: string,
|
id: string,
|
||||||
url: string,
|
url: string,
|
||||||
|
} | {
|
||||||
|
type: typeof MODAL.OPEN_SCAN_QR,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const onPinSubmit = (value: string): Action => {
|
export const onPinSubmit = (value: string): Action => {
|
||||||
TrezorConnect.uiResponse({ type: UI.RECEIVE_PIN, payload: value });
|
TrezorConnect.uiResponse({ type: UI.RECEIVE_PIN, payload: value });
|
||||||
return {
|
return {
|
||||||
@ -51,6 +59,17 @@ export const onPassphraseSubmit = (passphrase: string): AsyncAction => async (di
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const onReceiveConfirmation = (confirmation: any): AsyncAction => async (dispatch: Dispatch): Promise<void> => {
|
||||||
|
await TrezorConnect.uiResponse({
|
||||||
|
type: UI.RECEIVE_CONFIRMATION,
|
||||||
|
payload: confirmation,
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: MODAL.CLOSE,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const onRememberDevice = (device: TrezorDevice): Action => ({
|
export const onRememberDevice = (device: TrezorDevice): Action => ({
|
||||||
type: CONNECT.REMEMBER,
|
type: CONNECT.REMEMBER,
|
||||||
device,
|
device,
|
||||||
@ -139,9 +158,33 @@ export const gotoExternalWallet = (id: string, url: string): ThunkAction => (dis
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const openQrModal = (): ThunkAction => (dispatch: Dispatch): void => {
|
||||||
|
dispatch({
|
||||||
|
type: MODAL.OPEN_SCAN_QR,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const onQrScan = (parsedUri: parsedURI, networkType: string): ThunkAction => (dispatch: Dispatch): void => {
|
||||||
|
const { address = '', amount } = parsedUri;
|
||||||
|
switch (networkType) {
|
||||||
|
case 'ethereum':
|
||||||
|
dispatch(sendEthereumFormActions.onAddressChange(address));
|
||||||
|
if (amount) dispatch(sendEthereumFormActions.onAmountChange(amount));
|
||||||
|
break;
|
||||||
|
case 'ripple':
|
||||||
|
dispatch(sendRippleFormActions.onAddressChange(address));
|
||||||
|
if (amount) dispatch(sendRippleFormActions.onAmountChange(amount));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
onPinSubmit,
|
onPinSubmit,
|
||||||
onPassphraseSubmit,
|
onPassphraseSubmit,
|
||||||
|
onReceiveConfirmation,
|
||||||
onRememberDevice,
|
onRememberDevice,
|
||||||
onForgetDevice,
|
onForgetDevice,
|
||||||
onForgetSingleDevice,
|
onForgetSingleDevice,
|
||||||
@ -149,4 +192,6 @@ export default {
|
|||||||
onDuplicateDevice,
|
onDuplicateDevice,
|
||||||
onWalletTypeRequest,
|
onWalletTypeRequest,
|
||||||
gotoExternalWallet,
|
gotoExternalWallet,
|
||||||
|
openQrModal,
|
||||||
|
onQrScan,
|
||||||
};
|
};
|
@ -94,6 +94,10 @@ export const showAddress = (path: Array<number>): AsyncAction => async (dispatch
|
|||||||
type: RECEIVE.HIDE_ADDRESS,
|
type: RECEIVE.HIDE_ADDRESS,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// special case: device no-backup permissions not granted
|
||||||
|
// $FlowIssue: remove this after trezor-connect@7.0.0 release
|
||||||
|
if (response.payload.code === 403) return;
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: NOTIFICATION.ADD,
|
type: NOTIFICATION.ADD,
|
||||||
payload: {
|
payload: {
|
||||||
|
@ -347,6 +347,17 @@ export const gotoFirmwareUpdate = (): ThunkAction => (dispatch: Dispatch, getSta
|
|||||||
dispatch(goto(`/device/${devUrl}/firmware-update`));
|
dispatch(goto(`/device/${devUrl}/firmware-update`));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Go to NoBackup page
|
||||||
|
*/
|
||||||
|
export const gotoBackup = (): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
||||||
|
const { selectedDevice } = getState().wallet;
|
||||||
|
if (!selectedDevice || !selectedDevice.features) return;
|
||||||
|
const devUrl: string = `${selectedDevice.features.device_id}${selectedDevice.instance ? `:${selectedDevice.instance}` : ''}`;
|
||||||
|
dispatch(goto(`/device/${devUrl}/backup`));
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Try to redirect to initial url
|
* Try to redirect to initial url
|
||||||
*/
|
*/
|
||||||
|
@ -8,6 +8,7 @@ import * as TOKEN from 'actions/constants/token';
|
|||||||
import * as PENDING from 'actions/constants/pendingTx';
|
import * as PENDING from 'actions/constants/pendingTx';
|
||||||
|
|
||||||
import * as reducerUtils from 'reducers/utils';
|
import * as reducerUtils from 'reducers/utils';
|
||||||
|
import { getVersion } from 'utils/device';
|
||||||
import { initialState } from 'reducers/SelectedAccountReducer';
|
import { initialState } from 'reducers/SelectedAccountReducer';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@ -51,10 +52,11 @@ const getExceptionPage = (state: State, selectedAccount: SelectedAccountState):
|
|||||||
shortcut: 'not-used',
|
shortcut: 'not-used',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (discovery.fwNotSupported) {
|
if (discovery.fwNotSupported) {
|
||||||
return {
|
return {
|
||||||
type: 'fwNotSupported',
|
type: 'fwNotSupported',
|
||||||
title: `${network.name} is not supported with Trezor ${device.features.model}`,
|
title: `${network.name} is not supported with Trezor ${getVersion(device)}`,
|
||||||
message: 'Find more information on Trezor Wiki.',
|
message: 'Find more information on Trezor Wiki.',
|
||||||
shortcut: network.shortcut,
|
shortcut: network.shortcut,
|
||||||
};
|
};
|
||||||
|
@ -18,11 +18,11 @@ import * as EthereumSendFormActions from './ethereum/SendFormActions';
|
|||||||
import * as RippleSendFormActions from './ripple/SendFormActions';
|
import * as RippleSendFormActions from './ripple/SendFormActions';
|
||||||
|
|
||||||
export type SendFormAction = {
|
export type SendFormAction = {
|
||||||
type: typeof SEND.INIT | typeof SEND.VALIDATION | typeof SEND.CHANGE,
|
type: typeof SEND.INIT | typeof SEND.VALIDATION | typeof SEND.CHANGE | typeof SEND.CLEAR,
|
||||||
networkType: 'ethereum',
|
networkType: 'ethereum',
|
||||||
state: EthereumState,
|
state: EthereumState,
|
||||||
} | {
|
} | {
|
||||||
type: typeof SEND.INIT | typeof SEND.VALIDATION | typeof SEND.CHANGE,
|
type: typeof SEND.INIT | typeof SEND.VALIDATION | typeof SEND.CHANGE | typeof SEND.CLEAR,
|
||||||
networkType: 'ripple',
|
networkType: 'ripple',
|
||||||
state: RippleState,
|
state: RippleState,
|
||||||
} | {
|
} | {
|
||||||
|
@ -3,6 +3,7 @@ import TrezorConnect, {
|
|||||||
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 { CONTEXT_NONE } from 'actions/constants/modal';
|
import { CONTEXT_NONE } from 'actions/constants/modal';
|
||||||
|
import urlConstants from 'constants/urls';
|
||||||
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 { getDuplicateInstanceNumber } from 'reducers/utils';
|
import { getDuplicateInstanceNumber } from 'reducers/utils';
|
||||||
@ -120,7 +121,7 @@ export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetS
|
|||||||
|
|
||||||
if (buildUtils.isDev()) {
|
if (buildUtils.isDev()) {
|
||||||
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://sisyfos.trezor.io/connect/'; // eslint-disable-line no-underscore-dangle
|
||||||
// window.__TREZOR_CONNECT_SRC = typeof LOCAL === 'string' ? LOCAL : 'https://connect.trezor.io/5/'; // eslint-disable-line no-underscore-dangle
|
// window.__TREZOR_CONNECT_SRC = typeof LOCAL === 'string' ? LOCAL : 'https://localhost:8088/'; // eslint-disable-line no-underscore-dangle
|
||||||
window.TrezorConnect = TrezorConnect;
|
window.TrezorConnect = TrezorConnect;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,6 +132,10 @@ export const init = (): AsyncAction => async (dispatch: Dispatch, getState: GetS
|
|||||||
popup: false,
|
popup: false,
|
||||||
webusb: true,
|
webusb: true,
|
||||||
pendingTransportEvent: (getState().devices.length < 1),
|
pendingTransportEvent: (getState().devices.length < 1),
|
||||||
|
manifest: {
|
||||||
|
email: 'info@trezor.io',
|
||||||
|
appUrl: urlConstants.NEXT_WALLET,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
dispatch({
|
dispatch({
|
||||||
|
@ -40,6 +40,8 @@ export type WalletAction = {
|
|||||||
devices: Array<TrezorDevice>
|
devices: Array<TrezorDevice>
|
||||||
} | {
|
} | {
|
||||||
type: typeof WALLET.SHOW_BETA_DISCLAIMER | typeof WALLET.HIDE_BETA_DISCLAIMER | typeof WALLET.SET_FIRST_LOCATION_CHANGE,
|
type: typeof WALLET.SHOW_BETA_DISCLAIMER | typeof WALLET.HIDE_BETA_DISCLAIMER | typeof WALLET.SET_FIRST_LOCATION_CHANGE,
|
||||||
|
} | {
|
||||||
|
type: typeof WALLET.TOGGLE_SIDEBAR,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const init = (): ThunkAction => (dispatch: Dispatch): void => {
|
export const init = (): ThunkAction => (dispatch: Dispatch): void => {
|
||||||
@ -62,6 +64,10 @@ export const toggleDeviceDropdown = (opened: boolean): WalletAction => ({
|
|||||||
opened,
|
opened,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const toggleSidebar = (): WalletAction => ({
|
||||||
|
type: WALLET.TOGGLE_SIDEBAR,
|
||||||
|
});
|
||||||
|
|
||||||
// This method will be called after each DEVICE.CONNECT action
|
// This method will be called after each DEVICE.CONNECT action
|
||||||
// if connected device has different "passphrase_protection" settings than saved instances
|
// if connected device has different "passphrase_protection" settings than saved instances
|
||||||
// all saved instances will be removed immediately inside DevicesReducer
|
// all saved instances will be removed immediately inside DevicesReducer
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
|
|
||||||
|
export const START_SUBSCRIBE: 'blockchain__start_subscribe' = 'blockchain__start_subscribe';
|
||||||
export const READY: 'blockchain__ready' = 'blockchain__ready';
|
export const READY: 'blockchain__ready' = 'blockchain__ready';
|
||||||
export const UPDATE_FEE: 'blockchain__update_fee' = 'blockchain__update_fee';
|
export const UPDATE_FEE: 'blockchain__update_fee' = 'blockchain__update_fee';
|
@ -5,3 +5,6 @@ export const OPEN_EXTERNAL_WALLET: 'modal__external_wallet' = 'modal__external_w
|
|||||||
export const CONTEXT_NONE: 'modal_ctx_none' = 'modal_ctx_none';
|
export const CONTEXT_NONE: 'modal_ctx_none' = 'modal_ctx_none';
|
||||||
export const CONTEXT_DEVICE: 'modal_ctx_device' = 'modal_ctx_device';
|
export const CONTEXT_DEVICE: 'modal_ctx_device' = 'modal_ctx_device';
|
||||||
export const CONTEXT_EXTERNAL_WALLET: 'modal_ctx_external-wallet' = 'modal_ctx_external-wallet';
|
export const CONTEXT_EXTERNAL_WALLET: 'modal_ctx_external-wallet' = 'modal_ctx_external-wallet';
|
||||||
|
export const OPEN_SCAN_QR: 'modal__open_scan_qr' = 'modal__open_scan_qr';
|
||||||
|
export const CONTEXT_SCAN_QR: 'modal__ctx_scan_qr' = 'modal__ctx_scan_qr';
|
||||||
|
export const CONTEXT_CONFIRMATION: 'modal__ctx_confirmation' = 'modal__ctx_confirmation';
|
||||||
|
@ -7,3 +7,4 @@ export const TX_SENDING: 'send__tx_sending' = 'send__tx_sending';
|
|||||||
export const TX_COMPLETE: 'send__tx_complete' = 'send__tx_complete';
|
export const TX_COMPLETE: 'send__tx_complete' = 'send__tx_complete';
|
||||||
export const TX_ERROR: 'send__tx_error' = 'send__tx_error';
|
export const TX_ERROR: 'send__tx_error' = 'send__tx_error';
|
||||||
export const TOGGLE_ADVANCED: 'send__toggle_advanced' = 'send__toggle_advanced';
|
export const TOGGLE_ADVANCED: 'send__toggle_advanced' = 'send__toggle_advanced';
|
||||||
|
export const CLEAR: 'send__clear' = 'send__clear';
|
@ -11,3 +11,5 @@ export const SHOW_BETA_DISCLAIMER: 'wallet__show_beta_disclaimer' = 'wallet__sho
|
|||||||
export const HIDE_BETA_DISCLAIMER: 'wallet__hide_beta_disclaimer' = 'wallet__hide_beta_disclaimer';
|
export const HIDE_BETA_DISCLAIMER: 'wallet__hide_beta_disclaimer' = 'wallet__hide_beta_disclaimer';
|
||||||
|
|
||||||
export const CLEAR_UNAVAILABLE_DEVICE_DATA: 'wallet__clear_unavailable_device_data' = 'wallet__clear_unavailable_device_data';
|
export const CLEAR_UNAVAILABLE_DEVICE_DATA: 'wallet__clear_unavailable_device_data' = 'wallet__clear_unavailable_device_data';
|
||||||
|
|
||||||
|
export const TOGGLE_SIDEBAR: 'wallet__toggle_sidebar' = 'wallet__toggle_sidebar';
|
@ -152,6 +152,41 @@ export const toggleAdvanced = (): Action => ({
|
|||||||
networkType: 'ethereum',
|
networkType: 'ethereum',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Called from UI from "clear" button
|
||||||
|
*/
|
||||||
|
export const onClear = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||||
|
const { network } = getState().selectedAccount;
|
||||||
|
const { advanced } = getState().sendFormEthereum;
|
||||||
|
|
||||||
|
if (!network) return;
|
||||||
|
|
||||||
|
// clear transaction draft from session storage
|
||||||
|
dispatch(SessionStorageActions.clear());
|
||||||
|
|
||||||
|
const gasPrice: BigNumber = await dispatch(BlockchainActions.getGasPrice(network.shortcut, network.defaultGasPrice));
|
||||||
|
const gasLimit = network.defaultGasLimit.toString();
|
||||||
|
const feeLevels = ValidationActions.getFeeLevels(network.symbol, gasPrice, gasLimit);
|
||||||
|
const selectedFeeLevel = ValidationActions.getSelectedFeeLevel(feeLevels, initialState.selectedFeeLevel);
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: SEND.CLEAR,
|
||||||
|
networkType: 'ethereum',
|
||||||
|
state: {
|
||||||
|
...initialState,
|
||||||
|
networkName: network.shortcut,
|
||||||
|
networkSymbol: network.symbol,
|
||||||
|
currency: network.symbol,
|
||||||
|
feeLevels,
|
||||||
|
selectedFeeLevel,
|
||||||
|
recommendedGasPrice: gasPrice.toString(),
|
||||||
|
gasLimit,
|
||||||
|
gasPrice: gasPrice.toString(),
|
||||||
|
advanced,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Called from UI on "address" field change
|
* Called from UI on "address" field change
|
||||||
*/
|
*/
|
||||||
@ -613,4 +648,5 @@ export default {
|
|||||||
onNonceChange,
|
onNonceChange,
|
||||||
onDataChange,
|
onDataChange,
|
||||||
onSend,
|
onSend,
|
||||||
|
onClear,
|
||||||
};
|
};
|
@ -115,7 +115,7 @@ export const recalculateTotalAmount = ($state: State): PayloadAction<State> => (
|
|||||||
if (isToken) {
|
if (isToken) {
|
||||||
const token = findToken(tokens, account.descriptor, state.currency, account.deviceState);
|
const token = findToken(tokens, account.descriptor, state.currency, account.deviceState);
|
||||||
if (token) {
|
if (token) {
|
||||||
state.amount = new BigNumber(token.balance).minus(pendingAmount).toString(10);
|
state.amount = new BigNumber(token.balance).minus(pendingAmount).toFixed();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const b = new BigNumber(account.balance).minus(pendingAmount);
|
const b = new BigNumber(account.balance).minus(pendingAmount);
|
||||||
@ -226,16 +226,16 @@ export const amountValidation = ($state: State): PayloadAction<State> => (dispat
|
|||||||
|
|
||||||
if (!state.amount.match(decimalRegExp)) {
|
if (!state.amount.match(decimalRegExp)) {
|
||||||
state.errors.amount = `Maximum ${token.decimals} decimals allowed`;
|
state.errors.amount = `Maximum ${token.decimals} decimals allowed`;
|
||||||
} else if (new BigNumber(state.total).greaterThan(account.balance)) {
|
} else if (new BigNumber(state.total).isGreaterThan(account.balance)) {
|
||||||
state.errors.amount = `Not enough ${state.networkSymbol} to cover transaction fee`;
|
state.errors.amount = `Not enough ${state.networkSymbol} to cover transaction fee`;
|
||||||
} else if (new BigNumber(state.amount).greaterThan(new BigNumber(token.balance).minus(pendingAmount))) {
|
} else if (new BigNumber(state.amount).isGreaterThan(new BigNumber(token.balance).minus(pendingAmount))) {
|
||||||
state.errors.amount = 'Not enough funds';
|
state.errors.amount = 'Not enough funds';
|
||||||
} else if (new BigNumber(state.amount).lessThanOrEqualTo('0')) {
|
} else if (new BigNumber(state.amount).isLessThanOrEqualTo('0')) {
|
||||||
state.errors.amount = 'Amount is too low';
|
state.errors.amount = 'Amount is too low';
|
||||||
}
|
}
|
||||||
} else if (!state.amount.match(ETH_18_RE)) {
|
} else if (!state.amount.match(ETH_18_RE)) {
|
||||||
state.errors.amount = 'Maximum 18 decimals allowed';
|
state.errors.amount = 'Maximum 18 decimals allowed';
|
||||||
} else if (new BigNumber(state.total).greaterThan(new BigNumber(account.balance).minus(pendingAmount))) {
|
} else if (new BigNumber(state.total).isGreaterThan(new BigNumber(account.balance).minus(pendingAmount))) {
|
||||||
state.errors.amount = 'Not enough funds';
|
state.errors.amount = 'Not enough funds';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -261,9 +261,9 @@ export const gasLimitValidation = ($state: State): PayloadAction<State> => (disp
|
|||||||
state.errors.gasLimit = 'Gas limit is not a number';
|
state.errors.gasLimit = 'Gas limit is not a number';
|
||||||
} else {
|
} else {
|
||||||
const gl: BigNumber = new BigNumber(gasLimit);
|
const gl: BigNumber = new BigNumber(gasLimit);
|
||||||
if (gl.lessThan(1)) {
|
if (gl.isLessThan(1)) {
|
||||||
state.errors.gasLimit = 'Gas limit is too low';
|
state.errors.gasLimit = 'Gas limit is too low';
|
||||||
} else if (gl.lessThan(state.currency !== state.networkSymbol ? network.defaultGasLimitTokens : network.defaultGasLimit)) {
|
} else if (gl.isLessThan(state.currency !== state.networkSymbol ? network.defaultGasLimitTokens : network.defaultGasLimit)) {
|
||||||
state.warnings.gasLimit = 'Gas limit is below recommended';
|
state.warnings.gasLimit = 'Gas limit is below recommended';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -284,9 +284,9 @@ export const gasPriceValidation = ($state: State): PayloadAction<State> => (): S
|
|||||||
state.errors.gasPrice = 'Gas price is not a number';
|
state.errors.gasPrice = 'Gas price is not a number';
|
||||||
} else {
|
} else {
|
||||||
const gp: BigNumber = new BigNumber(gasPrice);
|
const gp: BigNumber = new BigNumber(gasPrice);
|
||||||
if (gp.greaterThan(1000)) {
|
if (gp.isGreaterThan(1000)) {
|
||||||
state.warnings.gasPrice = 'Gas price is too high';
|
state.warnings.gasPrice = 'Gas price is too high';
|
||||||
} else if (gp.lessThanOrEqualTo('0')) {
|
} else if (gp.isLessThanOrEqualTo('0')) {
|
||||||
state.errors.gasPrice = 'Gas price is too low';
|
state.errors.gasPrice = 'Gas price is too low';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -312,9 +312,9 @@ export const nonceValidation = ($state: State): PayloadAction<State> => (dispatc
|
|||||||
state.errors.nonce = 'Nonce is not a valid number';
|
state.errors.nonce = 'Nonce is not a valid number';
|
||||||
} else {
|
} else {
|
||||||
const n: BigNumber = new BigNumber(nonce);
|
const n: BigNumber = new BigNumber(nonce);
|
||||||
if (n.lessThan(account.nonce)) {
|
if (n.isLessThan(account.nonce)) {
|
||||||
state.warnings.nonce = 'Nonce is lower than recommended';
|
state.warnings.nonce = 'Nonce is lower than recommended';
|
||||||
} else if (n.greaterThan(account.nonce)) {
|
} else if (n.isGreaterThan(account.nonce)) {
|
||||||
state.warnings.nonce = 'Nonce is greater than recommended';
|
state.warnings.nonce = 'Nonce is greater than recommended';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -339,7 +339,7 @@ export const dataValidation = ($state: State): PayloadAction<State> => (): State
|
|||||||
|
|
||||||
export const calculateFee = (gasPrice: string, gasLimit: string): string => {
|
export const calculateFee = (gasPrice: string, gasLimit: string): string => {
|
||||||
try {
|
try {
|
||||||
return EthereumjsUnits.convert(new BigNumber(gasPrice).times(gasLimit), 'gwei', 'ether');
|
return EthereumjsUnits.convert(new BigNumber(gasPrice).times(gasLimit).toFixed(), 'gwei', 'ether');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return '0';
|
return '0';
|
||||||
}
|
}
|
||||||
@ -347,7 +347,12 @@ export const calculateFee = (gasPrice: string, gasLimit: string): string => {
|
|||||||
|
|
||||||
export const calculateTotal = (amount: string, gasPrice: string, gasLimit: string): string => {
|
export const calculateTotal = (amount: string, gasPrice: string, gasLimit: string): string => {
|
||||||
try {
|
try {
|
||||||
return new BigNumber(amount).plus(calculateFee(gasPrice, gasLimit)).toString(10);
|
const bAmount = new BigNumber(amount);
|
||||||
|
// BigNumber() returns NaN on non-numeric string
|
||||||
|
if (bAmount.isNaN()) {
|
||||||
|
throw new Error('Amount is not a number');
|
||||||
|
}
|
||||||
|
return bAmount.plus(calculateFee(gasPrice, gasLimit)).toFixed();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return '0';
|
return '0';
|
||||||
}
|
}
|
||||||
@ -358,8 +363,8 @@ export const calculateMaxAmount = (balance: BigNumber, gasPrice: string, gasLimi
|
|||||||
// TODO - minus pendings
|
// TODO - minus pendings
|
||||||
const fee = calculateFee(gasPrice, gasLimit);
|
const fee = calculateFee(gasPrice, gasLimit);
|
||||||
const max = balance.minus(fee);
|
const max = balance.minus(fee);
|
||||||
if (max.lessThan(0)) return '0';
|
if (max.isLessThan(0)) return '0';
|
||||||
return max.toString(10);
|
return max.toFixed();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return '0';
|
return '0';
|
||||||
}
|
}
|
||||||
@ -368,8 +373,8 @@ export const calculateMaxAmount = (balance: BigNumber, gasPrice: string, gasLimi
|
|||||||
export const getFeeLevels = (symbol: string, gasPrice: BigNumber | string, gasLimit: string, selected?: FeeLevel): Array<FeeLevel> => {
|
export const getFeeLevels = (symbol: string, gasPrice: BigNumber | string, gasLimit: string, selected?: FeeLevel): Array<FeeLevel> => {
|
||||||
const price: BigNumber = typeof gasPrice === 'string' ? new BigNumber(gasPrice) : gasPrice;
|
const price: BigNumber = typeof gasPrice === 'string' ? new BigNumber(gasPrice) : gasPrice;
|
||||||
const quarter: BigNumber = price.dividedBy(4);
|
const quarter: BigNumber = price.dividedBy(4);
|
||||||
const high: string = price.plus(quarter.times(2)).toString(10);
|
const high: string = price.plus(quarter.times(2)).toFixed();
|
||||||
const low: string = price.minus(quarter.times(2)).toString(10);
|
const low: string = price.minus(quarter.times(2)).toFixed();
|
||||||
|
|
||||||
const customLevel: FeeLevel = selected && selected.value === 'Custom' ? {
|
const customLevel: FeeLevel = selected && selected.value === 'Custom' ? {
|
||||||
value: 'Custom',
|
value: 'Custom',
|
||||||
@ -391,7 +396,7 @@ export const getFeeLevels = (symbol: string, gasPrice: BigNumber | string, gasLi
|
|||||||
{
|
{
|
||||||
value: 'Normal',
|
value: 'Normal',
|
||||||
gasPrice: gasPrice.toString(),
|
gasPrice: gasPrice.toString(),
|
||||||
label: `${calculateFee(price.toString(10), gasLimit)} ${symbol}`,
|
label: `${calculateFee(price.toFixed(), gasLimit)} ${symbol}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'Low',
|
value: 'Low',
|
||||||
|
@ -118,7 +118,7 @@ export const onNotification = (payload: $ElementType<BlockchainNotification, 'pa
|
|||||||
|
|
||||||
const updatedAccount = await TrezorConnect.rippleGetAccountInfo({
|
const updatedAccount = await TrezorConnect.rippleGetAccountInfo({
|
||||||
account: {
|
account: {
|
||||||
address: account.descriptor,
|
descriptor: account.descriptor,
|
||||||
from: account.block,
|
from: account.block,
|
||||||
history: false,
|
history: false,
|
||||||
},
|
},
|
||||||
|
@ -76,6 +76,6 @@ export const discoverAccount = (device: TrezorDevice, discoveryProcess: Discover
|
|||||||
|
|
||||||
networkType: 'ripple',
|
networkType: 'ripple',
|
||||||
sequence: account.sequence,
|
sequence: account.sequence,
|
||||||
reserve: '0',
|
reserve: toDecimalAmount(account.reserve, network.decimals),
|
||||||
};
|
};
|
||||||
};
|
};
|
@ -60,7 +60,7 @@ export const observe = (prevState: ReducersState, action: Action): ThunkAction =
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (shouldUpdate) {
|
if (shouldUpdate) {
|
||||||
const validated = dispatch(ValidationActions.validation());
|
const validated = dispatch(ValidationActions.validation(prevState.sendFormRipple));
|
||||||
dispatch({
|
dispatch({
|
||||||
type: SEND.VALIDATION,
|
type: SEND.VALIDATION,
|
||||||
networkType: 'ripple',
|
networkType: 'ripple',
|
||||||
@ -119,6 +119,38 @@ export const toggleAdvanced = (): Action => ({
|
|||||||
networkType: 'ripple',
|
networkType: 'ripple',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Called from UI from "clear" button
|
||||||
|
*/
|
||||||
|
export const onClear = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||||
|
const { network } = getState().selectedAccount;
|
||||||
|
const { advanced } = getState().sendFormRipple;
|
||||||
|
|
||||||
|
if (!network) return;
|
||||||
|
|
||||||
|
// clear transaction draft from session storage
|
||||||
|
dispatch(SessionStorageActions.clear());
|
||||||
|
|
||||||
|
const blockchainFeeLevels = dispatch(BlockchainActions.getFeeLevels(network));
|
||||||
|
const feeLevels = dispatch(ValidationActions.getFeeLevels(blockchainFeeLevels));
|
||||||
|
const selectedFeeLevel = ValidationActions.getSelectedFeeLevel(feeLevels, initialState.selectedFeeLevel);
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: SEND.CLEAR,
|
||||||
|
networkType: 'ripple',
|
||||||
|
state: {
|
||||||
|
...initialState,
|
||||||
|
networkName: network.shortcut,
|
||||||
|
networkSymbol: network.symbol,
|
||||||
|
feeLevels,
|
||||||
|
selectedFeeLevel,
|
||||||
|
fee: network.fee.defaultFee,
|
||||||
|
sequence: '1',
|
||||||
|
advanced,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Called from UI on "address" field change
|
* Called from UI on "address" field change
|
||||||
*/
|
*/
|
||||||
@ -244,6 +276,23 @@ export const onFeeChange = (fee: string): ThunkAction => (dispatch: Dispatch, ge
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Called from UI on "advanced / destination tag" field change
|
||||||
|
*/
|
||||||
|
export const onDestinationTagChange = (destinationTag: string): ThunkAction => (dispatch: Dispatch, getState: GetState): void => {
|
||||||
|
const state: State = getState().sendFormRipple;
|
||||||
|
dispatch({
|
||||||
|
type: SEND.CHANGE,
|
||||||
|
networkType: 'ripple',
|
||||||
|
state: {
|
||||||
|
...state,
|
||||||
|
untouched: false,
|
||||||
|
touched: { ...state.touched, destinationTag: true },
|
||||||
|
destinationTag,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Called from UI from "send" button
|
* Called from UI from "send" button
|
||||||
*/
|
*/
|
||||||
@ -262,7 +311,13 @@ export const onSend = (): AsyncAction => async (dispatch: Dispatch, getState: Ge
|
|||||||
if (!blockchain) return;
|
if (!blockchain) return;
|
||||||
|
|
||||||
const currentState: State = getState().sendFormRipple;
|
const currentState: State = getState().sendFormRipple;
|
||||||
const amount = fromDecimalAmount(currentState.amount, 6);
|
const payment: { amount: string, destination: string, destinationTag?: number } = {
|
||||||
|
amount: fromDecimalAmount(currentState.amount, network.decimals),
|
||||||
|
destination: currentState.address,
|
||||||
|
};
|
||||||
|
if (currentState.destinationTag.length > 0) {
|
||||||
|
payment.destinationTag = parseInt(currentState.destinationTag, 10);
|
||||||
|
}
|
||||||
|
|
||||||
const signedTransaction = await TrezorConnect.rippleSignTransaction({
|
const signedTransaction = await TrezorConnect.rippleSignTransaction({
|
||||||
device: {
|
device: {
|
||||||
@ -276,10 +331,7 @@ export const onSend = (): AsyncAction => async (dispatch: Dispatch, getState: Ge
|
|||||||
fee: currentState.selectedFeeLevel.fee, // Fee must be in the range of 10 to 10,000 drops
|
fee: currentState.selectedFeeLevel.fee, // Fee must be in the range of 10 to 10,000 drops
|
||||||
flags: 0x80000000,
|
flags: 0x80000000,
|
||||||
sequence: account.sequence,
|
sequence: account.sequence,
|
||||||
payment: {
|
payment,
|
||||||
amount,
|
|
||||||
destination: currentState.address,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -346,5 +398,7 @@ export default {
|
|||||||
onFeeLevelChange,
|
onFeeLevelChange,
|
||||||
updateFeeLevels,
|
updateFeeLevels,
|
||||||
onFeeChange,
|
onFeeChange,
|
||||||
|
onDestinationTagChange,
|
||||||
onSend,
|
onSend,
|
||||||
|
onClear,
|
||||||
};
|
};
|
@ -1,5 +1,5 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
|
import TrezorConnect from 'trezor-connect';
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
import * as SEND from 'actions/constants/send';
|
import * as SEND from 'actions/constants/send';
|
||||||
import { findDevice, getPendingAmount } from 'reducers/utils';
|
import { findDevice, getPendingAmount } from 'reducers/utils';
|
||||||
@ -9,6 +9,7 @@ import type {
|
|||||||
Dispatch,
|
Dispatch,
|
||||||
GetState,
|
GetState,
|
||||||
PayloadAction,
|
PayloadAction,
|
||||||
|
PromiseAction,
|
||||||
BlockchainFeeLevel,
|
BlockchainFeeLevel,
|
||||||
} from 'flowtype';
|
} from 'flowtype';
|
||||||
import type { State, FeeLevel } from 'reducers/SendFormRippleReducer';
|
import type { State, FeeLevel } from 'reducers/SendFormRippleReducer';
|
||||||
@ -59,7 +60,7 @@ export const onFeeUpdated = (network: string, feeLevels: Array<BlockchainFeeLeve
|
|||||||
/*
|
/*
|
||||||
* Recalculate amount, total and fees
|
* Recalculate amount, total and fees
|
||||||
*/
|
*/
|
||||||
export const validation = (): PayloadAction<State> => (dispatch: Dispatch, getState: GetState): State => {
|
export const validation = (prevState: State): PayloadAction<State> => (dispatch: Dispatch, getState: GetState): State => {
|
||||||
// clone deep nested object
|
// clone deep nested object
|
||||||
// to avoid overrides across state history
|
// to avoid overrides across state history
|
||||||
let state: State = JSON.parse(JSON.stringify(getState().sendFormRipple));
|
let state: State = JSON.parse(JSON.stringify(getState().sendFormRipple));
|
||||||
@ -73,6 +74,10 @@ export const validation = (): PayloadAction<State> => (dispatch: Dispatch, getSt
|
|||||||
state = dispatch(addressLabel(state));
|
state = dispatch(addressLabel(state));
|
||||||
state = dispatch(amountValidation(state));
|
state = dispatch(amountValidation(state));
|
||||||
state = dispatch(feeValidation(state));
|
state = dispatch(feeValidation(state));
|
||||||
|
state = dispatch(destinationTagValidation(state));
|
||||||
|
if (state.touched.address && prevState.address !== state.address) {
|
||||||
|
dispatch(addressBalanceValidation(state));
|
||||||
|
}
|
||||||
return state;
|
return state;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -82,14 +87,14 @@ const recalculateTotalAmount = ($state: State): PayloadAction<State> => (dispatc
|
|||||||
network,
|
network,
|
||||||
pending,
|
pending,
|
||||||
} = getState().selectedAccount;
|
} = getState().selectedAccount;
|
||||||
if (!account || !network) return $state;
|
if (!account || account.networkType !== 'ripple' || !network) return $state;
|
||||||
|
|
||||||
const state = { ...$state };
|
const state = { ...$state };
|
||||||
const fee = toDecimalAmount(state.selectedFeeLevel.fee, network.decimals);
|
const fee = toDecimalAmount(state.selectedFeeLevel.fee, network.decimals);
|
||||||
|
|
||||||
if (state.setMax) {
|
if (state.setMax) {
|
||||||
const pendingAmount = getPendingAmount(pending, state.networkSymbol, false);
|
const pendingAmount = getPendingAmount(pending, state.networkSymbol, false);
|
||||||
const availableBalance = new BigNumber(account.balance).minus(pendingAmount);
|
const availableBalance = new BigNumber(account.balance).minus(account.reserve).minus(pendingAmount);
|
||||||
state.amount = calculateMaxAmount(availableBalance, fee);
|
state.amount = calculateMaxAmount(availableBalance, fee);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,6 +139,43 @@ const addressValidation = ($state: State): PayloadAction<State> => (dispatch: Di
|
|||||||
return state;
|
return state;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Address balance validation
|
||||||
|
* Fetch data from trezor-connect and set minimum required amount in reducer
|
||||||
|
*/
|
||||||
|
const addressBalanceValidation = ($state: State): PromiseAction<void> => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||||
|
const { network } = getState().selectedAccount;
|
||||||
|
if (!network) return;
|
||||||
|
|
||||||
|
let minAmount: string = '0';
|
||||||
|
const response = await TrezorConnect.rippleGetAccountInfo({
|
||||||
|
account: {
|
||||||
|
descriptor: $state.address,
|
||||||
|
},
|
||||||
|
coin: network.shortcut,
|
||||||
|
});
|
||||||
|
if (response.success) {
|
||||||
|
const empty = response.payload.sequence <= 0 && response.payload.balance === '0';
|
||||||
|
if (empty) {
|
||||||
|
minAmount = toDecimalAmount(response.payload.reserve, network.decimals);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: consider checking local (known) accounts reserve instead of async fetching
|
||||||
|
|
||||||
|
// a2 (not empty): rJX2KwzaLJDyFhhtXKi3htaLfaUH2tptEX
|
||||||
|
// a4 (empty): r9skfe7kZkvqss7oMB3tuj4a59PXD5wRa2
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: SEND.CHANGE,
|
||||||
|
networkType: 'ripple',
|
||||||
|
state: {
|
||||||
|
...getState().sendFormRipple,
|
||||||
|
minAmount,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Address label assignation
|
* Address label assignation
|
||||||
*/
|
*/
|
||||||
@ -183,7 +225,7 @@ const amountValidation = ($state: State): PayloadAction<State> => (dispatch: Dis
|
|||||||
account,
|
account,
|
||||||
pending,
|
pending,
|
||||||
} = getState().selectedAccount;
|
} = getState().selectedAccount;
|
||||||
if (!account) return state;
|
if (!account || account.networkType !== 'ripple') return state;
|
||||||
|
|
||||||
const { amount } = state;
|
const { amount } = state;
|
||||||
if (amount.length < 1) {
|
if (amount.length < 1) {
|
||||||
@ -192,13 +234,21 @@ const amountValidation = ($state: State): PayloadAction<State> => (dispatch: Dis
|
|||||||
state.errors.amount = 'Amount is not a number';
|
state.errors.amount = 'Amount is not a number';
|
||||||
} else {
|
} else {
|
||||||
const pendingAmount: BigNumber = getPendingAmount(pending, state.networkSymbol);
|
const pendingAmount: BigNumber = getPendingAmount(pending, state.networkSymbol);
|
||||||
|
|
||||||
if (!state.amount.match(XRP_6_RE)) {
|
if (!state.amount.match(XRP_6_RE)) {
|
||||||
state.errors.amount = 'Maximum 6 decimals allowed';
|
state.errors.amount = 'Maximum 6 decimals allowed';
|
||||||
} else if (new BigNumber(state.total).greaterThan(new BigNumber(account.balance).minus(pendingAmount))) {
|
} else if (new BigNumber(state.total).isGreaterThan(new BigNumber(account.balance).minus(pendingAmount))) {
|
||||||
state.errors.amount = 'Not enough funds';
|
state.errors.amount = 'Not enough funds';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!state.errors.amount && new BigNumber(account.balance).minus(state.total).lt(account.reserve)) {
|
||||||
|
state.errors.amount = `Not enough funds. Reserved amount for this account is ${account.reserve} ${state.networkSymbol}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!state.errors.amount && new BigNumber(state.amount).lt(state.minAmount)) {
|
||||||
|
state.errors.amount = `Amount is too low. Minimum amount for creating a new account is ${state.minAmount} ${state.networkSymbol}`;
|
||||||
|
}
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -221,14 +271,27 @@ export const feeValidation = ($state: State): PayloadAction<State> => (dispatch:
|
|||||||
state.errors.fee = 'Fee must be an absolute number';
|
state.errors.fee = 'Fee must be an absolute number';
|
||||||
} else {
|
} else {
|
||||||
const gl: BigNumber = new BigNumber(fee);
|
const gl: BigNumber = new BigNumber(fee);
|
||||||
if (gl.lessThan(network.fee.minFee)) {
|
if (gl.isLessThan(network.fee.minFee)) {
|
||||||
state.errors.fee = 'Fee is below recommended';
|
state.errors.fee = 'Fee is below recommended';
|
||||||
} else if (gl.greaterThan(network.fee.maxFee)) {
|
} else if (gl.isGreaterThan(network.fee.maxFee)) {
|
||||||
state.errors.fee = 'Fee is above recommended';
|
state.errors.fee = 'Fee is above recommended';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return state;
|
return state;
|
||||||
};
|
};
|
||||||
|
/*
|
||||||
|
* Destination Tag value validation
|
||||||
|
*/
|
||||||
|
export const destinationTagValidation = ($state: State): PayloadAction<State> => (): State => {
|
||||||
|
const state = { ...$state };
|
||||||
|
if (!state.touched.destinationTag) return state;
|
||||||
|
|
||||||
|
const { destinationTag } = state;
|
||||||
|
if (destinationTag.length > 0 && !destinationTag.match(ABS_RE)) {
|
||||||
|
state.errors.destinationTag = 'Destination tag must be an absolute number';
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -237,7 +300,12 @@ export const feeValidation = ($state: State): PayloadAction<State> => (dispatch:
|
|||||||
|
|
||||||
const calculateTotal = (amount: string, fee: string): string => {
|
const calculateTotal = (amount: string, fee: string): string => {
|
||||||
try {
|
try {
|
||||||
return new BigNumber(amount).plus(fee).toString(10);
|
const bAmount = new BigNumber(amount);
|
||||||
|
// BigNumber() returns NaN on non-numeric string
|
||||||
|
if (bAmount.isNaN()) {
|
||||||
|
throw new Error('Amount is not a number');
|
||||||
|
}
|
||||||
|
return bAmount.plus(fee).toFixed();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return '0';
|
return '0';
|
||||||
}
|
}
|
||||||
@ -247,8 +315,8 @@ const calculateMaxAmount = (balance: BigNumber, fee: string): string => {
|
|||||||
try {
|
try {
|
||||||
// TODO - minus pendings
|
// TODO - minus pendings
|
||||||
const max = balance.minus(fee);
|
const max = balance.minus(fee);
|
||||||
if (max.lessThan(0)) return '0';
|
if (max.isLessThan(0)) return '0';
|
||||||
return max.toString(10);
|
return max.toFixed();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return '0';
|
return '0';
|
||||||
}
|
}
|
||||||
|
37
src/components/Backdrop/index.js
Normal file
37
src/components/Backdrop/index.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styled, { css } from 'styled-components';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { FADE_IN } from 'config/animations';
|
||||||
|
|
||||||
|
|
||||||
|
const StyledBackdrop = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 100;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
background-color: rgba(0,0,0,0.5);
|
||||||
|
|
||||||
|
${props => props.animated && css`
|
||||||
|
animation: ${FADE_IN} 0.3s;
|
||||||
|
`};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Backdrop = ({
|
||||||
|
className,
|
||||||
|
show,
|
||||||
|
animated,
|
||||||
|
onClick,
|
||||||
|
}) => (
|
||||||
|
show ? <StyledBackdrop className={className} animated={animated} onClick={onClick} /> : null
|
||||||
|
);
|
||||||
|
|
||||||
|
Backdrop.propTypes = {
|
||||||
|
show: PropTypes.bool,
|
||||||
|
className: PropTypes.string,
|
||||||
|
animated: PropTypes.bool,
|
||||||
|
onClick: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Backdrop;
|
@ -17,6 +17,7 @@ type Props = {
|
|||||||
isWhite?: boolean,
|
isWhite?: boolean,
|
||||||
isWebUsb?: boolean,
|
isWebUsb?: boolean,
|
||||||
isTransparent?: boolean,
|
isTransparent?: boolean,
|
||||||
|
dataTest?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const Wrapper = styled.button`
|
const Wrapper = styled.button`
|
||||||
@ -37,6 +38,11 @@ const Wrapper = styled.button`
|
|||||||
background: ${colors.GREEN_TERTIARY};
|
background: ${colors.GREEN_TERTIARY};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
border-color: ${colors.INPUT_FOCUSED_BORDER};
|
||||||
|
box-shadow: 0 0px 6px 0 ${colors.INPUT_FOCUSED_SHADOW};
|
||||||
|
}
|
||||||
|
|
||||||
${props => props.isDisabled && css`
|
${props => props.isDisabled && css`
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
color: ${colors.TEXT_SECONDARY};
|
color: ${colors.TEXT_SECONDARY};
|
||||||
@ -48,6 +54,10 @@ const Wrapper = styled.button`
|
|||||||
color: ${colors.TEXT_SECONDARY};
|
color: ${colors.TEXT_SECONDARY};
|
||||||
border: 1px solid ${colors.DIVIDER};
|
border: 1px solid ${colors.DIVIDER};
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
border-color: ${colors.INPUT_FOCUSED_BORDER};
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: ${colors.TEXT_PRIMARY};
|
color: ${colors.TEXT_PRIMARY};
|
||||||
background: ${colors.DIVIDER};
|
background: ${colors.DIVIDER};
|
||||||
@ -64,6 +74,11 @@ const Wrapper = styled.button`
|
|||||||
border: 0px;
|
border: 0px;
|
||||||
color: ${colors.TEXT_SECONDARY};
|
color: ${colors.TEXT_SECONDARY};
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
color: ${colors.TEXT_PRIMARY};
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&:active {
|
&:active {
|
||||||
color: ${colors.TEXT_PRIMARY};
|
color: ${colors.TEXT_PRIMARY};
|
||||||
@ -132,10 +147,12 @@ const Button = ({
|
|||||||
isWhite = false,
|
isWhite = false,
|
||||||
isWebUsb = false,
|
isWebUsb = false,
|
||||||
isTransparent = false,
|
isTransparent = false,
|
||||||
|
dataTest,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const newClassName = isWebUsb ? `${className} trezor-webusb-button` : className;
|
const newClassName = isWebUsb ? `${className} trezor-webusb-button` : className;
|
||||||
return (
|
return (
|
||||||
<Wrapper
|
<Wrapper
|
||||||
|
data-test={dataTest}
|
||||||
className={newClassName}
|
className={newClassName}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
onMouseEnter={onMouseEnter}
|
onMouseEnter={onMouseEnter}
|
||||||
@ -162,6 +179,7 @@ Button.propTypes = {
|
|||||||
isWhite: PropTypes.bool,
|
isWhite: PropTypes.bool,
|
||||||
isWebUsb: PropTypes.bool,
|
isWebUsb: PropTypes.bool,
|
||||||
isTransparent: PropTypes.bool,
|
isTransparent: PropTypes.bool,
|
||||||
|
dataTest: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Button;
|
export default Button;
|
@ -15,10 +15,13 @@ const Wrapper = styled.div`
|
|||||||
position: relative;
|
position: relative;
|
||||||
height: 70px;
|
height: 70px;
|
||||||
width: 320px;
|
width: 320px;
|
||||||
|
z-index: 10;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
padding: 0px 25px;
|
||||||
background: ${props => (props.disabled ? colors.GRAY_LIGHT : 'transparent')};
|
background: ${props => (props.disabled ? colors.GRAY_LIGHT : 'transparent')};
|
||||||
background: ${props => (props.isSelected ? colors.WHITE : 'transparent')};
|
background: ${props => (props.isSelected ? colors.WHITE : 'transparent')};
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
border-radius: 4px 0 0 0;
|
border-radius: 4px 0 0 0;
|
||||||
box-shadow: ${props => (props.disabled ? 'none' : '0 3px 8px rgba(0, 0, 0, 0.04)')};
|
box-shadow: ${props => (props.disabled ? 'none' : '0 3px 8px rgba(0, 0, 0, 0.04)')};
|
||||||
@ -27,6 +30,10 @@ const Wrapper = styled.div`
|
|||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
`}
|
`}
|
||||||
|
|
||||||
|
${props => props.disabled && css`
|
||||||
|
cursor: default;
|
||||||
|
`}
|
||||||
|
|
||||||
${props => props.isHoverable && !props.disabled && css`
|
${props => props.isHoverable && !props.disabled && css`
|
||||||
&:hover {
|
&:hover {
|
||||||
background: ${colors.GRAY_LIGHT};
|
background: ${colors.GRAY_LIGHT};
|
||||||
@ -34,22 +41,10 @@ const Wrapper = styled.div`
|
|||||||
`}
|
`}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ClickWrapper = styled.div`
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
padding-left: 25px;
|
|
||||||
height: 100%;
|
|
||||||
align-items: center;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
${props => props.disabled && css`
|
|
||||||
cursor: default;
|
|
||||||
`}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const LabelWrapper = styled.div`
|
const LabelWrapper = styled.div`
|
||||||
flex: 1;
|
flex: 1 1 auto;
|
||||||
padding-left: 18px;
|
padding-left: 18px;
|
||||||
|
overflow: hidden;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Name = styled.div`
|
const Name = styled.div`
|
||||||
@ -71,8 +66,9 @@ const Status = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const IconWrapper = styled.div`
|
const IconWrapper = styled.div`
|
||||||
padding-right: 25px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex: 1 0 0;
|
||||||
|
justify-content: flex-end;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ImageWrapper = styled.div`
|
const ImageWrapper = styled.div`
|
||||||
@ -102,32 +98,30 @@ const DeviceHeader = ({
|
|||||||
disabled = false,
|
disabled = false,
|
||||||
isSelected = false,
|
isSelected = false,
|
||||||
className,
|
className,
|
||||||
|
testId,
|
||||||
}) => {
|
}) => {
|
||||||
const status = getStatus(device);
|
const status = getStatus(device);
|
||||||
return (
|
return (
|
||||||
<Wrapper
|
<Wrapper
|
||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
|
data-test={testId}
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
isHoverable={isHoverable}
|
isHoverable={isHoverable}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className={className}
|
className={className}
|
||||||
|
onClick={onClickWrapper}
|
||||||
>
|
>
|
||||||
<ClickWrapper
|
<ImageWrapper>
|
||||||
disabled={disabled}
|
<Dot color={getStatusColor(status)} />
|
||||||
onClick={onClickWrapper}
|
<TrezorImage model={getVersion(device)} />
|
||||||
>
|
</ImageWrapper>
|
||||||
<ImageWrapper>
|
<LabelWrapper>
|
||||||
<Dot color={getStatusColor(status)} />
|
<Name>{device.instanceLabel}</Name>
|
||||||
<TrezorImage model={getVersion(device)} />
|
<Status title={getStatusName(status)}>{getStatusName(status)}</Status>
|
||||||
</ImageWrapper>
|
</LabelWrapper>
|
||||||
<LabelWrapper>
|
<IconWrapper>
|
||||||
<Name>{device.instanceLabel}</Name>
|
{icon && !disabled && isAccessible && icon}
|
||||||
<Status>{getStatusName(status)}</Status>
|
</IconWrapper>
|
||||||
</LabelWrapper>
|
|
||||||
<IconWrapper>
|
|
||||||
{icon && !disabled && isAccessible && icon}
|
|
||||||
</IconWrapper>
|
|
||||||
</ClickWrapper>
|
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -142,6 +136,7 @@ DeviceHeader.propTypes = {
|
|||||||
isSelected: PropTypes.bool,
|
isSelected: PropTypes.bool,
|
||||||
onClickWrapper: PropTypes.func.isRequired,
|
onClickWrapper: PropTypes.func.isRequired,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
|
testId: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DeviceHeader;
|
export default DeviceHeader;
|
||||||
|
@ -12,8 +12,6 @@ import colors from 'config/colors';
|
|||||||
import { FONT_SIZE } from 'config/variables';
|
import { FONT_SIZE } from 'config/variables';
|
||||||
import * as LogActions from 'actions/LogActions';
|
import * as LogActions from 'actions/LogActions';
|
||||||
|
|
||||||
declare var COMMITHASH: string;
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
opened: boolean,
|
opened: boolean,
|
||||||
isLanding: boolean,
|
isLanding: boolean,
|
||||||
@ -60,7 +58,7 @@ const Right = styled.div`
|
|||||||
const Footer = ({ opened, toggle, isLanding }: Props) => (
|
const Footer = ({ opened, toggle, isLanding }: Props) => (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<Left>
|
<Left>
|
||||||
<Copy title={COMMITHASH}>© {getYear(new Date())}</Copy>
|
<Copy>© {getYear(new Date())}</Copy>
|
||||||
<StyledLink href="http://satoshilabs.com" isGreen>SatoshiLabs</StyledLink>
|
<StyledLink href="http://satoshilabs.com" isGreen>SatoshiLabs</StyledLink>
|
||||||
<StyledLink href="./assets/tos.pdf" isGreen>Terms</StyledLink>
|
<StyledLink href="./assets/tos.pdf" isGreen>Terms</StyledLink>
|
||||||
<StyledLink onClick={toggle} isGreen>{ opened ? 'Hide Log' : 'Show Log' }</StyledLink>
|
<StyledLink onClick={toggle} isGreen>{ opened ? 'Hide Log' : 'Show Log' }</StyledLink>
|
||||||
|
@ -3,17 +3,18 @@ import React from 'react';
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { NavLink } from 'react-router-dom';
|
import { NavLink } from 'react-router-dom';
|
||||||
import colors from 'config/colors';
|
import colors from 'config/colors';
|
||||||
|
import { SCREEN_SIZE } from 'config/variables';
|
||||||
|
import Icon from 'components/Icon';
|
||||||
|
import icons from 'config/icons';
|
||||||
|
|
||||||
|
import type { toggleSidebar as toggleSidebarType } from 'actions/WalletActions';
|
||||||
|
|
||||||
const Wrapper = styled.header`
|
const Wrapper = styled.header`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 52px;
|
height: 52px;
|
||||||
background: ${colors.HEADER};
|
background: ${colors.HEADER};
|
||||||
|
overflow: hidden;
|
||||||
svg {
|
z-index: 200;
|
||||||
fill: ${colors.WHITE};
|
|
||||||
height: 28px;
|
|
||||||
width: 100px;
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const LayoutWrapper = styled.div`
|
const LayoutWrapper = styled.div`
|
||||||
@ -31,12 +32,65 @@ const LayoutWrapper = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const Left = styled.div`
|
const Left = styled.div`
|
||||||
flex: 1;
|
display: none;
|
||||||
display: flex;
|
flex: 0 0 33%;
|
||||||
justify-content: flex-start;
|
|
||||||
|
@media screen and (max-width: ${SCREEN_SIZE.SM}) {
|
||||||
|
display: initial;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Right = styled.div``;
|
const MenuToggler = styled.div`
|
||||||
|
display: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
color: ${colors.WHITE};
|
||||||
|
align-self: center;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
padding: 10px 0px;
|
||||||
|
transition: all .1s ease-in;
|
||||||
|
|
||||||
|
@media screen and (max-width: ${SCREEN_SIZE.SM}) {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const TogglerText = styled.div`
|
||||||
|
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Logo = styled.div`
|
||||||
|
flex: 1;
|
||||||
|
justify-content: flex-start;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
fill: ${colors.WHITE};
|
||||||
|
height: 28px;
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: ${SCREEN_SIZE.SM}) {
|
||||||
|
flex: 1 0 33%;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const MenuLinks = styled.div`
|
||||||
|
flex: 0;
|
||||||
|
|
||||||
|
@media screen and (max-width: ${SCREEN_SIZE.SM}) {
|
||||||
|
flex: 0 1 33%;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Projects = styled.div`
|
||||||
|
|
||||||
|
@media screen and (max-width: ${SCREEN_SIZE.SM}) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
const A = styled.a`
|
const A = styled.a`
|
||||||
color: ${colors.WHITE};
|
color: ${colors.WHITE};
|
||||||
@ -58,10 +112,42 @@ const A = styled.a`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Header = (): React$Element<string> => (
|
type Props = {
|
||||||
<Wrapper>
|
sidebarEnabled?: boolean,
|
||||||
|
sidebarOpened?: ?boolean,
|
||||||
|
toggleSidebar?: toggleSidebarType,
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
const Header = ({ sidebarEnabled, sidebarOpened, toggleSidebar }: Props) => (
|
||||||
|
<Wrapper data-test="Main__page__navigation">
|
||||||
<LayoutWrapper>
|
<LayoutWrapper>
|
||||||
<Left>
|
<Left>
|
||||||
|
{ sidebarEnabled && (
|
||||||
|
<MenuToggler onClick={toggleSidebar}>
|
||||||
|
{sidebarOpened ? (
|
||||||
|
<>
|
||||||
|
<Icon
|
||||||
|
size={24}
|
||||||
|
color={colors.WHITE}
|
||||||
|
icon={icons.CLOSE}
|
||||||
|
/>
|
||||||
|
<TogglerText>Close</TogglerText>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Icon
|
||||||
|
color={colors.WHITE}
|
||||||
|
size={24}
|
||||||
|
icon={icons.MENU}
|
||||||
|
/>
|
||||||
|
<TogglerText>Menu</TogglerText>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</MenuToggler>
|
||||||
|
)}
|
||||||
|
</Left>
|
||||||
|
<Logo>
|
||||||
<NavLink to="/">
|
<NavLink to="/">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 163.7 41.9" width="100%" height="100%" preserveAspectRatio="xMinYMin meet">
|
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 163.7 41.9" width="100%" height="100%" preserveAspectRatio="xMinYMin meet">
|
||||||
<polygon points="101.1,12.8 118.2,12.8 118.2,17.3 108.9,29.9 118.2,29.9 118.2,35.2 101.1,35.2 101.1,30.7 110.4,18.1 101.1,18.1" />
|
<polygon points="101.1,12.8 118.2,12.8 118.2,17.3 108.9,29.9 118.2,29.9 118.2,35.2 101.1,35.2 101.1,30.7 110.4,18.1 101.1,18.1" />
|
||||||
@ -73,13 +159,15 @@ const Header = (): React$Element<string> => (
|
|||||||
<polygon points="40.5,12.8 58.6,12.8 58.6,18.1 52.4,18.1 52.4,35.2 46.6,35.2 46.6,18.1 40.5,18.1 " />
|
<polygon points="40.5,12.8 58.6,12.8 58.6,18.1 52.4,18.1 52.4,35.2 46.6,35.2 46.6,18.1 40.5,18.1 " />
|
||||||
</svg>
|
</svg>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</Left>
|
</Logo>
|
||||||
<Right>
|
<MenuLinks>
|
||||||
<A href="https://trezor.io/" target="_blank" rel="noreferrer noopener">Trezor</A>
|
<Projects>
|
||||||
<A href="https://wiki.trezor.io/" target="_blank" rel="noreferrer noopener">Wiki</A>
|
<A href="https://trezor.io/" target="_blank" rel="noreferrer noopener">Trezor</A>
|
||||||
<A href="https://blog.trezor.io/" target="_blank" rel="noreferrer noopener">Blog</A>
|
<A href="https://wiki.trezor.io/" target="_blank" rel="noreferrer noopener">Wiki</A>
|
||||||
<A href="https://trezor.io/support/" target="_blank" rel="noreferrer noopener">Support</A>
|
<A href="https://blog.trezor.io/" target="_blank" rel="noreferrer noopener">Blog</A>
|
||||||
</Right>
|
<A href="https://trezor.io/support/" target="_blank" rel="noreferrer noopener">Support</A>
|
||||||
|
</Projects>
|
||||||
|
</MenuLinks>
|
||||||
</LayoutWrapper>
|
</LayoutWrapper>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
|
@ -12,10 +12,12 @@ const A = styled.a`
|
|||||||
font-size: ${FONT_SIZE.SMALL};
|
font-size: ${FONT_SIZE.SMALL};
|
||||||
|
|
||||||
${props => props.isGreen && css`
|
${props => props.isGreen && css`
|
||||||
border-bottom: 1px solid ${colors.GREEN_PRIMARY};
|
text-decoration: underline;
|
||||||
|
text-decoration-color: ${colors.GREEN_PRIMARY};
|
||||||
`}
|
`}
|
||||||
${props => props.isGray && css`
|
${props => props.isGray && css`
|
||||||
border-bottom: 1px solid ${colors.TEXT_SECONDARY};
|
text-decoration: underline;
|
||||||
|
text-decoration-color: ${colors.TEXT_SECONDARY};
|
||||||
`}
|
`}
|
||||||
|
|
||||||
&,
|
&,
|
||||||
|
@ -25,13 +25,14 @@ const SvgWrapper = styled.svg`
|
|||||||
|
|
||||||
const CircleWrapper = styled.circle`
|
const CircleWrapper = styled.circle`
|
||||||
${props => props.isRoute && css`
|
${props => props.isRoute && css`
|
||||||
stroke: ${colors.GRAY_LIGHT};
|
stroke: ${props.transparentRoute ? 'transparent' : colors.GRAY_LIGHT};
|
||||||
`}
|
`}
|
||||||
|
|
||||||
${props => props.isPath && css`
|
${props => props.isPath && css`
|
||||||
|
stroke-width: ${props.transparentRoute ? '2px' : '1px'};
|
||||||
stroke-dasharray: 1, 200;
|
stroke-dasharray: 1, 200;
|
||||||
stroke-dashoffset: 0;
|
stroke-dashoffset: 0;
|
||||||
animation: ${DASH} 1.5s ease-in-out infinite, ${GREEN_COLOR} 6s ease-in-out infinite;
|
animation: ${DASH} 1.5s ease-in-out infinite, ${props.animationColor || GREEN_COLOR} 6s ease-in-out infinite;
|
||||||
stroke-linecap: round;
|
stroke-linecap: round;
|
||||||
`};
|
`};
|
||||||
`;
|
`;
|
||||||
@ -42,29 +43,31 @@ const StyledParagraph = styled(Paragraph)`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const Loader = ({
|
const Loader = ({
|
||||||
className, text, isWhiteText = false, isSmallText, size = 100,
|
className, text, isWhiteText = false, isSmallText, size = 100, animationColor, transparentRoute,
|
||||||
}) => (
|
}) => (
|
||||||
<Wrapper className={className} size={size}>
|
<Wrapper className={className} size={size}>
|
||||||
<StyledParagraph isSmallText={isSmallText} isWhiteText={isWhiteText}>{text}</StyledParagraph>
|
<StyledParagraph isSmallText={isSmallText} isWhiteText={isWhiteText}>{text}</StyledParagraph>
|
||||||
<SvgWrapper viewBox="25 25 50 50">
|
<SvgWrapper viewBox="25 25 50 50">
|
||||||
<CircleWrapper
|
<CircleWrapper
|
||||||
|
animationColor={animationColor}
|
||||||
cx="50"
|
cx="50"
|
||||||
cy="50"
|
cy="50"
|
||||||
r="20"
|
r="20"
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke=""
|
stroke=""
|
||||||
strokeWidth="1"
|
|
||||||
strokeMiterlimit="10"
|
strokeMiterlimit="10"
|
||||||
isRoute
|
isRoute
|
||||||
|
transparentRoute={transparentRoute}
|
||||||
/>
|
/>
|
||||||
<CircleWrapper
|
<CircleWrapper
|
||||||
|
animationColor={animationColor}
|
||||||
cx="50"
|
cx="50"
|
||||||
cy="50"
|
cy="50"
|
||||||
r="20"
|
r="20"
|
||||||
fill="none"
|
fill="none"
|
||||||
strokeWidth="1"
|
|
||||||
strokeMiterlimit="10"
|
strokeMiterlimit="10"
|
||||||
isPath
|
isPath
|
||||||
|
transparentRoute={transparentRoute}
|
||||||
/>
|
/>
|
||||||
</SvgWrapper>
|
</SvgWrapper>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
@ -75,6 +78,8 @@ Loader.propTypes = {
|
|||||||
isSmallText: PropTypes.bool,
|
isSmallText: PropTypes.bool,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
text: PropTypes.string,
|
text: PropTypes.string,
|
||||||
|
animationColor: PropTypes.object,
|
||||||
|
transparentRoute: PropTypes.bool,
|
||||||
size: PropTypes.number,
|
size: PropTypes.number,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ const Wrapper = styled.div`
|
|||||||
position: relative;
|
position: relative;
|
||||||
color: ${colors.INFO_PRIMARY};
|
color: ${colors.INFO_PRIMARY};
|
||||||
background: ${colors.INFO_SECONDARY};
|
background: ${colors.INFO_SECONDARY};
|
||||||
padding: 24px 48px;
|
padding: 24px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
@ -32,9 +32,10 @@ const Wrapper = styled.div`
|
|||||||
const Click = styled.div`
|
const Click = styled.div`
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 8px;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
padding: 12px;
|
padding-right: inherit;
|
||||||
|
padding-top: inherit;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
transition: opacity 0.3s;
|
transition: opacity 0.3s;
|
||||||
|
|
||||||
@ -61,7 +62,7 @@ const Log = (props: Props): ?React$Element<string> => {
|
|||||||
return (
|
return (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<Click onClick={props.toggle}>
|
<Click onClick={props.toggle}>
|
||||||
<Icon size={25} color={colors.INFO_PRIMARY} icon={icons.CLOSE} />
|
<Icon size={24} color={colors.INFO_PRIMARY} icon={icons.CLOSE} />
|
||||||
</Click>
|
</Click>
|
||||||
<H2>Log</H2>
|
<H2>Log</H2>
|
||||||
<StyledParagraph isSmaller>Attention: The log contains your XPUBs. Anyone with your XPUBs can see your account history.</StyledParagraph>
|
<StyledParagraph isSmaller>Attention: The log contains your XPUBs. Anyone with your XPUBs can see your account history.</StyledParagraph>
|
||||||
|
@ -5,9 +5,12 @@ import styled from 'styled-components';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Icon from 'components/Icon';
|
import Icon from 'components/Icon';
|
||||||
import colors from 'config/colors';
|
import colors from 'config/colors';
|
||||||
import { getPrimaryColor, getSecondaryColor } from 'utils/notification';
|
import { WHITE_COLOR } from 'config/animations';
|
||||||
|
import { getPrimaryColor } from 'utils/notification';
|
||||||
import Loader from 'components/Loader';
|
import Loader from 'components/Loader';
|
||||||
import { TRANSITION, FONT_SIZE, FONT_WEIGHT } from 'config/variables';
|
import {
|
||||||
|
TRANSITION, FONT_SIZE, FONT_WEIGHT, SCREEN_SIZE,
|
||||||
|
} from 'config/variables';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
type: string;
|
type: string;
|
||||||
@ -31,7 +34,8 @@ const LoaderContent = styled.div`
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
background: ${props => getSecondaryColor(props.type)};
|
color: ${colors.WHITE};
|
||||||
|
background: ${props => getPrimaryColor(props.type)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Wrapper = styled.button`
|
const Wrapper = styled.button`
|
||||||
@ -46,6 +50,10 @@ const Wrapper = styled.button`
|
|||||||
border: 1px solid ${props => getPrimaryColor(props.type)};
|
border: 1px solid ${props => getPrimaryColor(props.type)};
|
||||||
transition: ${TRANSITION.HOVER};
|
transition: ${TRANSITION.HOVER};
|
||||||
|
|
||||||
|
@media screen and (max-width: ${SCREEN_SIZE.SM}){
|
||||||
|
padding: 12px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: ${colors.WHITE};
|
color: ${colors.WHITE};
|
||||||
background: ${props => getPrimaryColor(props.type)};
|
background: ${props => getPrimaryColor(props.type)};
|
||||||
@ -66,7 +74,7 @@ const NotificationButton = ({
|
|||||||
>
|
>
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
<LoaderContent type={type}>
|
<LoaderContent type={type}>
|
||||||
<Loader size={30} />
|
<Loader transparentRoute animationColor={WHITE_COLOR} size={30} />
|
||||||
</LoaderContent>
|
</LoaderContent>
|
||||||
)}
|
)}
|
||||||
{icon && (
|
{icon && (
|
||||||
|
@ -17,8 +17,9 @@ type Props = {
|
|||||||
cancelable?: boolean;
|
cancelable?: boolean;
|
||||||
title: string;
|
title: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
message?: ?string;
|
message?: ?React.Node;
|
||||||
actions?: Array<CallbackAction>;
|
actions?: Array<CallbackAction>;
|
||||||
|
isActionInProgress?: boolean;
|
||||||
close?: typeof NotificationActions.close,
|
close?: typeof NotificationActions.close,
|
||||||
loading?: boolean
|
loading?: boolean
|
||||||
};
|
};
|
||||||
@ -26,14 +27,20 @@ type Props = {
|
|||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 24px 48px 9px 24px;
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
color: ${props => getPrimaryColor(props.type)};
|
||||||
|
background: ${props => getSecondaryColor(props.type)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Content = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
max-width: 1170px;
|
||||||
|
padding: 24px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: ${props => getPrimaryColor(props.type)};
|
|
||||||
background: ${props => getSecondaryColor(props.type)};
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Body = styled.div`
|
const Body = styled.div`
|
||||||
@ -41,7 +48,6 @@ const Body = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const Message = styled.div`
|
const Message = styled.div`
|
||||||
padding-bottom: 13px;
|
|
||||||
font-size: ${FONT_SIZE.SMALL};
|
font-size: ${FONT_SIZE.SMALL};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -52,10 +58,9 @@ const Title = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const CloseClick = styled.div`
|
const CloseClick = styled.div`
|
||||||
position: absolute;
|
margin-left: 24px;
|
||||||
right: 0;
|
align-self: flex-start;
|
||||||
top: 0;
|
cursor: pointer;
|
||||||
padding: 20px 10px 0 0;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledIcon = styled(Icon)`
|
const StyledIcon = styled(Icon)`
|
||||||
@ -85,7 +90,6 @@ const ActionContent = styled.div`
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: right;
|
justify-content: right;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
padding-bottom: 14px;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Notification = (props: Props): React$Element<string> => {
|
const Notification = (props: Props): React$Element<string> => {
|
||||||
@ -93,42 +97,45 @@ const Notification = (props: Props): React$Element<string> => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Wrapper className={props.className} type={props.type}>
|
<Wrapper className={props.className} type={props.type}>
|
||||||
{props.loading && <Loader size={50} /> }
|
<Content>
|
||||||
{props.cancelable && (
|
{props.loading && <Loader size={50} /> }
|
||||||
<CloseClick onClick={() => close()}>
|
<Body>
|
||||||
<Icon
|
<IconWrapper>
|
||||||
color={getPrimaryColor(props.type)}
|
<StyledIcon
|
||||||
icon={icons.CLOSE}
|
color={getPrimaryColor(props.type)}
|
||||||
size={20}
|
icon={getIcon(props.type)}
|
||||||
/>
|
/>
|
||||||
</CloseClick>
|
</IconWrapper>
|
||||||
)}
|
<Texts>
|
||||||
<Body>
|
<Title>{ props.title }</Title>
|
||||||
<IconWrapper>
|
{ props.message ? <Message>{props.message}</Message> : '' }
|
||||||
<StyledIcon
|
</Texts>
|
||||||
color={getPrimaryColor(props.type)}
|
</Body>
|
||||||
icon={getIcon(props.type)}
|
<AdditionalContent>
|
||||||
/>
|
{props.actions && props.actions.length > 0 && (
|
||||||
</IconWrapper>
|
<ActionContent>
|
||||||
<Texts>
|
{props.actions.map(action => (
|
||||||
<Title>{ props.title }</Title>
|
<NotificationButton
|
||||||
{ props.message ? <Message>{props.message}</Message> : '' }
|
key={action.label}
|
||||||
</Texts>
|
type={props.type}
|
||||||
</Body>
|
isLoading={props.isActionInProgress}
|
||||||
<AdditionalContent>
|
onClick={() => { close(); action.callback(); }}
|
||||||
{props.actions && props.actions.length > 0 && (
|
>{action.label}
|
||||||
<ActionContent>
|
</NotificationButton>
|
||||||
{props.actions.map(action => (
|
))}
|
||||||
<NotificationButton
|
</ActionContent>
|
||||||
key={action.label}
|
)}
|
||||||
type={props.type}
|
</AdditionalContent>
|
||||||
onClick={() => { close(); action.callback(); }}
|
{props.cancelable && (
|
||||||
>{action.label}
|
<CloseClick onClick={() => close()}>
|
||||||
</NotificationButton>
|
<Icon
|
||||||
))}
|
color={getPrimaryColor(props.type)}
|
||||||
</ActionContent>
|
icon={icons.CLOSE}
|
||||||
|
size={20}
|
||||||
|
/>
|
||||||
|
</CloseClick>
|
||||||
)}
|
)}
|
||||||
</AdditionalContent>
|
</Content>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -11,17 +11,16 @@ const styles = isSearchable => ({
|
|||||||
width: '100%',
|
width: '100%',
|
||||||
color: colors.TEXT_SECONDARY,
|
color: colors.TEXT_SECONDARY,
|
||||||
}),
|
}),
|
||||||
control: (base, { isDisabled }) => ({
|
control: (base, { isDisabled, isFocused }) => ({
|
||||||
...base,
|
...base,
|
||||||
minHeight: 'initial',
|
minHeight: 'initial',
|
||||||
height: '40px',
|
height: '40px',
|
||||||
borderRadius: '2px',
|
borderRadius: '2px',
|
||||||
borderColor: colors.DIVIDER,
|
borderColor: isFocused ? colors.INPUT_FOCUSED_BORDER : colors.DIVIDER,
|
||||||
boxShadow: 'none',
|
boxShadow: isFocused ? `0 0px 6px 0 ${colors.INPUT_FOCUSED_SHADOW}` : 'none',
|
||||||
background: isDisabled ? colors.LANDING : colors.WHITE,
|
background: isDisabled ? colors.LANDING : colors.WHITE,
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
cursor: isSearchable ? 'text' : 'pointer',
|
cursor: isSearchable ? 'text' : 'pointer',
|
||||||
borderColor: colors.DIVIDER,
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
indicatorSeparator: () => ({
|
indicatorSeparator: () => ({
|
||||||
|
@ -67,6 +67,11 @@ const StyledTextarea = styled(Textarea)`
|
|||||||
color: ${colors.TEXT_SECONDARY};
|
color: ${colors.TEXT_SECONDARY};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
border-color: ${colors.INPUT_FOCUSED_BORDER};
|
||||||
|
box-shadow: 0 0px 6px 0 ${colors.INPUT_FOCUSED_SHADOW};
|
||||||
|
}
|
||||||
|
|
||||||
&:disabled {
|
&:disabled {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
background: ${colors.GRAY_LIGHT};
|
background: ${colors.GRAY_LIGHT};
|
||||||
@ -109,6 +114,7 @@ const TopLabel = styled.span`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const BottomText = styled.span`
|
const BottomText = styled.span`
|
||||||
|
margin-top: 10px;
|
||||||
font-size: ${FONT_SIZE.SMALL};
|
font-size: ${FONT_SIZE.SMALL};
|
||||||
color: ${props => (props.color ? props.color : colors.TEXT_SECONDARY)};
|
color: ${props => (props.color ? props.color : colors.TEXT_SECONDARY)};
|
||||||
`;
|
`;
|
||||||
|
@ -31,21 +31,24 @@ const Tooltip = ({
|
|||||||
content,
|
content,
|
||||||
readMoreLink,
|
readMoreLink,
|
||||||
children,
|
children,
|
||||||
|
enterDelayMs,
|
||||||
}) => (
|
}) => (
|
||||||
<Wrapper className={className}>
|
<Wrapper className={className}>
|
||||||
<RcTooltip
|
<RcTooltip
|
||||||
arrowContent={<div className="rc-tooltip-arrow-inner" />}
|
arrowContent={<div className="rc-tooltip-arrow-inner" />}
|
||||||
placement={placement}
|
placement={placement}
|
||||||
|
mouseEnterDelay={enterDelayMs || 0}
|
||||||
overlay={() => (
|
overlay={() => (
|
||||||
<ContentWrapper>
|
<ContentWrapper>
|
||||||
<Content maxWidth={maxWidth}>{content}</Content>
|
<Content maxWidth={maxWidth}>{content}</Content>
|
||||||
{readMoreLink && (
|
{readMoreLink && (
|
||||||
<Link href={readMoreLink}>
|
<Link href={readMoreLink}>
|
||||||
<ReadMore>Read more</ReadMore>
|
<ReadMore>Learn more</ReadMore>
|
||||||
</Link>
|
</Link>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</ContentWrapper>)}
|
</ContentWrapper>
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</RcTooltip>
|
</RcTooltip>
|
||||||
@ -65,6 +68,7 @@ Tooltip.propTypes = {
|
|||||||
PropTypes.string,
|
PropTypes.string,
|
||||||
]),
|
]),
|
||||||
readMoreLink: PropTypes.string,
|
readMoreLink: PropTypes.string,
|
||||||
|
enterDelayMs: PropTypes.number,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Tooltip;
|
export default Tooltip;
|
||||||
|
74
src/components/images/DeviceIcon/index.js
Normal file
74
src/components/images/DeviceIcon/index.js
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
/* @flow */
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import COLORS from 'config/colors';
|
||||||
|
import ICONS from 'config/icons';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import type { TrezorDevice } from 'flowtype';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
device: TrezorDevice,
|
||||||
|
size?: number,
|
||||||
|
color?: string,
|
||||||
|
hoverColor?: string,
|
||||||
|
onClick?: any,
|
||||||
|
}
|
||||||
|
|
||||||
|
const SvgWrapper = styled.svg`
|
||||||
|
:hover {
|
||||||
|
path {
|
||||||
|
fill: ${props => props.hoverColor}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Path = styled.path`
|
||||||
|
fill: ${props => props.color};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const getDeviceIcon = (majorVersion: number) => {
|
||||||
|
switch (majorVersion) {
|
||||||
|
case 1:
|
||||||
|
return ICONS.T1;
|
||||||
|
case 2:
|
||||||
|
return ICONS.T2;
|
||||||
|
default:
|
||||||
|
return ICONS.T2;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const DeviceIcon = ({
|
||||||
|
device,
|
||||||
|
size = 32,
|
||||||
|
color = COLORS.TEXT_SECONDARY,
|
||||||
|
hoverColor,
|
||||||
|
onClick,
|
||||||
|
}: Props) => {
|
||||||
|
const majorVersion = device.features ? device.features.major_version : 2;
|
||||||
|
return (
|
||||||
|
<SvgWrapper
|
||||||
|
hoverColor={hoverColor}
|
||||||
|
width={`${size}`}
|
||||||
|
height={`${size}`}
|
||||||
|
viewBox="0 0 1024 1024"
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
<Path
|
||||||
|
key={majorVersion}
|
||||||
|
color={color}
|
||||||
|
d={getDeviceIcon(majorVersion)}
|
||||||
|
/>
|
||||||
|
</SvgWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
DeviceIcon.propTypes = {
|
||||||
|
device: PropTypes.object,
|
||||||
|
size: PropTypes.number,
|
||||||
|
color: PropTypes.string,
|
||||||
|
hoverColor: PropTypes.string,
|
||||||
|
onClick: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DeviceIcon;
|
@ -15,8 +15,9 @@ const Img = styled.img`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const TrezorImage = ({ model }: Props) => {
|
const TrezorImage = ({ model }: Props) => {
|
||||||
|
const imageName = model === 'One' ? 1 : model;
|
||||||
// $FlowIssue: `require` must be a string literal.
|
// $FlowIssue: `require` must be a string literal.
|
||||||
const src = require(`./images/trezor-${model}.png`); // eslint-disable-line
|
const src = require(`./images/trezor-${imageName}.png`); // eslint-disable-line
|
||||||
return (
|
return (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<Img model={model} src={src} />
|
<Img model={model} src={src} />
|
||||||
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import colors from 'config/colors';
|
import colors from 'config/colors';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
import ICONS from 'config/icons';
|
||||||
|
|
||||||
const SvgWrapper = styled.svg`
|
const SvgWrapper = styled.svg`
|
||||||
:hover {
|
:hover {
|
||||||
@ -15,9 +16,6 @@ const Path = styled.path`
|
|||||||
fill: ${props => props.color};
|
fill: ${props => props.color};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const HIDDEN = 'M25.421,17.28l-3.167-8.8C22.175,8.24,21.858,8,21.542,8h-2.375c-0.475,0-0.792,0.32-0.792,0.8c0,0.48,0.317,0.8,0.792,0.8h1.821l2.612,7.2h-6.017h-3.167H8.4l2.613-7.2h1.821c0.475,0,0.792-0.32,0.792-0.8c0-0.48-0.317-0.8-0.792-0.8h-2.375c-0.317,0-0.633,0.24-0.712,0.56l-3.167,8.8C6.5,17.36,6.5,17.52,6.5,17.6l0,0l0,0l0,0v4c0,1.36,1.029,2.4,2.375,2.4h3.958c1.346,0,2.375-1.04,2.375-2.4v-3.2h1.583v3.2c0,1.36,1.029,2.4,2.375,2.4h3.958c1.346,0,2.375-1.04,2.375-2.4v-4l0,0l0,0l0,0C25.5,17.52,25.5,17.36,25.421,17.28z';
|
|
||||||
export const STANDARD = 'M23.333,10.667H10.667H10c-0.367,0-0.667-0.299-0.667-0.667S9.633,9.333,10,9.333h10V10h1.333V8.667C21.333,8.299,21.035,8,20.667,8H10c-1.105,0-2,0.895-2,2v11.333C8,22.806,9.194,24,10.667,24h12.667C23.701,24,24,23.701,24,23.333v-12C24,10.965,23.701,10.667,23.333,10.667z M20,18.667c-0.737,0-1.333-0.597-1.333-1.333C18.667,16.597,19.263,16,20,16s1.333,0.597,1.333,1.333C21.333,18.07,20.737,18.667,20,18.667z';
|
|
||||||
|
|
||||||
const Icon = ({
|
const Icon = ({
|
||||||
type = 'standard',
|
type = 'standard',
|
||||||
size = 24,
|
size = 24,
|
||||||
@ -29,13 +27,13 @@ const Icon = ({
|
|||||||
hoverColor={hoverColor}
|
hoverColor={hoverColor}
|
||||||
width={`${size}`}
|
width={`${size}`}
|
||||||
height={`${size}`}
|
height={`${size}`}
|
||||||
viewBox="0 0 32 32"
|
viewBox="0 0 1024 1024"
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
<Path
|
<Path
|
||||||
key={type}
|
key={type}
|
||||||
color={color}
|
color={color}
|
||||||
d={type === 'hidden' ? HIDDEN : STANDARD}
|
d={type === 'hidden' ? ICONS.WALLET_HIDDEN : ICONS.WALLET_STANDARD}
|
||||||
/>
|
/>
|
||||||
</SvgWrapper>
|
</SvgWrapper>
|
||||||
);
|
);
|
||||||
|
@ -59,6 +59,11 @@ const StyledInput = styled.input`
|
|||||||
background-color: ${colors.WHITE};
|
background-color: ${colors.WHITE};
|
||||||
transition: ${TRANSITION.HOVER};
|
transition: ${TRANSITION.HOVER};
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
border-color: ${colors.INPUT_FOCUSED_BORDER};
|
||||||
|
box-shadow: 0 0px 6px 0 ${colors.INPUT_FOCUSED_SHADOW};
|
||||||
|
}
|
||||||
|
|
||||||
&:disabled {
|
&:disabled {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
background: ${colors.GRAY_LIGHT};
|
background: ${colors.GRAY_LIGHT};
|
||||||
@ -183,7 +188,7 @@ class Input extends PureComponent {
|
|||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
height={this.props.height}
|
height={this.props.height}
|
||||||
trezorAction={this.props.trezorAction}
|
trezorAction={this.props.trezorAction}
|
||||||
hasIcon={this.getIcon(this.props.state).length > 0}
|
hasIcon={this.props.icon || this.getIcon(this.props.state).length > 0}
|
||||||
ref={this.props.innerRef}
|
ref={this.props.innerRef}
|
||||||
hasAddon={!!this.props.sideAddons}
|
hasAddon={!!this.props.sideAddons}
|
||||||
type={this.props.type}
|
type={this.props.type}
|
||||||
|
175
src/components/modals/QrModal/index.js
Normal file
175
src/components/modals/QrModal/index.js
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
/* @flow */
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import QrReader from 'react-qr-reader';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
import colors from 'config/colors';
|
||||||
|
import icons from 'config/icons';
|
||||||
|
|
||||||
|
import { H2 } from 'components/Heading';
|
||||||
|
import P from 'components/Paragraph';
|
||||||
|
import Icon from 'components/Icon';
|
||||||
|
import Link from 'components/Link';
|
||||||
|
|
||||||
|
import { parseUri } from 'utils/cryptoUriParser';
|
||||||
|
import type { parsedURI } from 'utils/cryptoUriParser';
|
||||||
|
import type { Props as BaseProps } from '../Container';
|
||||||
|
|
||||||
|
const Wrapper = styled.div`
|
||||||
|
width: 90vw;
|
||||||
|
max-width: 450px;
|
||||||
|
padding: 30px 0px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Padding = styled.div`
|
||||||
|
padding: 0px 48px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const CloseLink = styled(Link)`
|
||||||
|
position: absolute;
|
||||||
|
right: 15px;
|
||||||
|
top: 15px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const CameraPlaceholder = styled(P)`
|
||||||
|
text-align: center;
|
||||||
|
padding: 10px 0;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Error = styled.div`
|
||||||
|
padding: 10px 0;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ErrorTitle = styled(P)`
|
||||||
|
text-align: center;
|
||||||
|
color: ${colors.ERROR_PRIMARY};
|
||||||
|
`;
|
||||||
|
const ErrorMessage = styled.span`
|
||||||
|
text-align: center;
|
||||||
|
color: ${colors.TEXT_PRIMARY};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledQrReader = styled(QrReader)`
|
||||||
|
padding: 10px 0;
|
||||||
|
`;
|
||||||
|
|
||||||
|
// TODO fix types
|
||||||
|
type Props = {
|
||||||
|
onScan: (data: parsedURI) => any,
|
||||||
|
onError?: (error: any) => any,
|
||||||
|
onCancel?: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onCancel'>;
|
||||||
|
}
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
readerLoaded: boolean,
|
||||||
|
error: any,
|
||||||
|
};
|
||||||
|
|
||||||
|
class QrModal extends React.Component<Props, State> {
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
readerLoaded: false,
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoad = () => {
|
||||||
|
this.setState({
|
||||||
|
readerLoaded: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleScan = (data: string) => {
|
||||||
|
if (data) {
|
||||||
|
try {
|
||||||
|
const parsedUri = parseUri(data);
|
||||||
|
if (parsedUri) {
|
||||||
|
this.props.onScan(parsedUri);
|
||||||
|
// reset error
|
||||||
|
this.setState({
|
||||||
|
error: null,
|
||||||
|
});
|
||||||
|
// close window
|
||||||
|
this.handleCancel();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.handleError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleError = (err: any) => {
|
||||||
|
// log thrown error
|
||||||
|
console.error(err);
|
||||||
|
if (this.props.onError) {
|
||||||
|
this.props.onError(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err.name === 'NotAllowedError' || err.name === 'PermissionDeniedError'
|
||||||
|
|| err.name === 'NotReadableError' || err.name === 'TrackStartError') {
|
||||||
|
this.setState({
|
||||||
|
error: 'Permission to access the camera was denied.',
|
||||||
|
});
|
||||||
|
} else if (err.name === 'NotFoundError' || err.name === 'DevicesNotFoundError') {
|
||||||
|
this.setState({
|
||||||
|
error: 'The camera was not recognized.',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
error: 'Unknown error. See console logs for details.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCancel = () => {
|
||||||
|
if (this.props.onCancel) {
|
||||||
|
this.props.onCancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Wrapper>
|
||||||
|
<CloseLink onClick={this.handleCancel}>
|
||||||
|
<Icon
|
||||||
|
size={24}
|
||||||
|
color={colors.TEXT_SECONDARY}
|
||||||
|
icon={icons.CLOSE}
|
||||||
|
/>
|
||||||
|
</CloseLink>
|
||||||
|
<Padding>
|
||||||
|
<H2>Scan QR code</H2>
|
||||||
|
{!this.state.readerLoaded && !this.state.error && <CameraPlaceholder>Waiting for camera...</CameraPlaceholder>}
|
||||||
|
{this.state.error && (
|
||||||
|
<Error>
|
||||||
|
<ErrorTitle>Oops! Something went wrong!</ErrorTitle>
|
||||||
|
<ErrorMessage>{this.state.error.toString()}</ErrorMessage>
|
||||||
|
</Error>
|
||||||
|
)}
|
||||||
|
</Padding>
|
||||||
|
{!this.state.error && (
|
||||||
|
<StyledQrReader
|
||||||
|
delay={500}
|
||||||
|
onError={this.handleError}
|
||||||
|
onScan={this.handleScan}
|
||||||
|
onLoad={this.onLoad}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
showViewFinder={false}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QrModal.propTypes = {
|
||||||
|
onScan: PropTypes.func.isRequired,
|
||||||
|
onError: PropTypes.func,
|
||||||
|
onCancel: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default QrModal;
|
@ -1,10 +1,15 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { H3 } from 'components/Heading';
|
import { H3 } from 'components/Heading';
|
||||||
import ICONS from 'config/icons';
|
import DeviceIcon from 'components/images/DeviceIcon';
|
||||||
import Icon from 'components/Icon';
|
import type { TrezorDevice } from 'flowtype';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
device: TrezorDevice;
|
||||||
|
}
|
||||||
|
|
||||||
const Wrapper = styled.div``;
|
const Wrapper = styled.div``;
|
||||||
|
|
||||||
@ -12,13 +17,18 @@ const Header = styled.div`
|
|||||||
padding: 48px;
|
padding: 48px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ConfirmAction = () => (
|
const ConfirmAction = (props: Props) => (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<Header>
|
<Header>
|
||||||
<Icon icon={ICONS.T1} size={100} />
|
<DeviceIcon device={props.device} size={100} />
|
||||||
<H3>Confirm action on your Trezor</H3>
|
<H3>Confirm action on your Trezor</H3>
|
||||||
</Header>
|
</Header>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ConfirmAction.propTypes = {
|
||||||
|
device: PropTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export default ConfirmAction;
|
export default ConfirmAction;
|
@ -13,11 +13,11 @@ import P from 'components/Paragraph';
|
|||||||
import type { Props } from '../../Container';
|
import type { Props } from '../../Container';
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
width: 390px;
|
max-width: 390px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Header = styled.div`
|
const Header = styled.div`
|
||||||
padding: 24px 48px;
|
padding: 30px 48px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Content = styled.div`
|
const Content = styled.div`
|
||||||
|
85
src/components/modals/confirm/NoBackup/index.js
Normal file
85
src/components/modals/confirm/NoBackup/index.js
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
/* @flow */
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
import icons from 'config/icons';
|
||||||
|
import { getOldWalletUrl } from 'utils/url';
|
||||||
|
import colors from 'config/colors';
|
||||||
|
|
||||||
|
import { H2 } from 'components/Heading';
|
||||||
|
import P from 'components/Paragraph';
|
||||||
|
import Icon from 'components/Icon';
|
||||||
|
import Button from 'components/Button';
|
||||||
|
import Link from 'components/Link';
|
||||||
|
|
||||||
|
import type { TrezorDevice } from 'flowtype';
|
||||||
|
import type { Props as BaseProps } from '../../Container';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onReceiveConfirmation: $ElementType<$ElementType<BaseProps, 'modalActions'>, 'onReceiveConfirmation'>;
|
||||||
|
device: ?TrezorDevice;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Wrapper = styled.div`
|
||||||
|
max-width: 370px;
|
||||||
|
padding: 30px 48px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledLink = styled(Link)`
|
||||||
|
position: absolute;
|
||||||
|
right: 15px;
|
||||||
|
top: 15px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const BackupButton = styled(Button)`
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ProceedButton = styled(Button)`
|
||||||
|
background: transparent;
|
||||||
|
border-color: ${colors.WARNING_PRIMARY};
|
||||||
|
color: ${colors.WARNING_PRIMARY};
|
||||||
|
|
||||||
|
&:focus,
|
||||||
|
&:hover,
|
||||||
|
&:active {
|
||||||
|
color: ${colors.WHITE};
|
||||||
|
background: ${colors.WARNING_PRIMARY};
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledP = styled(P)`
|
||||||
|
padding-bottom: 20px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Row = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Confirmation = (props: Props) => (
|
||||||
|
<Wrapper>
|
||||||
|
<StyledLink onClick={() => props.onReceiveConfirmation(false)}>
|
||||||
|
<Icon size={24} color={colors.TEXT_SECONDARY} icon={icons.CLOSE} />
|
||||||
|
</StyledLink>
|
||||||
|
<H2>Your Trezor is not backed up</H2>
|
||||||
|
<Icon size={48} color={colors.WARNING_PRIMARY} icon={icons.WARNING} />
|
||||||
|
<StyledP isSmaller>If your device is ever lost or damaged, your funds will be lost. Backup your device first, to protect your coins against such events.</StyledP>
|
||||||
|
<Row>
|
||||||
|
<Link href={`${getOldWalletUrl(props.device)}/?backup`}>
|
||||||
|
<BackupButton onClick={() => props.onReceiveConfirmation(false)}>Create a backup in 3 minutes</BackupButton>
|
||||||
|
</Link>
|
||||||
|
<ProceedButton isWhite onClick={() => props.onReceiveConfirmation(true)}>Show address, I will take the risk</ProceedButton>
|
||||||
|
</Row>
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
|
|
||||||
|
Confirmation.propTypes = {
|
||||||
|
onReceiveConfirmation: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Confirmation;
|
@ -4,12 +4,11 @@ import React from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import icons from 'config/icons';
|
|
||||||
import colors from 'config/colors';
|
import colors from 'config/colors';
|
||||||
import { LINE_HEIGHT, FONT_SIZE } from 'config/variables';
|
import { LINE_HEIGHT, FONT_SIZE, FONT_WEIGHT } from 'config/variables';
|
||||||
|
|
||||||
import P from 'components/Paragraph';
|
import P from 'components/Paragraph';
|
||||||
import Icon from 'components/Icon';
|
import DeviceIcon from 'components/images/DeviceIcon';
|
||||||
import { H3 } from 'components/Heading';
|
import { H3 } from 'components/Heading';
|
||||||
|
|
||||||
import type { TrezorDevice, State } from 'flowtype';
|
import type { TrezorDevice, State } from 'flowtype';
|
||||||
@ -20,32 +19,45 @@ type Props = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
width: 390px;
|
max-width: 390px;
|
||||||
padding: 12px 10px;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Header = styled.div`
|
const Header = styled.div`
|
||||||
padding: 24px 48px;
|
padding: 30px 48px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Content = styled.div`
|
const Content = styled.div`
|
||||||
border-top: 1px solid ${colors.DIVIDER};
|
border-top: 1px solid ${colors.DIVIDER};
|
||||||
background: ${colors.MAIN};
|
background: ${colors.MAIN};
|
||||||
padding: 24px 48px;
|
padding: 30px 48px;
|
||||||
|
border-radius: 4px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledP = styled(P)`
|
const StyledP = styled(P)`
|
||||||
|
padding-bottom: 20px;
|
||||||
|
color: ${colors.TEXT};
|
||||||
|
font-size: ${FONT_SIZE.BASE};
|
||||||
|
&:last-child {
|
||||||
|
padding-bottom: 0px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Address = styled(StyledP)`
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
padding: 5px 0;
|
|
||||||
line-height: ${LINE_HEIGHT.SMALL};
|
line-height: ${LINE_HEIGHT.SMALL};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Label = styled.div`
|
const Label = styled.div`
|
||||||
padding-top: 5px;
|
padding-bottom: 6px;
|
||||||
|
font-weight: ${FONT_WEIGHT.MEDIUM};
|
||||||
font-size: ${FONT_SIZE.SMALL};
|
font-size: ${FONT_SIZE.SMALL};
|
||||||
color: ${colors.TEXT_SECONDARY};
|
color: ${colors.TEXT_SECONDARY};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const FeeLevelName = styled(StyledP)`
|
||||||
|
padding-bottom: 0px;
|
||||||
|
`;
|
||||||
|
|
||||||
const ConfirmSignTx = (props: Props) => {
|
const ConfirmSignTx = (props: Props) => {
|
||||||
const {
|
const {
|
||||||
amount,
|
amount,
|
||||||
@ -58,17 +70,18 @@ const ConfirmSignTx = (props: Props) => {
|
|||||||
return (
|
return (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<Header>
|
<Header>
|
||||||
<Icon icon={icons.T1} size={60} color={colors.TEXT_SECONDARY} />
|
<DeviceIcon device={props.device} size={60} color={colors.TEXT_SECONDARY} />
|
||||||
<H3>Confirm transaction on { props.device.label } device</H3>
|
<H3>Confirm transaction on { props.device.label } device</H3>
|
||||||
<P isSmaller>Details are shown on display</P>
|
<P isSmaller>Details are shown on display</P>
|
||||||
</Header>
|
</Header>
|
||||||
<Content>
|
<Content>
|
||||||
<Label>Send</Label>
|
<Label>Send</Label>
|
||||||
<P>{`${amount} ${currency}` }</P>
|
<StyledP>{`${amount} ${currency}` }</StyledP>
|
||||||
<Label>To</Label>
|
<Label>To</Label>
|
||||||
<StyledP>{ address }</StyledP>
|
<Address>{ address }</Address>
|
||||||
<Label>Fee</Label>
|
<Label>Fee</Label>
|
||||||
<P>{ selectedFeeLevel.label }</P>
|
<FeeLevelName>{selectedFeeLevel.value}</FeeLevelName>
|
||||||
|
<StyledP>{ selectedFeeLevel.label }</StyledP>
|
||||||
</Content>
|
</Content>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
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 from 'styled-components';
|
||||||
|
import { getOldWalletUrl } from 'utils/url';
|
||||||
import icons from 'config/icons';
|
import icons from 'config/icons';
|
||||||
import colors from 'config/colors';
|
import colors from 'config/colors';
|
||||||
|
|
||||||
@ -30,22 +30,51 @@ const StyledLink = styled(Link)`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
width: 370px;
|
max-width: 370px;
|
||||||
padding: 24px 48px;
|
padding: 30px 0px;
|
||||||
|
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Content = styled.div`
|
||||||
|
padding: 0px 48px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledP = styled(P)`
|
const StyledP = styled(P)`
|
||||||
padding: 10px 0px;
|
padding-bottom: 20px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Divider = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
height: 1px;
|
||||||
|
background: ${colors.DIVIDER};
|
||||||
|
margin: 20px 0px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Row = styled.div`
|
const Row = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 10px 0;
|
|
||||||
|
Button + Button {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledButton = styled(Button)`
|
const BackupButton = styled(Button)`
|
||||||
margin: 0 0 10px 0;
|
width: 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const WarnButton = styled(Button)`
|
||||||
|
background: transparent;
|
||||||
|
border-color: ${colors.WARNING_PRIMARY};
|
||||||
|
color: ${colors.WARNING_PRIMARY};
|
||||||
|
|
||||||
|
&:focus,
|
||||||
|
&:hover,
|
||||||
|
&:active {
|
||||||
|
color: ${colors.WHITE};
|
||||||
|
background: ${colors.WARNING_PRIMARY};
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
class ConfirmUnverifiedAddress extends PureComponent<Props> {
|
class ConfirmUnverifiedAddress extends PureComponent<Props> {
|
||||||
@ -87,26 +116,48 @@ class ConfirmUnverifiedAddress extends PureComponent<Props> {
|
|||||||
let claim: string;
|
let claim: string;
|
||||||
|
|
||||||
if (!device.connected) {
|
if (!device.connected) {
|
||||||
deviceStatus = `${device.label} is not connected`;
|
deviceStatus = `Device ${device.label} is not connected`;
|
||||||
claim = 'Please connect your device';
|
claim = 'Please connect your device';
|
||||||
} else {
|
} else {
|
||||||
// corner-case where device is connected but it is unavailable because it was created with different "passphrase_protection" settings
|
// corner-case where device is connected but it is unavailable because it was created with different "passphrase_protection" settings
|
||||||
const enable: string = device.features && device.features.passphrase_protection ? 'enable' : 'disable';
|
const enable: string = device.features && device.features.passphrase_protection ? 'enable' : 'disable';
|
||||||
deviceStatus = `${device.label} is unavailable`;
|
deviceStatus = `Device ${device.label} is unavailable`;
|
||||||
claim = `Please ${enable} passphrase settings`;
|
claim = `Please ${enable} passphrase settings`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const needsBackup = device.features && device.features.needs_backup;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<StyledLink onClick={onCancel}>
|
<Content>
|
||||||
<Icon size={20} color={colors.TEXT_SECONDARY} icon={icons.CLOSE} />
|
<StyledLink onClick={onCancel}>
|
||||||
</StyledLink>
|
<Icon size={24} color={colors.TEXT_SECONDARY} icon={icons.CLOSE} />
|
||||||
<H2>{ deviceStatus }</H2>
|
</StyledLink>
|
||||||
<StyledP isSmaller>To prevent phishing attacks, you should verify the address on your Trezor first. { claim } to continue with the verification process.</StyledP>
|
<H2>{ deviceStatus }</H2>
|
||||||
<Row>
|
<StyledP isSmaller>To prevent phishing attacks, you should verify the address on your Trezor first. { claim } to continue with the verification process.</StyledP>
|
||||||
<StyledButton onClick={() => (!account ? this.verifyAddress() : 'false')}>Try again</StyledButton>
|
</Content>
|
||||||
<StyledButton isWhite onClick={() => this.showUnverifiedAddress()}>Show unverified address</StyledButton>
|
<Content>
|
||||||
</Row>
|
<Row>
|
||||||
|
<Button onClick={() => (!account ? this.verifyAddress() : 'false')}>Try again</Button>
|
||||||
|
<WarnButton isWhite onClick={() => this.showUnverifiedAddress()}>Show unverified address</WarnButton>
|
||||||
|
</Row>
|
||||||
|
</Content>
|
||||||
|
{needsBackup && <Divider />}
|
||||||
|
{needsBackup && (
|
||||||
|
<>
|
||||||
|
<Content>
|
||||||
|
<H2>Device {device.label} is not backed up</H2>
|
||||||
|
<StyledP isSmaller>If your device is ever lost or damaged, your funds will be lost. Backup your device first, to protect your coins against such events.</StyledP>
|
||||||
|
</Content>
|
||||||
|
<Content>
|
||||||
|
<Row>
|
||||||
|
<Link href={`${getOldWalletUrl(device)}/?backup`}>
|
||||||
|
<BackupButton>Create a backup in 3 minutes</BackupButton>
|
||||||
|
</Link>
|
||||||
|
</Row>
|
||||||
|
</Content>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ const StyledLink = styled(Link)`
|
|||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
width: 360px;
|
width: 360px;
|
||||||
padding: 24px 48px;
|
padding: 30px 48px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Column = styled.div`
|
const Column = styled.div`
|
||||||
@ -138,7 +138,7 @@ class DuplicateDevice extends PureComponent<Props, State> {
|
|||||||
return (
|
return (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<StyledLink onClick={onCancel}>
|
<StyledLink onClick={onCancel}>
|
||||||
<Icon size={20} color={colors.TEXT_SECONDARY} icon={icons.CLOSE} />
|
<Icon size={24} color={colors.TEXT_SECONDARY} icon={icons.CLOSE} />
|
||||||
</StyledLink>
|
</StyledLink>
|
||||||
<H3>Clone { device.label }?</H3>
|
<H3>Clone { device.label }?</H3>
|
||||||
<StyledP isSmaller>This will create new instance of device which can be used with different passphrase</StyledP>
|
<StyledP isSmaller>This will create new instance of device which can be used with different passphrase</StyledP>
|
||||||
|
@ -4,7 +4,7 @@ import React, { PureComponent } from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import { H3 } from 'components/Heading';
|
import { H2 } from 'components/Heading';
|
||||||
import P from 'components/Paragraph';
|
import P from 'components/Paragraph';
|
||||||
import Button from 'components/Button';
|
import Button from 'components/Button';
|
||||||
|
|
||||||
@ -19,21 +19,20 @@ type Props = {
|
|||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
width: 360px;
|
width: 360px;
|
||||||
padding: 24px 48px;
|
padding: 30px 48px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledP = styled(P)`
|
const StyledP = styled(P)`
|
||||||
padding: 7px 0px;
|
padding: 20px 0px;
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledButton = styled(Button)`
|
|
||||||
margin: 0 0 10px 0;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Row = styled.div`
|
const Row = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 10px 0;
|
|
||||||
|
Button + Button {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
class ForgetDevice extends PureComponent<Props> {
|
class ForgetDevice extends PureComponent<Props> {
|
||||||
@ -62,11 +61,11 @@ class ForgetDevice extends PureComponent<Props> {
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<H3>Forget { this.props.device.instanceLabel }?</H3>
|
<H2>Forget { this.props.device.instanceLabel }?</H2>
|
||||||
<StyledP isSmaller>Forgetting only removes the device from the list on the left, your coins are still safe and you can access them by reconnecting your Trezor again.</StyledP>
|
<StyledP isSmaller>Forgetting only removes the device from the list on the left, your coins are still safe and you can access them by reconnecting your Trezor again.</StyledP>
|
||||||
<Row>
|
<Row>
|
||||||
<StyledButton onClick={() => this.forget()}>Forget</StyledButton>
|
<Button onClick={() => this.forget()}>Forget</Button>
|
||||||
<StyledButton isWhite onClick={this.props.onCancel}>Don't forget</StyledButton>
|
<Button isWhite onClick={this.props.onCancel}>Don't forget</Button>
|
||||||
</Row>
|
</Row>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
|
@ -31,12 +31,12 @@ const ButtonContent = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledP = styled(P)`
|
const StyledP = styled(P)`
|
||||||
padding: 10px 0;
|
padding: 20px 0;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
width: 360px;
|
width: 360px;
|
||||||
padding: 24px 48px;
|
padding: 30px 48px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Text = styled.div`
|
const Text = styled.div`
|
||||||
@ -46,10 +46,10 @@ const Text = styled.div`
|
|||||||
const Column = styled.div`
|
const Column = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledButton = styled(Button)`
|
Button + Button {
|
||||||
margin: 5px 0;
|
margin-top: 10px;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledLoader = styled(Loader)`
|
const StyledLoader = styled(Loader)`
|
||||||
@ -128,7 +128,7 @@ class RememberDevice extends PureComponent<Props, State> {
|
|||||||
<H3>Forget {label}?</H3>
|
<H3>Forget {label}?</H3>
|
||||||
<StyledP isSmaller>Would you like Trezor Wallet to forget your { devicePlural }, so that it is still visible even while disconnected?</StyledP>
|
<StyledP isSmaller>Would you like Trezor Wallet to forget your { devicePlural }, so that it is still visible even while disconnected?</StyledP>
|
||||||
<Column>
|
<Column>
|
||||||
<StyledButton onClick={() => this.forget()}>
|
<Button onClick={() => this.forget()}>
|
||||||
<ButtonContent>
|
<ButtonContent>
|
||||||
<Text>Forget</Text>
|
<Text>Forget</Text>
|
||||||
<StyledLoader
|
<StyledLoader
|
||||||
@ -138,12 +138,12 @@ class RememberDevice extends PureComponent<Props, State> {
|
|||||||
text={this.state.countdown.toString()}
|
text={this.state.countdown.toString()}
|
||||||
/>
|
/>
|
||||||
</ButtonContent>
|
</ButtonContent>
|
||||||
</StyledButton>
|
</Button>
|
||||||
<StyledButton
|
<Button
|
||||||
isWhite
|
isWhite
|
||||||
onClick={() => onRememberDevice(device)}
|
onClick={() => onRememberDevice(device)}
|
||||||
>Remember
|
>Remember
|
||||||
</StyledButton>
|
</Button>
|
||||||
</Column>
|
</Column>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
|
@ -7,7 +7,7 @@ import styled, { css } from 'styled-components';
|
|||||||
import icons from 'config/icons';
|
import icons from 'config/icons';
|
||||||
import colors from 'config/colors';
|
import colors from 'config/colors';
|
||||||
|
|
||||||
import { H3 } from 'components/Heading';
|
import { H2 } from 'components/Heading';
|
||||||
import P from 'components/Paragraph';
|
import P from 'components/Paragraph';
|
||||||
import Button from 'components/Button';
|
import Button from 'components/Button';
|
||||||
import Tooltip from 'components/Tooltip';
|
import Tooltip from 'components/Tooltip';
|
||||||
@ -25,7 +25,6 @@ type Props = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
width: 360px;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Header = styled.div`
|
const Header = styled.div`
|
||||||
@ -36,8 +35,8 @@ const Header = styled.div`
|
|||||||
color: ${colors.TEXT_PRIMARY};
|
color: ${colors.TEXT_PRIMARY};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledHeading = styled(H3)`
|
const StyledHeading = styled(H2)`
|
||||||
padding-top: 30px;
|
padding: 30px 48px 10px 48px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledLink = styled(Link)`
|
const StyledLink = styled(Link)`
|
||||||
@ -105,7 +104,7 @@ class WalletType extends PureComponent<Props> {
|
|||||||
{ device.state && (
|
{ device.state && (
|
||||||
<StyledLink onClick={onCancel}>
|
<StyledLink onClick={onCancel}>
|
||||||
<Icon
|
<Icon
|
||||||
size={20}
|
size={24}
|
||||||
color={colors.TEXT_SECONDARY}
|
color={colors.TEXT_SECONDARY}
|
||||||
icon={icons.CLOSE}
|
icon={icons.CLOSE}
|
||||||
/>
|
/>
|
||||||
|
@ -26,7 +26,7 @@ const Wrapper = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledButton = styled(Button)`
|
const StyledButton = styled(Button)`
|
||||||
margin: 10px 0 10px 0;
|
margin-top: 10px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -48,7 +48,7 @@ const CardanoWallet = (props: Props) => (
|
|||||||
<Wrapper>
|
<Wrapper>
|
||||||
<StyledLink onClick={props.onCancel}>
|
<StyledLink onClick={props.onCancel}>
|
||||||
<Icon
|
<Icon
|
||||||
size={20}
|
size={24}
|
||||||
color={colors.TEXT_SECONDARY}
|
color={colors.TEXT_SECONDARY}
|
||||||
icon={icons.CLOSE}
|
icon={icons.CLOSE}
|
||||||
/>
|
/>
|
||||||
|
6
src/components/modals/external/Nem/index.js
vendored
6
src/components/modals/external/Nem/index.js
vendored
@ -22,11 +22,11 @@ type Props = {
|
|||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 620px;
|
max-width: 620px;
|
||||||
padding: 24px 48px;
|
padding: 30px 48px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledButton = styled(Button)`
|
const StyledButton = styled(Button)`
|
||||||
margin: 0 0 10px 0;
|
margin-top: 10px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -46,7 +46,7 @@ const NemWallet = (props: Props) => (
|
|||||||
<Wrapper>
|
<Wrapper>
|
||||||
<StyledLink onClick={props.onCancel}>
|
<StyledLink onClick={props.onCancel}>
|
||||||
<Icon
|
<Icon
|
||||||
size={20}
|
size={24}
|
||||||
color={colors.TEXT_SECONDARY}
|
color={colors.TEXT_SECONDARY}
|
||||||
icon={icons.CLOSE}
|
icon={icons.CLOSE}
|
||||||
/>
|
/>
|
||||||
|
@ -26,7 +26,7 @@ const Wrapper = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledButton = styled(Button)`
|
const StyledButton = styled(Button)`
|
||||||
margin: 10px 0 10px 0;
|
margin-top: 10px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -48,7 +48,7 @@ const StellarWallet = (props: Props) => (
|
|||||||
<Wrapper>
|
<Wrapper>
|
||||||
<StyledLink onClick={props.onCancel}>
|
<StyledLink onClick={props.onCancel}>
|
||||||
<Icon
|
<Icon
|
||||||
size={20}
|
size={24}
|
||||||
color={colors.TEXT_SECONDARY}
|
color={colors.TEXT_SECONDARY}
|
||||||
icon={icons.CLOSE}
|
icon={icons.CLOSE}
|
||||||
/>
|
/>
|
||||||
|
@ -19,6 +19,7 @@ import PassphraseType from 'components/modals/passphrase/Type';
|
|||||||
import ConfirmSignTx from 'components/modals/confirm/SignTx';
|
import ConfirmSignTx from 'components/modals/confirm/SignTx';
|
||||||
import ConfirmAction from 'components/modals/confirm/Action';
|
import ConfirmAction from 'components/modals/confirm/Action';
|
||||||
import ConfirmUnverifiedAddress from 'components/modals/confirm/UnverifiedAddress';
|
import ConfirmUnverifiedAddress from 'components/modals/confirm/UnverifiedAddress';
|
||||||
|
import ConfirmNoBackup from 'components/modals/confirm/NoBackup';
|
||||||
import ForgetDevice from 'components/modals/device/Forget';
|
import ForgetDevice from 'components/modals/device/Forget';
|
||||||
import RememberDevice from 'components/modals/device/Remember';
|
import RememberDevice from 'components/modals/device/Remember';
|
||||||
import DuplicateDevice from 'components/modals/device/Duplicate';
|
import DuplicateDevice from 'components/modals/device/Duplicate';
|
||||||
@ -29,6 +30,8 @@ import Nem from 'components/modals/external/Nem';
|
|||||||
import Cardano from 'components/modals/external/Cardano';
|
import Cardano from 'components/modals/external/Cardano';
|
||||||
import Stellar from 'components/modals/external/Stellar';
|
import Stellar from 'components/modals/external/Stellar';
|
||||||
|
|
||||||
|
import QrModal from 'components/modals/QrModal';
|
||||||
|
|
||||||
import type { Props } from './Container';
|
import type { Props } from './Container';
|
||||||
|
|
||||||
const ModalContainer = styled.div`
|
const ModalContainer = styled.div`
|
||||||
@ -66,7 +69,8 @@ const getDeviceContextModal = (props: Props) => {
|
|||||||
<Pin
|
<Pin
|
||||||
device={modal.device}
|
device={modal.device}
|
||||||
onPinSubmit={modalActions.onPinSubmit}
|
onPinSubmit={modalActions.onPinSubmit}
|
||||||
/>);
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
case UI.INVALID_PIN:
|
case UI.INVALID_PIN:
|
||||||
return <InvalidPin device={modal.device} />;
|
return <InvalidPin device={modal.device} />;
|
||||||
@ -77,7 +81,8 @@ const getDeviceContextModal = (props: Props) => {
|
|||||||
device={modal.device}
|
device={modal.device}
|
||||||
selectedDevice={props.wallet.selectedDevice}
|
selectedDevice={props.wallet.selectedDevice}
|
||||||
onPassphraseSubmit={modalActions.onPassphraseSubmit}
|
onPassphraseSubmit={modalActions.onPassphraseSubmit}
|
||||||
/>);
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
case 'ButtonRequest_PassphraseType':
|
case 'ButtonRequest_PassphraseType':
|
||||||
return <PassphraseType device={modal.device} />;
|
return <PassphraseType device={modal.device} />;
|
||||||
@ -94,11 +99,11 @@ const getDeviceContextModal = (props: Props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case 'ButtonRequest_ProtectCall':
|
case 'ButtonRequest_ProtectCall':
|
||||||
return <ConfirmAction />;
|
return <ConfirmAction device={modal.device} />;
|
||||||
|
|
||||||
case 'ButtonRequest_Other':
|
case 'ButtonRequest_Other':
|
||||||
case 'ButtonRequest_ConfirmOutput':
|
case 'ButtonRequest_ConfirmOutput':
|
||||||
return <ConfirmAction />;
|
return <ConfirmAction device={modal.device} />;
|
||||||
|
|
||||||
case RECEIVE.REQUEST_UNVERIFIED:
|
case RECEIVE.REQUEST_UNVERIFIED:
|
||||||
return (
|
return (
|
||||||
@ -108,7 +113,8 @@ const getDeviceContextModal = (props: Props) => {
|
|||||||
onCancel={modalActions.onCancel}
|
onCancel={modalActions.onCancel}
|
||||||
showAddress={props.receiveActions.showAddress}
|
showAddress={props.receiveActions.showAddress}
|
||||||
showUnverifiedAddress={props.receiveActions.showUnverifiedAddress}
|
showUnverifiedAddress={props.receiveActions.showUnverifiedAddress}
|
||||||
/>);
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
case CONNECT.REMEMBER_REQUEST:
|
case CONNECT.REMEMBER_REQUEST:
|
||||||
return (
|
return (
|
||||||
@ -117,7 +123,8 @@ const getDeviceContextModal = (props: Props) => {
|
|||||||
instances={modal.instances}
|
instances={modal.instances}
|
||||||
onRememberDevice={modalActions.onRememberDevice}
|
onRememberDevice={modalActions.onRememberDevice}
|
||||||
onForgetDevice={modalActions.onForgetDevice}
|
onForgetDevice={modalActions.onForgetDevice}
|
||||||
/>);
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
case CONNECT.FORGET_REQUEST:
|
case CONNECT.FORGET_REQUEST:
|
||||||
return (
|
return (
|
||||||
@ -125,7 +132,8 @@ const getDeviceContextModal = (props: Props) => {
|
|||||||
device={modal.device}
|
device={modal.device}
|
||||||
onForgetSingleDevice={modalActions.onForgetSingleDevice}
|
onForgetSingleDevice={modalActions.onForgetSingleDevice}
|
||||||
onCancel={modalActions.onCancel}
|
onCancel={modalActions.onCancel}
|
||||||
/>);
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
case CONNECT.TRY_TO_DUPLICATE:
|
case CONNECT.TRY_TO_DUPLICATE:
|
||||||
return (
|
return (
|
||||||
@ -134,7 +142,8 @@ const getDeviceContextModal = (props: Props) => {
|
|||||||
devices={props.devices}
|
devices={props.devices}
|
||||||
onDuplicateDevice={modalActions.onDuplicateDevice}
|
onDuplicateDevice={modalActions.onDuplicateDevice}
|
||||||
onCancel={modalActions.onCancel}
|
onCancel={modalActions.onCancel}
|
||||||
/>);
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
case CONNECT.REQUEST_WALLET_TYPE:
|
case CONNECT.REQUEST_WALLET_TYPE:
|
||||||
return (
|
return (
|
||||||
@ -142,7 +151,8 @@ const getDeviceContextModal = (props: Props) => {
|
|||||||
device={modal.device}
|
device={modal.device}
|
||||||
onWalletTypeRequest={modalActions.onWalletTypeRequest}
|
onWalletTypeRequest={modalActions.onWalletTypeRequest}
|
||||||
onCancel={modalActions.onCancel}
|
onCancel={modalActions.onCancel}
|
||||||
/>);
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
@ -166,6 +176,33 @@ const getExternalContextModal = (props: Props) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getQrModal = (props: Props) => {
|
||||||
|
const { modalActions, selectedAccount } = props;
|
||||||
|
|
||||||
|
if (!selectedAccount.network) return null;
|
||||||
|
const networkType = selectedAccount.network.type;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<QrModal
|
||||||
|
onCancel={modalActions.onCancel}
|
||||||
|
onScan={parsedUri => modalActions.onQrScan(parsedUri, networkType)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getConfirmationModal = (props: Props) => {
|
||||||
|
const { modal, modalActions, wallet } = props;
|
||||||
|
|
||||||
|
if (modal.context !== MODAL.CONTEXT_CONFIRMATION) return null;
|
||||||
|
|
||||||
|
switch (modal.windowType) {
|
||||||
|
case 'no-backup':
|
||||||
|
return (<ConfirmNoBackup device={wallet.selectedDevice} onReceiveConfirmation={modalActions.onReceiveConfirmation} />);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// modal container component
|
// modal container component
|
||||||
const Modal = (props: Props) => {
|
const Modal = (props: Props) => {
|
||||||
const { modal } = props;
|
const { modal } = props;
|
||||||
@ -179,6 +216,12 @@ const Modal = (props: Props) => {
|
|||||||
case MODAL.CONTEXT_EXTERNAL_WALLET:
|
case MODAL.CONTEXT_EXTERNAL_WALLET:
|
||||||
component = getExternalContextModal(props);
|
component = getExternalContextModal(props);
|
||||||
break;
|
break;
|
||||||
|
case MODAL.CONTEXT_SCAN_QR:
|
||||||
|
component = getQrModal(props);
|
||||||
|
break;
|
||||||
|
case MODAL.CONTEXT_CONFIRMATION:
|
||||||
|
component = getConfirmationModal(props);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ type State = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
padding: 24px 48px;
|
padding: 30px 48px;
|
||||||
max-width: 390px;
|
max-width: 390px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -4,10 +4,9 @@ import React from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import icons from 'config/icons';
|
|
||||||
import colors from 'config/colors';
|
import colors from 'config/colors';
|
||||||
|
|
||||||
import Icon from 'components/Icon';
|
import DeviceIcon from 'components/images/DeviceIcon';
|
||||||
import { H3 } from 'components/Heading';
|
import { H3 } from 'components/Heading';
|
||||||
import P from 'components/Paragraph';
|
import P from 'components/Paragraph';
|
||||||
|
|
||||||
@ -18,8 +17,8 @@ type Props = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
width: 360px;
|
max-width: 360px;
|
||||||
padding: 24px 48px;
|
padding: 30px 48px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Header = styled.div``;
|
const Header = styled.div``;
|
||||||
@ -27,7 +26,7 @@ const Header = styled.div``;
|
|||||||
const PassphraseType = (props: Props) => (
|
const PassphraseType = (props: Props) => (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<Header>
|
<Header>
|
||||||
<Icon icon={icons.T1} size={60} color={colors.TEXT_SECONDARY} />
|
<DeviceIcon device={props.device} size={60} color={colors.TEXT_SECONDARY} />
|
||||||
<H3>Complete the action on { props.device.label } device</H3>
|
<H3>Complete the action on { props.device.label } device</H3>
|
||||||
<P isSmaller>If you enter a wrong passphrase, you will not unlock the desired hidden wallet.</P>
|
<P isSmaller>If you enter a wrong passphrase, you will not unlock the desired hidden wallet.</P>
|
||||||
</Header>
|
</Header>
|
||||||
|
@ -14,7 +14,7 @@ type Props = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
padding: 24px 48px;
|
padding: 30px 48px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const InvalidPin = (props: Props) => (
|
const InvalidPin = (props: Props) => (
|
||||||
|
@ -4,7 +4,7 @@ import * as React from 'react';
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import colors from 'config/colors';
|
import colors from 'config/colors';
|
||||||
import { FONT_SIZE, FONT_WEIGHT } from 'config/variables';
|
import { FONT_SIZE, FONT_WEIGHT, SCREEN_SIZE } from 'config/variables';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
@ -22,6 +22,12 @@ const Wrapper = styled.button`
|
|||||||
border: 1px solid ${colors.DIVIDER};
|
border: 1px solid ${colors.DIVIDER};
|
||||||
background: ${colors.WHITE};
|
background: ${colors.WHITE};
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
@media screen and (max-width: ${SCREEN_SIZE.XS}) {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
&:first-child {
|
&:first-child {
|
||||||
margin-left: 0px;
|
margin-left: 0px;
|
||||||
|
@ -25,7 +25,7 @@ type State = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
padding: 24px 48px;
|
padding: 30px 48px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const InputRow = styled.div`
|
const InputRow = styled.div`
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
/* @flow */
|
||||||
|
import * as React from 'react';
|
||||||
|
import Notification from 'components/Notification';
|
||||||
|
|
||||||
|
import type { Props } from '../../index';
|
||||||
|
|
||||||
|
export default (props: Props) => {
|
||||||
|
const { selectedDevice } = props.wallet;
|
||||||
|
const needsBackup = selectedDevice && selectedDevice.features && selectedDevice.features.needs_backup;
|
||||||
|
if (!needsBackup) return null;
|
||||||
|
return (
|
||||||
|
<Notification
|
||||||
|
key="no-backup"
|
||||||
|
type="warning"
|
||||||
|
title="Your Trezor is not backed up!"
|
||||||
|
message="If your device is ever lost or damaged, your funds will be lost. Backup your device first, to protect your coins against such events."
|
||||||
|
actions={
|
||||||
|
[{
|
||||||
|
label: 'Create a backup',
|
||||||
|
callback: props.routerActions.gotoBackup,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -12,6 +12,7 @@ import * as RouterActions from 'actions/RouterActions';
|
|||||||
import OnlineStatus from './components/OnlineStatus';
|
import OnlineStatus from './components/OnlineStatus';
|
||||||
import UpdateBridge from './components/UpdateBridge';
|
import UpdateBridge from './components/UpdateBridge';
|
||||||
import UpdateFirmware from './components/UpdateFirmware';
|
import UpdateFirmware from './components/UpdateFirmware';
|
||||||
|
import NoBackup from './components/NoBackup';
|
||||||
|
|
||||||
export type StateProps = {
|
export type StateProps = {
|
||||||
connect: $ElementType<State, 'connect'>;
|
connect: $ElementType<State, 'connect'>;
|
||||||
@ -33,6 +34,7 @@ const Notifications = (props: Props) => (
|
|||||||
<OnlineStatus {...props} />
|
<OnlineStatus {...props} />
|
||||||
<UpdateBridge {...props} />
|
<UpdateBridge {...props} />
|
||||||
<UpdateFirmware {...props} />
|
<UpdateFirmware {...props} />
|
||||||
|
<NoBackup {...props} />
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import type { Props } from '../../index';
|
|||||||
export default (props: Props) => {
|
export default (props: Props) => {
|
||||||
const { network, notification } = props.selectedAccount;
|
const { network, notification } = props.selectedAccount;
|
||||||
if (!network || !notification) return null;
|
if (!network || !notification) return null;
|
||||||
|
const blockchain = props.blockchain.find(b => b.shortcut === network.shortcut);
|
||||||
|
|
||||||
if (notification.type === 'backend') {
|
if (notification.type === 'backend') {
|
||||||
// special case: backend is down
|
// special case: backend is down
|
||||||
@ -17,6 +18,7 @@ export default (props: Props) => {
|
|||||||
type="error"
|
type="error"
|
||||||
title={notification.title}
|
title={notification.title}
|
||||||
message={notification.message}
|
message={notification.message}
|
||||||
|
isActionInProgress={blockchain && blockchain.connecting}
|
||||||
actions={
|
actions={
|
||||||
[{
|
[{
|
||||||
label: 'Connect',
|
label: 'Connect',
|
||||||
|
@ -2,18 +2,39 @@
|
|||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import Notification from 'components/Notification';
|
import Notification from 'components/Notification';
|
||||||
|
import Bignumber from 'bignumber.js';
|
||||||
|
import Link from 'components/Link';
|
||||||
import type { Props } from '../../index';
|
import type { Props } from '../../index';
|
||||||
|
|
||||||
export default (props: Props) => {
|
export default (props: Props) => {
|
||||||
|
const { selectedAccount } = props;
|
||||||
|
const { account } = selectedAccount;
|
||||||
const { location } = props.router;
|
const { location } = props.router;
|
||||||
if (!location) return null;
|
|
||||||
|
|
||||||
const notifications: Array<Notification> = [];
|
const notifications: Array<Notification> = [];
|
||||||
// Example:
|
|
||||||
// if (location.state.device) {
|
if (!location || !selectedAccount || !account) return null;
|
||||||
// notifications.push(<Notification key="example" type="info" title="Static example" />);
|
|
||||||
// }
|
// Ripple minimum reserve notification
|
||||||
|
if (account.networkType === 'ripple') {
|
||||||
|
const { reserve, balance } = account;
|
||||||
|
const bigBalance = new Bignumber(balance);
|
||||||
|
const bigReserve = new Bignumber(reserve);
|
||||||
|
if (bigBalance.isLessThan(bigReserve)) {
|
||||||
|
notifications.push(
|
||||||
|
<Notification
|
||||||
|
key="xrp-warning"
|
||||||
|
type="warning"
|
||||||
|
title="Minimum account reserve required"
|
||||||
|
message={(
|
||||||
|
<>
|
||||||
|
{`Ripple addresses require a minimum balance of ${bigReserve.toString()} XRP to activate and maintain the account. `}
|
||||||
|
<Link isGreen href="https://wiki.trezor.io/Ripple_(XRP)">Learn more</Link>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
|
@ -49,6 +49,12 @@ export const GREEN_COLOR = keyframes`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const WHITE_COLOR = keyframes`
|
||||||
|
0%, 100% {
|
||||||
|
stroke: white;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
export const PULSATE = keyframes`
|
export const PULSATE = keyframes`
|
||||||
0%, 100% {
|
0%, 100% {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
@ -66,3 +72,30 @@ export const FADE_IN = keyframes`
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const SLIDE_DOWN = keyframes`
|
||||||
|
0% {
|
||||||
|
transform: translateY(-100%);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateY(0%);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const SLIDE_RIGHT = keyframes`
|
||||||
|
0% {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateX(0%);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const SLIDE_LEFT = keyframes`
|
||||||
|
0% {
|
||||||
|
transform: translateX(0%);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
}
|
||||||
|
`;
|
@ -34,4 +34,7 @@ export default {
|
|||||||
|
|
||||||
LABEL_COLOR: '#A9A9A9',
|
LABEL_COLOR: '#A9A9A9',
|
||||||
TOOLTIP_BACKGROUND: '#333333',
|
TOOLTIP_BACKGROUND: '#333333',
|
||||||
|
|
||||||
|
INPUT_FOCUSED_BORDER: '#A9A9A9',
|
||||||
|
INPUT_FOCUSED_SHADOW: '#d6d7d7',
|
||||||
};
|
};
|
@ -24,6 +24,9 @@ export default {
|
|||||||
T1: [
|
T1: [
|
||||||
'M603.2 265.6h-6.4c-25.494-5.341-54.79-8.398-84.8-8.398s-59.305 3.058-87.592 8.879l2.792-0.48h-6.72c-30.053 5.643-52.489 31.68-52.489 62.956 0 0.367 0.003 0.733 0.009 1.099l-0.001-0.055v234.88c0.075 40.921 11.238 79.22 30.643 112.071l-0.563-1.031 35.2 60.48c11.655 19.297 32.515 32.001 56.342 32.001 0.105 0 0.209 0 0.314-0.001h44.144c0.359 0.007 0.783 0.011 1.208 0.011 23.569 0 44.162-12.74 55.269-31.709l0.164-0.302 36.16-64c18.232-31.447 29.027-69.173 29.12-109.413v-232.987c0.005-0.293 0.008-0.639 0.008-0.986 0-31.391-22.599-57.503-52.416-62.954l-0.392-0.059zM629.76 563.2c-0.193 35.364-9.792 68.446-26.418 96.923l0.498-0.923-35.84 64c-6.868 11.865-19.463 19.742-33.906 19.84h-44.174c-0.073 0-0.159 0.001-0.246 0.001-14.427 0-27.041-7.762-33.894-19.338l-0.1-0.183-34.88-59.84c-16.656-28.155-26.515-62.042-26.56-98.227v-235.853c0.133-19.025 13.742-34.833 31.751-38.359l0.249-0.041h6.72c24.050-5.126 51.682-8.062 80-8.062s55.949 2.936 82.608 8.519l-2.608-0.457h6.72c18.258 3.568 31.867 19.375 32 38.386v0.014zM422.4 353.92h179.2c3.535 0 6.4 2.865 6.4 6.4v99.2c0 3.535-2.865 6.4-6.4 6.4h-179.2c-3.535 0-6.4-2.865-6.4-6.4v-99.2c0-3.535 2.865-6.4 6.4-6.4z',
|
'M603.2 265.6h-6.4c-25.494-5.341-54.79-8.398-84.8-8.398s-59.305 3.058-87.592 8.879l2.792-0.48h-6.72c-30.053 5.643-52.489 31.68-52.489 62.956 0 0.367 0.003 0.733 0.009 1.099l-0.001-0.055v234.88c0.075 40.921 11.238 79.22 30.643 112.071l-0.563-1.031 35.2 60.48c11.655 19.297 32.515 32.001 56.342 32.001 0.105 0 0.209 0 0.314-0.001h44.144c0.359 0.007 0.783 0.011 1.208 0.011 23.569 0 44.162-12.74 55.269-31.709l0.164-0.302 36.16-64c18.232-31.447 29.027-69.173 29.12-109.413v-232.987c0.005-0.293 0.008-0.639 0.008-0.986 0-31.391-22.599-57.503-52.416-62.954l-0.392-0.059zM629.76 563.2c-0.193 35.364-9.792 68.446-26.418 96.923l0.498-0.923-35.84 64c-6.868 11.865-19.463 19.742-33.906 19.84h-44.174c-0.073 0-0.159 0.001-0.246 0.001-14.427 0-27.041-7.762-33.894-19.338l-0.1-0.183-34.88-59.84c-16.656-28.155-26.515-62.042-26.56-98.227v-235.853c0.133-19.025 13.742-34.833 31.751-38.359l0.249-0.041h6.72c24.050-5.126 51.682-8.062 80-8.062s55.949 2.936 82.608 8.519l-2.608-0.457h6.72c18.258 3.568 31.867 19.375 32 38.386v0.014zM422.4 353.92h179.2c3.535 0 6.4 2.865 6.4 6.4v99.2c0 3.535-2.865 6.4-6.4 6.4h-179.2c-3.535 0-6.4-2.865-6.4-6.4v-99.2c0-3.535 2.865-6.4 6.4-6.4z',
|
||||||
],
|
],
|
||||||
|
T2: [
|
||||||
|
'M 625.28 546.304 c 0 4.512 -3.84 8 -8.32 8 l -209.92 0 c -4.48 0 -8.32 -3.488 -8.32 -8 l 0 -202.208 c 0 -4.512 3.84 -8.32 8.32 -8.32 l 209.92 0 c 4.48 0 8.32 3.808 8.32 8.32 l 0 202.208 Z m 18.56 -304.32 l -263.68 0 c -23.04 0 -41.92 18.56 -41.92 41.28 l 0 233.952 c 0 55.04 16 108.768 46.72 155.168 l 64.64 96.992 c 5.12 8 13.76 12.448 23.36 12.448 l 78.4 0 c 9.28 0 17.92 -4.448 23.04 -11.84 l 60.16 -86.048 c 33.6 -47.68 51.2 -103.392 51.2 -161.28 l 0 -239.392 c 0 -22.72 -18.88 -41.28 -41.92 -41.28',
|
||||||
|
],
|
||||||
COG: [
|
COG: [
|
||||||
'M739.552 462.144h-71.328c-4.256-13.664-10.208-26.56-17.472-38.56l47.264-47.424c11.2-11.008 11.2-29.056 0-40.192l-20.064-20.032c-11.136-11.104-29.152-11.040-40.192 0l-48.128 48.032c-12.992-7.392-27.072-13.152-42.080-16.992v-62.496c0-15.68-12.672-28.48-28.448-28.48h-28.448c-15.68 0-28.416 12.8-28.416 28.48v62.464c-16.352 4.128-31.68 10.656-45.728 19.2l-40.288-40.224c-11.072-11.040-29.184-11.104-40.288 0l-20.096 20.096c-11.104 11.072-10.976 29.152 0.064 40.288l40.992 40.992c-8.672 15.136-15.168 31.648-18.88 49.152h-53.504c-15.776 0-28.544 12.736-28.544 28.48v28.416c0 15.68 12.768 28.416 28.544 28.416h57.152c5.184 17.152 12.992 32.928 23.008 47.328l-38.656 38.656c-11.136 11.136-11.136 29.216-0.064 40.288l20.064 20.096c11.2 11.040 29.248 11.040 40.32-0.032l43.232-43.2c14.528 7.232 30.336 12.48 46.944 15.2v59.488c0 15.68 12.736 28.448 28.448 28.48h28.448c15.68-0.032 28.448-12.8 28.448-28.48v-66.816c14.336-5.088 27.904-11.872 40.224-20.544l45.76 45.888c11.104 11.072 29.12 11.072 40.224 0l20.096-20.128c11.168-11.072 11.168-29.056-0.096-40.288l-50.144-50.24c6.144-12.512 10.944-25.792 13.92-39.904h67.776c15.744 0 28.448-12.672 28.48-28.448v-28.448c-0.096-15.68-12.8-28.512-28.544-28.512zM504.928 583.072c-39.264 0-71.072-31.776-71.072-71.104 0-39.264 31.808-71.040 71.072-71.040 39.296 0 71.136 31.776 71.136 71.040 0 39.328-31.84 71.104-71.136 71.104z',
|
'M739.552 462.144h-71.328c-4.256-13.664-10.208-26.56-17.472-38.56l47.264-47.424c11.2-11.008 11.2-29.056 0-40.192l-20.064-20.032c-11.136-11.104-29.152-11.040-40.192 0l-48.128 48.032c-12.992-7.392-27.072-13.152-42.080-16.992v-62.496c0-15.68-12.672-28.48-28.448-28.48h-28.448c-15.68 0-28.416 12.8-28.416 28.48v62.464c-16.352 4.128-31.68 10.656-45.728 19.2l-40.288-40.224c-11.072-11.040-29.184-11.104-40.288 0l-20.096 20.096c-11.104 11.072-10.976 29.152 0.064 40.288l40.992 40.992c-8.672 15.136-15.168 31.648-18.88 49.152h-53.504c-15.776 0-28.544 12.736-28.544 28.48v28.416c0 15.68 12.768 28.416 28.544 28.416h57.152c5.184 17.152 12.992 32.928 23.008 47.328l-38.656 38.656c-11.136 11.136-11.136 29.216-0.064 40.288l20.064 20.096c11.2 11.040 29.248 11.040 40.32-0.032l43.232-43.2c14.528 7.232 30.336 12.48 46.944 15.2v59.488c0 15.68 12.736 28.448 28.448 28.48h28.448c15.68-0.032 28.448-12.8 28.448-28.48v-66.816c14.336-5.088 27.904-11.872 40.224-20.544l45.76 45.888c11.104 11.072 29.12 11.072 40.224 0l20.096-20.128c11.168-11.072 11.168-29.056-0.096-40.288l-50.144-50.24c6.144-12.512 10.944-25.792 13.92-39.904h67.776c15.744 0 28.448-12.672 28.48-28.448v-28.448c-0.096-15.68-12.8-28.512-28.544-28.512zM504.928 583.072c-39.264 0-71.072-31.776-71.072-71.104 0-39.264 31.808-71.040 71.072-71.040 39.296 0 71.136 31.776 71.136 71.040 0 39.328-31.84 71.104-71.136 71.104z',
|
||||||
],
|
],
|
||||||
@ -67,6 +70,18 @@ export default {
|
|||||||
SUCCESS: [
|
SUCCESS: [
|
||||||
'M692.8 313.92l-1.92-1.92c-6.246-7.057-15.326-11.484-25.44-11.484s-19.194 4.427-25.409 11.448l-0.031 0.036-196.48 224-3.84 1.6-3.84-1.92-48.64-57.28c-7.010-7.905-17.193-12.862-28.533-12.862-21.031 0-38.080 17.049-38.080 38.080 0 7.495 2.165 14.485 5.905 20.377l-0.092-0.155 100.8 148.16c5.391 8.036 14.386 13.292 24.618 13.44h8.662c17.251-0.146 32.385-9.075 41.163-22.529l0.117-0.191 195.2-296.32c4.473-6.632 7.141-14.803 7.141-23.597 0-11.162-4.297-21.32-11.326-28.911l0.025 0.028z',
|
'M692.8 313.92l-1.92-1.92c-6.246-7.057-15.326-11.484-25.44-11.484s-19.194 4.427-25.409 11.448l-0.031 0.036-196.48 224-3.84 1.6-3.84-1.92-48.64-57.28c-7.010-7.905-17.193-12.862-28.533-12.862-21.031 0-38.080 17.049-38.080 38.080 0 7.495 2.165 14.485 5.905 20.377l-0.092-0.155 100.8 148.16c5.391 8.036 14.386 13.292 24.618 13.44h8.662c17.251-0.146 32.385-9.075 41.163-22.529l0.117-0.191 195.2-296.32c4.473-6.632 7.141-14.803 7.141-23.597 0-11.162-4.297-21.32-11.326-28.911l0.025 0.028z',
|
||||||
],
|
],
|
||||||
|
WALLET_STANDARD: [
|
||||||
|
'M746.656,341.344l-405.312,0l-21.344,0c-11.744,0 -21.344,-9.568 -21.344,-21.344c0,-11.776 9.6,-21.344 21.344,-21.344l320,0l0,21.344l42.656,0l0,-42.656c0,-11.776 -9.536,-21.344 -21.312,-21.344l-341.344,0c-35.36,0 -64,28.64 -64,64l0,362.656c0,47.136 38.208,85.344 85.344,85.344l405.344,0c11.744,0 21.312,-9.568 21.312,-21.344l0,-384c0,-11.776 -9.568,-21.312 -21.344,-21.312Zm-106.656,256c-23.584,0 -42.656,-19.104 -42.656,-42.656c0,-23.584 19.072,-42.688 42.656,-42.688c23.584,0 42.656,19.104 42.656,42.656c0,23.584 -19.072,42.688 -42.656,42.688Z',
|
||||||
|
],
|
||||||
|
WALLET_HIDDEN: [
|
||||||
|
'M813.472,552.96l-101.344,-281.6c-2.528,-7.68 -12.672,-15.36 -22.784,-15.36l-76,0c-15.2,0 -25.344,10.24 -25.344,25.6c0,15.36 10.144,25.6 25.344,25.6l58.272,0l83.584,230.4l-192.544,0l-101.344,0l-192.512,0l83.616,-230.4l58.272,0c15.2,0 25.344,-10.24 25.344,-25.6c0,-15.36 -10.144,-25.6 -25.344,-25.6l-76,0c-10.144,0 -20.256,7.68 -22.784,17.92l-101.344,281.6c-2.56,0 -2.56,5.12 -2.56,7.68l0,128c0,43.52 32.928,76.8 76,76.8l126.656,0c43.072,0 76,-33.28 76,-76.8l0,-102.4l50.656,0l0,102.4c0,43.52 32.928,76.8 76,76.8l126.656,0c43.072,0 76,-33.28 76,-76.8l0,-128c0.032,-2.56 0.032,-7.68 -2.496,-10.24Z',
|
||||||
|
],
|
||||||
|
QRCODE: [
|
||||||
|
'M832 1024l-64 0l0 -128l64 0l0 128Zm-320 0l-64 0l0 -128l64 0l0 128Zm192 0l-128 0l0 -128l128 0l0 128Zm192 -192l64 0l0 64l64 0l0 128l-128 0l0 -192Zm-896 -192l384 0l0 384l-384 0l0 -384Zm320 320l0 -256l-256 0l0 256l256 0Zm-64 -64l-128 0l0 -128l128 0l0 128Zm512 0l-64 0l0 -64l64 0l0 64Zm-192 -128l0 128l-64 0l0 -64l-64 0l0 -64l128 0Zm128 64l-64 0l0 -64l64 0l0 64Zm192 0l-128 0l0 -64l128 0l0 64Zm-256 -64l-64 0l0 -64l64 0l0 64Zm320 -64l-64 0l0 -64l128 0l0 128l-64 0l0 -64Zm-384 0l-128 0l0 -128l128 0l0 128Zm64 -64l64 0l0 -64l128 0l0 128l-192 0l0 -64Zm-320 -128l64 0l0 -64l64 0l0 128l-128 0l0 -64Zm256 0l-64 0l0 -64l192 0l0 128l-128 0l0 -64Zm-576 -64l128 0l0 64l64 0l0 64l-192 0l0 -128Zm896 64l-128 0l0 -64l256 0l0 128l-128 0l0 -64Zm-576 0l-128 0l0 -64l128 0l0 64Zm192 -64l-64 0l0 -64l64 0l0 64Zm-512 -448l384 0l0 384l-384 0l0 -384Zm576 384l-64 0l0 -128l64 0l0 128Zm64 -384l384 0l0 384l-384 0l0 -384Zm-320 320l0 -256l-256 0l0 256l256 0Zm640 0l0 -256l-256 0l0 256l256 0Zm-704 -64l-128 0l0 -128l128 0l0 128Zm640 0l-128 0l0 -128l128 0l0 128Zm-384 -256l0 64l64 0l0 128l-64 0l0 64l-64 0l0 -256l64 0Z',
|
||||||
|
],
|
||||||
|
MENU: [
|
||||||
|
'M192,265.497l640,0l0,119.906l-640,0l0,-119.906Zm0,186.56l640,0l0,119.946l-640,0l0,-119.946Zm0,186.56l640,0l0,119.886l-640,0l0,-119.886Z',
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1,3 +1,15 @@
|
|||||||
|
// Bootstrap 3 breakpoints
|
||||||
|
/* XS - Extra Small Devices, Phones */
|
||||||
|
/* SM - Small Devices, Tablets */
|
||||||
|
/* MD - Medium Devices, Desktops */
|
||||||
|
/* LG - Large Devices, Wide Screens */
|
||||||
|
export const SCREEN_SIZE = {
|
||||||
|
XS: '480px',
|
||||||
|
SM: '768px',
|
||||||
|
MD: '992px',
|
||||||
|
LG: '1170px',
|
||||||
|
};
|
||||||
|
|
||||||
// OLD UNITS
|
// OLD UNITS
|
||||||
// SMALLEST: '10px',
|
// SMALLEST: '10px',
|
||||||
// SMALLER: '12px',
|
// SMALLER: '12px',
|
||||||
@ -13,7 +25,6 @@
|
|||||||
// H3: '14px',
|
// H3: '14px',
|
||||||
// H4: '12px',
|
// H4: '12px',
|
||||||
// COUNTER: '11px',
|
// COUNTER: '11px',
|
||||||
|
|
||||||
export const FONT_SIZE = {
|
export const FONT_SIZE = {
|
||||||
SMALL: '0.8571rem',
|
SMALL: '0.8571rem',
|
||||||
BASE: '1rem',
|
BASE: '1rem',
|
||||||
|
5
src/constants/urls.js
Normal file
5
src/constants/urls.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export default {
|
||||||
|
NEXT_WALLET: 'https://beta-wallet.trezor.io/next',
|
||||||
|
OLD_WALLET: 'https://wallet.trezor.io',
|
||||||
|
OLD_WALLET_BETA: 'https://beta-wallet.trezor.io',
|
||||||
|
};
|
@ -38,6 +38,7 @@ import type {
|
|||||||
Device,
|
Device,
|
||||||
Features,
|
Features,
|
||||||
DeviceStatus,
|
DeviceStatus,
|
||||||
|
FirmwareRelease,
|
||||||
DeviceFirmwareStatus,
|
DeviceFirmwareStatus,
|
||||||
DeviceMode,
|
DeviceMode,
|
||||||
DeviceMessageType,
|
DeviceMessageType,
|
||||||
@ -55,6 +56,7 @@ export type AcquiredDevice = $Exact<{
|
|||||||
+label: string,
|
+label: string,
|
||||||
+features: Features,
|
+features: Features,
|
||||||
+firmware: DeviceFirmwareStatus,
|
+firmware: DeviceFirmwareStatus,
|
||||||
|
+firmwareRelease: ?FirmwareRelease,
|
||||||
status: DeviceStatus,
|
status: DeviceStatus,
|
||||||
+mode: DeviceMode,
|
+mode: DeviceMode,
|
||||||
state: ?string,
|
state: ?string,
|
||||||
|
@ -27,22 +27,21 @@ declare module 'bignumber.js' {
|
|||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
abs(): T_BigNumber;
|
abs(): T_BigNumber;
|
||||||
cmp(n: $npm$big$number$object): $npm$cmp$result;
|
|
||||||
div(n: $npm$big$number$object): T_BigNumber;
|
div(n: $npm$big$number$object): T_BigNumber;
|
||||||
dividedBy(n: $npm$big$number$object): T_BigNumber;
|
dividedBy(n: $npm$big$number$object): T_BigNumber;
|
||||||
eq(n: $npm$big$number$object): boolean;
|
eq(n: $npm$big$number$object): boolean;
|
||||||
gt(n: $npm$big$number$object): boolean;
|
gt(n: $npm$big$number$object): boolean;
|
||||||
greaterThan(n: $npm$big$number$object): boolean;
|
isGreaterThan(n: $npm$big$number$object): boolean;
|
||||||
gte(n: $npm$big$number$object): boolean;
|
gte(n: $npm$big$number$object): boolean;
|
||||||
lt(n: $npm$big$number$object): boolean;
|
lt(n: $npm$big$number$object): boolean;
|
||||||
lessThan(n: $npm$big$number$object): boolean;
|
isLessThan(n: $npm$big$number$object): boolean;
|
||||||
lte(n: $npm$big$number$object): boolean;
|
lte(n: $npm$big$number$object): boolean;
|
||||||
lessThanOrEqualTo(n: $npm$big$number$object): boolean;
|
isLessThanOrEqualTo(n: $npm$big$number$object): boolean;
|
||||||
|
isNaN(): boolean;
|
||||||
minus(n: $npm$big$number$object): T_BigNumber;
|
minus(n: $npm$big$number$object): T_BigNumber;
|
||||||
mod(n: $npm$big$number$object): T_BigNumber;
|
mod(n: $npm$big$number$object): T_BigNumber;
|
||||||
plus(n: $npm$big$number$object): T_BigNumber;
|
plus(n: $npm$big$number$object): T_BigNumber;
|
||||||
pow(exp: number): BigNumber;
|
pow(exp: number): BigNumber;
|
||||||
round(dp: ?number, rm: ?RM): T_BigNumber;
|
|
||||||
sqrt(): T_BigNumber;
|
sqrt(): T_BigNumber;
|
||||||
times(n: $npm$big$number$object): T_BigNumber;
|
times(n: $npm$big$number$object): T_BigNumber;
|
||||||
toExponential(dp: ?number): string;
|
toExponential(dp: ?number): string;
|
||||||
|
@ -92,8 +92,8 @@ export default (state: State = initialState, action: Action): State => {
|
|||||||
case WALLET.CLEAR_UNAVAILABLE_DEVICE_DATA:
|
case WALLET.CLEAR_UNAVAILABLE_DEVICE_DATA:
|
||||||
return clear(state, action.devices);
|
return clear(state, action.devices);
|
||||||
|
|
||||||
//case CONNECT.FORGET_SINGLE :
|
//case CONNECT.FORGET_SINGLE :
|
||||||
// return forgetAccounts(state, action);
|
// return forgetAccounts(state, action);
|
||||||
|
|
||||||
case ACCOUNT.UPDATE:
|
case ACCOUNT.UPDATE:
|
||||||
return updateAccount(state, action.payload);
|
return updateAccount(state, action.payload);
|
||||||
|
@ -16,6 +16,7 @@ export type BlockchainNetwork = {
|
|||||||
feeTimestamp: number,
|
feeTimestamp: number,
|
||||||
feeLevels: Array<BlockchainFeeLevel>,
|
feeLevels: Array<BlockchainFeeLevel>,
|
||||||
connected: boolean,
|
connected: boolean,
|
||||||
|
connecting: boolean,
|
||||||
block: number,
|
block: number,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -23,6 +24,26 @@ export type State = Array<BlockchainNetwork>;
|
|||||||
|
|
||||||
export const initialState: State = [];
|
export const initialState: State = [];
|
||||||
|
|
||||||
|
const onStartSubscribe = (state: State, shortcut: string): State => {
|
||||||
|
const network = state.find(b => b.shortcut === shortcut);
|
||||||
|
if (network) {
|
||||||
|
const others = state.filter(b => b !== network);
|
||||||
|
return others.concat([{
|
||||||
|
...network,
|
||||||
|
connecting: true,
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return state.concat([{
|
||||||
|
shortcut,
|
||||||
|
connected: false,
|
||||||
|
connecting: true,
|
||||||
|
block: 0,
|
||||||
|
feeTimestamp: 0,
|
||||||
|
feeLevels: [],
|
||||||
|
}]);
|
||||||
|
};
|
||||||
|
|
||||||
const onConnect = (state: State, action: BlockchainConnect): State => {
|
const onConnect = (state: State, action: BlockchainConnect): State => {
|
||||||
const shortcut = action.payload.coin.shortcut.toLowerCase();
|
const shortcut = action.payload.coin.shortcut.toLowerCase();
|
||||||
const network = state.find(b => b.shortcut === shortcut);
|
const network = state.find(b => b.shortcut === shortcut);
|
||||||
@ -31,13 +52,16 @@ const onConnect = (state: State, action: BlockchainConnect): State => {
|
|||||||
const others = state.filter(b => b !== network);
|
const others = state.filter(b => b !== network);
|
||||||
return others.concat([{
|
return others.concat([{
|
||||||
...network,
|
...network,
|
||||||
|
block: info.block,
|
||||||
connected: true,
|
connected: true,
|
||||||
|
connecting: false,
|
||||||
}]);
|
}]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return state.concat([{
|
return state.concat([{
|
||||||
shortcut,
|
shortcut,
|
||||||
connected: true,
|
connected: true,
|
||||||
|
connecting: false,
|
||||||
block: info.block,
|
block: info.block,
|
||||||
feeTimestamp: 0,
|
feeTimestamp: 0,
|
||||||
feeLevels: [],
|
feeLevels: [],
|
||||||
@ -52,12 +76,14 @@ const onError = (state: State, action: BlockchainError): State => {
|
|||||||
return others.concat([{
|
return others.concat([{
|
||||||
...network,
|
...network,
|
||||||
connected: false,
|
connected: false,
|
||||||
|
connecting: false,
|
||||||
}]);
|
}]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return state.concat([{
|
return state.concat([{
|
||||||
shortcut,
|
shortcut,
|
||||||
connected: false,
|
connected: false,
|
||||||
|
connecting: false,
|
||||||
block: 0,
|
block: 0,
|
||||||
feeTimestamp: 0,
|
feeTimestamp: 0,
|
||||||
feeLevels: [],
|
feeLevels: [],
|
||||||
@ -93,6 +119,8 @@ const updateFee = (state: State, shortcut: string, feeLevels: Array<BlockchainFe
|
|||||||
|
|
||||||
export default (state: State = initialState, action: Action): State => {
|
export default (state: State = initialState, action: Action): State => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
case BLOCKCHAIN_ACTION.START_SUBSCRIBE:
|
||||||
|
return onStartSubscribe(state, action.shortcut);
|
||||||
case BLOCKCHAIN_EVENT.CONNECT:
|
case BLOCKCHAIN_EVENT.CONNECT:
|
||||||
return onConnect(state, action);
|
return onConnect(state, action);
|
||||||
case BLOCKCHAIN_EVENT.ERROR:
|
case BLOCKCHAIN_EVENT.ERROR:
|
||||||
|
@ -18,7 +18,12 @@ export type State = {
|
|||||||
} | {
|
} | {
|
||||||
context: typeof MODAL.CONTEXT_EXTERNAL_WALLET,
|
context: typeof MODAL.CONTEXT_EXTERNAL_WALLET,
|
||||||
windowType?: string;
|
windowType?: string;
|
||||||
}
|
} | {
|
||||||
|
context: typeof MODAL.CONTEXT_SCAN_QR,
|
||||||
|
} | {
|
||||||
|
context: typeof MODAL.CONTEXT_CONFIRMATION,
|
||||||
|
windowType: string;
|
||||||
|
};
|
||||||
|
|
||||||
const initialState: State = {
|
const initialState: State = {
|
||||||
context: MODAL.CONTEXT_NONE,
|
context: MODAL.CONTEXT_NONE,
|
||||||
@ -91,6 +96,17 @@ export default function modal(state: State = initialState, action: Action): Stat
|
|||||||
windowType: action.id,
|
windowType: action.id,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
case MODAL.OPEN_SCAN_QR:
|
||||||
|
return {
|
||||||
|
context: MODAL.CONTEXT_SCAN_QR,
|
||||||
|
};
|
||||||
|
|
||||||
|
case UI.REQUEST_CONFIRMATION:
|
||||||
|
return {
|
||||||
|
context: MODAL.CONTEXT_CONFIRMATION,
|
||||||
|
windowType: action.payload.view,
|
||||||
|
};
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
@ -82,6 +82,7 @@ export default (state: State = initialState, action: Action): State => {
|
|||||||
case SEND.INIT:
|
case SEND.INIT:
|
||||||
case SEND.CHANGE:
|
case SEND.CHANGE:
|
||||||
case SEND.VALIDATION:
|
case SEND.VALIDATION:
|
||||||
|
case SEND.CLEAR:
|
||||||
return action.state;
|
return action.state;
|
||||||
|
|
||||||
case SEND.TOGGLE_ADVANCED:
|
case SEND.TOGGLE_ADVANCED:
|
||||||
|
@ -21,12 +21,14 @@ export type State = {
|
|||||||
touched: {[k: string]: boolean};
|
touched: {[k: string]: boolean};
|
||||||
address: string;
|
address: string;
|
||||||
amount: string;
|
amount: string;
|
||||||
|
minAmount: string;
|
||||||
setMax: boolean;
|
setMax: boolean;
|
||||||
feeLevels: Array<FeeLevel>;
|
feeLevels: Array<FeeLevel>;
|
||||||
selectedFeeLevel: FeeLevel;
|
selectedFeeLevel: FeeLevel;
|
||||||
fee: string;
|
fee: string;
|
||||||
feeNeedsUpdate: boolean;
|
feeNeedsUpdate: boolean;
|
||||||
sequence: string;
|
sequence: string;
|
||||||
|
destinationTag: string;
|
||||||
total: string;
|
total: string;
|
||||||
|
|
||||||
errors: {[k: string]: string};
|
errors: {[k: string]: string};
|
||||||
@ -46,6 +48,7 @@ export const initialState: State = {
|
|||||||
touched: {},
|
touched: {},
|
||||||
address: '',
|
address: '',
|
||||||
amount: '',
|
amount: '',
|
||||||
|
minAmount: '0',
|
||||||
setMax: false,
|
setMax: false,
|
||||||
feeLevels: [],
|
feeLevels: [],
|
||||||
selectedFeeLevel: {
|
selectedFeeLevel: {
|
||||||
@ -56,6 +59,7 @@ export const initialState: State = {
|
|||||||
fee: '0',
|
fee: '0',
|
||||||
feeNeedsUpdate: false,
|
feeNeedsUpdate: false,
|
||||||
sequence: '0',
|
sequence: '0',
|
||||||
|
destinationTag: '',
|
||||||
total: '0',
|
total: '0',
|
||||||
|
|
||||||
errors: {},
|
errors: {},
|
||||||
@ -73,6 +77,7 @@ export default (state: State = initialState, action: Action): State => {
|
|||||||
case SEND.INIT:
|
case SEND.INIT:
|
||||||
case SEND.CHANGE:
|
case SEND.CHANGE:
|
||||||
case SEND.VALIDATION:
|
case SEND.VALIDATION:
|
||||||
|
case SEND.CLEAR:
|
||||||
return action.state;
|
return action.state;
|
||||||
|
|
||||||
case SEND.TOGGLE_ADVANCED:
|
case SEND.TOGGLE_ADVANCED:
|
||||||
|
@ -6,6 +6,7 @@ import { DEVICE, TRANSPORT } from 'trezor-connect';
|
|||||||
import * as MODAL from 'actions/constants/modal';
|
import * as MODAL from 'actions/constants/modal';
|
||||||
import * as WALLET from 'actions/constants/wallet';
|
import * as WALLET from 'actions/constants/wallet';
|
||||||
import * as CONNECT from 'actions/constants/TrezorConnect';
|
import * as CONNECT from 'actions/constants/TrezorConnect';
|
||||||
|
import * as ACCOUNT from 'actions/constants/account';
|
||||||
|
|
||||||
import type { Action, RouterLocationState, TrezorDevice } from 'flowtype';
|
import type { Action, RouterLocationState, TrezorDevice } from 'flowtype';
|
||||||
|
|
||||||
@ -14,6 +15,7 @@ type State = {
|
|||||||
online: boolean;
|
online: boolean;
|
||||||
dropdownOpened: boolean;
|
dropdownOpened: boolean;
|
||||||
showBetaDisclaimer: boolean;
|
showBetaDisclaimer: boolean;
|
||||||
|
showSidebar: ?boolean;
|
||||||
initialParams: ?RouterLocationState;
|
initialParams: ?RouterLocationState;
|
||||||
initialPathname: ?string;
|
initialPathname: ?string;
|
||||||
firstLocationChange: boolean;
|
firstLocationChange: boolean;
|
||||||
@ -27,6 +29,7 @@ const initialState: State = {
|
|||||||
dropdownOpened: false,
|
dropdownOpened: false,
|
||||||
firstLocationChange: true,
|
firstLocationChange: true,
|
||||||
showBetaDisclaimer: false,
|
showBetaDisclaimer: false,
|
||||||
|
showSidebar: null,
|
||||||
initialParams: null,
|
initialParams: null,
|
||||||
initialPathname: null,
|
initialPathname: null,
|
||||||
disconnectRequest: null,
|
disconnectRequest: null,
|
||||||
@ -71,6 +74,11 @@ export default function wallet(state: State = initialState, action: Action): Sta
|
|||||||
...state,
|
...state,
|
||||||
dropdownOpened: false,
|
dropdownOpened: false,
|
||||||
};
|
};
|
||||||
|
case ACCOUNT.UPDATE_SELECTED_ACCOUNT:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
showSidebar: false,
|
||||||
|
};
|
||||||
|
|
||||||
case CONNECT.DISCONNECT_REQUEST:
|
case CONNECT.DISCONNECT_REQUEST:
|
||||||
return {
|
return {
|
||||||
@ -94,6 +102,12 @@ export default function wallet(state: State = initialState, action: Action): Sta
|
|||||||
selectedDevice: action.device,
|
selectedDevice: action.device,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
case WALLET.TOGGLE_SIDEBAR:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
showSidebar: !state.showSidebar,
|
||||||
|
};
|
||||||
|
|
||||||
case WALLET.SHOW_BETA_DISCLAIMER:
|
case WALLET.SHOW_BETA_DISCLAIMER:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
@ -95,6 +95,7 @@ export const getPendingSequence = (pending: Array<Transaction>): number => pendi
|
|||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
export const getPendingAmount = (pending: Array<Transaction>, currency: string, token: boolean = false): BigNumber => pending.reduce((value: BigNumber, tx: Transaction): BigNumber => {
|
export const getPendingAmount = (pending: Array<Transaction>, currency: string, token: boolean = false): BigNumber => pending.reduce((value: BigNumber, tx: Transaction): BigNumber => {
|
||||||
|
if (tx.type !== 'send') return value;
|
||||||
if (!token) {
|
if (!token) {
|
||||||
// regular transactions
|
// regular transactions
|
||||||
// add fees from token txs and amount from regular txs
|
// add fees from token txs and amount from regular txs
|
||||||
|
@ -12,6 +12,11 @@ export const routes: Array<Route> = [
|
|||||||
pattern: '/',
|
pattern: '/',
|
||||||
fields: [],
|
fields: [],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'landing-version',
|
||||||
|
pattern: '/version',
|
||||||
|
fields: ['version'],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'landing-bridge',
|
name: 'landing-bridge',
|
||||||
pattern: '/bridge',
|
pattern: '/bridge',
|
||||||
@ -57,6 +62,11 @@ export const routes: Array<Route> = [
|
|||||||
pattern: '/device/:device/firmware-update',
|
pattern: '/device/:device/firmware-update',
|
||||||
fields: ['device', 'firmware-update'],
|
fields: ['device', 'firmware-update'],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'wallet-backup',
|
||||||
|
pattern: '/device/:device/backup',
|
||||||
|
fields: ['device', 'backup'],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'wallet-device-settings',
|
name: 'wallet-device-settings',
|
||||||
pattern: '/device/:device/settings',
|
pattern: '/device/:device/settings',
|
||||||
|
@ -8,7 +8,7 @@ import animationStyles from './Animations';
|
|||||||
const baseStyles = createGlobalStyle`
|
const baseStyles = createGlobalStyle`
|
||||||
html, body {
|
html, body {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
min-height: 100vh;
|
||||||
position: relative;
|
position: relative;
|
||||||
font-family: "Roboto", -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
|
font-family: "Roboto", -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
|
||||||
font-weight: ${FONT_WEIGHT.NORMAL};
|
font-weight: ${FONT_WEIGHT.NORMAL};
|
||||||
@ -43,7 +43,7 @@ const baseStyles = createGlobalStyle`
|
|||||||
}
|
}
|
||||||
|
|
||||||
#trezor-wallet-root {
|
#trezor-wallet-root {
|
||||||
height: 100%;
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
${animationStyles};
|
${animationStyles};
|
||||||
|
@ -1,67 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`device utils get status 1`] = `"disconnected"`;
|
|
||||||
|
|
||||||
exports[`device utils get status 2`] = `"unavailable"`;
|
|
||||||
|
|
||||||
exports[`device utils get status 3`] = `"unavailable"`;
|
|
||||||
|
|
||||||
exports[`device utils get status 4`] = `"connected"`;
|
|
||||||
|
|
||||||
exports[`device utils get status 5`] = `"unacquired"`;
|
|
||||||
|
|
||||||
exports[`device utils get status 6`] = `"used-in-other-window"`;
|
|
||||||
|
|
||||||
exports[`device utils get status color 1`] = `"#494949"`;
|
|
||||||
|
|
||||||
exports[`device utils get status color 2`] = `"#494949"`;
|
|
||||||
|
|
||||||
exports[`device utils get status color 3`] = `"#494949"`;
|
|
||||||
|
|
||||||
exports[`device utils get status color 4`] = `"#EB8A00"`;
|
|
||||||
|
|
||||||
exports[`device utils get status color 5`] = `"#01B757"`;
|
|
||||||
|
|
||||||
exports[`device utils get status color 6`] = `"#EB8A00"`;
|
|
||||||
|
|
||||||
exports[`device utils get status color 7`] = `"#ED1212"`;
|
|
||||||
|
|
||||||
exports[`device utils get status color 8`] = `"#ED1212"`;
|
|
||||||
|
|
||||||
exports[`device utils get status name 1`] = `"Status unknown"`;
|
|
||||||
|
|
||||||
exports[`device utils get status name 2`] = `"Status unknown"`;
|
|
||||||
|
|
||||||
exports[`device utils get status name 3`] = `"Status unknown"`;
|
|
||||||
|
|
||||||
exports[`device utils get status name 4`] = `"Used in other window"`;
|
|
||||||
|
|
||||||
exports[`device utils get status name 5`] = `"Connected"`;
|
|
||||||
|
|
||||||
exports[`device utils get status name 6`] = `"Used in other window"`;
|
|
||||||
|
|
||||||
exports[`device utils get status name 7`] = `"Disconnected"`;
|
|
||||||
|
|
||||||
exports[`device utils get status name 8`] = `"Unavailable"`;
|
|
||||||
|
|
||||||
exports[`device utils get version 1`] = `"1"`;
|
|
||||||
|
|
||||||
exports[`device utils get version 2`] = `"1"`;
|
|
||||||
|
|
||||||
exports[`device utils get version 3`] = `"1"`;
|
|
||||||
|
|
||||||
exports[`device utils get version 4`] = `"1"`;
|
|
||||||
|
|
||||||
exports[`device utils get version 5`] = `"1"`;
|
|
||||||
|
|
||||||
exports[`device utils get version 6`] = `"T"`;
|
|
||||||
|
|
||||||
exports[`device utils isDisabled 1`] = `false`;
|
|
||||||
|
|
||||||
exports[`device utils isDisabled 2`] = `true`;
|
|
||||||
|
|
||||||
exports[`device utils isWebUSB 1`] = `true`;
|
|
||||||
|
|
||||||
exports[`device utils isWebUSB 2`] = `false`;
|
|
||||||
|
|
||||||
exports[`device utils isWebUSB 3`] = `true`;
|
|
@ -1,45 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`eth utils calcGasPrice 1`] = `"89090990901"`;
|
|
||||||
|
|
||||||
exports[`eth utils decimalToHex 1`] = `"0"`;
|
|
||||||
|
|
||||||
exports[`eth utils decimalToHex 2`] = `"1"`;
|
|
||||||
|
|
||||||
exports[`eth utils decimalToHex 3`] = `"2"`;
|
|
||||||
|
|
||||||
exports[`eth utils decimalToHex 4`] = `"64"`;
|
|
||||||
|
|
||||||
exports[`eth utils decimalToHex 5`] = `"2540be3ff"`;
|
|
||||||
|
|
||||||
exports[`eth utils hexToDecimal 1`] = `"9999999999"`;
|
|
||||||
|
|
||||||
exports[`eth utils hexToDecimal 2`] = `"100"`;
|
|
||||||
|
|
||||||
exports[`eth utils hexToDecimal 3`] = `"2"`;
|
|
||||||
|
|
||||||
exports[`eth utils hexToDecimal 4`] = `"1"`;
|
|
||||||
|
|
||||||
exports[`eth utils hexToDecimal 5`] = `"0"`;
|
|
||||||
|
|
||||||
exports[`eth utils hexToDecimal 6`] = `"null"`;
|
|
||||||
|
|
||||||
exports[`eth utils padLeftEven 1`] = `"02540be3ff"`;
|
|
||||||
|
|
||||||
exports[`eth utils sanitizeHex 1`] = `"0x02540be3ff"`;
|
|
||||||
|
|
||||||
exports[`eth utils sanitizeHex 2`] = `"0x01"`;
|
|
||||||
|
|
||||||
exports[`eth utils sanitizeHex 3`] = `"0x02"`;
|
|
||||||
|
|
||||||
exports[`eth utils sanitizeHex 4`] = `"0x0100"`;
|
|
||||||
|
|
||||||
exports[`eth utils sanitizeHex 5`] = `"0x0999"`;
|
|
||||||
|
|
||||||
exports[`eth utils sanitizeHex 6`] = `""`;
|
|
||||||
|
|
||||||
exports[`eth utils strip 1`] = `""`;
|
|
||||||
|
|
||||||
exports[`eth utils strip 2`] = `"02540be3ff"`;
|
|
||||||
|
|
||||||
exports[`eth utils strip 3`] = `"02540be3ff"`;
|
|
@ -1,45 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`format utils btckb2satoshib 1`] = `0`;
|
|
||||||
|
|
||||||
exports[`format utils btckb2satoshib 2`] = `100000`;
|
|
||||||
|
|
||||||
exports[`format utils btckb2satoshib 3`] = `200000`;
|
|
||||||
|
|
||||||
exports[`format utils btckb2satoshib 4`] = `10000000`;
|
|
||||||
|
|
||||||
exports[`format utils btckb2satoshib 5`] = `99900000`;
|
|
||||||
|
|
||||||
exports[`format utils formatAmount 1`] = `"0 btc"`;
|
|
||||||
|
|
||||||
exports[`format utils formatAmount 2`] = `"10 mBTC"`;
|
|
||||||
|
|
||||||
exports[`format utils formatAmount 3`] = `"0.000005 mBTC"`;
|
|
||||||
|
|
||||||
exports[`format utils formatAmount 4`] = `"1e-8 eth"`;
|
|
||||||
|
|
||||||
exports[`format utils formatAmount 5`] = `"0.00099999 tau"`;
|
|
||||||
|
|
||||||
exports[`format utils formatTime 1`] = `"No time estimate"`;
|
|
||||||
|
|
||||||
exports[`format utils formatTime 2`] = `"1 minutes"`;
|
|
||||||
|
|
||||||
exports[`format utils formatTime 3`] = `"2 minutes"`;
|
|
||||||
|
|
||||||
exports[`format utils formatTime 4`] = `"1 hour 40 minutes"`;
|
|
||||||
|
|
||||||
exports[`format utils formatTime 5`] = `"16 hours 39 minutes"`;
|
|
||||||
|
|
||||||
exports[`format utils formatTime 6`] = `"45 minutes"`;
|
|
||||||
|
|
||||||
exports[`format utils hexToString 1`] = `"test"`;
|
|
||||||
|
|
||||||
exports[`format utils hexToString 2`] = `"0001"`;
|
|
||||||
|
|
||||||
exports[`format utils hexToString 3`] = `"test99999"`;
|
|
||||||
|
|
||||||
exports[`format utils stringToHex 1`] = `"0074006500730074"`;
|
|
||||||
|
|
||||||
exports[`format utils stringToHex 2`] = `"0030003000300031"`;
|
|
||||||
|
|
||||||
exports[`format utils stringToHex 3`] = `"007400650073007400390039003900390039"`;
|
|
@ -1,41 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`device utils get icon 1`] = `
|
|
||||||
Array [
|
|
||||||
"M693.024 330.944c-99.968-99.936-262.080-99.936-362.048 0s-99.968 262.112 0 362.080c99.968 100 262.144 99.936 362.048 0 99.968-99.904 99.968-262.176 0-362.080zM507.904 300.192c27.008 0 48.992 21.984 48.992 49.088 0 27.296-21.984 49.472-48.992 49.472-27.264 0-49.536-22.176-49.536-49.472 0-27.552 21.728-49.088 49.536-49.088zM586.656 660.8c0 10.304-4.96 15.328-15.264 15.328h-126.464c-10.304 0-15.328-5.024-15.328-15.328v-32.256c0-10.304 5.024-15.264 15.328-15.264h23.36v-136.064h-23.872c-10.304 0-15.264-5.024-15.264-15.328v-32.224c0-10.304 4.96-15.264 15.264-15.264h88.288c10.304 0 15.264 4.96 15.264 15.264v183.648h23.424c10.304 0 15.264 4.96 15.264 15.264v32.224z",
|
|
||||||
]
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`device utils get icon 2`] = `
|
|
||||||
Array [
|
|
||||||
"M693.12 330.88c-46.317-46.267-110.276-74.88-180.919-74.88-141.385 0-256 114.615-256 256s114.615 256 256 256c70.642 0 134.602-28.613 180.921-74.882l-0.002 0.002c46.387-46.337 75.081-110.377 75.081-181.12s-28.694-134.783-75.079-181.118l-0.002-0.002zM494.080 344.32h53.12c16 0 18.24 9.28 18.24 14.72v10.24l-10.88 194.56c0 14.4-8 17.28-18.88 17.28h-28.16c-10.56 0-17.28-2.88-18.88-17.92l-10.88-193.92v-10.56c-1.28-4.8 2.24-14.080 16.32-14.080zM521.28 717.76c-0.095 0.001-0.207 0.001-0.319 0.001-27.747 0-50.24-22.493-50.24-50.24s22.493-50.24 50.24-50.24c27.747 0 50.24 22.493 50.24 50.24 0 0.112 0 0.224-0.001 0.336v-0.017c0 0 0 0.001 0 0.001 0 27.634-22.311 50.057-49.903 50.239h-0.017z",
|
|
||||||
]
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`device utils get icon 3`] = `
|
|
||||||
Array [
|
|
||||||
"M795.616 735.008l-264.896-465.44c-10.272-18.080-27.168-18.080-37.504 0l-264.864 465.44c-10.272 18.176-1.696 32.992 19.040 32.992h529.184c20.8 0 29.376-14.816 19.040-32.992zM549.76 673.12c0 10.464-8.48 18.976-18.912 18.976h-37.792c-10.336 0-18.912-8.512-18.912-18.976v-37.952c0-10.464 8.576-18.976 18.912-18.976h37.792c10.4 0 18.912 8.544 18.912 18.976v37.952zM549.76 559.264c0 10.464-8.48 18.976-18.912 18.976h-37.792c-10.336 0-18.912-8.512-18.912-18.976v-113.856c0-10.464 8.576-18.976 18.912-18.976h37.792c10.4 0 18.912 8.544 18.912 18.976v113.856z",
|
|
||||||
]
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`device utils get icon 4`] = `
|
|
||||||
Array [
|
|
||||||
"M692.8 313.92l-1.92-1.92c-6.246-7.057-15.326-11.484-25.44-11.484s-19.194 4.427-25.409 11.448l-0.031 0.036-196.48 224-3.84 1.6-3.84-1.92-48.64-57.28c-7.010-7.905-17.193-12.862-28.533-12.862-21.031 0-38.080 17.049-38.080 38.080 0 7.495 2.165 14.485 5.905 20.377l-0.092-0.155 100.8 148.16c5.391 8.036 14.386 13.292 24.618 13.44h8.662c17.251-0.146 32.385-9.075 41.163-22.529l0.117-0.191 195.2-296.32c4.473-6.632 7.141-14.803 7.141-23.597 0-11.162-4.297-21.32-11.326-28.911l0.025 0.028z",
|
|
||||||
]
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`device utils get icon 5`] = `undefined`;
|
|
||||||
|
|
||||||
exports[`device utils get icon 6`] = `undefined`;
|
|
||||||
|
|
||||||
exports[`device utils get status 1`] = `"#1E7FF0"`;
|
|
||||||
|
|
||||||
exports[`device utils get status 2`] = `"#ED1212"`;
|
|
||||||
|
|
||||||
exports[`device utils get status 3`] = `"#EB8A00"`;
|
|
||||||
|
|
||||||
exports[`device utils get status 4`] = `"#01B757"`;
|
|
||||||
|
|
||||||
exports[`device utils get status 5`] = `null`;
|
|
||||||
|
|
||||||
exports[`device utils get status 6`] = `null`;
|
|
@ -1,112 +1,87 @@
|
|||||||
import * as dUtils from 'utils/device';
|
import * as utils from 'utils/device';
|
||||||
|
|
||||||
describe('device utils', () => {
|
describe('device utils', () => {
|
||||||
it('get status', () => {
|
it('get status', () => {
|
||||||
const deviceMock = [
|
expect(utils.getStatus({ connected: false }))
|
||||||
{
|
.toBe('disconnected');
|
||||||
connected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
connected: true,
|
|
||||||
available: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
connected: true,
|
|
||||||
available: false,
|
|
||||||
type: null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
connected: true,
|
|
||||||
available: true,
|
|
||||||
type: 'acquired',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
connected: true,
|
|
||||||
available: true,
|
|
||||||
type: 'unacquired',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
connected: true,
|
|
||||||
available: true,
|
|
||||||
type: 'acquired',
|
|
||||||
status: 'occupied',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
deviceMock.forEach((device) => {
|
expect(utils.getStatus({ connected: true, available: false }))
|
||||||
expect(dUtils.getStatus(device)).toMatchSnapshot();
|
.toBe('unavailable');
|
||||||
});
|
|
||||||
|
expect(utils.getStatus({
|
||||||
|
connected: true,
|
||||||
|
available: false,
|
||||||
|
type: null,
|
||||||
|
})).toBe('unavailable');
|
||||||
|
|
||||||
|
expect(utils.getStatus({
|
||||||
|
connected: true,
|
||||||
|
available: true,
|
||||||
|
type: 'acquired',
|
||||||
|
})).toBe('connected');
|
||||||
|
|
||||||
|
expect(utils.getStatus({
|
||||||
|
connected: true,
|
||||||
|
available: true,
|
||||||
|
type: 'unacquired',
|
||||||
|
})).toBe('unacquired');
|
||||||
|
|
||||||
|
expect(utils.getStatus({
|
||||||
|
connected: true,
|
||||||
|
available: true,
|
||||||
|
type: 'acquired',
|
||||||
|
status: 'occupied',
|
||||||
|
})).toBe('used-in-other-window');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('isWebUSB', () => {
|
it('isWebUSB', () => {
|
||||||
const data = [
|
expect(utils.isWebUSB({ type: 'webusb', version: '1.6.0' })).toBe(true);
|
||||||
{ transport: { type: 'ParallelTransport', version: 'webusb' } },
|
expect(utils.isWebUSB({ type: 'aaaa', version: 'aaaaaa' })).toBe(false);
|
||||||
{ transport: { type: null, version: 'aaaaaa' } },
|
expect(utils.isWebUSB({ type: 'webusb' })).toBe(true);
|
||||||
{ transport: { type: 'ParallelTransport', version: 'webusb' } },
|
|
||||||
];
|
|
||||||
|
|
||||||
data.forEach((item) => {
|
|
||||||
expect(dUtils.isWebUSB(item.transport)).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('isDisabled', () => {
|
it('isDisabled', () => {
|
||||||
const data = [
|
expect(utils.isDisabled(
|
||||||
{ selectedDevice: { features: null }, devices: [1, 2, 3], transport: { version: 'webusb' } },
|
{ selectedDevice: { features: null } },
|
||||||
{ selectedDevice: { features: null }, devices: [], transport: { version: 'test' } },
|
[1, 2, 3],
|
||||||
];
|
{
|
||||||
|
version: 'webusb',
|
||||||
|
},
|
||||||
|
)).toBe(false);
|
||||||
|
|
||||||
data.forEach((item) => {
|
expect(utils.isDisabled(
|
||||||
expect(dUtils.isDisabled(item.selectedDevice, item.devices, item.transport)).toMatchSnapshot();
|
{ features: null }, [], { version: 'test' },
|
||||||
});
|
)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('get version', () => {
|
it('get version', () => {
|
||||||
const deviceMock = [
|
expect(utils.getVersion({})).toBe('One');
|
||||||
{ },
|
expect(utils.getVersion({ features: {} })).toBe('One');
|
||||||
{ features: {} },
|
expect(utils.getVersion({ features: { major_version: null } })).toBe('One');
|
||||||
{ features: { major_version: null } },
|
expect(utils.getVersion({ features: { major_version: 0 } })).toBe('One');
|
||||||
{ features: { major_version: 0 } },
|
expect(utils.getVersion({ features: { major_version: 1 } })).toBe('One');
|
||||||
{ features: { major_version: 1 } },
|
expect(utils.getVersion({ features: { major_version: 2 } })).toBe('T');
|
||||||
{ features: { major_version: 2 } },
|
|
||||||
];
|
|
||||||
|
|
||||||
deviceMock.forEach((device) => {
|
|
||||||
expect(dUtils.getVersion(device)).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('get status color', () => {
|
it('get status color', () => {
|
||||||
const entry = [
|
expect(utils.getStatusColor(0)).toBe('#494949');
|
||||||
0,
|
expect(utils.getStatusColor(null)).toBe('#494949');
|
||||||
null,
|
expect(utils.getStatusColor('sdsdsdsd')).toBe('#494949');
|
||||||
'sdsdsdsd',
|
expect(utils.getStatusColor('used-in-other-window')).toBe('#EB8A00');
|
||||||
'used-in-other-window',
|
expect(utils.getStatusColor('connected')).toBe('#01B757');
|
||||||
'connected',
|
expect(utils.getStatusColor('unacquired')).toBe('#EB8A00');
|
||||||
'unacquired',
|
expect(utils.getStatusColor('disconnected')).toBe('#ED1212');
|
||||||
'disconnected',
|
expect(utils.getStatusColor('unavailable')).toBe('#ED1212');
|
||||||
'unavailable',
|
|
||||||
];
|
|
||||||
|
|
||||||
entry.forEach((status) => {
|
|
||||||
expect(dUtils.getStatusColor(status)).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('get status name', () => {
|
it('get status name', () => {
|
||||||
const entry = [
|
expect(utils.getStatusName(0)).toBe('Status unknown');
|
||||||
0,
|
expect(utils.getStatusName(null)).toBe('Status unknown');
|
||||||
null,
|
expect(utils.getStatusName('sdsdsdsd')).toBe('Status unknown');
|
||||||
'sdsdsdsd',
|
expect(utils.getStatusName('used-in-other-window')).toBe('Used in other window');
|
||||||
'used-in-other-window',
|
expect(utils.getStatusName('connected')).toBe('Connected');
|
||||||
'connected',
|
expect(utils.getStatusName('unacquired')).toBe('Used in other window');
|
||||||
'unacquired',
|
expect(utils.getStatusName('disconnected')).toBe('Disconnected');
|
||||||
'disconnected',
|
expect(utils.getStatusName('unavailable')).toBe('Unavailable');
|
||||||
'unavailable',
|
|
||||||
];
|
|
||||||
|
|
||||||
entry.forEach((status) => {
|
|
||||||
expect(dUtils.getStatusName(status)).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
@ -1,53 +1,44 @@
|
|||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
import * as ethUtils from '../ethUtils';
|
import * as utils from '../ethUtils';
|
||||||
|
|
||||||
describe('eth utils', () => {
|
describe('eth utils', () => {
|
||||||
it('decimalToHex', () => {
|
it('decimalToHex', () => {
|
||||||
const input = [0, 1, 2, 100, 9999999999];
|
expect(utils.decimalToHex(0)).toBe('0');
|
||||||
|
expect(utils.decimalToHex(1)).toBe('1');
|
||||||
input.forEach((entry) => {
|
expect(utils.decimalToHex(2)).toBe('2');
|
||||||
expect(ethUtils.decimalToHex(entry)).toMatchSnapshot();
|
expect(utils.decimalToHex(100)).toBe('64');
|
||||||
});
|
expect(utils.decimalToHex(9999999999)).toBe('2540be3ff');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO: decimal as string ?????
|
||||||
it('hexToDecimal', () => {
|
it('hexToDecimal', () => {
|
||||||
const input = ['2540be3ff', '64', '2', '1', '0', ''];
|
expect(utils.hexToDecimal('2540be3ff')).toBe('9999999999');
|
||||||
|
expect(utils.hexToDecimal(64)).toBe('100');
|
||||||
input.forEach((entry) => {
|
expect(utils.hexToDecimal(2)).toBe('2');
|
||||||
expect(ethUtils.hexToDecimal(entry)).toMatchSnapshot();
|
expect(utils.hexToDecimal(1)).toBe('1');
|
||||||
});
|
expect(utils.hexToDecimal(0)).toBe('0');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('padLeftEven', () => {
|
it('padLeftEven', () => {
|
||||||
const input = ['2540be3ff'];
|
expect(utils.padLeftEven('2540be3ff')).toBe('02540be3ff');
|
||||||
|
|
||||||
input.forEach((entry) => {
|
|
||||||
expect(ethUtils.padLeftEven(entry)).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sanitizeHex', () => {
|
it('sanitizeHex', () => {
|
||||||
const input = ['0x2540be3ff', '1', '2', '100', '999', ''];
|
expect(utils.sanitizeHex('0x2540be3ff')).toBe('0x02540be3ff');
|
||||||
|
expect(utils.sanitizeHex('1')).toBe('0x01');
|
||||||
input.forEach((entry) => {
|
expect(utils.sanitizeHex('2')).toBe('0x02');
|
||||||
expect(ethUtils.sanitizeHex(entry)).toMatchSnapshot();
|
expect(utils.sanitizeHex('100')).toBe('0x0100');
|
||||||
});
|
expect(utils.sanitizeHex('999')).toBe('0x0999');
|
||||||
|
expect(utils.sanitizeHex('')).toBe('');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('strip', () => {
|
it('strip', () => {
|
||||||
const input = ['0x', '0x2540be3ff', '2540be3ff'];
|
expect(utils.strip('0x')).toBe('');
|
||||||
|
expect(utils.strip('0x2540be3ff')).toBe('02540be3ff');
|
||||||
input.forEach((entry) => {
|
expect(utils.strip('2540be3ff')).toBe('02540be3ff');
|
||||||
expect(ethUtils.strip(entry)).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calcGasPrice', () => {
|
it('calculate gas price', () => {
|
||||||
const input = [{ price: new BigNumber(9898998989), limit: '9' }];
|
expect(utils.calcGasPrice(new BigNumber(9898998989), 9)).toBe('89090990901');
|
||||||
|
|
||||||
input.forEach((entry) => {
|
|
||||||
expect(ethUtils.calcGasPrice(entry.price, entry.limit)).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,49 +1,41 @@
|
|||||||
import * as formatUtils from '../formatUtils';
|
import * as utils from '../formatUtils';
|
||||||
|
|
||||||
describe('format utils', () => {
|
describe('format utils', () => {
|
||||||
|
// TODO: check this weird function
|
||||||
it('formatAmount', () => {
|
it('formatAmount', () => {
|
||||||
const input = [
|
expect(utils.formatAmount(0, { isBitcoin: false, shortcut: 'mbtc' }, 'mbtc')).toBe('0 mbtc');
|
||||||
{ amount: 0, coinInfo: { isBitcoin: true, currencyUnits: 'mbtc', shortcut: 'btc' } },
|
expect(utils.formatAmount(1000000, { isBitcoin: true }, 'mbtc')).toBe('10 mBTC');
|
||||||
{ amount: 1000000, coinInfo: { isBitcoin: true, currencyUnits: 'mbtc', shortcut: 'btc' } },
|
expect(utils.formatAmount(0.5, { isBitcoin: true }, 'mbtc')).toBe('0.000005 mBTC');
|
||||||
{ amount: 0.5, coinInfo: { isBitcoin: true, currencyUnits: 'mbtc', shortcut: 'btc' } },
|
expect(utils.formatAmount(1, { isBitcoin: false, shortcut: 'eth' }, null)).toBe('1e-8 eth');
|
||||||
{ amount: 1, coinInfo: { isBitcoin: false, shortcut: 'eth' } },
|
expect(utils.formatAmount(99999, { isBitcoin: false, shortcut: 'tau' }, null)).toBe('0.00099999 tau');
|
||||||
{ amount: 99999, coinInfo: { isBitcoin: false, shortcut: 'tau' } },
|
|
||||||
];
|
|
||||||
|
|
||||||
input.forEach((entry) => {
|
|
||||||
expect(formatUtils.formatAmount(entry.amount, entry.coinInfo, entry.coinInfo.currencyUnits)).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('formatTime', () => {
|
it('format time', () => {
|
||||||
const input = [0, 1, 2, 100, 999, 45];
|
expect(utils.formatTime(0)).toBe('No time estimate');
|
||||||
|
expect(utils.formatTime(1)).toBe('1 minutes'); // TODO: should be minute
|
||||||
input.forEach((entry) => {
|
expect(utils.formatTime(2)).toBe('2 minutes');
|
||||||
expect(formatUtils.formatTime(entry)).toMatchSnapshot();
|
expect(utils.formatTime(45)).toBe('45 minutes');
|
||||||
});
|
expect(utils.formatTime(100)).toBe('1 hour 40 minutes');
|
||||||
|
expect(utils.formatTime(999)).toBe('16 hours 39 minutes');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('btckb2satoshib', () => {
|
it('btckb2satoshib', () => {
|
||||||
const input = [0, 1, 2, 100, 999];
|
expect(utils.btckb2satoshib(0)).toBe(0);
|
||||||
|
expect(utils.btckb2satoshib(1)).toBe(100000);
|
||||||
input.forEach((entry) => {
|
expect(utils.btckb2satoshib(2)).toBe(200000);
|
||||||
expect(formatUtils.btckb2satoshib(entry)).toMatchSnapshot();
|
expect(utils.btckb2satoshib(100)).toBe(10000000);
|
||||||
});
|
expect(utils.btckb2satoshib(999)).toBe(99900000);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('stringToHex', () => {
|
it('string to hex', () => {
|
||||||
const input = ['test', '0001', 'test99999'];
|
expect(utils.stringToHex('test')).toBe('0074006500730074');
|
||||||
|
expect(utils.stringToHex('0001')).toBe('0030003000300031');
|
||||||
input.forEach((entry) => {
|
expect(utils.stringToHex('test99999')).toBe('007400650073007400390039003900390039');
|
||||||
expect(formatUtils.stringToHex(entry)).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('hexToString', () => {
|
it('hex to string', () => {
|
||||||
const input = ['0074006500730074', '0030003000300031', '007400650073007400390039003900390039'];
|
expect(utils.hexToString('0074006500730074')).toBe('test');
|
||||||
|
expect(utils.hexToString('0030003000300031')).toBe('0001');
|
||||||
input.forEach((entry) => {
|
expect(utils.hexToString('007400650073007400390039003900390039')).toBe('test99999');
|
||||||
expect(formatUtils.hexToString(entry)).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,32 +1,12 @@
|
|||||||
import * as nUtils from 'utils/notification';
|
import * as utils from 'utils/notification';
|
||||||
|
|
||||||
describe('device utils', () => {
|
describe('notification utils', () => {
|
||||||
it('get status', () => {
|
it('get colors from status', () => {
|
||||||
const types = [
|
expect(utils.getPrimaryColor('info')).toBe('#1E7FF0');
|
||||||
'info',
|
expect(utils.getPrimaryColor('warning')).toBe('#EB8A00');
|
||||||
'error',
|
expect(utils.getPrimaryColor('error')).toBe('#ED1212');
|
||||||
'warning',
|
expect(utils.getPrimaryColor('success')).toBe('#01B757');
|
||||||
'success',
|
expect(utils.getPrimaryColor('kdsjflds')).toBe(null);
|
||||||
'kdsjflds',
|
expect(utils.getPrimaryColor('')).toBe(null);
|
||||||
'',
|
|
||||||
];
|
|
||||||
|
|
||||||
types.forEach((type) => {
|
|
||||||
expect(nUtils.getPrimaryColor(type)).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('get icon', () => {
|
|
||||||
const types = [
|
|
||||||
'info',
|
|
||||||
'error',
|
|
||||||
'warning',
|
|
||||||
'success',
|
|
||||||
'kdsjflds',
|
|
||||||
'',
|
|
||||||
];
|
|
||||||
|
|
||||||
types.forEach((type) => {
|
|
||||||
expect(nUtils.getIcon(type)).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
39
src/utils/cryptoUriParser.js
Normal file
39
src/utils/cryptoUriParser.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/* eslint-disable no-param-reassign */
|
||||||
|
/* eslint-disable prefer-destructuring */
|
||||||
|
/* @flow */
|
||||||
|
|
||||||
|
// copy paste from mytrezor (old wallet) https://github.com/satoshilabs/mytrezor/blob/87f8a8d9ca82a27b3941c5ec0f399079903f2bfd/app/components/address-input/address-input.js
|
||||||
|
|
||||||
|
export type parsedURI = {
|
||||||
|
address: string,
|
||||||
|
amount: ?string,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Parse a string read from a bitcoin QR code into an object
|
||||||
|
export const parseUri = (uri: string): ?parsedURI => {
|
||||||
|
const str = stripPrefix(uri);
|
||||||
|
const query: Array<string> = str.split('?');
|
||||||
|
const values: Object = (query.length > 1) ? parseQuery(query[1]) : {};
|
||||||
|
values.address = query[0];
|
||||||
|
|
||||||
|
return values;
|
||||||
|
};
|
||||||
|
|
||||||
|
const stripPrefix = (str: string): string => {
|
||||||
|
if (!str.match(':')) {
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
const parts = str.split(':');
|
||||||
|
parts.shift();
|
||||||
|
return parts.join('');
|
||||||
|
};
|
||||||
|
|
||||||
|
// Parse URL query string (like 'foo=bar&baz=1337) into an object
|
||||||
|
const parseQuery = (str: string): {} => str.split('&')
|
||||||
|
.map(val => val.split('='))
|
||||||
|
.reduce((vals, pair) => {
|
||||||
|
if (pair.length > 1) {
|
||||||
|
vals[pair[0]] = pair[1];
|
||||||
|
}
|
||||||
|
return vals;
|
||||||
|
}, {});
|
@ -79,7 +79,7 @@ export const getStatusName = (deviceStatus: string): string => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isWebUSB = (transport: Transport) => !!((transport.type && transport.version.indexOf('webusb') >= 0));
|
export const isWebUSB = (transport: Transport) => !!((transport.type && transport.type === 'webusb'));
|
||||||
|
|
||||||
export const isDisabled = (selectedDevice: TrezorDevice, devices: Array<TrezorDevice>, transport: Transport) => {
|
export const isDisabled = (selectedDevice: TrezorDevice, devices: Array<TrezorDevice>, transport: Transport) => {
|
||||||
if (isWebUSB(transport)) return false; // always enabled if webusb
|
if (isWebUSB(transport)) return false; // always enabled if webusb
|
||||||
@ -104,7 +104,7 @@ export const getVersion = (device: TrezorDevice): string => {
|
|||||||
if (device.features && device.features.major_version > 1) {
|
if (device.features && device.features.major_version > 1) {
|
||||||
version = 'T';
|
version = 'T';
|
||||||
} else {
|
} else {
|
||||||
version = '1';
|
version = 'One';
|
||||||
}
|
}
|
||||||
return version;
|
return version;
|
||||||
};
|
};
|
||||||
|
@ -56,7 +56,12 @@ export const hexToString = (hex: string): string => {
|
|||||||
|
|
||||||
export const toDecimalAmount = (amount: string | number, decimals: number): string => {
|
export const toDecimalAmount = (amount: string | number, decimals: number): string => {
|
||||||
try {
|
try {
|
||||||
return new BigNumber(amount).div(10 ** decimals).toString(10);
|
const bAmount = new BigNumber(amount);
|
||||||
|
// BigNumber() returns NaN on non-numeric string
|
||||||
|
if (bAmount.isNaN()) {
|
||||||
|
throw new Error('Amount is not a number');
|
||||||
|
}
|
||||||
|
return bAmount.div(10 ** decimals).toString(10);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return '0';
|
return '0';
|
||||||
}
|
}
|
||||||
@ -64,7 +69,12 @@ export const toDecimalAmount = (amount: string | number, decimals: number): stri
|
|||||||
|
|
||||||
export const fromDecimalAmount = (amount: string | number, decimals: number): string => {
|
export const fromDecimalAmount = (amount: string | number, decimals: number): string => {
|
||||||
try {
|
try {
|
||||||
return new BigNumber(amount).times(10 ** decimals).toString(10);
|
const bAmount = new BigNumber(amount);
|
||||||
|
// BigNumber() returns NaN on non-numeric string
|
||||||
|
if (bAmount.isNaN()) {
|
||||||
|
throw new Error('Amount is not a number');
|
||||||
|
}
|
||||||
|
return bAmount.times(10 ** decimals).toString(10);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return '0';
|
return '0';
|
||||||
}
|
}
|
||||||
|
24
src/utils/url.js
Normal file
24
src/utils/url.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
/* @flow */
|
||||||
|
import urlConstants from 'constants/urls';
|
||||||
|
import type { TrezorDevice } from 'flowtype';
|
||||||
|
|
||||||
|
const getOldWalletUrl = (device: ?TrezorDevice): string => {
|
||||||
|
if (!device || !device.firmwareRelease) return urlConstants.OLD_WALLET_BETA;
|
||||||
|
const release = device.firmwareRelease;
|
||||||
|
const url = release.channel === 'beta' ? urlConstants.OLD_WALLET_BETA : urlConstants.OLD_WALLET;
|
||||||
|
return url;
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: use uri template to build urls
|
||||||
|
const getOldWalletReleaseUrl = (device: ?TrezorDevice): string => {
|
||||||
|
if (!device || !device.firmwareRelease) return urlConstants.OLD_WALLET_BETA;
|
||||||
|
const release = device.firmwareRelease;
|
||||||
|
const url = getOldWalletUrl(device);
|
||||||
|
const version = release.version.join('.');
|
||||||
|
return `${url}?fw=${version}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
getOldWalletUrl,
|
||||||
|
getOldWalletReleaseUrl,
|
||||||
|
};
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user