diff --git a/.eslintrc b/.eslintrc index a0b0cbab3b..603b09bc9a 100755 --- a/.eslintrc +++ b/.eslintrc @@ -67,10 +67,8 @@ "react/no-array-index-key": "warn", "react/no-unused-prop-types": "warn", "react/prefer-stateless-function": 0, - "react/prop-types": "warn", + "react/prop-types": 0, "react/require-default-props": 0, - "react/sort-comp": 0, - "react/state-in-constructor": 0, "react/jsx-first-prop-new-line": [1, "multiline-multiprop"], "@typescript-eslint/ban-ts-comment": 1, "@typescript-eslint/no-empty-function": 1, @@ -78,22 +76,15 @@ "import/no-unresolved": 1, "@typescript-eslint/no-var-requires": 1, "camelcase": 1, - "no-empty": 1, + "no-empty": 1, "@typescript-eslint/no-explicit-any": 1, "no-shadow": 1, "react/no-did-update-set-state": 1, - "react/static-property-placement": 0, - "react/jsx-one-expression-per-line": 0, - "react/jsx-curly-newline": 0, - "react/jsx-indent": 0 + "react/sort-comp": 0, + "react/state-in-constructor": 0, + "react/static-property-placement": 0 }, - "plugins": [ - "@typescript-eslint", - "import", - "promise", - "react", - "jest" - ], + "plugins": ["@typescript-eslint", "import", "promise", "react", "jest"], "globals": { "API": true, "API_VERSION": true, @@ -103,10 +94,19 @@ "Process": true // TODO: remove after fix }, "settings": { - "import/resolver": { + "import/resolver": { "node": { - "extensions": [".js", ".jsx",".ts", ".tsx"] + "extensions": [".js", ".jsx", ".ts", ".tsx"] + } + } + }, + "overrides": [ + { + "files": "**/*.ts", + "rules": { + "no-useless-constructor": "off", + "@typescript-eslint/no-useless-constructor": "error" } } - } + ] } diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index de92566601..a91508a26b 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -20,7 +20,7 @@ or animated GIFs of important UI changes in both English and Japanese. Do not use shadow or any effects. On macOS this can be accomplished the following way: 1. Use the Command+Shift+4 keyboard shortcut. 2. Press the Spacebar. -3. Hold the Option button and click the window you want to capture. +3. Hold the Option button and click the window you want to capture. --> ## Testing Checklist @@ -29,7 +29,6 @@ Do not use shadow or any effects. On macOS this can be accomplished the followin Open a thread on #daedalus-qa on Slack, mention `@daedalusqa` and `@daedalusteam`, link the thread below --> - - [Slack QA thread](https://input-output-rnd.slack.com/messages/GGKFXSKC6) - [ ] Test @@ -38,15 +37,17 @@ Open a thread on #daedalus-qa on Slack, mention `@daedalusqa` and `@daedalusteam ## Review Checklist ### Basics -- [ ] PR assigned to the PR author(s) + +- [ ] PR assigned to the PR author(s) - [ ] `input-output-hk/daedalus-dev` and `input-output-hk/daedalus-qa` assigned as PR reviewers - [ ] If there are UI changes, Alexander Rukin assigned as an additional reviewer +- [ ] All visual regression testing has been reviewed (assign `run Chromatic` label to PR to trigger the run) - [ ] PR has appropriate labels (`release-vNext`, `feature`/`bug`/`chore`, `WIP`) - [ ] PR link is added to a Jira ticket, ticket moved to In Review - [ ] PR is updated to the most recent version of the target branch (and there are no conflicts) - [ ] PR has a good description that summarizes all changes - [ ] PR contains screenshots (in case of UI changes) -- [ ] CHANGELOG entry has been added to the top of the appropriate section (*Features*, *Fixes*, *Chores*) and is linked to the correct PR on GitHub +- [ ] CHANGELOG entry has been added to the top of the appropriate section (_Features_, _Fixes_, _Chores_) and is linked to the correct PR on GitHub - [ ] There are no missing translations (running `yarn manage:translations` produces no changes) - [ ] Text changes are proofread and approved (Jane Wild / Amy Reeve) - [ ] Japanese text changes are proofread and approved (Junko Oda) @@ -54,12 +55,14 @@ Open a thread on #daedalus-qa on Slack, mention `@daedalusqa` and `@daedalusteam - [ ] In case of dependency changes `yarn.lock` file is updated ### Code Quality + - [ ] Important parts of the code are properly commented and documented -- [ ] Code is properly typed with flow +- [ ] Code is properly typed with typescript types - [ ] React components are split-up enough to avoid unnecessary re-renderings - [ ] Any code that only works in main process is neatly separated from components ### Testing + - [ ] New feature/change is covered by acceptance tests - [ ] New feature/change is manually tested and approved by QA team - [ ] All existing acceptance tests are still up-to-date @@ -67,4 +70,5 @@ Open a thread on #daedalus-qa on Slack, mention `@daedalusqa` and `@daedalusteam - [ ] All existing Daedalus Testing scenarios are still up-to-date ### After Review + - [ ] Update Slack QA thread by marking it with a green checkmark diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml index aeddc615d6..fc5a939007 100644 --- a/.github/workflows/chromatic.yml +++ b/.github/workflows/chromatic.yml @@ -1,8 +1,18 @@ name: 'Chromatic' -on: push +on: + pull_request: + types: + - labeled + push: + branches: + - develop + jobs: chromatic-deployment: + if: contains(github.event.pull_request.labels.*.name, 'run Chromatic') || github.ref == 'refs/heads/develop' runs-on: ubuntu-latest + env: + STORYBOOK_FREEZE_DATE: "true" steps: - uses: actions/checkout@v1 - name: Setup Node.js @@ -18,6 +28,7 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} exitZeroOnChanges: true + buildScriptName: 'storybook:build:chromatic' - name: Publish to Chromatic and auto accept changes if: github.ref == 'refs/heads/develop' uses: chromaui/action@v1 @@ -25,3 +36,4 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} autoAcceptChanges: true + buildScriptName: 'storybook:build:chromatic' diff --git a/.github/workflows/run_tests_on_pr.yml b/.github/workflows/run_tests_on_pr.yml deleted file mode 100644 index 3ce7787de8..0000000000 --- a/.github/workflows/run_tests_on_pr.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: Run Tests for Pull Requests -on: - pull_request: - branches: - - develop - - master -jobs: - tests: - runs-on: ubuntu-latest - steps: - - name: Checkout Repo - uses: actions/checkout@v2 - - name: Setup Node.js - uses: actions/setup-node@v2 - with: - node-version: "12" - - run: yarn install - - run: yarn test:jest diff --git a/.github/workflows/verify_pr.yml b/.github/workflows/verify_pr.yml new file mode 100644 index 0000000000..1d8df8c6bd --- /dev/null +++ b/.github/workflows/verify_pr.yml @@ -0,0 +1,26 @@ +name: Verify Pull Request +on: + pull_request: +jobs: + tests: + runs-on: ubuntu-latest + steps: + - name: Checkout Repo + uses: actions/checkout@v2 + - name: Setup Node.js + uses: actions/setup-node@v2 + with: + node-version: "14" + - name: Restore node_modules from cache + uses: actions/cache@v2 + with: + path: '**/node_modules' + key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} + - name: Install dependencies + run: yarn --frozen-lockfile + - name: Run checks + run: yarn check:all + - name: Ensure there are no uncommited changes + run: git diff --exit-code || (echo "Did you forget to run 'yarn check:all' and commit changes?" && exit 1) + - name: Run tests + run: yarn test:jest --maxWorkers=3 diff --git a/.gitignore b/.gitignore index b43c8bb717..d785b3b350 100755 --- a/.gitignore +++ b/.gitignore @@ -119,3 +119,6 @@ Debug /cardano-cli /cardano-wallet .vscode + +# Typescript +*.scss.d.ts diff --git a/BESTPRACTICES.md b/BESTPRACTICES.md index 538779a638..785cb2f16e 100644 --- a/BESTPRACTICES.md +++ b/BESTPRACTICES.md @@ -1108,7 +1108,7 @@ type Props = { }; ``` -For preventing syntax errors, leave a comma after the last key/value pair incase the type definition's properties are rearranged or expanded upon in the future. +For preventing syntax errors, leave a comma after the last key/value pair in case the type definition's properties are rearranged or expanded upon in the future. :white_check_mark: ***Do*** @@ -1204,7 +1204,7 @@ const Names = () => (
## Formatting Selectors * Use class selectors instead of ID selectors. -* Use camelCase or dashed-case instead of PascelCase or names_with_underscores. +* Use camel case or dashed-case instead of pascal case or names_with_underscores. * Give each selector its own line. * Put a space before the opening brace `{` in rule declarations. * Put closing braces `}` of rule declarations on a new line. diff --git a/CHANGELOG.md b/CHANGELOG.md index f49216e1df..8d0380703c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,110 @@ ## vNext +### Features + +- Added ASCII name to token header when metadata name is missing ([PR 2904](https://github.com/input-output-hk/daedalus/pull/2904)) +- Improved IPC by reducing the amount of messages from periodic events ([PR 2892](https://github.com/input-output-hk/daedalus/pull/2892)) +- Improved RTS flags splash screen message ([PR 2901](https://github.com/input-output-hk/daedalus/pull/2901)) +- Implemented error message when trying to leave wallet without enough ada to support tokens ([PR 2783](https://github.com/input-output-hk/daedalus/pull/2783)) + +### Fixes + +- Fixed stake pool list view overlapping news feed ([PR 2917](https://github.com/input-output-hk/daedalus/pull/2917)) +- Restored opacity for search icon when focused ([PR 2909](https://github.com/input-output-hk/daedalus/pull/2909)) +- Fixed styling of the incentivized testnet rewards wallet dropdown ([PR 2907](https://github.com/input-output-hk/daedalus/pull/2907)) +- Fix warning sign displayed when recommend decimals is zero ([PR 2905](https://github.com/input-output-hk/daedalus/pull/2905)) +- Fixed discrete tooltip being clipped by loading overlay when stake pools are adjusted ([PR 2902](https://github.com/input-output-hk/daedalus/pull/2902)) +- Sets minimum transaction fee to ada input field when tokens are removed ([PR 2918](https://github.com/input-output-hk/daedalus/pull/2918)) + +### Chores + +- Fixed spelling issues and typos ([PR 2915](https://github.com/input-output-hk/daedalus/pull/2915)) +- Removed SASS ts-lint ignore comments ([PR 2870](https://github.com/input-output-hk/daedalus/pull/2870)) +- Enabled debugging of the main process ([PR 2893](https://github.com/input-output-hk/daedalus/pull/2893)) + +## 4.9.0 + +### Features + +- Added display of current/unspent rewards ([PR 2803](https://github.com/input-output-hk/daedalus/pull/2803)) +- Improve the syncing screen by showing syncing progress split into three stages ([PR 2877](https://github.com/input-output-hk/daedalus/pull/2877)) +- Improved stake pool searchbar ([PR 2847](https://github.com/input-output-hk/daedalus/pull/2847)) +- Implemented catalyst dynamic content ([PR 2856](https://github.com/input-output-hk/daedalus/pull/2856)) + +### Fixes + +- Fixed main container zIndex ([PR 2863](https://github.com/input-output-hk/daedalus/pull/2863)) +- Fixed ui overlap issues ([PR 2881](https://github.com/input-output-hk/daedalus/pull/2881)) +- Fixed the gap between Stake Pool View options ([PR 2899](https://github.com/input-output-hk/daedalus/pull/2899)) + +### Chores + +- Fixed Daedalus menu in Storybook used for theme and language selection ([PR 2886](https://github.com/input-output-hk/daedalus/pull/2886)) + +## 4.9.0-FC1 + +### Features + +- Added table view for delegated stake pools list ([PR 2837](https://github.com/input-output-hk/daedalus/pull/2837)) +- Removed Discreet mode notification ([PR 2852](https://github.com/input-output-hk/daedalus/pull/2852)) +- Unified CPU info in diagnostics dialog ([PR 2818](https://github.com/input-output-hk/daedalus/pull/2818)) +- Implemented wallet sorting on sidebar menu ([PR 2775](https://github.com/input-output-hk/daedalus/pull/2775)) +- Implemented new token picker ([PR 2787](https://github.com/input-output-hk/daedalus/pull/2787)) +- Improved wallet send form ([PR 2791](https://github.com/input-output-hk/daedalus/pull/2791), [PR 2859](https://github.com/input-output-hk/daedalus/pull/2859)) + +### Fixes + +- Fixed rewards CSV export issues ([PR 2885](https://github.com/input-output-hk/daedalus/pull/2885)) +- Fixed behaviour of wallet settings option of the app menu ([PR 2838](https://github.com/input-output-hk/daedalus/pull/2838)) +- Fixed styling of ITN rewards feature ([PR 2861](https://github.com/input-output-hk/daedalus/pull/2861)) +- Fixed available disk space takes a long time to show ([PR 2849](https://github.com/input-output-hk/daedalus/pull/2849)) + +### Chores + +- Migrated codebase from javascript to typescript ([PR 2843](https://github.com/input-output-hk/daedalus/pull/2843)) +- Updated the list of team members ([PR 2805](https://github.com/input-output-hk/daedalus/pull/2805)) + +## 4.8.0 + +### Features + +- Added dynamic RTS flags setting ([PR 2758](https://github.com/input-output-hk/daedalus/pull/2758/files)) +- Improved UI/UX of RTS flags settings ([PR 2842](https://github.com/input-output-hk/daedalus/pull/2842), [PR 2846](https://github.com/input-output-hk/daedalus/pull/2846)) +- Updated messages about Cardano node sync on the initial screen ([PR 2827](https://github.com/input-output-hk/daedalus/pull/2827), [PR 2831](https://github.com/input-output-hk/daedalus/pull/2831)) + +### Chores + +- Updated check-disk-space version ([PR 2845](https://github.com/input-output-hk/daedalus/pull/2845)) +- Updated CWB and Cardano Node ([PR 2822](https://github.com/input-output-hk/daedalus/pull/2822)) + +### Fixes + +- Fixed blockchain verification progress text ([PR 2840](https://github.com/input-output-hk/daedalus/pull/2840)) + +## 4.8.0-FC1 + +### Features + +- Added dynamic RTS flags setting ([PR 2758](https://github.com/input-output-hk/daedalus/pull/2758/files)) +- Improved UI/UX of RTS flags settings ([PR 2842](https://github.com/input-output-hk/daedalus/pull/2842), [PR 2846](https://github.com/input-output-hk/daedalus/pull/2846)) +- Updated messages about Cardano node sync on the initial screen ([PR 2827](https://github.com/input-output-hk/daedalus/pull/2827), [PR 2831](https://github.com/input-output-hk/daedalus/pull/2831)) + +### Chores + +- Updated check-disk-space version ([PR 2845](https://github.com/input-output-hk/daedalus/pull/2845)) +- Updated CWB and Cardano Node ([PR 2822](https://github.com/input-output-hk/daedalus/pull/2822)) + +### Fixes + +- Fixed blockchain verification progress text ([PR 2840](https://github.com/input-output-hk/daedalus/pull/2840)) + +## 4.7.0 + +### Features + +- Updated Catalyst dates ([PR 2812](https://github.com/input-output-hk/daedalus/pull/2812)) + ### Fixes - Fixed immediate language updates of application top menu bar ([PR 2813](https://github.com/input-output-hk/daedalus/pull/2813)) diff --git a/README.md b/README.md index 4cd19ffcd9..58330abddf 100644 --- a/README.md +++ b/README.md @@ -65,8 +65,8 @@ If you get SSL error when running `nix-shell` (SSL peer certificate or SSH remot 1. Run `yarn nix:selfnode` from `daedalus`. 2. Run `yarn dev` from the subsequent `nix-shell` (use `KEEP_LOCAL_CLUSTER_RUNNING` environment variable to keep the local cluster running after Daedalus exits: `KEEP_LOCAL_CLUSTER_RUNNING=true yarn dev`) -3. Run `yarn start` from a second `nix-shell` instance (running step 1 from a new terminal instance) -4. Once Daedalus has started and has gotten past the loading screen run the following commands from a new terminal window if you wish to import funded wallets: + 1. Alternatively: run `yarn nix:selfnode yarn dev` to achieve the same thing in a single command. Note: after `yarn dev` exits, you will still remain in the `nix-shell`. +3. Once Daedalus has started and has gotten past the loading screen run the following commands from a new terminal window if you wish to import funded wallets: - Byron wallets: `yarn byron:wallet:importer` - Shelley wallets: `yarn shelley:wallet:importer` - Mary wallets: `yarn mary:wallet:importer` (all of which contain native tokens which are visible once selfnode enters Mary era) @@ -95,37 +95,37 @@ If you get SSL error when running `nix-shell` (SSL peer certificate or SSH remot 1. Run `yarn nix:mainnet` from `daedalus`. 2. Run `yarn dev` from the subsequent `nix-shell` -3. Run `yarn start` from a second `nix-shell` instance (running step 1 from a new terminal instance) +3. Or in one command: `yarn nix:mainnet yarn dev` #### Flight 1. Run `yarn nix:flight` from `daedalus`. 2. Run `yarn dev` from the subsequent `nix-shell` -3. Run `yarn start` from a second `nix-shell` instance (running step 1 from a new terminal instance) +3. Or in one command: `yarn nix:flight yarn dev` #### Testnet 1. Run `yarn nix:testnet` from `daedalus`. 2. Run `yarn dev` from the subsequent `nix-shell` -3. Run `yarn start` from a second `nix-shell` instance (running step 1 from a new terminal instance) +3. Or in one command: `yarn nix:testnet yarn dev` #### Staging 1. Run `yarn nix:staging` from `daedalus`. 2. Run `yarn dev` from the subsequent `nix-shell` -3. Run `yarn start` from a second `nix-shell` instance (running step 1 from a new terminal instance) +3. Or in one command: `yarn nix:staging yarn dev` #### Shelley QA 1. Run `yarn nix:shelley_qa` from `daedalus`. 2. Run `yarn dev` from the subsequent `nix-shell` -3. Run `yarn start` from a second `nix-shell` instance (running step 1 from a new terminal instance) +3. Or in one command: `yarn nix:shelley_qa yarn dev` #### Alonzo Purple 1. Run `yarn nix:alonzo_purple` from `daedalus`. 2. Run `yarn dev` from the subsequent `nix-shell` -3. Run `yarn start` from a second `nix-shell` instance (running step 1 from a new terminal instance) +3. Or in one command: `yarn nix:alonzo_purple yarn dev` #### Native token metadata server @@ -201,6 +201,15 @@ Make sure to list bootstrap in externals in `webpack.config.base.js` or the app externals: ['bootstrap'] ``` +### Debugging + +You can debug the main process by following one of these approaches: +- [VSCode](https://code.visualstudio.com/docs/nodejs/nodejs-debugging#_attaching-to-nodejs) +- [Chrome](https://nodejs.org/en/docs/guides/debugging-getting-started/#inspector-clients) +- [IntelliJ](https://www.jetbrains.com/help/idea/run-debug-configuration-node-js-remote-debug.html) + +The inspector runs on port 9229 + ## Testing You can find more details regarding tests setup within diff --git a/bors.toml b/bors.toml index 5375315116..c8cf3c886a 100644 --- a/bors.toml +++ b/bors.toml @@ -1,11 +1,6 @@ status = [ # Buildkite: osx/linux installers "buildkite/daedalus", - - # Hydra: we just care about tests attribute set - "ci/hydra:Cardano:daedalus:tests.runTsc", - "ci/hydra:Cardano:daedalus:tests.runLint", - "ci/hydra:Cardano:daedalus:tests.runShellcheck" ] timeout_sec = 7200 required_approvals = 1 diff --git a/declaration.d.ts b/declaration.d.ts index cf2f235f2d..e598e2a913 100644 --- a/declaration.d.ts +++ b/declaration.d.ts @@ -22,6 +22,17 @@ type Daedalus = { }; }; +export type $ElementType< + T extends { [P in K & any]: any }, + K extends keyof T | number +> = T[K]; + +export type EnumMap< + K extends string, + V, + O extends Record = any +> = O & Record>; + declare global { namespace NodeJS { interface ProcessEnv { diff --git a/default.nix b/default.nix index 1f8d485afa..09f9c5f7bd 100644 --- a/default.nix +++ b/default.nix @@ -40,7 +40,7 @@ let inherit (pkgs.lib) optionalString optional concatStringsSep; inherit (pkgs) writeTextFile; crossSystem = lib: (crossSystemTable lib).${target} or null; - # TODO, nsis cant cross-compile with the nixpkgs daedalus currently uses + # TODO, nsis can't cross-compile with the nixpkgs daedalus currently uses nsisNixPkgs = import localLib.sources.nixpkgs-nsis {}; installPath = ".daedalus"; needSignedBinaries = (signingKeys != null) || (HSMServer != null); @@ -55,7 +55,7 @@ let cardanoLib = localLib.iohkNix.cardanoLib; daedalus-bridge = self.bridgeTable.${nodeImplementation}; - nodejs = pkgs.nodejs-16_x; + nodejs = pkgs.nodejs-14_x; nodePackages = pkgs.nodePackages.override { nodejs = self.nodejs; }; yarnInfo = { version = "1.22.4"; @@ -319,8 +319,6 @@ let electron = pkgs.callPackage ./installers/nix/electron.nix {}; tests = { - runTsc = self.callPackage ./tests/tsc.nix {}; - runLint = self.callPackage ./tests/lint.nix {}; runShellcheck = self.callPackage ./tests/shellcheck.nix { src = ./.;}; }; nix-bundle = import sources.nix-bundle { nixpkgs = pkgs; }; diff --git a/gulpfile.js b/gulpfile.js index 691d5de763..1bee16bc46 100755 --- a/gulpfile.js +++ b/gulpfile.js @@ -51,6 +51,8 @@ gulp.task( ) ); +gulp.task('typedef:sass', shell.task('yarn typedef:sass --watch')); + gulp.task( 'clear:cache', shell.task('rimraf ./node_modules/.cache && rimraf .cache-loader') diff --git a/installers/common/MacInstaller.hs b/installers/common/MacInstaller.hs index fc3ec70d7d..666907a6d0 100644 --- a/installers/common/MacInstaller.hs +++ b/installers/common/MacInstaller.hs @@ -152,6 +152,8 @@ sign_cmd "$ABS_PATH/Contents/Resources/app/build/HID.node" sign_cmd "$ABS_PATH/Contents/Resources/app/node_modules/keccak/bin/darwin-x64-"*"/keccak.node" sign_cmd "$ABS_PATH/Contents/Resources/app/node_modules/keccak/build/Release/addon.node" sign_cmd "$ABS_PATH/Contents/Resources/app/node_modules/keccak/prebuilds/darwin-x64/node.napi.node" +sign_cmd "$ABS_PATH/Contents/Resources/app/node_modules/blake-hash/prebuilds/darwin-x64/node.napi.node" +sign_cmd "$ABS_PATH/Contents/Resources/app/node_modules/tiny-secp256k1/build/Release/secp256k1.node" # Sign the whole component deeply sign_cmd "$ABS_PATH" diff --git a/jest.config.js b/jest.config.js index fba7a4c536..536e373727 100644 --- a/jest.config.js +++ b/jest.config.js @@ -89,6 +89,7 @@ module.exports = { // Jest does not support WASM imports from ESM modules // https://github.com/facebook/jest/issues/9430 '^@iohk-jormungandr/wallet-js$': 'identity-obj-proxy', + 'tests/(.*)': '/tests/$1', }, // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader @@ -182,10 +183,10 @@ module.exports = { }, // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation - // transformIgnorePatterns: [ - // "/node_modules/", - // "\\.pnp\\.[^\\/]+$" - // ], + transformIgnorePatterns: [ + 'node_modules/(?!react-polymorph)', + // "\\.pnp\\.[^\\/]+$" + ], // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them // unmockedModulePathPatterns: undefined, diff --git a/nix/nsis-inner.nix b/nix/nsis-inner.nix index 32ef9d2377..4dc4e26c08 100644 --- a/nix/nsis-inner.nix +++ b/nix/nsis-inner.nix @@ -43,7 +43,7 @@ in stdenv.mkDerivation { ''; meta = with lib; { - descripition = "System to create Windows installers"; + description = "System to create Windows installers"; homepage = "https://nsis.sourceforge.io/"; }; } diff --git a/nix/sources.json b/nix/sources.json index 7c132cb34e..940f079cd6 100644 --- a/nix/sources.json +++ b/nix/sources.json @@ -1,14 +1,14 @@ { "cardano-node": { - "branch": "tags/1.32.1", + "branch": "tags/1.33.0", "description": null, "homepage": null, "owner": "input-output-hk", "repo": "cardano-node", - "rev": "4f65fb9a27aa7e3a1873ab4211e412af780a3648", - "sha256": "00k9fqrm0gphjji23x0nc9z6bqh8bqrncgivn3mi3csacjzicrrx", + "rev": "814df2c146f5d56f8c35a681fe75e85b905aed5d", + "sha256": "1hr00wqzmcyc3x0kp2hyw78rfmimf6z4zd4vv85b9zv3nqbjgrik", "type": "tarball", - "url": "https://github.com/input-output-hk/cardano-node/archive/4f65fb9a27aa7e3a1873ab4211e412af780a3648.tar.gz", + "url": "https://github.com/input-output-hk/cardano-node/archive/814df2c146f5d56f8c35a681fe75e85b905aed5d.tar.gz", "url_template": "https://github.com///archive/.tar.gz" }, "cardano-shell": { @@ -24,15 +24,15 @@ "url_template": "https://github.com///archive/.tar.gz" }, "cardano-wallet": { - "branch": "anviking/ADP-1081/node-alonzo-rc", + "branch": "master", "description": "Official Wallet Backend & API for Cardano decentralized", "homepage": null, "owner": "input-output-hk", "repo": "cardano-wallet", - "rev": "760140e238a5fbca61d1b286d7a80ece058dc729", - "sha256": "014njpddrlqm9bbab636h2gf58zkm0bx04i1jsn07vh5j3k0gri6", + "rev": "a5085acbd2670c24251cf8d76a4e83c77a2679ba", + "sha256": "1apzfy7qdgf6l0lb3icqz3rvaq2w3a53xq6wvhqnbfi8i7cacy03", "type": "tarball", - "url": "https://github.com/input-output-hk/cardano-wallet/archive/760140e238a5fbca61d1b286d7a80ece058dc729.tar.gz", + "url": "https://github.com/input-output-hk/cardano-wallet/archive/a5085acbd2670c24251cf8d76a4e83c77a2679ba.tar.gz", "url_template": "https://github.com///archive/.tar.gz" }, "gitignore": { diff --git a/nix/yarn-nix-shell.sh b/nix/yarn-nix-shell.sh new file mode 100755 index 0000000000..bccb6bf67e --- /dev/null +++ b/nix/yarn-nix-shell.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash + +# This small wrapper over `nix-shell`, called from package.json +# (e.g. `yarn nix:testnet`) allows to specify an initial command to be +# run inside the `nix-shell` once its ready, e.g.: +# +# $ yarn nix:testnet yarn dev +# +# After the command finishes, you will still be left inside the +# nix-shell. + +if [ $# -lt 2 ] ; then + echo >&2 'fatal: usage: '"$0"' [ ...]' + exit 1 +fi + +NETWORK="$1" ; shift +cluster="$1" ; shift + +# Unfortunately, we need to shell-escape the command: +# cf. +command='' +while [ $# -gt 0 ] ; do + command="$command '""${1//\'/\'\\\'\'}""'" ; shift +done + +if [ -z "$command" ] ; then + command=':' # no-op, if no command +fi + +export NETWORK +# `return` will make the user stay in `nix-shell` after the initial command finishes: +exec nix-shell --argstr nodeImplementation cardano --argstr cluster "$cluster" --command "$command ; return" diff --git a/package.json b/package.json index bd6ad38e66..ce4c578c0c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "daedalus", "productName": "Daedalus", - "version": "4.6.0", + "version": "4.9.0", "description": "Cryptocurrency Wallet", "main": "./dist/main/index.js", "scripts": { @@ -40,25 +40,27 @@ "cleanup": "mop -v", "lint": "eslint --format=node_modules/eslint-formatter-pretty source storybook utils --ext .ts,.tsx", "compile": "tsc --noEmit", + "precompile": "yarn typedef:sass", "prettier": "./node_modules/.bin/prettier \"**/*.*\"", "prettier:check": "yarn prettier --check", "prettier:format": "yarn prettier --write --loglevel warn", "stylelint": "./node_modules/.bin/stylelint \"**/*.scss\"", "stylelint:fix": "yarn stylelint --fix", - "manage:translations": "gulp purge:translations && gulp clear:cache && yarn build && ts-node ./translations/translation-runner.ts", + "manage:translations": "gulp purge:translations && gulp clear:cache && gulp build && ts-node ./translations/translation-runner.ts", "storybook": "start-storybook -p 6006 -c storybook --ci /", "storybook:build": "build-storybook -c storybook -o dist/storybook", + "storybook:build:chromatic": "build-storybook -c storybook", "themes:check:createTheme": "gulp build:themes && ts-node -r esm ./dist/scripts/check.js", "themes:update": "gulp build:themes && ts-node -r esm ./dist/scripts/update.js && yarn prettier --loglevel warn --write source/renderer/app/themes/daedalus/*.ts", - "themes:copy": "babel-node source/renderer/app/themes/utils/copyTheme.ts && yarn prettier --loglevel warn --write source/renderer/app/themes/daedalus/*.ts", + "themes:copy": "ts-node -r @babel/register -r @babel/polyfill source/renderer/app/themes/utils/copyTheme.ts && yarn prettier --loglevel warn --write source/renderer/app/themes/daedalus/*.ts", "clear:cache": "gulp clear:cache", - "nix:alonzo_purple": "NETWORK=alonzo_purple nix-shell --argstr nodeImplementation cardano --argstr cluster alonzo_purple", - "nix:mainnet": "NETWORK=mainnet nix-shell --argstr nodeImplementation cardano --argstr cluster mainnet", - "nix:flight": "NETWORK=mainnet nix-shell --argstr nodeImplementation cardano --argstr cluster mainnet_flight", - "nix:selfnode": "NETWORK=selfnode nix-shell --argstr nodeImplementation cardano --argstr cluster selfnode", - "nix:shelley_qa": "NETWORK=shelley_qa nix-shell --argstr nodeImplementation cardano --argstr cluster shelley_qa", - "nix:staging": "NETWORK=staging nix-shell --argstr nodeImplementation cardano --argstr cluster staging", - "nix:testnet": "NETWORK=testnet nix-shell --argstr nodeImplementation cardano --argstr cluster testnet", + "nix:alonzo_purple": "./nix/yarn-nix-shell.sh alonzo_purple alonzo_purple", + "nix:mainnet": "./nix/yarn-nix-shell.sh mainnet mainnet", + "nix:flight": "./nix/yarn-nix-shell.sh mainnet mainnet_flight", + "nix:selfnode": "./nix/yarn-nix-shell.sh selfnode selfnode", + "nix:shelley_qa": "./nix/yarn-nix-shell.sh shelley_qa shelley_qa", + "nix:staging": "./nix/yarn-nix-shell.sh staging staging", + "nix:testnet": "./nix/yarn-nix-shell.sh testnet testnet", "byron:wallet:importer": "ts-node utils/api-importer/byron-wallet-importer.ts", "shelley:wallet:importer": "ts-node utils/api-importer/shelley-wallet-importer.ts", "mary:wallet:importer": "ts-node utils/api-importer/mary-wallet-importer.ts", @@ -67,7 +69,9 @@ "yoroi:wallet:importer": "ts-node utils/api-importer/yoroi-wallet-importer.ts", "create-news-verification-hashes": "ts-node utils/create-news-verification-hashes/index.ts", "lockfile:check": "ts-node utils/lockfile-checker/index.ts --check", - "lockfile:fix": "ts-node utils/lockfile-checker/index.ts --fix" + "lockfile:fix": "ts-node utils/lockfile-checker/index.ts --fix", + "postinstall": "./scripts/postinstall.sh", + "typedef:sass": "typed-scss-modules source/renderer/app" }, "bin": { "electron": "./node_modules/.bin/electron" @@ -100,12 +104,13 @@ "@testing-library/jest-dom": "5.15.1", "@testing-library/react": "12.1.2", "@types/aes-js": "3.1.1", - "@types/node": "17.0.7", + "@types/jest": "27.4.0", + "@types/node": "17.0.12", "@types/qrcode.react": "1.0.2", "@types/react": "17.0.38", "@types/react-svg-inline": "2.1.3", - "@typescript-eslint/eslint-plugin": "5.9.0", - "@typescript-eslint/parser": "5.9.0", + "@typescript-eslint/eslint-plugin": "5.10.1", + "@typescript-eslint/parser": "5.10.1", "asar": "2.1.0", "autodll-webpack-plugin": "0.4.2", "axios": "0.24.0", @@ -115,7 +120,7 @@ "babel-plugin-react-intl": "3.0.1", "bufferutil": "4.0.1", "cache-loader": "4.1.0", - "chai": "4.2.0", + "chai": "4.3.4", "chalk": "4.1.0", "concurrently": "5.3.0", "cross-env": "7.0.2", @@ -157,7 +162,7 @@ "mini-css-extract-plugin": "2.3.0", "minimist": "1.2.5", "mobx-react-devtools": "6.1.1", - "node-forge": "0.10.0", + "node-forge": "1.0.0", "nodemon": "2.0.15", "npmlog": "4.1.2", "path-browserify": "1.0.1", @@ -179,11 +184,12 @@ "style-loader": "3.2.1", "stylelint": "13.7.2", "stylelint-order": "4.1.0", - "svg-inline-loader": "^0.8.2", + "svg-inline-loader": "0.8.2", "thread-loader": "2.1.3", "timemachine": "0.3.2", "transform-loader": "0.2.4", "ts-node": "10.4.0", + "typed-scss-modules": "5.0.0", "typescript": "4.5.4", "utf-8-validate": "5.0.2", "webdriverio": "5.18.7", @@ -211,22 +217,24 @@ "cardano-js": "0.4.8", "cardano-launcher": "0.20211105.1", "cbor": "5.0.2", - "check-disk-space": "3.1.0", + "check-disk-space": "3.2.0", "chroma-js": "2.1.0", "classnames": "2.2.6", "csv-stringify": "5.5.1", "cucumber-html-reporter": "5.2.0", "electron": "13.6.3", "electron-log-daedalus": "2.2.21", - "electron-store": "8.0.0", + "electron-store": "8.0.1", "es6-error": "4.1.1", - "find-process": "1.4.4", + "find-process": "1.4.7", "fireworks-js": "1.0.4", "form-data": "3.0.0", "fs-extra": "9.0.1", + "fuse.js": "6.5.3", "glob": "7.1.6", "graceful-fs": "4.2.4", "gulp": "4.0.2", + "highlight-words": "1.2.0", "history": "4.10.1", "humanize-duration": "3.23.1", "inquirer": "7.3.3", @@ -240,6 +248,7 @@ "mobx-react-form": "2.0.8", "mobx-react-router": "4.1.0", "moment": "2.29.0", + "nanoid": "3.2.0", "node-downloader-helper": "1.0.18", "omit-deep-lodash": "1.1.5", "pbkdf2": "3.1.2", @@ -258,7 +267,7 @@ "react-intl": "2.7.2", "react-lottie": "1.2.3", "react-markdown": "4.3.1", - "react-polymorph": "1.0.1", + "react-polymorph": "1.0.3", "react-router": "5.2.0", "react-router-dom": "5.2.0", "react-svg-inline": "2.1.1", @@ -275,6 +284,7 @@ "shasum": "1.0.2", "source-map-support": "0.5.19", "spectron-fake-dialog": "0.0.1", + "tail": "2.2.4", "tcp-port-used": "1.0.1", "trezor-connect": "8.2.4-extended", "unorm": "1.6.0", @@ -283,7 +293,7 @@ "validator": "13.7.0" }, "devEngines": { - "node": "16.13.2", + "node": ">=16.13.2", "yarn": "1.22.4" }, "husky": { diff --git a/scripts/build-cross-windows.sh b/scripts/build-cross-windows.sh index 1f9b0adcb5..5cec7e32d3 100755 --- a/scripts/build-cross-windows.sh +++ b/scripts/build-cross-windows.sh @@ -1,15 +1,15 @@ #!/usr/bin/env bash - set -e +source "$(dirname "$0")/utils.sh" BUILDKITE_BUILD_NUMBER="$1" upload_artifacts() { - buildkite-agent artifact upload "$@" --job "$BUILDKITE_JOB_ID" + retry 5 buildkite-agent artifact upload "$@" --job "$BUILDKITE_JOB_ID" } upload_artifacts_public() { - buildkite-agent artifact upload "$@" "${ARTIFACT_BUCKET:-}" --job "$BUILDKITE_JOB_ID" + retry 5 buildkite-agent artifact upload "$@" "${ARTIFACT_BUCKET:-}" --job "$BUILDKITE_JOB_ID" } CLUSTERS="$(xargs echo -n < "$(dirname "$0")/../installer-clusters.cfg")" diff --git a/scripts/build-installer-nix.sh b/scripts/build-installer-nix.sh index d144c7923d..ea175313a3 100755 --- a/scripts/build-installer-nix.sh +++ b/scripts/build-installer-nix.sh @@ -1,15 +1,15 @@ #!/usr/bin/env bash - set -e +source "$(dirname "$0")/utils.sh" BUILDKITE_BUILD_NUMBER="$1" upload_artifacts() { - buildkite-agent artifact upload "$@" --job "$BUILDKITE_JOB_ID" + retry 5 buildkite-agent artifact upload "$@" --job "$BUILDKITE_JOB_ID" } upload_artifacts_public() { - buildkite-agent artifact upload "$@" "${ARTIFACT_BUCKET:-}" --job "$BUILDKITE_JOB_ID" + retry 5 buildkite-agent artifact upload "$@" "${ARTIFACT_BUCKET:-}" --job "$BUILDKITE_JOB_ID" } rm -rf dist || true diff --git a/scripts/build-installer-unix.sh b/scripts/build-installer-unix.sh index ccb2bf6d1d..dbf717f4e3 100755 --- a/scripts/build-installer-unix.sh +++ b/scripts/build-installer-unix.sh @@ -1,53 +1,14 @@ #!/usr/bin/env bash +set -e +source "$(dirname "$0")/utils.sh" + # DEPENDENCIES (binaries should be in PATH): # 0. 'git' # 1. 'curl' # 2. 'nix-shell' -set -e - CLUSTERS="$(xargs echo -n < "$(dirname "$0")"/../installer-clusters.cfg)" -usage() { - test -z "$1" || { echo "ERROR: $*" >&2; echo >&2; } - cat >&2 <&2; exit 1; } -retry() { - local tries=$1; arg2nz "iteration count" "$1"; shift - for i in $(seq 1 "${tries}") - do if "$@" - then return 0 - else echo "failed, retry #$i of ${tries}" - fi - sleep 5s - done - fail "persistent failure to exec: $*" -} - ### ### Argument processing ### @@ -76,8 +37,8 @@ while test $# -ge 1 do case "$1" in --clusters ) CLUSTERS="$2"; shift;; --fast-impure ) export fast_impure=true;; - --build-id ) arg2nz "build identifier" "$2"; build_id="$2"; shift;; - --nix-path ) arg2nz "NIX_PATH value" "$2"; + --build-id ) validate_arguments "build identifier" "$2"; build_id="$2"; shift;; + --nix-path ) validate_arguments "NIX_PATH value" "$2"; export NIX_PATH="$2"; shift;; --test-installer ) test_installer="--test-installer";; @@ -117,11 +78,11 @@ export PATH=$HOME/.local/bin:$PATH if [ -n "${NIX_SSL_CERT_FILE-}" ]; then export SSL_CERT_FILE=$NIX_SSL_CERT_FILE; fi upload_artifacts() { - buildkite-agent artifact upload "$@" --job "$BUILDKITE_JOB_ID" + retry 5 buildkite-agent artifact upload "$@" --job "$BUILDKITE_JOB_ID" } upload_artifacts_public() { - buildkite-agent artifact upload "$@" "${ARTIFACT_BUCKET:-}" --job "$BUILDKITE_JOB_ID" + retry 5 buildkite-agent artifact upload "$@" "${ARTIFACT_BUCKET:-}" --job "$BUILDKITE_JOB_ID" } function checkItnCluster() { diff --git a/scripts/postinstall.sh b/scripts/postinstall.sh new file mode 100755 index 0000000000..b361f58733 --- /dev/null +++ b/scripts/postinstall.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +if [[ "$CI" != "true" ]]; then + yarn lockfile:fix +fi diff --git a/scripts/utils.sh b/scripts/utils.sh new file mode 100755 index 0000000000..1174bb4503 --- /dev/null +++ b/scripts/utils.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash + +usage() { + test -z "$1" || { echo "ERROR: $*" >&2; echo >&2; } + cat >&2 <&2 + exit 1 +} + +retry() { + local tries=$1 + validate_arguments "iteration count" "$1" + shift + for i in $(seq 1 "${tries}"); do + if "$@"; then + return 0 + else + echo "failed, retry #$i of ${tries}" + fi + sleep 5 + done + fail "persistent failure to exec: $*" +} diff --git a/source/common/config/downloadManagerConfig.ts b/source/common/config/downloadManagerConfig.ts index 8e2b82b4a3..4030c5e22a 100644 --- a/source/common/config/downloadManagerConfig.ts +++ b/source/common/config/downloadManagerConfig.ts @@ -1,14 +1,13 @@ // https://www.npmjs.com/package/node-downloader-helper import type { - AllowedDownloadDirectories, + AllowedDownloadDirectoriesValues, + AllowedDownloadDirectoriesKeys, + AllowedDownloadDirectoriesMap, DownloadState, DownloadEventType, } from '../types/downloadManager.types'; -export const ALLOWED_DOWNLOAD_DIRECTORIES: Record< - string, - AllowedDownloadDirectories -> = { +export const ALLOWED_DOWNLOAD_DIRECTORIES: AllowedDownloadDirectoriesMap = { DOWNLOADS: 'downloads', DESKTOP: 'desktop', STATE_DIRECTORY: 'stateDirectory', diff --git a/source/common/config/electron-store.config.ts b/source/common/config/electron-store.config.ts index ba08b4aa2e..7455f7c4ff 100644 --- a/source/common/config/electron-store.config.ts +++ b/source/common/config/electron-store.config.ts @@ -10,19 +10,17 @@ export const STORAGE_KEYS: Record = { APP_AUTOMATIC_UPDATE_FAILED: 'APP-AUTOMATIC-UPDATE-FAILED', APP_UPDATE_COMPLETED: 'APP-UPDATE-COMPLETED', ASSET_DATA: 'ASSET-DATA', - ASSET_SETTINGS_DIALOG_WAS_OPENED: 'ASSET-SETTINGS-DIALOG-WAS-OPENED', CURRENCY_ACTIVE: 'CURRENCY-ACTIVE', CURRENCY_SELECTED: 'CURRENCY-SELECTED', DATA_LAYER_MIGRATION_ACCEPTANCE: 'DATA-LAYER-MIGRATION-ACCEPTANCE', DISCREET_MODE_ENABLED: 'DISCREET-MODE-ENABLED', - DISCREET_MODE_SETTINGS_TOOLTIP: 'DISCREET-MODE-SETTINGS-TOOLTIP', - DISCREET_MODE_NOTIFICATION: 'DISCREET-MODE-NOTIFICATION', DOWNLOAD_MANAGER: 'DOWNLOAD-MANAGER', HARDWARE_WALLETS: 'HARDWARE-WALLETS', HARDWARE_WALLET_DEVICES: 'HARDWARE-WALLET-DEVICES', READ_NEWS: 'READ-NEWS', RESET: 'RESET', SMASH_SERVER: 'SMASH-SERVER', + STAKE_POOLS_LIST_VIEW_TOOLTIP: 'STAKE-POOLS-LIST-VIEW-TOOLTIP', STAKING_INFO_WAS_OPEN: 'ALONZO-INFO-WAS-OPEN', TERMS_OF_USE_ACCEPTANCE: 'TERMS-OF-USE-ACCEPTANCE', THEME: 'THEME', diff --git a/source/common/ipc/api.ts b/source/common/ipc/api.ts index dd69d9cb06..fe58d9c20d 100644 --- a/source/common/ipc/api.ts +++ b/source/common/ipc/api.ts @@ -14,6 +14,7 @@ import type { GenerateVotingPDFParams } from '../types/voting-pdf-request.types' import type { GenerateCsvParams } from '../types/csv-request.types'; import type { GenerateQRCodeParams } from '../types/save-qrCode.types'; import type { + BlockSyncType, CardanoNodeState, CardanoStatus, FaultInjectionIpcRequest, @@ -177,9 +178,16 @@ export type SubmitBugReportRequestMainResponse = void; /** * Channel to rebuild the electron application menu after the language setting changes */ +export enum WalletSettingsStateEnum { + hidden = 'hidden', + disabled = 'disabled', + enabled = 'enabled', +} + export const REBUILD_APP_MENU_CHANNEL = 'REBUILD_APP_MENU_CHANNEL'; export type RebuildAppMenuRendererRequest = { isNavigationEnabled: boolean; + walletSettingsState: WalletSettingsStateEnum; }; export type RebuildAppMenuMainResponse = void; @@ -312,14 +320,6 @@ export type GenerateWalletMigrationReportRendererRequest = WalletMigrationReportData; export type GenerateWalletMigrationReportMainResponse = void; -/** - * Channel for enabling application menu navigation - */ -export const ENABLE_APPLICATION_MENU_NAVIGATION_CHANNEL = - 'ENABLE_APPLICATION_MENU_NAVIGATION_CHANNEL'; -export type EnableApplicationMenuNavigationRendererRequest = void; -export type EnableApplicationMenuNavigationMainResponse = void; - /** * Channel for generating wallet migration report */ @@ -456,9 +456,13 @@ export type IntrospectAddressMainResponse = IntrospectAddressResponse; /** * Channel for checking block replay progress */ -export const GET_BLOCK_REPLAY_STATUS_CHANNEL = 'GetBlockReplayProgressChannel'; -export type GetBlockReplayProgressRendererRequest = void; -export type GetBlockReplayProgressMainResponse = number; +export const GET_BLOCK_SYNC_PROGRESS_CHANNEL = 'GetBlockSyncProgressChannel'; +export type GetBlockSyncProgressType = BlockSyncType; +export type GetBlockSyncProgressRendererRequest = void; +export type GetBlockSyncProgressMainResponse = { + progress: number; + type: GetBlockSyncProgressType; +}; /** * Channels for connecting / interacting with Hardware Wallet devices @@ -482,9 +486,9 @@ export type getCardanoAdaAppRendererRequest = { export type getCardanoAdaAppMainResponse = HardwareWalletCardanoAdaAppResponse; export const GET_HARDWARE_WALLET_CONNECTION_CHANNEL = 'GET_HARDWARE_WALLET_CONNECTION_CHANNEL'; -export type getHardwareWalletConnectiontMainRequest = +export type getHardwareWalletConnectionMainRequest = HardwareWalletConnectionRequest; -export type getHardwareWalletConnectiontRendererResponse = Record; +export type getHardwareWalletConnectionRendererResponse = Record; export const SIGN_TRANSACTION_LEDGER_CHANNEL = 'SIGN_TRANSACTION_LEDGER_CHANNEL'; export type signTransactionLedgerRendererRequest = LedgerSignTransactionRequest; @@ -513,3 +517,6 @@ export type deriveAddressMainResponse = string; export const SHOW_ADDRESS_CHANNEL = 'SHOW_ADDRESS_CHANNEL'; export type showAddressRendererRequest = showAddressRendererRequestType; export type showAddressMainResponse = void; +export const TOGGLE_RTS_FLAGS_MODE_CHANNEL = 'TOGGLE_RTS_FLAGS_MODE_CHANNEL'; +export type ToggleRTSFlagsModeRendererRequest = void; +export type ToggleRTSFlagsModeMainResponse = void; diff --git a/source/common/ipc/constants.ts b/source/common/ipc/constants.ts index 727b0ca928..75bdd93820 100644 --- a/source/common/ipc/constants.ts +++ b/source/common/ipc/constants.ts @@ -2,6 +2,7 @@ export const DIALOGS = { ABOUT: 'ABOUT_DIALOG', DAEDALUS_DIAGNOSTICS: 'DAEDALUS_DIAGNOSTICS_DIALOG', ITN_REWARDS_REDEMPTION: 'ITN_REWARDS_REDEMPTION_DIALOG', + TOGGLE_RTS_FLAGS_MODE: 'TOGGLE_RTS_FLAGS_MODE_DIALOG', }; export const NOTIFICATIONS = { DOWNLOAD_LOGS: 'DOWNLOAD_LOGS_NOTIFICATION', diff --git a/source/common/types/address-introspection.types.ts b/source/common/types/address-introspection.types.ts index 7c96e0bea1..fca6abf3cf 100644 --- a/source/common/types/address-introspection.types.ts +++ b/source/common/types/address-introspection.types.ts @@ -1,33 +1,26 @@ export type IntrospectAddressRequest = { input: string; }; - export type AddressStyle = 'Byron' | 'Icarus' | 'Shelley'; - export type AddressType = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 14 | 15; - export type ChainPointer = { slot_num: number; transaction_index: number; output_index: number; }; - export type AddressBase = { address_type: AddressType; address_style: AddressStyle; network_tag: number | null; stake_reference: 'none' | 'by pointer' | 'by value'; }; - export type ByronAddress = AddressBase & { address_root: string; derivation_path: string; }; - export type IcarusAddress = AddressBase & { address_root: string; }; - export type ShelleyAddress = AddressBase & { pointer?: ChainPointer; script_hash?: string; @@ -35,7 +28,6 @@ export type ShelleyAddress = AddressBase & { stake_key_hash?: string; stake_script_hash?: string; }; - export type IntrospectAddressResponse = | { introspection: ByronAddress | IcarusAddress | ShelleyAddress; diff --git a/source/common/types/cardano-node.types.ts b/source/common/types/cardano-node.types.ts index 563516195c..fcc526b29f 100644 --- a/source/common/types/cardano-node.types.ts +++ b/source/common/types/cardano-node.types.ts @@ -1,4 +1,3 @@ -import { EnumMap } from '../../renderer/app/types/enumTypes'; import { MAINNET, TESTNET, @@ -54,7 +53,7 @@ export type CardanoNodeState = | 'errored' | 'unknown' | 'unrecoverable'; - +// @ts-ignore ts-migrate(2304) FIXME: Cannot find name 'EnumMap'. export const CardanoNodeStates: EnumMap = { STARTING: 'starting', RUNNING: 'running', @@ -136,6 +135,7 @@ export type CardanoStatus = { hasBeenConnected: boolean; cardanoNodePID: number; cardanoWalletPID: number; + isRTSFlagsModeEnabled: boolean; }; export type NetworkMagicType = Array; export const NetworkMagics: { @@ -159,3 +159,8 @@ export const NetworkMagics: { // Cardano Selfnode network magic [SELFNODE]: [1, null], }; +export enum BlockSyncType { + pushingLedger = 'pushingLedger', + replayedBlock = 'replayedBlock', + validatingChunk = 'validatingChunk', +} diff --git a/source/common/types/downloadManager.types.ts b/source/common/types/downloadManager.types.ts index 6b7b2ddd51..33c7fba9d3 100644 --- a/source/common/types/downloadManager.types.ts +++ b/source/common/types/downloadManager.types.ts @@ -1,7 +1,17 @@ -export type AllowedDownloadDirectories = +export type AllowedDownloadDirectoriesValues = | 'stateDirectory' | 'downloads' | 'desktop'; + +export type AllowedDownloadDirectoriesKeys = + | 'DOWNLOADS' + | 'DESKTOP' + | 'STATE_DIRECTORY'; + +export type AllowedDownloadDirectoriesMap = { + [day in AllowedDownloadDirectoriesKeys]: AllowedDownloadDirectoriesValues; +}; + // https://www.npmjs.com/package/node-downloader-helper export type DownloadRequest = { /** @@ -12,7 +22,7 @@ export type DownloadRequest = { */ id?: string; fileUrl: string; - destinationDirectoryName?: AllowedDownloadDirectories; + destinationDirectoryName?: AllowedDownloadDirectoriesValues; options?: DownloadRequestOptions | null | undefined; resumeDownload?: { temporaryFilename: string; @@ -47,7 +57,7 @@ export type DownloadRequestOptions = { httpsRequestOptions?: Record; // Override the https request options, ex: to add SSL Certs progressIsThrottled?: boolean; - // by default, the progress is sent every second. if `false` it will be sent every milisecond + // by default, the progress is sent every second. if `false` it will be sent every millisecond persistLocalData?: boolean; // by default, the localdata information is deleted after the end of the download }; // https://www.npmjs.com/package/node-downloader-helper @@ -82,7 +92,7 @@ export type DownloadInfo = { fileUrl: string; originalFilename: string; temporaryFilename: string; - destinationDirectoryName: AllowedDownloadDirectories; + destinationDirectoryName: AllowedDownloadDirectoriesValues; destinationPath: string; options: DownloadRequestOptions; }; @@ -138,7 +148,7 @@ export type DownloadInfoProgress = { downloaded: number; // downloaded size in bytes progress: number; - // progress porcentage 0-100% + // progress percentage 0-100% speed: number; // download speed in bytes }; export type DownloadInfoEnd = { @@ -147,7 +157,7 @@ export type DownloadInfoEnd = { totalSize: number; // total file size got from the server incomplete: boolean; - // true/false if the download endend but still incomplete + // true/false if the download ended but still incomplete onDiskSize: number; // total size of file on the disk downloadedSize: number; // the total size downloaded diff --git a/source/common/types/electron-store.types.ts b/source/common/types/electron-store.types.ts index 01b13b9c82..bb3481c833 100644 --- a/source/common/types/electron-store.types.ts +++ b/source/common/types/electron-store.types.ts @@ -9,14 +9,13 @@ export type StorageKey = | 'CURRENCY-SELECTED' | 'DATA-LAYER-MIGRATION-ACCEPTANCE' | 'DISCREET-MODE-ENABLED' - | 'DISCREET-MODE-SETTINGS-TOOLTIP' - | 'DISCREET-MODE-NOTIFICATION' | 'DOWNLOAD-MANAGER' | 'HARDWARE-WALLET-DEVICES' | 'HARDWARE-WALLETS' | 'READ-NEWS' | 'RESET' | 'SMASH-SERVER' + | 'STAKE-POOLS-LIST-VIEW-TOOLTIP' | 'TERMS-OF-USE-ACCEPTANCE' | 'THEME' | 'TOKEN-FAVORITES' diff --git a/source/common/types/environment.types.ts b/source/common/types/environment.types.ts index 3b8ca04250..8513e67d0f 100644 --- a/source/common/types/environment.types.ts +++ b/source/common/types/environment.types.ts @@ -24,6 +24,7 @@ export type Environment = { os: string; cpu: string; ram: number; + hasMetHardwareRequirements: boolean; installerVersion: string; version: string; isWindows: boolean; @@ -71,3 +72,9 @@ export const networkPrettyNames = { selfnode: 'Selfnode', development: 'Development', }; +export type CpuThreadData = { + model: string; + speed: number; + times: Record; +}; +export type Cpu = Array; diff --git a/source/common/types/logging.types.ts b/source/common/types/logging.types.ts index 9aa2f77368..e8d93af11e 100644 --- a/source/common/types/logging.types.ts +++ b/source/common/types/logging.types.ts @@ -19,10 +19,10 @@ import type { AdaApiStakePool } from '../../renderer/app/api/staking/types'; export type LoggingLevel = 'debug' | 'info' | 'error' | 'warn'; export type Logger = { - debug: (arg0: string, arg1: Record | null | undefined) => void; - info: (arg0: string, arg1: Record | null | undefined) => void; - error: (arg0: string, arg1: Record | null | undefined) => void; - warn: (arg0: string, arg1: Record | null | undefined) => void; + debug: (arg0: string, arg1?: Record | null | undefined) => void; + info: (arg0: string, arg1?: Record | null | undefined) => void; + error: (arg0: string, arg1?: Record | null | undefined) => void; + warn: (arg0: string, arg1?: Record | null | undefined) => void; }; export type FormatMessageContextParams = { appName: string; diff --git a/source/main/cardano/CardanoNode.ts b/source/main/cardano/CardanoNode.ts index 6f707257de..4f2e34292a 100644 --- a/source/main/cardano/CardanoNode.ts +++ b/source/main/cardano/CardanoNode.ts @@ -29,6 +29,7 @@ import { CardanoSelfnodeLauncher } from './CardanoSelfnodeLauncher'; import { launcherConfig } from '../config'; import type { NodeConfig } from '../config'; import type { Logger } from '../../common/types/logging.types'; +import { containsRTSFlags } from '../utils/containsRTSFlags'; /* eslint-disable consistent-return */ type Actions = { @@ -84,6 +85,7 @@ export type CardanoNodeConfig = { // Path to cardano-cli executable isStaging: boolean; metadataUrl?: string; + rtsFlags: Array; }; const CARDANO_UPDATE_EXIT_CODE = 20; // grab the current network on which Daedalus is running @@ -229,6 +231,7 @@ export class CardanoNode { return Object.assign({}, this._status, { cardanoNodePID: get(this, '_node.pid', 0), cardanoWalletPID: get(this, '_node.wpid', 0), + isRTSFlagsModeEnabled: containsRTSFlags(this._config.rtsFlags), }); } @@ -519,7 +522,7 @@ export class CardanoNode { } /** - * Kills cardano-node and waitsup to `killTimeout` for the node to + * Kills cardano-node and waits up to `killTimeout` for the node to * report the exit message. * * @returns {Promise} resolves if the node could be killed, rejects with error otherwise. diff --git a/source/main/cardano/CardanoWalletLauncher.ts b/source/main/cardano/CardanoWalletLauncher.ts index 70afc2f22b..c825ae36c7 100644 --- a/source/main/cardano/CardanoWalletLauncher.ts +++ b/source/main/cardano/CardanoWalletLauncher.ts @@ -35,6 +35,7 @@ export type WalletOptions = { cliBin: string; isStaging: boolean; metadataUrl?: string; + rtsFlags: Array; }; export async function CardanoWalletLauncher( walletOptions: WalletOptions @@ -54,6 +55,7 @@ export async function CardanoWalletLauncher( cliBin, isStaging, metadataUrl, + rtsFlags, } = walletOptions; // TODO: Update launcher config to pass number const syncToleranceSeconds = parseInt(syncTolerance.replace('s', ''), 10); @@ -146,11 +148,20 @@ export async function CardanoWalletLauncher( logger.info('Launching Wallet with --token-metadata-server flag', { tokenMetadataServer, }); + // RTS flags: - nodeConfig.rtsOpts = []; - logger.info('Launching Cardano Node with RTS flags', { - rtsFlags: nodeConfig.rtsOpts, - }); + // 1) "-H4G -M6553M -c70" 16.0% peak RSS reduction and a sub-percentile CPU regression + // 2) "-H4G -M6553M" 18.5% peak RSS reduction and a second-best CPU regression + if (!!rtsFlags && rtsFlags?.length > 0) { + nodeConfig.rtsOpts = rtsFlags; + logger.info('Launching Cardano Node with RTS flags', { + rtsFlags, + }); + } else { + // @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. + logger.info('Launching Cardano Node without RTS flags'); + } + merge(launcherConfig, { nodeConfig, tlsConfiguration, diff --git a/source/main/cardano/setup.ts b/source/main/cardano/setup.ts index 5be36aa199..f56f496ad2 100644 --- a/source/main/cardano/setup.ts +++ b/source/main/cardano/setup.ts @@ -49,10 +49,12 @@ const restartCardanoNode = async (node: CardanoNode) => { * * @param launcherConfig {LauncherConfig} * @param mainWindow + * @param rtsFlags flags used to start cardano-node */ export const setupCardanoNode = ( launcherConfig: LauncherConfig, - mainWindow: BrowserWindow + mainWindow: BrowserWindow, + rtsFlags: Array ): CardanoNode => { const { logsPrefix, @@ -78,6 +80,7 @@ export const setupCardanoNode = ( configPath, syncTolerance, cliBin, + rtsFlags, isStaging, metadataUrl, startupTimeout: NODE_STARTUP_TIMEOUT, diff --git a/source/main/config.ts b/source/main/config.ts index 93968a36b0..9dd57d40b9 100644 --- a/source/main/config.ts +++ b/source/main/config.ts @@ -103,7 +103,7 @@ export const windowOptions: WindowOptionsType = { nodeIntegration: isTest, webviewTag: false, enableRemoteModule: isTest, - preload: path.join(__dirname, './preload.ts'), + preload: path.join(__dirname, './preload.js'), // @ts-ignore ts-migrate(2322) FIXME: Type '{ nodeIntegration: boolean; webviewTag: fals... Remove this comment to see the full error message additionalArguments: isBlankScreenFixActive ? ['--safe-mode'] : [], }, @@ -132,11 +132,11 @@ export const buildLabel = getBuildLabel( ); // Logging config export const ALLOWED_LOGS = [ - 'Daedalus.tson', - 'System-info.tson', - 'Daedalus-versions.tson', - 'State-snapshot.tson', - 'Wallet-migration-report.tson', + 'Daedalus.json', + 'System-info.json', + 'Daedalus-versions.json', + 'State-snapshot.json', + 'Wallet-migration-report.json', 'cardano-wallet.log', 'node.log', ]; @@ -171,12 +171,13 @@ export const DISK_SPACE_RECOMMENDED_PERCENTAGE = 15; // 15% of the total disk sp export const DISK_SPACE_CHECK_TIMEOUT = 9 * 1000; // Timeout for checking disks pace -export const BLOCK_REPLAY_PROGRESS_CHECK_INTERVAL = 1 * 1000; // 1 seconds | unit: milliseconds - // Used if token metadata server URL is not defined in launcher config export const FALLBACK_TOKEN_METADATA_SERVER_URL = 'https://metadata.cardano-testnet.iohkdev.io'; +export const MINIMUM_AMOUNT_OF_RAM_FOR_RTS_FLAGS = 16 * 1024 * 1024 * 1024; // 16gb RAM + // Used by mock-token-metadata-server export const MOCK_TOKEN_METADATA_SERVER_URL = 'http://localhost'; export const MOCK_TOKEN_METADATA_SERVER_PORT = process.env.MOCK_TOKEN_METADATA_SERVER_PORT || 0; +export const RTS_FLAGS = ['-c']; diff --git a/source/main/environment.ts b/source/main/environment.ts index da9286c61e..89eab9f420 100644 --- a/source/main/environment.ts +++ b/source/main/environment.ts @@ -19,6 +19,12 @@ import { checkIsWindows, checkIsLinux, } from '../common/utils/environmentCheckers'; +// Daedalus requires minimum 16 gigabytes of RAM, but some devices having 16 GB +// actually have a slightly smaller RAM size (eg. 15.99 GB), therefore we used 15 GB threshold +// +// TODO figure out better place for it - can't import from config.js as it would be a circular dep +// https://input-output.atlassian.net/browse/DDW-928 +export const RECOMMENDED_RAM_IN_BYTES = 15 * 1024 * 1024 * 1024; /* ================================================================== = Evaluations = @@ -36,14 +42,10 @@ const isAlonzoPurple = checkIsAlonzoPurple(NETWORK); const isShelleyQA = checkIsShelleyQA(NETWORK); const isSelfnode = checkIsSelfnode(NETWORK); const isDevelopment = checkIsDevelopment(NETWORK); -const isWatchMode = - (process.env.IS_WATCH_MODE && process.env.IS_WATCH_MODE === 'true') || false; -const keepLocalClusterRunning = - (process.env.KEEP_LOCAL_CLUSTER_RUNNING && - process.env.KEEP_LOCAL_CLUSTER_RUNNING === 'true') || - false; +const keepLocalClusterRunning = process.env.KEEP_LOCAL_CLUSTER_RUNNING; const API_VERSION = process.env.API_VERSION || 'dev'; -const NODE_VERSION = '1.32.1'; // TODO: pick up this value from process.env +const NODE_VERSION = '1.33.0'; // TODO: pick up this value from process.env + const mainProcessID = get(process, 'ppid', '-'); const rendererProcessID = process.pid; const PLATFORM = os.platform(); @@ -51,13 +53,12 @@ const PLATFORM_VERSION = os.release(); const OS = OS_NAMES[PLATFORM] || PLATFORM; const cpu = os.cpus(); const ram = os.totalmem(); +const hasMetHardwareRequirements = ram >= RECOMMENDED_RAM_IN_BYTES; const isBlankScreenFixActive = includes(process.argv.slice(1), '--safe-mode'); const BUILD = process.env.BUILD_NUMBER || 'dev'; const BUILD_NUMBER = uniq([API_VERSION, BUILD]).join('.'); const INSTALLER_VERSION = uniq([API_VERSION, BUILD]).join('.'); -const MOBX_DEV_TOOLS = - (process.env.MOBX_DEV_TOOLS && process.env.MOBX_DEV_TOOLS === 'true') || - false; +const MOBX_DEV_TOOLS = process.env.MOBX_DEV_TOOLS || false; const isMacOS = checkIsMacOS(PLATFORM); const isWindows = checkIsWindows(PLATFORM); const isLinux = checkIsLinux(PLATFORM); @@ -101,6 +102,7 @@ export const environment: Environment = Object.assign( isLinux, isBlankScreenFixActive, keepLocalClusterRunning, + hasMetHardwareRequirements, }, process.env ); diff --git a/source/main/index.ts b/source/main/index.ts index c21a57c0af..c3d1794ec7 100644 --- a/source/main/index.ts +++ b/source/main/index.ts @@ -1,7 +1,10 @@ import os from 'os'; import path from 'path'; import { app, dialog, BrowserWindow, screen, shell } from 'electron'; +import type { Event } from 'electron'; +import { client } from 'electron-connect'; import EventEmitter from 'events'; +import { WalletSettingsStateEnum } from '../common/ipc/api'; import { requestElectronStore } from './ipc/electronStoreConversation'; import { logger } from './utils/logging'; import { @@ -19,6 +22,7 @@ import mainErrorHandler from './utils/mainErrorHandler'; import { launcherConfig, pubLogsFolderPath, + RTS_FLAGS, stateDirectoryPath, } from './config'; import { setupCardanoNode } from './cardano/setup'; @@ -40,12 +44,17 @@ import type { import { logUsedVersion } from './utils/logUsedVersion'; import { setStateSnapshotLogChannel } from './ipc/set-log-state-snapshot'; import { generateWalletMigrationReportChannel } from './ipc/generateWalletMigrationReportChannel'; -import { enableApplicationMenuNavigationChannel } from './ipc/enableApplicationMenuNavigationChannel'; import { pauseActiveDownloads } from './ipc/downloadManagerChannel'; import { restoreSavedWindowBounds, saveWindowBoundsOnSizeAndPositionChange, } from './windows/windowBounds'; +import { + getRtsFlagsSettings, + storeRtsFlagsSettings, +} from './utils/rtsFlagsSettings'; +import { toggleRTSFlagsModeChannel } from './ipc/toggleRTSFlagsModeChannel'; +import { containsRTSFlags } from './utils/containsRTSFlags'; /* eslint-disable consistent-return */ // Global references to windows to prevent them from being garbage collected @@ -54,6 +63,7 @@ let cardanoNode: CardanoNode; const { isDev, isTest, + isWatchMode, isBlankScreenFixActive, isSelfnode, network, @@ -80,6 +90,7 @@ const safeExit = async () => { pauseActiveDownloads(); if (!cardanoNode || cardanoNode.state === CardanoNodeStates.STOPPED) { + // @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. logger.info('Daedalus:safeExit: exiting Daedalus with code 0', { code: 0, }); @@ -95,15 +106,18 @@ const safeExit = async () => { try { const pid = cardanoNode.pid || 'null'; + // @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. logger.info(`Daedalus:safeExit: stopping cardano-node with PID: ${pid}`, { pid, }); await cardanoNode.stop(); + // @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. logger.info('Daedalus:safeExit: exiting Daedalus with code 0', { code: 0, }); safeExitWithCode(0); } catch (error) { + // @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. logger.error('Daedalus:safeExit: cardano-node did not exit correctly', { error, }); @@ -111,6 +125,13 @@ const safeExit = async () => { } }; +const handleWindowClose = async (event: Event | null | undefined) => { + // @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. + logger.info('mainWindow received event. Safe exiting Daedalus now.'); + event?.preventDefault(); + await safeExit(); +}; + const onAppReady = async () => { setupLogging(); logUsedVersion( @@ -142,13 +163,17 @@ const onAppReady = async () => { process.env.PATH, process.env.DAEDALUS_INSTALL_DIRECTORY, ].join(path.delimiter); + // @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. logger.info(`Daedalus is starting at ${startTime}`, { startTime, }); + // @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. logger.info('Updating System-info.json file', { ...systemInfo.data }); + // @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. logger.info(`Current working directory is: ${process.cwd()}`, { cwd: process.cwd(), }); + // @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. logger.info('System and user locale', { systemLocale, userLocale, @@ -157,50 +182,48 @@ const onAppReady = async () => { await installChromeExtensions(isDev); // @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. logger.info('Setting up Main Window...'); - // @ts-ignore ts-migrate(2345) FIXME: Argument of type 'unknown' is not assignable to pa... Remove this comment to see the full error message - mainWindow = createMainWindow(userLocale, () => + mainWindow = createMainWindow( + // @ts-ignore ts-migrate(2345) FIXME: Argument of type 'unknown' is not assignable to pa... Remove this comment to see the full error message + userLocale, // @ts-ignore ts-migrate(2345) FIXME: Argument of type 'Electron.Screen' is not assignab... Remove this comment to see the full error message restoreSavedWindowBounds(screen, requestElectronStore) ); saveWindowBoundsOnSizeAndPositionChange(mainWindow, requestElectronStore); + const currentRtsFlags = getRtsFlagsSettings(network) || []; // @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. - logger.info('Setting up Cardano Node...'); - cardanoNode = setupCardanoNode(launcherConfig, mainWindow); + logger.info( + `Setting up Cardano Node... with flags: ${JSON.stringify(currentRtsFlags)}` + ); + cardanoNode = setupCardanoNode(launcherConfig, mainWindow, currentRtsFlags); // @ts-ignore ts-migrate(2345) FIXME: Argument of type 'unknown' is not assignable to pa... Remove this comment to see the full error message buildAppMenus(mainWindow, cardanoNode, userLocale, { isNavigationEnabled: false, + walletSettingsState: WalletSettingsStateEnum.hidden, }); - enableApplicationMenuNavigationChannel.onReceive( - () => - new Promise((resolve) => { - const locale = getLocale(network); - // @ts-ignore ts-migrate(2345) FIXME: Argument of type 'unknown' is not assignable to pa... Remove this comment to see the full error message - buildAppMenus(mainWindow, cardanoNode, locale, { - isNavigationEnabled: true, - }); - resolve(); - }) - ); rebuildApplicationMenu.onReceive( - (data) => + ({ walletSettingsState, isNavigationEnabled }) => new Promise((resolve) => { const locale = getLocale(network); // @ts-ignore ts-migrate(2345) FIXME: Argument of type 'unknown' is not assignable to pa... Remove this comment to see the full error message buildAppMenus(mainWindow, cardanoNode, locale, { - isNavigationEnabled: data.isNavigationEnabled, + isNavigationEnabled, + walletSettingsState, }); // @ts-ignore ts-migrate(2339) FIXME: Property 'updateTitle' does not exist on type 'Bro... Remove this comment to see the full error message mainWindow.updateTitle(locale); + // @ts-ignore ts-migrate(2794) FIXME: Expected 1 arguments, but got 0. Did you forget to... Remove this comment to see the full error message resolve(); }) ); setStateSnapshotLogChannel.onReceive( - (data: SetStateSnapshotLogMainResponse) => - Promise.resolve(logStateSnapshot(data)) + (data: SetStateSnapshotLogMainResponse) => { + return Promise.resolve(logStateSnapshot(data)); + } ); generateWalletMigrationReportChannel.onReceive( - (data: GenerateWalletMigrationReportRendererRequest) => - Promise.resolve(generateWalletMigrationReport(data)) + (data: GenerateWalletMigrationReportRendererRequest) => { + return Promise.resolve(generateWalletMigrationReport(data)); + } ); getStateDirectoryPathChannel.onRequest(() => Promise.resolve(stateDirectoryPath) @@ -209,6 +232,12 @@ const onAppReady = async () => { Promise.resolve(app.getPath('desktop')) ); getSystemLocaleChannel.onRequest(() => Promise.resolve(systemLocale)); + toggleRTSFlagsModeChannel.onReceive(() => { + const flagsToSet = containsRTSFlags(currentRtsFlags) ? [] : RTS_FLAGS; + storeRtsFlagsSettings(environment.network, flagsToSet); + // @ts-ignore ts-migrate(2554) FIXME: Expected 1 arguments, but got 0. + return handleWindowClose(); + }); const handleCheckDiskSpace = handleDiskSpace(mainWindow, cardanoNode); const onMainError = (error: string) => { @@ -219,22 +248,22 @@ const onAppReady = async () => { }; mainErrorHandler(onMainError); + handleCheckBlockReplayProgress(mainWindow, launcherConfig.logsPrefix); await handleCheckDiskSpace(); - await handleCheckBlockReplayProgress(mainWindow, launcherConfig.logsPrefix); - mainWindow.on('close', async (event) => { - // @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. - logger.info( - 'mainWindow received event. Safe exiting Daedalus now.' - ); - event.preventDefault(); - await safeExit(); - }); + + if (isWatchMode) { + // Connect to electron-connect server which restarts / reloads windows on file changes + client.create(mainWindow); + } + + mainWindow.on('close', handleWindowClose); // Security feature: Prevent creation of new browser windows // https://github.com/electron/electron/blob/master/docs/tutorial/security.md#14-disable-or-limit-creation-of-new-windows app.on('web-contents-created', (_, contents) => { contents.on('new-window', (event, url) => { // Prevent creation of new BrowserWindows via links / window.open event.preventDefault(); + // @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. logger.info('Prevented creation of new browser window', { url, }); @@ -250,6 +279,7 @@ const onAppReady = async () => { if (isSelfnode) { if (keepLocalClusterRunning || isTest) { + // @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. logger.info( 'ipcMain: Keeping the local cluster running while exiting Daedalus', { diff --git a/source/main/ipc/downloadManagerChannel.ts b/source/main/ipc/downloadManagerChannel.ts index 0ad41fd41f..a275b72160 100644 --- a/source/main/ipc/downloadManagerChannel.ts +++ b/source/main/ipc/downloadManagerChannel.ts @@ -206,8 +206,9 @@ const getDownloadLocalData = async ({ }; const getDownloadsLocalData = - // @ts-ignore ts-migrate(2322) FIXME: Type 'unknown' is not assignable to type 'Download... Remove this comment to see the full error message - async (): Promise => localStorage.getAll(); + async (): Promise => { + return localStorage.getAll() as Promise; + }; const clearDownloadLocalData = async ({ fileName, diff --git a/source/main/ipc/electronStoreConversation.ts b/source/main/ipc/electronStoreConversation.ts index 32f27c6647..f35dd075d1 100644 --- a/source/main/ipc/electronStoreConversation.ts +++ b/source/main/ipc/electronStoreConversation.ts @@ -31,14 +31,13 @@ const reset = async () => { await unset(keys.CURRENCY_SELECTED); await unset(keys.DATA_LAYER_MIGRATION_ACCEPTANCE); await unset(keys.DISCREET_MODE_ENABLED); - await unset(keys.DISCREET_MODE_SETTINGS_TOOLTIP); - await unset(keys.DISCREET_MODE_NOTIFICATION); await unset(keys.DOWNLOAD_MANAGER); await unset(keys.HARDWARE_WALLET_DEVICES); await unset(keys.HARDWARE_WALLETS); await unset(keys.READ_NEWS); await unset(keys.SMASH_SERVER); await unset(keys.STAKING_INFO_WAS_OPEN); + await unset(keys.STAKE_POOLS_LIST_VIEW_TOOLTIP); await unset(keys.TERMS_OF_USE_ACCEPTANCE); await unset(keys.THEME); await unset(keys.USER_DATE_FORMAT_ENGLISH); diff --git a/source/main/ipc/get-block-sync-progress.ts b/source/main/ipc/get-block-sync-progress.ts new file mode 100644 index 0000000000..5613f5a21c --- /dev/null +++ b/source/main/ipc/get-block-sync-progress.ts @@ -0,0 +1,13 @@ +import { MainIpcChannel } from './lib/MainIpcChannel'; +import { GET_BLOCK_SYNC_PROGRESS_CHANNEL } from '../../common/ipc/api'; +import type { + GetBlockSyncProgressRendererRequest, + GetBlockSyncProgressMainResponse, +} from '../../common/ipc/api'; + +// IpcChannel + +export const getBlockSyncProgressChannel: MainIpcChannel< + GetBlockSyncProgressRendererRequest, + GetBlockSyncProgressMainResponse +> = new MainIpcChannel(GET_BLOCK_SYNC_PROGRESS_CHANNEL); diff --git a/source/main/ipc/getHardwareWalletChannel.ts b/source/main/ipc/getHardwareWalletChannel.ts index 6bfa2adddf..c20b5f559c 100644 --- a/source/main/ipc/getHardwareWalletChannel.ts +++ b/source/main/ipc/getHardwareWalletChannel.ts @@ -22,8 +22,8 @@ import type { getCardanoAdaAppRendererRequest, getExtendedPublicKeyMainResponse, getExtendedPublicKeyRendererRequest, - getHardwareWalletConnectiontMainRequest, - getHardwareWalletConnectiontRendererResponse, + getHardwareWalletConnectionMainRequest, + getHardwareWalletConnectionRendererResponse, getHardwareWalletTransportMainResponse, getHardwareWalletTransportRendererRequest, handleInitLedgerConnectMainResponse, @@ -80,8 +80,8 @@ const getCardanoAdaAppChannel: MainIpcChannel< getCardanoAdaAppMainResponse > = new MainIpcChannel(GET_CARDANO_ADA_APP_CHANNEL); const getHardwareWalletConnectionChannel: MainIpcChannel< - getHardwareWalletConnectiontMainRequest, - getHardwareWalletConnectiontRendererResponse + getHardwareWalletConnectionMainRequest, + getHardwareWalletConnectionRendererResponse > = new MainIpcChannel(GET_HARDWARE_WALLET_CONNECTION_CHANNEL); const signTransactionLedgerChannel: MainIpcChannel< signTransactionLedgerRendererRequest, diff --git a/source/main/ipc/manageAppUpdateChannel.ts b/source/main/ipc/manageAppUpdateChannel.ts index 7bc2fbe66d..0aaa69df9b 100644 --- a/source/main/ipc/manageAppUpdateChannel.ts +++ b/source/main/ipc/manageAppUpdateChannel.ts @@ -79,8 +79,8 @@ export const handleManageAppUpdateRequests = (window: BrowserWindow) => { return true; }; - const installUpdate = async (filePath) => - new Promise((resolve, reject) => { + const installUpdate = async (filePath) => { + return new Promise((resolve, reject) => { const { name: functionPrefix } = installUpdate; response(null, functionPrefix, 'installation begin.'); const { updateRunnerBin } = launcherConfig; @@ -171,6 +171,7 @@ export const handleManageAppUpdateRequests = (window: BrowserWindow) => { return resolve(response(true, functionPrefix)); }); }); + }; // @ts-ignore ts-migrate(2345) FIXME: Argument of type '({ filePath, hash: expectedHash ... Remove this comment to see the full error message manageAppUpdateChannel.onRequest(async ({ filePath, hash: expectedHash }) => { diff --git a/source/main/ipc/toggleRTSFlagsModeChannel.ts b/source/main/ipc/toggleRTSFlagsModeChannel.ts new file mode 100644 index 0000000000..0375018bb5 --- /dev/null +++ b/source/main/ipc/toggleRTSFlagsModeChannel.ts @@ -0,0 +1,11 @@ +import { TOGGLE_RTS_FLAGS_MODE_CHANNEL } from '../../common/ipc/api'; +import type { + ToggleRTSFlagsModeMainResponse, + ToggleRTSFlagsModeRendererRequest, +} from '../../common/ipc/api'; +import { MainIpcChannel } from './lib/MainIpcChannel'; + +export const toggleRTSFlagsModeChannel: MainIpcChannel< + ToggleRTSFlagsModeRendererRequest, + ToggleRTSFlagsModeMainResponse +> = new MainIpcChannel(TOGGLE_RTS_FLAGS_MODE_CHANNEL); diff --git a/source/main/locales/en-US.json b/source/main/locales/en-US.json index 977b539f85..7fdc70661b 100644 --- a/source/main/locales/en-US.json +++ b/source/main/locales/en-US.json @@ -22,6 +22,13 @@ "menu.edit.undo": "Undo", "menu.helpSupport": "Help", "menu.helpSupport.blankScreenFix": "Blank Screen Fix", + "menu.helpSupport.usingRtsFlags": "Using RTS flags", + "menu.helpSupport.rtsFlagsDialogCancel": "Cancel", + "menu.helpSupport.rtsFlagsDialogConfirm": "Yes", + "menu.helpSupport.enableRtsFlagsDialogMessage": "When enabled, the Cardano node will attempt to reduce its RAM usage", + "menu.helpSupport.enableRtsFlagsDialogTitle": "Enable RAM management (RTS Flags)", + "menu.helpSupport.disableRtsFlagsDialogMessage": "When disabled, we will restart cardano-node in default mode", + "menu.helpSupport.disableRtsFlagsDialogTitle": "Disable RAM management (RTS Flags)", "menu.helpSupport.blankScreenFixDialogCancel": "Cancel", "menu.helpSupport.blankScreenFixDialogConfirm": "Yes", "menu.helpSupport.blankScreenFixDialogMessage": "Turn off 'Blank screen fix'? \n \nDisabling the blank screen fix setting will improve the performance of user interface rendering by enabling graphics acceleration, however, some users may find that Daedalus runs better with this setting enabled. If you see a blank screen instead of the Daedalus user interface after disabling this setting and restarting Daedalus, please turn this setting back on. \n \nDo you want to disable this setting and restart Daedalus?", @@ -43,5 +50,6 @@ "menu.view.toggleDeveloperTools": "Toggle Developer Tools", "menu.view.toggleFullScreen": "Toggle Full Screen", "menu.view.toggleMaximumWindowSize": "Toggle Maximum Window Size", - "window.title.blankScreenFix": "['Blank screen fix' active]" + "window.title.blankScreenFix": "['Blank screen fix' active]", + "window.title.usingRtsFlags": "['Using RTS flags' active]" } diff --git a/source/main/locales/ja-JP.json b/source/main/locales/ja-JP.json index c681f2dd91..e153ae0042 100644 --- a/source/main/locales/ja-JP.json +++ b/source/main/locales/ja-JP.json @@ -22,6 +22,13 @@ "menu.edit.undo": "元に戻す", "menu.helpSupport": "ヘルプ", "menu.helpSupport.blankScreenFix": "ブランク画面修正", + "menu.helpSupport.usingRtsFlags": "RTSフラグの使用", + "menu.helpSupport.rtsFlagsDialogCancel": "Cancel", + "menu.helpSupport.rtsFlagsDialogConfirm": "Yes", + "menu.helpSupport.enableRtsFlagsDialogMessage": "When enabled, the Cardano node will attempt to reduce its RAM usage", + "menu.helpSupport.enableRtsFlagsDialogTitle": "Enable RAM management (RTSフラグの使用\")", + "menu.helpSupport.disableRtsFlagsDialogMessage": "When disabled, we will restart cardano-node in default mode", + "menu.helpSupport.disableRtsFlagsDialogTitle": "Disable RAM management (RTSフラグの使用\")", "menu.helpSupport.blankScreenFixDialogCancel": "キャンセル", "menu.helpSupport.blankScreenFixDialogConfirm": "はい", "menu.helpSupport.blankScreenFixDialogMessage": "「ブランク画面修正」を無効にしますか? \n \nブランク画面修正設定を無効にすると、グラフィックアクセラレーションが有効化されてユーザーインターフェイスのレンダリングパフォーマンスが向上しますが、この設定を有効にした方がDaedalusがスムーズに作動する場合があります。この設定を無効にしてDaedalusを再起動した際にDaedalusユーザーインターフェイスの代わりにブランク画面が表示される場合は、この設定をもう一度有効にしてください。 \n \nこの設定を無効にしてDaedalusを再起動しますか。", @@ -43,5 +50,6 @@ "menu.view.toggleDeveloperTools": "開発者ツール切替え", "menu.view.toggleFullScreen": "フルスクリーン切替え", "menu.view.toggleMaximumWindowSize": "最大ウインドウサイズ切り切り替え", - "window.title.blankScreenFix": "[「ブランク画面修正」有効 ]" + "window.title.blankScreenFix": "[「ブランク画面修正」有効 ]", + "window.title.usingRtsFlags": "[「RTSフラグの使用」が有効]" } diff --git a/source/main/menus/MenuActions.types.ts b/source/main/menus/MenuActions.types.ts index 13e85aaa5c..3d239cb5ab 100644 --- a/source/main/menus/MenuActions.types.ts +++ b/source/main/menus/MenuActions.types.ts @@ -1,5 +1,6 @@ export type MenuActions = { toggleBlankScreenFix: (...args: Array) => any; + openToggleRTSFlagsModeDialog: (...args: Array) => any; openAboutDialog: (...args: Array) => any; openDaedalusDiagnosticsDialog: (...args: Array) => any; openItnRewardsRedemptionDialog: (...args: Array) => any; diff --git a/source/main/menus/osx.ts b/source/main/menus/osx.ts index 909ff6b35a..e1795a8e2c 100644 --- a/source/main/menus/osx.ts +++ b/source/main/menus/osx.ts @@ -7,9 +7,10 @@ import { environment } from '../environment'; import { showUiPartChannel } from '../ipc/control-ui-parts'; import { NOTIFICATIONS } from '../../common/ipc/constants'; import { generateSupportRequestLink } from '../../common/utils/reporting'; +import { buildKnownIssueFixesSubmenu } from './submenuBuilders'; +import { WalletSettingsStateEnum } from '../../common/ipc/api'; const id = 'menu'; -const { isBlankScreenFixActive } = environment; export const osxMenu = ( app: App, window: BrowserWindow, @@ -17,6 +18,7 @@ export const osxMenu = ( translations: {}, locale: string, isNavigationEnabled: boolean, + walletSettingsState: WalletSettingsStateEnum, translation: (...args: Array) => any = getTranslation(translations, id) ) => [ { @@ -65,7 +67,10 @@ export const osxMenu = ( actions.openWalletSettingsPage(); }, - enabled: isNavigationEnabled, + enabled: + isNavigationEnabled && + walletSettingsState === WalletSettingsStateEnum.enabled, + visible: walletSettingsState !== WalletSettingsStateEnum.hidden, }, { type: 'separator', @@ -157,23 +162,7 @@ export const osxMenu = ( { label: translation('helpSupport'), submenu: compact([ - { - label: translation('helpSupport.knownIssues'), - - click() { - const faqLink = translation('helpSupport.knownIssuesUrl'); - shell.openExternal(faqLink); - }, - }, - { - label: translation('helpSupport.blankScreenFix'), - type: 'checkbox', - checked: isBlankScreenFixActive, - - click(item) { - actions.toggleBlankScreenFix(item); - }, - }, + ...buildKnownIssueFixesSubmenu(actions, translations, translation), { type: 'separator', }, @@ -185,15 +174,6 @@ export const osxMenu = ( shell.openExternal(safetyTipsLinkUrl); }, }, - /* { - label: translation('helpSupport.featureRequest'), - click() { - const featureRequestLinkUrl = translation( - 'helpSupport.featureRequestUrl' - ); - shell.openExternal(featureRequestLinkUrl); - }, - }, */ { label: translation('helpSupport.supportRequest'), diff --git a/source/main/menus/submenuBuilders.ts b/source/main/menus/submenuBuilders.ts new file mode 100644 index 0000000000..5777e3ac18 --- /dev/null +++ b/source/main/menus/submenuBuilders.ts @@ -0,0 +1,49 @@ +import { shell } from 'electron'; +import type { MenuItem } from 'electron'; +import { getRtsFlagsSettings } from '../utils/rtsFlagsSettings'; +import { environment } from '../environment'; +import type { MenuActions } from './MenuActions.types'; +import { getTranslation } from '../utils/getTranslation'; + +export const buildKnownIssueFixesSubmenu = ( + actions: MenuActions, + translations: {}, + translate: (...args: Array) => any = getTranslation(translations, 'menu') +): MenuItem[] => { + const { isBlankScreenFixActive, network } = environment; + const rtsFlags = getRtsFlagsSettings(network); + const areRTSFlagsEnabled = !!rtsFlags?.length && rtsFlags.length > 0; + return [ + // @ts-ignore ts-migrate(2740) FIXME: Type '{ label: any; click(): void; }' is missing t... Remove this comment to see the full error message + { + label: translate('helpSupport.knownIssues'), + + click() { + const faqLink = translate('helpSupport.knownIssuesUrl'); + shell.openExternal(faqLink); + }, + }, + // @ts-ignore ts-migrate(2740) FIXME: Type '{ label: any; type: "checkbox"; checked: boo... Remove this comment to see the full error message + { + label: translate('helpSupport.blankScreenFix'), + type: 'checkbox', + checked: isBlankScreenFixActive, + + click(item) { + actions.toggleBlankScreenFix(item); + }, + }, + // @ts-ignore ts-migrate(2740) FIXME: Type '{ label: any; type: "checkbox"; checked: boo... Remove this comment to see the full error message + { + label: translate('helpSupport.usingRtsFlags'), + type: 'checkbox', + checked: areRTSFlagsEnabled, + + click(item) { + actions.openToggleRTSFlagsModeDialog(!areRTSFlagsEnabled); + // keep previous setting until app restart + item.checked = areRTSFlagsEnabled; + }, + }, + ]; +}; diff --git a/source/main/menus/win-linux.ts b/source/main/menus/win-linux.ts index 8dc416bb50..b070b5e0d2 100644 --- a/source/main/menus/win-linux.ts +++ b/source/main/menus/win-linux.ts @@ -7,9 +7,10 @@ import { environment } from '../environment'; import { NOTIFICATIONS } from '../../common/ipc/constants'; import { showUiPartChannel } from '../ipc/control-ui-parts'; import { generateSupportRequestLink } from '../../common/utils/reporting'; +import { buildKnownIssueFixesSubmenu } from './submenuBuilders'; +import { WalletSettingsStateEnum } from '../../common/ipc/api'; const id = 'menu'; -const { isWindows, isBlankScreenFixActive } = environment; export const winLinuxMenu = ( app: App, window: BrowserWindow, @@ -17,6 +18,7 @@ export const winLinuxMenu = ( translations: {}, locale: string, isNavigationEnabled: boolean, + walletSettingsState: WalletSettingsStateEnum, translation: (...args: Array) => any = getTranslation(translations, id) ) => [ { @@ -127,12 +129,15 @@ export const winLinuxMenu = ( actions.openWalletSettingsPage(); }, - enabled: isNavigationEnabled, + enabled: + isNavigationEnabled && + walletSettingsState === WalletSettingsStateEnum.enabled, + visible: walletSettingsState !== WalletSettingsStateEnum.hidden, }, { type: 'separator', }, - isWindows + environment.isWindows ? { label: translation('view.toggleFullScreen'), accelerator: 'F11', @@ -167,23 +172,7 @@ export const winLinuxMenu = ( { label: translation('helpSupport'), submenu: compact([ - { - label: translation('helpSupport.knownIssues'), - - click() { - const faqLink = translation('helpSupport.knownIssuesUrl'); - shell.openExternal(faqLink); - }, - }, - { - label: translation('helpSupport.blankScreenFix'), - type: 'checkbox', - checked: isBlankScreenFixActive, - - click(item) { - actions.toggleBlankScreenFix(item); - }, - }, + ...buildKnownIssueFixesSubmenu(actions, translations, translation), { type: 'separator', }, diff --git a/source/main/utils/blockSyncProgressHelpers.spec.ts b/source/main/utils/blockSyncProgressHelpers.spec.ts new file mode 100644 index 0000000000..853347b9df --- /dev/null +++ b/source/main/utils/blockSyncProgressHelpers.spec.ts @@ -0,0 +1,28 @@ +import moment from 'moment'; +import { isItFreshLog } from './blockSyncProgressHelpers'; + +describe('blockSyncProgressHelpers', () => { + it('should return true for newer logs', () => { + const time = '2022-02-18 13:18:47.66'; + const applicationStartDate = moment.utc(time).add(-1, 'minute'); + const line = `[34m[XX-M:cardano.node.ChainDB:Info:30][0m [${time} UTC] Validating chunk no. 2256 out of 2256. Progress: 99.96%`; + + expect(isItFreshLog(applicationStartDate, line)).toBe(true); + }); + + it('should return false for logs of same time', () => { + const time = '2022-02-18 13:18:47.66'; + const applicationStartDate = moment.utc(time); + const line = `[34m[XX-M:cardano.node.ChainDB:Info:30][0m [${time} UTC] Validating chunk no. 2256 out of 2256. Progress: 99.96%`; + + expect(isItFreshLog(applicationStartDate, line)).toBe(false); + }); + + it('should return false for older logs', () => { + const time = '2022-02-18 13:18:47.66'; + const applicationStartDate = moment.utc(time).add(1, 'minute'); + const line = `[34m[XX-M:cardano.node.ChainDB:Info:30][0m [${time} UTC] Validating chunk no. 2256 out of 2256. Progress: 99.96%`; + + expect(isItFreshLog(applicationStartDate, line)).toBe(false); + }); +}); diff --git a/source/main/utils/blockSyncProgressHelpers.ts b/source/main/utils/blockSyncProgressHelpers.ts new file mode 100644 index 0000000000..d7802474ad --- /dev/null +++ b/source/main/utils/blockSyncProgressHelpers.ts @@ -0,0 +1,12 @@ +import moment, { Moment } from 'moment'; + +export function isItFreshLog(applicationStartDate: Moment, line: string) { + const [, logDate] = + line.match(/\[(\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}\.\d+) UTC]/) || []; + + if (!logDate) { + return false; + } + + return applicationStartDate.isBefore(moment.utc(logDate)); +} diff --git a/source/main/utils/buildAppMenus.ts b/source/main/utils/buildAppMenus.ts index bd6cb622b2..31ee34b3d7 100644 --- a/source/main/utils/buildAppMenus.ts +++ b/source/main/utils/buildAppMenus.ts @@ -1,4 +1,5 @@ -import { app, globalShortcut, Menu, BrowserWindow, dialog } from 'electron'; +import { app, BrowserWindow, dialog, globalShortcut, Menu } from 'electron'; +import { WalletSettingsStateEnum } from '../../common/ipc/api'; import { environment } from '../environment'; import { winLinuxMenu } from '../menus/win-linux'; import { osxMenu } from '../menus/osx'; @@ -9,17 +10,23 @@ import { DIALOGS, PAGES } from '../../common/ipc/constants'; import { showUiPartChannel } from '../ipc/control-ui-parts'; import { getTranslation } from './getTranslation'; +interface Data { + isNavigationEnabled: boolean; + walletSettingsState: WalletSettingsStateEnum; +} export const buildAppMenus = async ( mainWindow: BrowserWindow, cardanoNode: CardanoNode | null | undefined, locale: string, - data: { - isNavigationEnabled: boolean; - } + { isNavigationEnabled, walletSettingsState }: Data ) => { - const { ABOUT, DAEDALUS_DIAGNOSTICS, ITN_REWARDS_REDEMPTION } = DIALOGS; + const { + ABOUT, + DAEDALUS_DIAGNOSTICS, + ITN_REWARDS_REDEMPTION, + TOGGLE_RTS_FLAGS_MODE, + } = DIALOGS; const { SETTINGS, WALLET_SETTINGS } = PAGES; - const { isNavigationEnabled } = data; const { isMacOS, isBlankScreenFixActive } = environment; const translations = require(`../locales/${locale}`); @@ -53,6 +60,7 @@ export const buildAppMenus = async ( // @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. logger.info('Restarting in BlankScreenFix...'); if (cardanoNode) await cardanoNode.stop(); + // @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. logger.info('Exiting Daedalus with code 21', { code: 21, }); @@ -63,6 +71,7 @@ export const buildAppMenus = async ( // @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. logger.info('Restarting without BlankScreenFix...'); if (cardanoNode) await cardanoNode.stop(); + // @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. logger.info('Exiting Daedalus with code 22', { code: 22, }); @@ -103,6 +112,11 @@ export const buildAppMenus = async ( item.checked = isBlankScreenFixActive; }; + const openToggleRTSFlagsModeDialog = () => { + // @ts-ignore ts-migrate(2345) FIXME: Argument of type 'BrowserWindow' is not assignable... Remove this comment to see the full error message + if (mainWindow) showUiPartChannel.send(TOGGLE_RTS_FLAGS_MODE, mainWindow); + }; + const menuActions = { openAboutDialog, openDaedalusDiagnosticsDialog, @@ -110,6 +124,7 @@ export const buildAppMenus = async ( openSettingsPage, openWalletSettingsPage, toggleBlankScreenFix, + openToggleRTSFlagsModeDialog, }; // Build app menus let menu; @@ -123,7 +138,8 @@ export const buildAppMenus = async ( menuActions, translations, locale, - isNavigationEnabled + isNavigationEnabled, + walletSettingsState ) ); Menu.setApplicationMenu(menu); @@ -136,7 +152,8 @@ export const buildAppMenus = async ( menuActions, translations, locale, - isNavigationEnabled + isNavigationEnabled, + walletSettingsState ) ); mainWindow.setMenu(menu); diff --git a/source/main/utils/containsRTSFlags.ts b/source/main/utils/containsRTSFlags.ts new file mode 100644 index 0000000000..c6c6a6266d --- /dev/null +++ b/source/main/utils/containsRTSFlags.ts @@ -0,0 +1,4 @@ +import { isEqual } from 'lodash'; +import { RTS_FLAGS } from '../config'; + +export const containsRTSFlags = (flags: string[]) => isEqual(flags, RTS_FLAGS); diff --git a/source/main/utils/downloadManager.ts b/source/main/utils/downloadManager.ts index 22b47b3947..dfe1bf7de4 100644 --- a/source/main/utils/downloadManager.ts +++ b/source/main/utils/downloadManager.ts @@ -17,7 +17,7 @@ import type { DownloadMainResponse, } from '../../common/ipc/api'; import type { - AllowedDownloadDirectories, + AllowedDownloadDirectoriesValues, DownloadInfoInit, DownloadInfoProgress, DownloadInfoEnd, @@ -31,7 +31,7 @@ export const downloads = {}; export const getIdFromFileName = (fileName: string): string => fileName.replace(/\./g, '-'); export const getPathFromDirectoryName = ( - directoryName: AllowedDownloadDirectories + directoryName: AllowedDownloadDirectoriesValues ) => { const downloadsDirectory = `${stateDirectoryPath}/Downloads`; @@ -39,7 +39,7 @@ export const getPathFromDirectoryName = ( case ALLOWED_DOWNLOAD_DIRECTORIES.DESKTOP: return app.getPath('desktop'); - case ALLOWED_DOWNLOAD_DIRECTORIES.DOWLOADS: + case ALLOWED_DOWNLOAD_DIRECTORIES.DOWNLOADS: return app.getPath('downloads'); default: diff --git a/source/main/utils/handleCheckBlockReplayProgress.ts b/source/main/utils/handleCheckBlockReplayProgress.ts index 6bc1d7594e..60510f5f93 100644 --- a/source/main/utils/handleCheckBlockReplayProgress.ts +++ b/source/main/utils/handleCheckBlockReplayProgress.ts @@ -1,49 +1,77 @@ import { BrowserWindow } from 'electron'; import fs from 'fs'; -import readline from 'readline'; +import moment from 'moment'; import path from 'path'; -import { getBlockReplayProgressChannel } from '../ipc/get-block-replay-progress'; -import { BLOCK_REPLAY_PROGRESS_CHECK_INTERVAL } from '../config'; +import { Tail } from 'tail'; +import { getBlockSyncProgressChannel } from '../ipc/get-block-sync-progress'; +import type { GetBlockSyncProgressType } from '../../common/ipc/api'; +import { BlockSyncType } from '../../common/types/cardano-node.types'; +import { isItFreshLog } from './blockSyncProgressHelpers'; + +const blockKeyword = 'Replayed block'; +const validatingChunkKeyword = 'Validating chunk'; +const validatedChunkKeyword = 'Validated chunk'; +const ledgerKeyword = 'Pushing ledger state'; + +const progressKeywords = [ + blockKeyword, + validatingChunkKeyword, + validatedChunkKeyword, + ledgerKeyword, +]; + +const keywordTypeMap: Record = { + [blockKeyword]: BlockSyncType.replayedBlock, + [validatingChunkKeyword]: BlockSyncType.validatingChunk, + [validatedChunkKeyword]: BlockSyncType.validatingChunk, + [ledgerKeyword]: BlockSyncType.pushingLedger, +}; + +function containProgressKeywords(line: string) { + return progressKeywords.some((keyword) => line.includes(keyword)); +} + +function getProgressType(line: string): GetBlockSyncProgressType | null { + const key = progressKeywords.find((k) => line.includes(k)); + + if (!key) { + return null; + } + + return keywordTypeMap[key]; +} + +const applicationStartDate = moment.utc(); export const handleCheckBlockReplayProgress = ( mainWindow: BrowserWindow, logsDirectoryPath: string ) => { - const checkBlockReplayProgress = async () => { - const filename = 'node.log'; - const logFilePath = `${logsDirectoryPath}/pub/`; - const filePath = path.join(logFilePath, filename); - if (!fs.existsSync(filePath)) return; - const fileStream = fs.createReadStream(filePath); - const rl = readline.createInterface({ - input: fileStream, - }); - const progress = []; - - for await (const line of rl) { - if (line.includes('block replay')) { - progress.push(line); - } + const filename = 'node.log'; + const logFilePath = `${logsDirectoryPath}/pub/`; + const filePath = path.join(logFilePath, filename); + if (!fs.existsSync(filePath)) return; + + const tail = new Tail(filePath); + + tail.on('line', (line) => { + if ( + !isItFreshLog(applicationStartDate, line) || + !containProgressKeywords(line) + ) { + return; } - if (!progress.length) return; - const finalProgress = progress.slice(-1).pop(); - const percentage = finalProgress.split('block replay progress (%) =').pop(); + const percentage = line.match(/Progress:([\s\d.,]+)%/)?.[1]; + const progressType = getProgressType(line); + if (!percentage || !progressType) { + return; + } const finalProgressPercentage = parseFloat(percentage); // Send result to renderer process (NetworkStatusStore) - getBlockReplayProgressChannel.send( - finalProgressPercentage, + getBlockSyncProgressChannel.send( + { progress: finalProgressPercentage, type: progressType }, mainWindow.webContents ); - }; - - const setBlockReplayProgressCheckingInterval = () => { - setInterval(async () => { - checkBlockReplayProgress(); - }, BLOCK_REPLAY_PROGRESS_CHECK_INTERVAL); - }; - - // Start default interval - setBlockReplayProgressCheckingInterval(); - return checkBlockReplayProgress; + }); }; diff --git a/source/main/utils/handleDiskSpace.ts b/source/main/utils/handleDiskSpace.ts index 720c00810c..c18618ed55 100644 --- a/source/main/utils/handleDiskSpace.ts +++ b/source/main/utils/handleDiskSpace.ts @@ -1,6 +1,4 @@ import { BrowserWindow } from 'electron'; - -/* eslint import/no-unresolved: "off" */ import checkDiskSpace from 'check-disk-space'; import prettysize from 'prettysize'; import { getDiskSpaceStatusChannel } from '../ipc/get-disk-space-status'; diff --git a/source/main/utils/restoreKeystore.ts b/source/main/utils/restoreKeystore.ts index d04a0d1c8b..df221f1ebb 100644 --- a/source/main/utils/restoreKeystore.ts +++ b/source/main/utils/restoreKeystore.ts @@ -12,8 +12,8 @@ export type EncryptedSecretKey = { export type WalletId = string; export const decodeKeystore = async ( bytes: Buffer -): Promise => - cbor.decodeAll(bytes).then((obj) => { +): Promise => { + return cbor.decodeAll(bytes).then((obj) => { /** * The original 'UserSecret' from cardano-sl looks like this: * @@ -48,6 +48,7 @@ export const decodeKeystore = async ( const usWalletSet = obj[0][3].map((x) => toEncryptedSecretKey(x[0])); return usKeys.concat(usWalletSet); }); +}; const toEncryptedSecretKey = ([encryptedPayload, passphraseHash]: [ Buffer, diff --git a/source/main/utils/rtsFlagsSettings.ts b/source/main/utils/rtsFlagsSettings.ts new file mode 100644 index 0000000000..185e0ec04e --- /dev/null +++ b/source/main/utils/rtsFlagsSettings.ts @@ -0,0 +1,34 @@ +import Store from 'electron-store'; +import { logger } from './logging'; + +const store = new Store(); + +const getStoreKey = (network: string): string => `${network}-RTS-FLAGS`; + +export const getRtsFlagsSettings = (network: string): string[] | null => { + try { + const flags = store.get(getStoreKey(network)); + // @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. + logger.info(`[RTS-FLAGS] Read ${network} flags: ${flags} from config`); + // @ts-ignore ts-migrate(2740) FIXME: Type '{}' is missing the following properties from... Remove this comment to see the full error message + return flags; + } catch (error) { + // @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. + logger.error( + `[RTS-FLAGS] Failed to read ${network} flags from config`, + error + ); + } + + return null; +}; +export const storeRtsFlagsSettings = ( + network: string, + flags: string[] +): void => { + // @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. + logger.info( + `[RTS-FLAGS] Persisted ${network} flags: [${flags.toString()}] in config` + ); + store.set(getStoreKey(network), flags); +}; diff --git a/source/main/utils/safeExitWithCode.ts b/source/main/utils/safeExitWithCode.ts index a7193a8163..de1f520478 100644 --- a/source/main/utils/safeExitWithCode.ts +++ b/source/main/utils/safeExitWithCode.ts @@ -12,3 +12,17 @@ export const safeExitWithCode = (exitCode = 0) => { app.exit(exitCode); }); }; +export const relaunch = () => { + const { file } = log.transports; + // Prevent electron-log from writing to stream + file.level = false; + // Flush the stream to the log file and exit afterwards. + // https://nodejs.org/api/stream.html#stream_writable_end_chunk_encoding_callback + file.stream.end('', 'utf8', () => { + app.releaseSingleInstanceLock(); + app.relaunch({ + args: process.argv.slice(1).concat(['--relaunch']), + }); + app.exit(0); + }); +}; diff --git a/source/main/windows/main.ts b/source/main/windows/main.ts index 1cfbcc7ec6..1e7fe70d88 100644 --- a/source/main/windows/main.ts +++ b/source/main/windows/main.ts @@ -7,9 +7,11 @@ import { getTranslation } from '../utils/getTranslation'; import { getContentMinimumSize } from '../utils/getContentMinimumSize'; import { buildLabel, launcherConfig } from '../config'; import { ledgerStatus } from '../ipc/getHardwareWalletChannel'; +import { getRtsFlagsSettings } from '../utils/rtsFlagsSettings'; const rendererErrorHandler = new RendererErrorHandler(); -const { isDev, isTest, isLinux, isBlankScreenFixActive } = environment; +const { isDev, isTest, isLinux, isBlankScreenFixActive, network } = environment; +const rtsFlags = getRtsFlagsSettings(network); const id = 'window'; const getWindowTitle = (locale: string): string => { @@ -19,6 +21,8 @@ const getWindowTitle = (locale: string): string => { let title = buildLabel; if (isBlankScreenFixActive) title += ` ${translation('title.blankScreenFix')}`; + if (!!rtsFlags && rtsFlags?.length > 0) + title += ` ${translation('title.usingRtsFlags')}`; return title; }; @@ -34,15 +38,12 @@ type WindowOptionsType = { }; icon?: string; }; -export const createMainWindow = ( - locale: string, - getWindowBounds: () => Rectangle | null | undefined -) => { +export const createMainWindow = (locale: string, windowBounds?: Rectangle) => { const windowOptions: WindowOptionsType = { show: false, width: 1150, height: 870, - ...getWindowBounds(), + ...windowBounds, webPreferences: { nodeIntegration: isTest, webviewTag: false, @@ -76,13 +77,7 @@ export const createMainWindow = ( if (event.sender !== window.webContents) return; window.close(); }); - - if (isDev) { - window.loadURL(`http://localhost:8080`); - } else { - window.loadURL(`file://${__dirname}/../renderer/index.html`); - } - + window.loadURL(`file://${__dirname}/../renderer/index.html`); window.on('page-title-updated', (event) => { event.preventDefault(); }); @@ -149,8 +144,6 @@ export const createMainWindow = ( * window constructor above was buggy (height was not correctly applied) */ window.on('ready-to-show', () => { - const windowBounds = getWindowBounds(); - if (windowBounds) { window.setBounds(windowBounds); } diff --git a/source/renderer/app/App.tsx b/source/renderer/app/App.tsx index d7f7433253..d89ec81216 100755 --- a/source/renderer/app/App.tsx +++ b/source/renderer/app/App.tsx @@ -19,6 +19,9 @@ import { DIALOGS } from '../../common/ipc/constants'; import type { StoresMap } from './stores/index'; import type { ActionsMap } from './actions/index'; import NewsFeedContainer from './containers/news/NewsFeedContainer'; +import ToggleRTSFlagsDialogContainer from './containers/knownIssues/ToggleRTSFlagsDialogContainer'; +import RTSFlagsRecommendationOverlayContainer from './containers/knownIssues/RTSFlagsRecommendationOverlayContainer'; +import { MenuUpdater } from './containers/MenuUpdater'; @observer class App extends Component<{ @@ -43,7 +46,7 @@ class App extends Component<{ const themeVars = require(`./themes/daedalus/${currentTheme}.ts`).default; - const { ABOUT, DAEDALUS_DIAGNOSTICS } = DIALOGS; + const { ABOUT, DAEDALUS_DIAGNOSTICS, TOGGLE_RTS_FLAGS_MODE } = DIALOGS; const canShowNews = !isSetupPage && // Active page is not "Language Selection" or "Terms of Use" !isNodeStopping && // Daedalus is not shutting down @@ -59,6 +62,7 @@ class App extends Component<{ {/* @ts-ignore ts-migrate(2769) FIXME: No overload matches this call. */} + ), - , + // @ts-ignore ts-migrate(2345) FIXME: Argument of type 'string' is not assignable to par... Remove this comment to see the full error message + isActiveDialog(TOGGLE_RTS_FLAGS_MODE) && ( + + ), ]} + + {canShowNews && [ , , diff --git a/source/renderer/app/actions/app-actions.ts b/source/renderer/app/actions/app-actions.ts index 50610aab49..952fff8903 100644 --- a/source/renderer/app/actions/app-actions.ts +++ b/source/renderer/app/actions/app-actions.ts @@ -14,4 +14,6 @@ export default class AppActions { // Daedalus Diagnostics dialog actions closeDaedalusDiagnosticsDialog: Action = new Action(); openDaedalusDiagnosticsDialog: Action = new Action(); + closeToggleRTSFlagsModeDialog: Action = new Action(); + openToggleRTSFlagsModeDialog: Action = new Action(); } diff --git a/source/renderer/app/actions/assets-actions.ts b/source/renderer/app/actions/assets-actions.ts index 3ce30aff9f..8767768b69 100644 --- a/source/renderer/app/actions/assets-actions.ts +++ b/source/renderer/app/actions/assets-actions.ts @@ -2,14 +2,14 @@ import Action from './lib/Action'; import type { AssetToken } from '../api/assets/types'; // ======= ASSETS ACTIONS ======= export default class AssetsActions { - onAssetSettingsOpen: Action<{ + setEditedAsset: Action<{ asset: AssetToken; }> = new Action(); onAssetSettingsSubmit: Action<{ asset: AssetToken; decimals: number; }> = new Action(); - onAssetSettingsCancel: Action = new Action(); + unsetEditedAsset: Action = new Action(); onOpenAssetSend: Action<{ uniqueId: string; }> = new Action(); diff --git a/source/renderer/app/actions/lib/Action.ts b/source/renderer/app/actions/lib/Action.ts index 34e1fbef6f..79b7aac220 100644 --- a/source/renderer/app/actions/lib/Action.ts +++ b/source/renderer/app/actions/lib/Action.ts @@ -30,7 +30,7 @@ export default class Action { this.listeners.push(listener); } - trigger(params: Params) { + trigger(params?: Params) { this.listeners.forEach((listener) => listener(params)); } diff --git a/source/renderer/app/actions/network-status-actions.ts b/source/renderer/app/actions/network-status-actions.ts index 1daff53f4d..224c73c2c9 100644 --- a/source/renderer/app/actions/network-status-actions.ts +++ b/source/renderer/app/actions/network-status-actions.ts @@ -7,4 +7,5 @@ export default class NetworkStatusActions { toggleSplash: Action = new Action(); copyStateDirectoryPath: Action = new Action(); forceCheckNetworkClock: Action = new Action(); + toggleRTSFlagsMode: Action = new Action(); } diff --git a/source/renderer/app/actions/profile-actions.ts b/source/renderer/app/actions/profile-actions.ts index 8599f0015f..5732ac47e4 100644 --- a/source/renderer/app/actions/profile-actions.ts +++ b/source/renderer/app/actions/profile-actions.ts @@ -20,4 +20,5 @@ export default class ProfileActions { theme: string; }> = new Action(); finishInitialScreenSettings: Action = new Action(); + acknowledgeRTSModeRecommendation: Action = new Action(); } diff --git a/source/renderer/app/actions/wallets-actions.ts b/source/renderer/app/actions/wallets-actions.ts index d05dc7795c..ed4149038b 100644 --- a/source/renderer/app/actions/wallets-actions.ts +++ b/source/renderer/app/actions/wallets-actions.ts @@ -60,6 +60,7 @@ export default class WalletsActions { passphrase: string; assets?: Array; assetsAmounts?: Array; + hasAssetsRemainingAfterTransaction?: boolean; }> = new Action(); chooseWalletExportType: Action<{ walletExportType: WalletExportTypeChoices; diff --git a/source/renderer/app/api/api.ts b/source/renderer/app/api/api.ts index f8a6d25c33..a4b54f5217 100644 --- a/source/renderer/app/api/api.ts +++ b/source/renderer/app/api/api.ts @@ -40,6 +40,7 @@ import { getPublicKey } from './transactions/requests/getPublicKey'; import { getICOPublicKey } from './transactions/requests/getICOPublicKey'; // Voting requests import { createWalletSignature } from './voting/requests/createWalletSignature'; +import { getCatalystFund } from './voting/requests/getCatalystFund'; // Wallets requests import { updateSpendingPassword } from './wallets/requests/updateSpendingPassword'; import { updateByronSpendingPassword } from './wallets/requests/updateByronSpendingPassword'; @@ -84,6 +85,7 @@ import { cardanoFaultInjectionChannel } from '../ipc/cardano.ipc'; import patchAdaApi from './utils/patchAdaApi'; import { getLegacyWalletId, utcStringToDate } from './utils'; import { logger } from '../utils/logging'; +import { hexToString } from '../utils/strings'; import { unscrambleMnemonics, scrambleMnemonics, @@ -126,7 +128,7 @@ import type { GetNetworkParametersApiResponse, } from './network/types'; // Transactions Types -import type { +import { Transaction, TransactionFee, TransactionWithdrawals, @@ -148,7 +150,7 @@ import type { ICOPublicKeyParams, } from './transactions/types'; // Wallets Types -import type { +import { AdaWallet, AdaWallets, CreateHardwareWalletRequest, @@ -203,6 +205,8 @@ import type { import type { CreateVotingRegistrationRequest, CreateWalletSignatureRequest, + GetCatalystFundResponse, + CatalystFund, } from './voting/types'; import type { StakePoolProps } from '../domains/StakePool'; import type { FaultInjectionIpcRequest } from '../../../common/types/cardano-node.types'; @@ -211,7 +215,7 @@ import { getSHA256HexForString } from './utils/hashing'; import { getNewsHash } from './news/requests/getNewsHash'; import { deleteTransaction } from './transactions/requests/deleteTransaction'; import { WALLET_BYRON_KINDS } from '../config/walletRestoreConfig'; -import ApiError from '../domains/ApiError'; +import ApiError, { ErrorType } from '../domains/ApiError'; import { formattedAmountToLovelace } from '../utils/formatters'; import type { GetAssetsRequest, @@ -223,11 +227,12 @@ import type { AssetLocalData } from './utils/localStorage'; import Asset from '../domains/Asset'; import { getAssets } from './assets/requests/getAssets'; import { getAccountPublicKey } from './wallets/requests/getAccountPublicKey'; +import { doesWalletRequireAdaToRemainToSupportTokens } from './utils/apiHelpers'; export default class AdaApi { config: RequestConfig; // We need to preserve all asset metadata during single runtime in order - // to avoid losing it in case of Token Metadata Registry server unvailability + // to avoid losing it in case of Token Metadata Registry server unavailability storedAssetMetadata: StoredAssetMetadata = {}; constructor(isTest: boolean, config: RequestConfig) { @@ -240,13 +245,13 @@ export default class AdaApi { } getWallets = async (): Promise> => { - // @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. logger.debug('AdaApi::getWallets called'); - const { getHardwareWalletLocalData, getHardwareWalletsLocalData } = - global.daedalus.api.localStorage; + const { getHardwareWalletsLocalData } = global.daedalus.api.localStorage; try { - const wallets: AdaWallets = await getWallets(this.config); + const wallets: Array = await getWallets( + this.config + ); const legacyWallets: LegacyAdaWallets = await getLegacyWallets( this.config ); @@ -267,14 +272,15 @@ export default class AdaApi { }, isLegacy: true, }; - // @ts-ignore ts-migrate(2345) FIXME: Argument of type '{ address_pool_gap: number; dele... Remove this comment to see the full error message wallets.push({ ...legacyAdaWallet, ...extraLegacyWalletProps }); }); // @TODO - Remove this once we get hardware wallet flag from WBE return await Promise.all( - wallets.map(async (wallet) => { + wallets.map(async (wallet: AdaWallet) => { const { id } = wallet; - const walletData = await getHardwareWalletLocalData(id); + + const walletData = hwLocalData[id]; + return _createWalletFromServerData({ ...wallet, isHardwareWallet: @@ -453,18 +459,19 @@ export default class AdaApi { parameters: request, }); const { walletId, order, fromDate, toDate, isLegacy } = request; + const params = Object.assign( {}, { order: order || 'descending', + }, + !!fromDate && { + start: `${moment.utc(fromDate).format('YYYY-MM-DDTHH:mm:ss')}Z`, + }, + !!toDate && { + end: `${moment.utc(toDate).format('YYYY-MM-DDTHH:mm:ss')}Z`, } ); - if (fromDate) - // @ts-ignore ts-migrate(2339) FIXME: Property 'start' does not exist on type '{ order: ... Remove this comment to see the full error message - params.start = `${moment.utc(fromDate).format('YYYY-MM-DDTHH:mm:ss')}Z`; - if (toDate) - // @ts-ignore ts-migrate(2339) FIXME: Property 'end' does not exist on type '{ order: "a... Remove this comment to see the full error message - params.end = `${moment.utc(toDate).format('YYYY-MM-DDTHH:mm:ss')}Z`; try { let response; @@ -832,7 +839,9 @@ export default class AdaApi { } }; createTransaction = async ( - request: CreateTransactionRequest + request: CreateTransactionRequest & { + hasAssetsRemainingAfterTransaction: boolean; + } ): Promise => { logger.debug('AdaApi::createTransaction called', { parameters: filterLogData(request), @@ -845,6 +854,7 @@ export default class AdaApi { isLegacy, assets, withdrawal = TransactionWithdrawal, + hasAssetsRemainingAfterTransaction, } = request; try { @@ -884,7 +894,8 @@ export default class AdaApi { logger.error('AdaApi::createTransaction error', { error, }); - throw new ApiError(error) + + const apiError = new ApiError(error) .set('wrongEncryptionPassphrase') .where('code', 'bad_request') .inc('message', 'passphrase is too short') @@ -892,8 +903,18 @@ export default class AdaApi { linkLabel: 'tooBigTransactionErrorLinkLabel', linkURL: 'tooBigTransactionErrorLinkURL', }) - .where('code', 'transaction_is_too_big') - .result(); + .where('code', 'transaction_is_too_big'); + + const { requiresAdaToRemainToSupportNativeTokens, adaToRemain } = + doesWalletRequireAdaToRemainToSupportTokens( + error, + hasAssetsRemainingAfterTransaction + ); + if (requiresAdaToRemainToSupportNativeTokens) { + apiError.set('cannotLeaveWalletEmpty', true, { adaToRemain }); + } + + throw apiError.result(); } }; // For testing purpose ONLY @@ -1020,6 +1041,7 @@ export default class AdaApi { const { fee, minimumAda } = _createTransactionFeeFromServerData(response); const amountWithFee = formattedTxAmount.plus(fee); + // @ts-ignore ts-migrate(2304) FIXME: Cannot find name 'Array'. const isRewardsRedemptionRequest = Array.isArray(withdrawal); if (!isRewardsRedemptionRequest && amountWithFee.gt(walletBalance)) { @@ -1067,6 +1089,7 @@ export default class AdaApi { .set(notEnoughMoneyError, true) .where('code', 'not_enough_money') .set('utxoTooSmall', true, { + // @ts-ignore ts-migrate(2339) FIXME: Property 'exec' does not exist on type '{}'. minimumAda: get( /(Expected min coin value: +)([0-9]+.[0-9]+)/.exec(error.message), 2, @@ -1135,7 +1158,7 @@ export default class AdaApi { walletId, data, }); - // @TODO - handle CHANGE paramete on smarter way and change corresponding downstream logic + // @TODO - handle CHANGE parameter on smarter way and change corresponding downstream logic const outputs = concat(response.outputs, response.change); // Calculate fee from inputs and outputs const inputsData = []; @@ -1145,7 +1168,7 @@ export default class AdaApi { let totalOutputs = new BigNumber(0); map(response.inputs, (input) => { const inputAmount = new BigNumber(input.amount.quantity.toString()); - // @ts-ignore ts-migrate(2339) FIXME: Property 'assets' does not exist on type '{ addres... Remove this comment to see the full error message + // @ts-ignore ts-migrate(2339) FIXME: Property 'assets' does not exist on type 'unknown'... Remove this comment to see the full error message const inputAssets = map(input.assets, (asset) => ({ policyId: asset.policy_id, assetName: asset.asset_name, @@ -1160,11 +1183,12 @@ export default class AdaApi { derivationPath: input.derivation_path, assets: inputAssets, }; + // @ts-ignore ts-migrate(2339) FIXME: Property 'push' does not exist on type '{}'. inputsData.push(inputData); }); map(outputs, (output) => { const outputAmount = new BigNumber(output.amount.quantity.toString()); - // @ts-ignore ts-migrate(2339) FIXME: Property 'assets' does not exist on type '{ addres... Remove this comment to see the full error message + // @ts-ignore ts-migrate(2339) FIXME: Property 'assets' does not exist on type 'unknown'... Remove this comment to see the full error message const outputAssets = map(output.assets, (asset) => ({ policyId: asset.policy_id, assetName: asset.asset_name, @@ -1177,6 +1201,7 @@ export default class AdaApi { derivationPath: output.derivation_path || null, assets: outputAssets, }; + // @ts-ignore ts-migrate(2339) FIXME: Property 'push' does not exist on type '{}'. outputsData.push(outputData); }); @@ -1187,6 +1212,7 @@ export default class AdaApi { rewardAccountPath: certificate.reward_account_path, pool: certificate.pool || null, }; + // @ts-ignore ts-migrate(2339) FIXME: Property 'push' does not exist on type '{}'. certificatesData.push(certificateData); }); } @@ -1454,14 +1480,12 @@ export default class AdaApi { mnemonic.split(' ').length === ADA_CERTIFICATE_MNEMONIC_LENGTH; getWalletRecoveryPhrase(): Promise> { - // @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. logger.debug('AdaApi::getWalletRecoveryPhrase called'); try { const response: Promise> = new Promise((resolve) => resolve(generateAccountMnemonics(WALLET_RECOVERY_PHRASE_WORD_COUNT)) ); - // @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. logger.debug('AdaApi::getWalletRecoveryPhrase success'); return response; } catch (error) { @@ -1473,14 +1497,12 @@ export default class AdaApi { } getWalletCertificateAdditionalMnemonics(): Promise> { - // @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. logger.debug('AdaApi::getWalletCertificateAdditionalMnemonics called'); try { const response: Promise> = new Promise((resolve) => resolve(generateAdditionalMnemonics()) ); - // @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. logger.debug('AdaApi::getWalletCertificateAdditionalMnemonics success'); return response; } catch (error) { @@ -1494,7 +1516,6 @@ export default class AdaApi { getWalletCertificateRecoveryPhrase( request: GetWalletCertificateRecoveryPhraseRequest ): Promise> { - // @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. logger.debug('AdaApi::getWalletCertificateRecoveryPhrase called'); const { passphrase, input: scrambledInput } = request; @@ -1507,7 +1528,6 @@ export default class AdaApi { }) ) ); - // @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. logger.debug('AdaApi::getWalletCertificateRecoveryPhrase success'); return response; } catch (error) { @@ -1521,7 +1541,6 @@ export default class AdaApi { getWalletRecoveryPhraseFromCertificate( request: GetWalletRecoveryPhraseFromCertificateRequest ): Promise> { - // @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. logger.debug('AdaApi::getWalletRecoveryPhraseFromCertificate called'); const { passphrase, scrambledInput } = request; @@ -1530,7 +1549,6 @@ export default class AdaApi { passphrase, scrambledInput, }); - // @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. logger.debug('AdaApi::getWalletRecoveryPhraseFromCertificate success'); return Promise.resolve(response); } catch (error) { @@ -1904,7 +1922,7 @@ export default class AdaApi { logger.debug('AdaApi::restoreByronLedgerWallet success', { wallet, }); - // @ts-ignore ts-migrate(2345) FIXME: Argument of type '{ address_pool_gap: number; dele... Remove this comment to see the full error message + // @ts-ignore ts-migrate(2345) FIXME: Argument of type '{ address_pool_gap: number; delegation... Remove this comment to see the full error message return _createWalletFromServerData(wallet); } catch (error) { logger.error('AdaApi::restoreByronLedgerWallet error', { @@ -2093,7 +2111,6 @@ export default class AdaApi { }); } - // @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. logger.debug('AdaApi::updateSpendingPassword success'); return true; } catch (error) { @@ -2136,7 +2153,6 @@ export default class AdaApi { } }; getSmashSettings = async (): Promise => { - // @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. logger.debug('AdaApi::getSmashSettings called'); try { @@ -2194,10 +2210,9 @@ export default class AdaApi { ); if (!isSmashServerValid) { - const error = { + const error: ErrorType = { code: 'invalid_smash_server', }; - // @ts-ignore ts-migrate(2345) FIXME: Argument of type '{ code: string; }' is not assign... Remove this comment to see the full error message throw new ApiError(error); } @@ -2322,8 +2337,7 @@ export default class AdaApi { }); try { - // @ts-ignore ts-migrate(2322) FIXME: Type '[]' is not assignable to type 'Promise<[]>'. - const response: Promise<[]> = await exportWalletAsJSON(this.config, { + const response: [] = await exportWalletAsJSON(this.config, { walletId, filePath, }); @@ -2469,7 +2483,6 @@ export default class AdaApi { } }; testReset = async (): Promise => { - // @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. logger.debug('AdaApi::testReset called'); try { @@ -2483,7 +2496,6 @@ export default class AdaApi { }) ) ); - // @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. logger.debug('AdaApi::testReset success'); } catch (error) { logger.error('AdaApi::testReset error', { @@ -2493,7 +2505,6 @@ export default class AdaApi { } }; getNetworkInfo = async (): Promise => { - // @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. logger.debug('AdaApi::getNetworkInfo called'); try { @@ -2554,6 +2565,7 @@ export default class AdaApi { throw new ApiError(error); } }; + // @ts-ignore ts-migrate(2583) FIXME: Cannot find name 'Promise'. Do you need to change ... Remove this comment to see the full error message getNetworkClock = async ( isForceCheck: boolean ): Promise => { @@ -2582,6 +2594,7 @@ export default class AdaApi { throw new ApiError(error); } }; + // @ts-ignore ts-migrate(2583) FIXME: Cannot find name 'Promise'. Do you need to change ... Remove this comment to see the full error message getNetworkParameters = async (): Promise => { // @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. logger.debug('AdaApi::getNetworkParameters called'); @@ -2626,7 +2639,6 @@ export default class AdaApi { } }; getNews = async (): Promise => { - // @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. logger.debug('AdaApi::getNews called'); // Fetch news json let rawNews: string; @@ -2919,6 +2931,42 @@ export default class AdaApi { fakeStakePoolsJson: Array ) => void; setStakePoolsFetchingFailed: () => void; + getCatalystFund = async (): Promise => { + logger.debug('AdaApi::getCatalystFund called', {}); + + try { + const catalystFund = await getCatalystFund(); + + logger.debug('AdaApi::getCatalystFund success', { + catalystFund, + }); + + return { + current: { + number: catalystFund.id + 1, + startTime: new Date(catalystFund.fund_start_time), + endTime: new Date(catalystFund.fund_end_time), + resultsTime: new Date( + catalystFund.chain_vote_plans?.[0]?.chain_committee_end_time + ), + registrationSnapshotTime: new Date( + catalystFund.registration_snapshot_time + ), + }, + next: { + number: catalystFund.id + 2, + startTime: new Date(catalystFund.next_fund_start_time), + registrationSnapshotTime: new Date( + catalystFund.next_registration_snapshot_time + ), + }, + }; + } catch (error) { + logger.error('AdaApi::getCatalystFund error', { + error, + }); + } + }; } // ========== TRANSFORM SERVER DATA INTO FRONTEND MODELS ========= const _createWalletFromServerData = action( @@ -2984,6 +3032,7 @@ const _createWalletFromServerData = action( uniqueId, policyId, assetName, + assetNameASCII: hexToString(assetName), quantity: new BigNumber(quantity.toString()), }; }), @@ -2994,6 +3043,7 @@ const _createWalletFromServerData = action( uniqueId, policyId, assetName, + assetNameASCII: hexToString(assetName), quantity: new BigNumber(quantity.toString()), }; }), diff --git a/source/renderer/app/api/assets/types.ts b/source/renderer/app/api/assets/types.ts index 7c33c9de64..47a2d43f16 100644 --- a/source/renderer/app/api/assets/types.ts +++ b/source/renderer/app/api/assets/types.ts @@ -49,6 +49,7 @@ export type ApiTokens = Array; export type Token = { policyId: string; assetName: string; + assetNameASCII?: string; quantity: BigNumber; address?: string | null | undefined; uniqueId: string; diff --git a/source/renderer/app/api/staking/types.ts b/source/renderer/app/api/staking/types.ts index 1a6729c6b2..eee5b6db55 100644 --- a/source/renderer/app/api/staking/types.ts +++ b/source/renderer/app/api/staking/types.ts @@ -56,8 +56,11 @@ export type AdaApiStakePools = Array; export type Reward = { date?: string; wallet: string; - reward: BigNumber; + total: BigNumber; + unspent: BigNumber; rewardsAddress: string; + isRestoring: boolean; + syncingProgress: number; pool?: StakePool; }; export type EpochData = { diff --git a/source/renderer/app/api/transactions/requests/selectCoins.ts b/source/renderer/app/api/transactions/requests/selectCoins.ts index 2f50197548..dfdd898023 100644 --- a/source/renderer/app/api/transactions/requests/selectCoins.ts +++ b/source/renderer/app/api/transactions/requests/selectCoins.ts @@ -63,8 +63,8 @@ export type SelectCoinsResponseType = { export const selectCoins = ( config: RequestConfig, { walletId, data }: SelectCoinsRequestType -): Promise => - request( +): Promise => { + return request( { method: 'POST', path: `/v2/wallets/${walletId}/coin-selections/random`, @@ -73,3 +73,4 @@ export const selectCoins = ( {}, data ); +}; diff --git a/source/renderer/app/api/utils/apiHelpers.spec.ts b/source/renderer/app/api/utils/apiHelpers.spec.ts new file mode 100644 index 0000000000..07fedfa470 --- /dev/null +++ b/source/renderer/app/api/utils/apiHelpers.spec.ts @@ -0,0 +1,76 @@ +import { ErrorType } from '../../domains/ApiError'; +import { doesWalletRequireAdaToRemainToSupportTokens } from './apiHelpers'; + +describe('throw error if not enough Ada to support tokens', () => { + it('should not throw if error.code is not "cannot_cover_fee"', () => { + const error: ErrorType = { code: 'bad_request' }; + + expect(doesWalletRequireAdaToRemainToSupportTokens(error, true)).toEqual({ + requiresAdaToRemainToSupportNativeTokens: false, + }); + }); + it('should not throw error if error code is "cannot_cover_fee" but hasAssetsRemainingAfterTransaction is undefined', () => { + const error: ErrorType = { + message: + 'I cannot proceed with transaction, I need approximately 1.6 ada to proceed', + code: 'cannot_cover_fee', + }; + + expect(doesWalletRequireAdaToRemainToSupportTokens(error)).toEqual({ + requiresAdaToRemainToSupportNativeTokens: false, + }); + }); + it('should not throw error if error code is "cannot_cover_fee" but message does not match regex', () => { + const error: ErrorType = { + message: 'other message', + code: 'cannot_cover_fee', + }; + + expect(doesWalletRequireAdaToRemainToSupportTokens(error, true)).toEqual({ + requiresAdaToRemainToSupportNativeTokens: false, + }); + }); + it('should not throw error if error code is not "cannot_cover_fee" and message matches regex', () => { + const error: ErrorType = { + message: + 'I cannot proceed with transaction, I need approximately 1.6 ada to proceed', + code: 'bad_request', + }; + + expect(doesWalletRequireAdaToRemainToSupportTokens(error, true)).toEqual({ + requiresAdaToRemainToSupportNativeTokens: false, + }); + }); + it('should not throw if there are no tokens remaining in wallet after transaction', () => { + const error: ErrorType = { code: 'cannot_cover_fee' }; + + expect(doesWalletRequireAdaToRemainToSupportTokens(error, true)).toEqual({ + requiresAdaToRemainToSupportNativeTokens: false, + }); + }); + it('should throw if there are tokens remaining in wallet after transaction and error is "cannot_cover_fee" and round to 2 minimum ada', () => { + const error: ErrorType = { + message: + 'I am unable to finalize the transaction, as there is not enough ada available to pay for the fee and also pay for the minimum ada quantities of all change outputs. I need approximately 0.629344 ada to proceed. Try increasing your wallet balance or sending a smaller amount.', + code: 'cannot_cover_fee', + }; + + expect(doesWalletRequireAdaToRemainToSupportTokens(error, true)).toEqual({ + requiresAdaToRemainToSupportNativeTokens: true, + adaToRemain: 2, + }); + }); + + it('should throw if there are tokens remaining in wallet after transaction and error is "cannot_cover_fee" and round to 2 nearest whole value provided by error', () => { + const error: ErrorType = { + message: + 'I am unable to finalize the transaction, as there is not enough ada available to pay for the fee and also pay for the minimum ada quantities of all change outputs. I need approximately 2.629344 ada to proceed. Try increasing your wallet balance or sending a smaller amount.', + code: 'cannot_cover_fee', + }; + + expect(doesWalletRequireAdaToRemainToSupportTokens(error, true)).toEqual({ + requiresAdaToRemainToSupportNativeTokens: true, + adaToRemain: 3, + }); + }); +}); diff --git a/source/renderer/app/api/utils/apiHelpers.ts b/source/renderer/app/api/utils/apiHelpers.ts index 8e20197929..85f51f2927 100644 --- a/source/renderer/app/api/utils/apiHelpers.ts +++ b/source/renderer/app/api/utils/apiHelpers.ts @@ -1,4 +1,5 @@ import { ApiMethodNotYetImplementedError } from '../common/errors'; +import { ErrorType } from '../../domains/ApiError'; export const notYetImplemented = async () => new Promise((resolve, reject) => { @@ -21,3 +22,27 @@ export const testSync = (apiMethod: (...args: Array) => any) => { // helper code for deferring API call execution export const wait = (ms: number): Promise => new Promise((resolve) => setTimeout(resolve, ms)); +export const doesWalletRequireAdaToRemainToSupportTokens = ( + error: ErrorType, + hasAssetsRemainingAfterTransaction?: boolean +): { + requiresAdaToRemainToSupportNativeTokens: boolean; + adaToRemain?: number; +} => { + const adaToProceedRegex = new RegExp( + /.*I need approximately([\s\d.,]+)ada to proceed.*/ + ); + + if ( + error.code === 'cannot_cover_fee' && + hasAssetsRemainingAfterTransaction && + adaToProceedRegex.test(error.message) + ) { + const roundedAda = Math.ceil( + Number(error.message.replace(adaToProceedRegex, '$1')) + ); + const adaToRemain = roundedAda > 2 ? roundedAda : 2; + return { requiresAdaToRemainToSupportNativeTokens: true, adaToRemain }; + } + return { requiresAdaToRemainToSupportNativeTokens: false }; +}; diff --git a/source/renderer/app/api/utils/localStorage.ts b/source/renderer/app/api/utils/localStorage.ts index 70bf003c15..4951c37aba 100644 --- a/source/renderer/app/api/utils/localStorage.ts +++ b/source/renderer/app/api/utils/localStorage.ts @@ -306,12 +306,6 @@ export default class LocalStorageApi { assetLocalData, `${policyId}${assetName}` ); - getAssetSettingsDialogWasOpened = (): Promise => - LocalStorageApi.get(keys.ASSET_SETTINGS_DIALOG_WAS_OPENED, false); - setAssetSettingsDialogWasOpened = (): Promise => - LocalStorageApi.set(keys.ASSET_SETTINGS_DIALOG_WAS_OPENED, true); - unsetAssetSettingsDialogWasOpened = (): Promise => - LocalStorageApi.unset(keys.ASSET_SETTINGS_DIALOG_WAS_OPENED); getSmashServer = (): Promise => LocalStorageApi.get(keys.SMASH_SERVER); setSmashServer = (smashServerUrl: string): Promise => @@ -404,6 +398,12 @@ export default class LocalStorageApi { LocalStorageApi.unset(keys.HARDWARE_WALLET_DEVICES, deviceId); unsetHardwareWalletDevicesAll = async (): Promise => LocalStorageApi.unset(keys.HARDWARE_WALLET_DEVICES); + setStakePoolsListViewTooltip = async (visited: boolean): Promise => + LocalStorageApi.set(keys.STAKE_POOLS_LIST_VIEW_TOOLTIP, visited); + getStakePoolsListViewTooltip = async (): Promise => + LocalStorageApi.get(keys.STAKE_POOLS_LIST_VIEW_TOOLTIP, true); + unsetStakePoolsListViewTooltip = async (): Promise => + LocalStorageApi.unset(keys.STAKE_POOLS_LIST_VIEW_TOOLTIP); reset = async () => { await LocalStorageApi.reset(); }; diff --git a/source/renderer/app/api/utils/patchAdaApi.ts b/source/renderer/app/api/utils/patchAdaApi.ts index 313e017011..b25bfeaede 100644 --- a/source/renderer/app/api/utils/patchAdaApi.ts +++ b/source/renderer/app/api/utils/patchAdaApi.ts @@ -86,24 +86,29 @@ export default (api: AdaApi) => { } // Always mutate newsfeed target version to current app version - const newsFeedItems = map(testingNewsFeedData.items, (item) => ({ - ...item, - target: { - ...item.target, - daedalusVersion: item.target.daedalusVersion ? packageJsonVersion : '', - }, - })); + const newsFeedItems = map(testingNewsFeedData.items, (item) => { + return { + ...item, + target: { + ...item.target, + daedalusVersion: item.target.daedalusVersion + ? packageJsonVersion + : '', + }, + }; + }); TESTING_NEWSFEED_JSON = { ...testingNewsFeedData, items: newsFeedItems }; }; - api.getNews = (): Promise => - new Promise((resolve, reject) => { + api.getNews = (): Promise => { + return new Promise((resolve, reject) => { if (!TESTING_NEWSFEED_JSON) { reject(new Error('Unable to fetch news')); } else { resolve(TESTING_NEWSFEED_JSON); } }); + }; api.setTestingWallet = ( testingWalletData: Record, @@ -155,10 +160,12 @@ export default (api: AdaApi) => { LOCAL_TIME_DIFFERENCE = timeDifference; }; - api.getNetworkClock = async () => ({ - status: 'available', - offset: LOCAL_TIME_DIFFERENCE, - }); + api.getNetworkClock = async () => { + return { + status: 'available', + offset: LOCAL_TIME_DIFFERENCE, + }; + }; api.resetTestOverrides = () => { TESTING_WALLETS_DATA = {}; diff --git a/source/renderer/app/api/utils/requestV0.ts b/source/renderer/app/api/utils/requestV0.ts index 32db8fa75c..fae10c3225 100644 --- a/source/renderer/app/api/utils/requestV0.ts +++ b/source/renderer/app/api/utils/requestV0.ts @@ -39,7 +39,7 @@ function typedRequest( queryString = `?passphrase=${encryptedPassphrase}`; } - // Passphrase must be ommited from rest query params + // Passphrase must be omitted from rest query params queryParams = omit(queryParams, 'passphrase'); // @ts-ignore ts-migrate(2345) FIXME: Argument of type 'boolean' is not assignable to pa... Remove this comment to see the full error message diff --git a/source/renderer/app/api/voting/requests/getCatalystFund.ts b/source/renderer/app/api/voting/requests/getCatalystFund.ts new file mode 100644 index 0000000000..dd0efc55cd --- /dev/null +++ b/source/renderer/app/api/voting/requests/getCatalystFund.ts @@ -0,0 +1,11 @@ +import { externalRequest } from '../../utils/externalRequest'; +import { MAINNET_SERVICING_STATION_URL } from '../../../config/urlsConfig'; +import { GetCatalystFundResponse } from '../types'; + +export const getCatalystFund = (): Promise => + externalRequest({ + hostname: MAINNET_SERVICING_STATION_URL, + path: '/api/v0/fund', + method: 'GET', + protocol: 'https', + }); diff --git a/source/renderer/app/api/voting/types.ts b/source/renderer/app/api/voting/types.ts index 288409ff2d..ab15db9d65 100644 --- a/source/renderer/app/api/voting/types.ts +++ b/source/renderer/app/api/voting/types.ts @@ -27,3 +27,29 @@ export type SignatureParams = { passphrase: string; }; }; +export type GetCatalystFundResponse = { + id: number; + fund_end_time: string; + fund_name: string; + fund_start_time: string; + next_fund_start_time: string; + next_registration_snapshot_time: string; + registration_snapshot_time: string; + chain_vote_plans: Array<{ + chain_committee_end_time: string; + }>; +}; +export type CatalystFund = { + current: { + number: number; + startTime: Date; + endTime: Date; + resultsTime: Date; + registrationSnapshotTime: Date; + }; + next: { + number: number; + startTime: Date; + registrationSnapshotTime: Date; + }; +}; diff --git a/source/renderer/app/api/wallets/requests/createHardwareWallet.ts b/source/renderer/app/api/wallets/requests/createHardwareWallet.ts index 270ccf5241..8318736118 100644 --- a/source/renderer/app/api/wallets/requests/createHardwareWallet.ts +++ b/source/renderer/app/api/wallets/requests/createHardwareWallet.ts @@ -13,8 +13,8 @@ export const createHardwareWallet = ( }: { walletInitData: WalletInitData; } -): Promise => - request( +): Promise => { + return request( { method: 'POST', path: '/v2/wallets', @@ -23,3 +23,4 @@ export const createHardwareWallet = ( {}, walletInitData ); +}; diff --git a/source/renderer/app/assets/images/check-mark-universal.inline.svg b/source/renderer/app/assets/images/check-mark-universal.inline.svg new file mode 100644 index 0000000000..9def2a6cc5 --- /dev/null +++ b/source/renderer/app/assets/images/check-mark-universal.inline.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/source/renderer/app/assets/images/question-mark-universal.inline.svg b/source/renderer/app/assets/images/question-mark-universal.inline.svg new file mode 100644 index 0000000000..f764a39b03 --- /dev/null +++ b/source/renderer/app/assets/images/question-mark-universal.inline.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/source/renderer/app/assets/images/remove.inline.svg b/source/renderer/app/assets/images/remove.inline.svg new file mode 100644 index 0000000000..89d76e64ba --- /dev/null +++ b/source/renderer/app/assets/images/remove.inline.svg @@ -0,0 +1,3 @@ + + + diff --git a/source/renderer/app/assets/images/search.inline.svg b/source/renderer/app/assets/images/search.inline.svg index 0304c9c043..f83eb9153d 100644 --- a/source/renderer/app/assets/images/search.inline.svg +++ b/source/renderer/app/assets/images/search.inline.svg @@ -1,7 +1,7 @@ - - + + diff --git a/source/renderer/app/assets/images/sort-arrow.inline.svg b/source/renderer/app/assets/images/sort-arrow.inline.svg new file mode 100644 index 0000000000..4a70848e34 --- /dev/null +++ b/source/renderer/app/assets/images/sort-arrow.inline.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/source/renderer/app/assets/images/spinner-universal.inline.svg b/source/renderer/app/assets/images/spinner-universal.inline.svg new file mode 100644 index 0000000000..ed7a719506 --- /dev/null +++ b/source/renderer/app/assets/images/spinner-universal.inline.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/source/renderer/app/components/appUpdate/AppUpdateOverlay.scss b/source/renderer/app/components/appUpdate/AppUpdateOverlay.scss index 44ca406396..e3435d97db 100644 --- a/source/renderer/app/components/appUpdate/AppUpdateOverlay.scss +++ b/source/renderer/app/components/appUpdate/AppUpdateOverlay.scss @@ -1,5 +1,6 @@ @import '../../themes/mixins/link'; @import '../../themes/mixins/overlay-backdrop'; +@import '../../themes/mixins/layers.scss'; .component { align-items: center; @@ -14,7 +15,7 @@ position: fixed; top: 0; width: 100vw; - z-index: 22; + z-index: $dialog-z-index; @include overlay-backrop; .content { diff --git a/source/renderer/app/components/appUpdate/AppUpdateOverlay.tsx b/source/renderer/app/components/appUpdate/AppUpdateOverlay.tsx index b5d9fce077..e41bf0e22e 100644 --- a/source/renderer/app/components/appUpdate/AppUpdateOverlay.tsx +++ b/source/renderer/app/components/appUpdate/AppUpdateOverlay.tsx @@ -13,13 +13,10 @@ import { ButtonSkin } from 'react-polymorph/lib/skins/simple/ButtonSkin'; import { ButtonSpinnerSkin } from 'react-polymorph/lib/skins/simple/ButtonSpinnerSkin'; import ReactMarkdown from 'react-markdown'; import News from '../../domains/News'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './AppUpdateOverlay.scss' or it... Remove this comment to see the full error message import styles from './AppUpdateOverlay.scss'; import DialogCloseButton from '../widgets/DialogCloseButton'; import ProgressBarLarge from '../widgets/ProgressBarLarge'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../assets/images/link-ic.in... Remove this comment to see the full error message import externalLinkIcon from '../../assets/images/link-ic.inline.svg'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../assets/images/close-cros... Remove this comment to see the full error message import closeCrossThin from '../../assets/images/close-cross-thin.inline.svg'; const messages = defineMessages({ @@ -209,7 +206,6 @@ class AppUpdateOverlay extends Component { const buttonStyles = classnames([ styles.button, isButtonDisabled ? styles.disabled : null, - isWaitingToQuitDaedalus ? styles.installing : null, ]); const buttonLabel = isLinux ? messages.buttonInstallUpdateLabel diff --git a/source/renderer/app/components/assets/Asset.scss b/source/renderer/app/components/assets/Asset.scss index 7338123177..bc387f2631 100644 --- a/source/renderer/app/components/assets/Asset.scss +++ b/source/renderer/app/components/assets/Asset.scss @@ -75,6 +75,10 @@ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + + &.ascii { + color: var(--theme-tokens-list-header-text-color); + } } .metadata { diff --git a/source/renderer/app/components/assets/Asset.spec.tsx b/source/renderer/app/components/assets/Asset.spec.tsx new file mode 100644 index 0000000000..ad78d00306 --- /dev/null +++ b/source/renderer/app/components/assets/Asset.spec.tsx @@ -0,0 +1,71 @@ +import React from 'react'; +import BigNumber from 'bignumber.js'; +import { render, screen, cleanup } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import Asset from './Asset'; +import { TestDecorator } from '../../../../../tests/_utils/TestDecorator'; + +const assetWithMetadataName = { + policyId: 'policyId', + assetName: '54657374636f696e', + quantity: new BigNumber(1), + fingerprint: 'fingerprint', + metadata: { + name: 'Testcoin', + description: 'Test coin', + }, + uniqueId: 'uniqueId', + decimals: 1, + recommendedDecimals: null, +}; +const assetWitoutMetadataName = { + policyId: 'policyId', + assetName: '436f696e74657374', + quantity: new BigNumber(1), + fingerprint: 'fingerprint', + uniqueId: 'uniqueId', + decimals: 1, + recommendedDecimals: null, +}; +const assetWithoutName = { + policyId: 'policyId', + assetName: '', + quantity: new BigNumber(1), + fingerprint: 'fingerprint', + uniqueId: 'uniqueId', + decimals: 1, + recommendedDecimals: null, +}; + +describe('Asset', () => { + afterEach(cleanup); + + test('Should display asset metadata name', () => { + render( + + + + ); + expect(screen.queryByTestId('assetName')).toHaveTextContent('Testcoin'); + }); + + test('Should display asset ASCII name when metadata name is not available', () => { + render( + + + + ); + expect(screen.queryByTestId('assetName')).toHaveTextContent( + 'ASCII: Cointest' + ); + }); + + test('Should not display asset name when metadata and ASCII name are not available', () => { + render( + + + + ); + expect(screen.queryByTestId('assetName')).toBeNull(); + }); +}); diff --git a/source/renderer/app/components/assets/Asset.tsx b/source/renderer/app/components/assets/Asset.tsx index da60f6f71b..7a252b4fe2 100644 --- a/source/renderer/app/components/assets/Asset.tsx +++ b/source/renderer/app/components/assets/Asset.tsx @@ -4,13 +4,10 @@ import classnames from 'classnames'; import { PopOver } from 'react-polymorph/lib/components/PopOver'; import { defineMessages, intlShape } from 'react-intl'; import { observer } from 'mobx-react'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './Asset.scss' or its correspon... Remove this comment to see the full error message import styles from './Asset.scss'; -import { ellipsis } from '../../utils/strings'; +import { ellipsis, hexToString } from '../../utils/strings'; import AssetContent from './AssetContent'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../assets/images/asset-toke... Remove this comment to see the full error message import settingsIcon from '../../assets/images/asset-token-settings-ic.inline.svg'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../assets/images/asset-toke... Remove this comment to see the full error message import warningIcon from '../../assets/images/asset-token-warning-ic.inline.svg'; import { ASSET_TOKEN_DISPLAY_DELAY } from '../../config/timingConfig'; import type { Asset as AssetProps } from '../../api/assets/types'; @@ -171,18 +168,25 @@ class Asset extends Component { const { asset, metadataNameChars, - small, + small = true, fullFingerprint, hasWarning, hasError, } = this.props; - const { fingerprint, metadata, decimals, recommendedDecimals } = asset; - const { name } = metadata || {}; + const { fingerprint, metadata, decimals, recommendedDecimals, assetName } = + asset; + const hasMetadataName = !!metadata?.name; + const name = + metadata?.name || (assetName && `ASCII: ${hexToString(assetName)}`) || ''; + + const displayName = metadataNameChars + ? ellipsis(name, metadataNameChars) + : name; const contentStyles = classnames([ styles.pill, - small ? styles.small : null, hasError ? styles.error : null, ]); + const [startCharAmount, endCharAmount] = small ? [9, 4] : [12, 12]; let warningPopOverMessage; if (hasWarning) { @@ -195,11 +199,19 @@ class Asset extends Component { return (
- {fullFingerprint ? fingerprint : ellipsis(fingerprint || '', 9, 4)} + {fullFingerprint + ? fingerprint + : ellipsis(fingerprint || '', startCharAmount, endCharAmount)}
- {name && ( -
- {metadataNameChars ? ellipsis(name, metadataNameChars) : name} + {displayName && ( +
+ {displayName}
)} {hasWarning && ( @@ -210,7 +222,9 @@ class Asset extends Component { })} className={styles.warningIconWrapper} > - + + +
)} @@ -219,19 +233,12 @@ class Asset extends Component { } renderPillPopOverContainer = () => { - const { - asset, - onCopyAssetParam, - assetSettingsDialogWasOpened, - anyAssetWasHovered, - } = this.props; + const { asset, onCopyAssetParam } = this.props; const pillContent = this.renderPillContent(); const popOverContent = ( @@ -255,7 +262,6 @@ class Asset extends Component { '--rp-pop-over-box-shadow': '0 5px 20px 0 var(--theme-widgets-asset-token-box-shadow)', }} - contentClassName={styles.popOver} content={popOverContent} visible={isPillPopOverVisible} appendTo="parent" @@ -293,7 +299,6 @@ class Asset extends Component { return (
{assetId === 'assetName' && ( -
+
(ASCII: {hexToString(value)})
)} diff --git a/source/renderer/app/components/assets/AssetSettingsDialog.spec.tsx b/source/renderer/app/components/assets/AssetSettingsDialog.spec.tsx new file mode 100644 index 0000000000..045e6207e4 --- /dev/null +++ b/source/renderer/app/components/assets/AssetSettingsDialog.spec.tsx @@ -0,0 +1,41 @@ +import '@testing-library/jest-dom'; + +import React from 'react'; +import noop from 'lodash/noop'; +import { cleanup, screen, waitFor } from '@testing-library/react'; + +import createTestBed from 'tests/_utils/TestBed'; +import { + withDecimalPlacesToken, + zeroDecimalPlacesToken, +} from 'tests/mocks/asset'; + +import AssetSettingsDialog from './AssetSettingsDialog'; + +describe('AssetSettingsDialog', () => { + afterEach(() => cleanup()); + + it('should not show a warning when an asset is set to zero recommended decimal places', async () => { + createTestBed( + + ); + await waitFor(() => screen.getByText('Number of decimal places')); + expect(screen.queryByTestId('warning-icon')).not.toBeInTheDocument(); + }); + + it('should show a warning when an asset is not set to the recommended decimal places', async () => { + createTestBed( + + ); + await waitFor(() => screen.getByText('Number of decimal places')); + expect(screen.queryByTestId('warning-icon')).toBeInTheDocument(); + }); +}); diff --git a/source/renderer/app/components/assets/AssetSettingsDialog.tsx b/source/renderer/app/components/assets/AssetSettingsDialog.tsx index 704e6fc773..00e56c4a3e 100644 --- a/source/renderer/app/components/assets/AssetSettingsDialog.tsx +++ b/source/renderer/app/components/assets/AssetSettingsDialog.tsx @@ -8,17 +8,16 @@ import { defineMessages, intlShape, FormattedHTMLMessage } from 'react-intl'; import Asset from './Asset'; import DialogCloseButton from '../widgets/DialogCloseButton'; import Dialog from '../widgets/Dialog'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './AssetSettingsDialog.scss' or... Remove this comment to see the full error message import styles from './AssetSettingsDialog.scss'; import globalMessages from '../../i18n/global-messages'; import type { AssetToken } from '../../api/assets/types'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../assets/images/asset-toke... Remove this comment to see the full error message import warningIcon from '../../assets/images/asset-token-warning-ic.inline.svg'; import { DEFAULT_DECIMAL_PRECISION, MAX_DECIMAL_PRECISION, } from '../../config/assetsConfig'; import { DiscreetTokenWalletAmount } from '../../features/discreet-mode'; +import { isRecommendedDecimal } from '../wallet/tokens/wallet-token/helpers'; const messages = defineMessages({ title: { @@ -34,12 +33,12 @@ const messages = defineMessages({ }, formattedBalanceLabel: { id: 'assets.settings.dialog.formattedAmount.label', - defaultMessage: '!!!Unformated amount', + defaultMessage: '!!!Unformatted amount', description: '"formattedBalanceLabel" for the Asset settings dialog', }, unformattedBalanceLabel: { id: 'assets.settings.dialog.unformattedAmount.label', - defaultMessage: '!!!Formated amount', + defaultMessage: '!!!Formatted amount', description: '"unformattedBalanceLabel" for the Asset settings dialog', }, decimalPrecisionLabel: { @@ -137,7 +136,6 @@ class AssetSettingsDialog extends Component { const { decimals: savedDecimals, recommendedDecimals } = asset; const { decimals } = this.state; const hasSavedDecimals = typeof savedDecimals === 'number'; - const hasRecommendedDecimals = typeof recommendedDecimals === 'number'; const options = range(MAX_DECIMAL_PRECISION + 1).map((value) => ({ value, })); @@ -155,8 +153,12 @@ class AssetSettingsDialog extends Component { onClick: () => onSubmit(asset, decimals), }, ]; - const hasWarning = - hasRecommendedDecimals && savedDecimals !== recommendedDecimals; + + const hasWarning = isRecommendedDecimal({ + recommendedDecimals, + decimals: savedDecimals, + }); + let warningPopOverMessage; if (hasWarning) { @@ -169,7 +171,9 @@ class AssetSettingsDialog extends Component { } + subtitle={ + + } actions={actions} closeOnOverlayClick onClose={onCancel} @@ -200,7 +204,6 @@ class AssetSettingsDialog extends Component { + {hasValue && ( + + )} + + ); +}; + +export const WalletSearch = injectIntl(observer(WalletSearchComponent)); diff --git a/source/renderer/app/components/sidebar/wallets/WalletSortButton.scss b/source/renderer/app/components/sidebar/wallets/WalletSortButton.scss new file mode 100644 index 0000000000..4cba3c2c34 --- /dev/null +++ b/source/renderer/app/components/sidebar/wallets/WalletSortButton.scss @@ -0,0 +1,86 @@ +.walletSortButtonContainer { + position: relative; + + .walletSortButton { + align-items: center; + background-color: rgba( + var(--theme-sidebar-sort-button-background-color), + 0.1 + ); + color: var(--theme-sidebar-menu-add-button-text-color); + display: flex; + font-size: 11px; + height: 22px; + opacity: 0.3; + padding: 0 8px; + width: auto; + + &.walletSortButtonActive, + &:hover, + &:active { + opacity: 1; + } + + &.walletSortButtonActive { + background-color: rgba( + var(--theme-sidebar-sort-button-background-color), + 0.1 + ); + } + + &:hover { + background-color: rgba( + var(--theme-sidebar-sort-button-background-color), + 0.05 + ); + } + + &:active { + background-color: rgba( + var(--theme-sidebar-sort-button-background-color), + 0.12 + ); + } + } +} + +.walletSortButtonActive { + opacity: 1; +} + +.walletSortOrderArrowContainer { + align-items: center; + background-color: var(--theme-sidebar-menu-background-color); + + border-radius: 4px; + + display: flex; + height: 12px; + justify-content: center; + position: absolute; + right: -4px; + top: -2px; + + width: 12px; + z-index: 1; +} + +.walletSortOrderArrow { + align-items: center; + display: flex; + height: 100%; + justify-content: center; + width: 100%; + + > svg { + transform: scale(0.5); + + path { + stroke: var(--theme-sidebar-menu-add-button-text-color); + } + } +} + +.walletSortOrderArrowAsc { + transform: rotate(-180deg); +} diff --git a/source/renderer/app/components/sidebar/wallets/WalletSortButton.tsx b/source/renderer/app/components/sidebar/wallets/WalletSortButton.tsx new file mode 100644 index 0000000000..9fd5460449 --- /dev/null +++ b/source/renderer/app/components/sidebar/wallets/WalletSortButton.tsx @@ -0,0 +1,57 @@ +import React from 'react'; +import SVGInline from 'react-svg-inline'; +import classNames from 'classnames'; +import { Button } from 'react-polymorph/lib/components/Button'; +import { PopOver } from 'react-polymorph/lib/components/PopOver'; +import type { WalletSortOrderOptions } from '../../../types/sidebarTypes'; +import { WalletSortOrder } from '../../../types/sidebarTypes'; +// @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../../assets/images/sort-ar... Remove this comment to see the full error message +import arrow from '../../../assets/images/sort-arrow.inline.svg'; +import styles from './WalletSortButton.scss'; + +type Props = { + onClick: () => void; + label: string; + isActive: boolean; + sortOrder: WalletSortOrderOptions; + tooltip: string; +}; +export function WalletSortButton({ + onClick, + label, + isActive, + sortOrder, + tooltip, +}: Props) { + const walletSortButtonStyles = classNames([ + styles.walletSortButton, + isActive ? styles.walletSortButtonActive : null, + ]); + const walletSortOrderArrowStyles = classNames([ + styles.walletSortOrderArrowContainer, + sortOrder === WalletSortOrder.Asc ? styles.walletSortOrderArrowAsc : null, + ]); + return ( + +
+
+
+ ); +} diff --git a/source/renderer/app/components/staking/StakingUnavailable.tsx b/source/renderer/app/components/staking/StakingUnavailable.tsx index 8da2b1c351..d511ff0f9c 100644 --- a/source/renderer/app/components/staking/StakingUnavailable.tsx +++ b/source/renderer/app/components/staking/StakingUnavailable.tsx @@ -4,7 +4,6 @@ import { FormattedHTMLMessage } from 'react-intl'; import BigNumber from 'bignumber.js'; import globalMessages from '../../i18n/global-messages'; import LoadingSpinner from '../widgets/LoadingSpinner'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './StakingUnavailable.scss' or ... Remove this comment to see the full error message import styles from './StakingUnavailable.scss'; type Props = { diff --git a/source/renderer/app/components/staking/_stakingConfig.scss b/source/renderer/app/components/staking/_stakingConfig.scss index e358a562df..2e3b45d381 100644 --- a/source/renderer/app/components/staking/_stakingConfig.scss +++ b/source/renderer/app/components/staking/_stakingConfig.scss @@ -32,3 +32,9 @@ color: var(--theme-staking-font-color-light); font-family: var(--font-regular); } + +%stakePoolTicker { + font-size: 14px; + letter-spacing: -0.5px; + text-transform: uppercase; +} diff --git a/source/renderer/app/components/staking/countdown/StakingCountdown.tsx b/source/renderer/app/components/staking/countdown/StakingCountdown.tsx index f1cd67b8d9..feefd05565 100644 --- a/source/renderer/app/components/staking/countdown/StakingCountdown.tsx +++ b/source/renderer/app/components/staking/countdown/StakingCountdown.tsx @@ -3,7 +3,6 @@ import { observer } from 'mobx-react'; import { defineMessages, intlShape } from 'react-intl'; import { ButtonSkin } from 'react-polymorph/lib/skins/simple/ButtonSkin'; import CountdownWidget from '../../widgets/CountdownWidget'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './StakingCountdown.scss' or it... Remove this comment to see the full error message import styles from './StakingCountdown.scss'; import ButtonLink from '../../widgets/ButtonLink'; diff --git a/source/renderer/app/components/staking/delegation-center/DelegationCenterBody.tsx b/source/renderer/app/components/staking/delegation-center/DelegationCenterBody.tsx index a6cac6115e..22c8c611c8 100644 --- a/source/renderer/app/components/staking/delegation-center/DelegationCenterBody.tsx +++ b/source/renderer/app/components/staking/delegation-center/DelegationCenterBody.tsx @@ -5,7 +5,6 @@ import { defineMessages, intlShape } from 'react-intl'; import { get } from 'lodash'; import Wallet from '../../../domains/Wallet'; import WalletRow from './WalletRow'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './DelegationCenterBody.scss' o... Remove this comment to see the full error message import styles from './DelegationCenterBody.scss'; import LoadingSpinner from '../../widgets/LoadingSpinner'; import type { FutureEpoch, NextEpoch } from '../../../api/network/types'; @@ -91,14 +90,14 @@ class DelegationCenterBody extends Component { return (
{isLoading ? ( -
+

{intl.formatMessage(messages.loadingStakePoolsMessage)}

{loadingSpinner}
) : (
-
{title}
+
{title}
{nextEpochNumber && futureEpochNumber && (
{currentEpochTitle} diff --git a/source/renderer/app/components/staking/delegation-center/DelegationCenterHeader.tsx b/source/renderer/app/components/staking/delegation-center/DelegationCenterHeader.tsx index 565b6bd946..0392df3b70 100644 --- a/source/renderer/app/components/staking/delegation-center/DelegationCenterHeader.tsx +++ b/source/renderer/app/components/staking/delegation-center/DelegationCenterHeader.tsx @@ -2,7 +2,6 @@ import React, { Component, Fragment } from 'react'; import { observer } from 'mobx-react'; import { defineMessages, intlShape } from 'react-intl'; import { get } from 'lodash'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './DelegationCenterHeader.scss'... Remove this comment to see the full error message import styles from './DelegationCenterHeader.scss'; import CountdownWidget from '../../widgets/CountdownWidget'; import humanizeDurationByLocale from '../../../utils/humanizeDurationByLocale'; diff --git a/source/renderer/app/components/staking/delegation-center/DelegationCenterNoWallets.tsx b/source/renderer/app/components/staking/delegation-center/DelegationCenterNoWallets.tsx index 5dc9187f96..ad82c18e0d 100644 --- a/source/renderer/app/components/staking/delegation-center/DelegationCenterNoWallets.tsx +++ b/source/renderer/app/components/staking/delegation-center/DelegationCenterNoWallets.tsx @@ -4,7 +4,6 @@ import SVGInline from 'react-svg-inline'; import BigNumber from 'bignumber.js'; import { Button } from 'react-polymorph/lib/components/Button'; import { ButtonSkin } from 'react-polymorph/lib/skins/simple/ButtonSkin'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './DelegationCenterNoWallets.sc... Remove this comment to see the full error message import styles from './DelegationCenterNoWallets.scss'; // @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../../assets/images/attenti... Remove this comment to see the full error message import icon from '../../../assets/images/attention-big-thin.inline.svg'; diff --git a/source/renderer/app/components/staking/delegation-center/DonutRing.tsx b/source/renderer/app/components/staking/delegation-center/DonutRing.tsx index ab89fa74c0..1496e76682 100644 --- a/source/renderer/app/components/staking/delegation-center/DonutRing.tsx +++ b/source/renderer/app/components/staking/delegation-center/DonutRing.tsx @@ -1,6 +1,5 @@ import React, { Component } from 'react'; import { observer } from 'mobx-react'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './DonutRing.scss' or its corre... Remove this comment to see the full error message import styles from './DonutRing.scss'; type Props = { diff --git a/source/renderer/app/components/staking/delegation-center/DropdownMenu.tsx b/source/renderer/app/components/staking/delegation-center/DropdownMenu.tsx index 1cff80964c..93440c8942 100644 --- a/source/renderer/app/components/staking/delegation-center/DropdownMenu.tsx +++ b/source/renderer/app/components/staking/delegation-center/DropdownMenu.tsx @@ -1,7 +1,6 @@ import React, { Component } from 'react'; import { observer } from 'mobx-react'; import { Dropdown } from 'react-polymorph/lib/components/Dropdown'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './DropdownMenu.scss' or its co... Remove this comment to see the full error message import styles from './DropdownMenu.scss'; type Props = { diff --git a/source/renderer/app/components/staking/delegation-center/WalletRow.scss b/source/renderer/app/components/staking/delegation-center/WalletRow.scss index 984431e747..947a239407 100644 --- a/source/renderer/app/components/staking/delegation-center/WalletRow.scss +++ b/source/renderer/app/components/staking/delegation-center/WalletRow.scss @@ -114,8 +114,7 @@ .stakePoolTicker { @extend %accentText; - font-size: 14px; - letter-spacing: -0.5px; + @extend %stakePoolTicker; line-height: 1.57; } @@ -142,6 +141,7 @@ .stakePoolTicker { padding-left: 0; position: relative; + text-transform: uppercase; top: 0; transition: top 200ms ease-in-out; } diff --git a/source/renderer/app/components/staking/delegation-center/WalletRow.tsx b/source/renderer/app/components/staking/delegation-center/WalletRow.tsx index 289a5b6ae3..8551c5fc42 100644 --- a/source/renderer/app/components/staking/delegation-center/WalletRow.tsx +++ b/source/renderer/app/components/staking/delegation-center/WalletRow.tsx @@ -13,14 +13,12 @@ import { getColorFromRange, getSaturationColor } from '../../../utils/colors'; import adaIcon from '../../../assets/images/ada-symbol.inline.svg'; import { DECIMAL_PLACES_IN_ADA } from '../../../config/numbersConfig'; import { PoolPopOver } from '../widgets/PoolPopOver'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './WalletRow.scss' or its corre... Remove this comment to see the full error message import styles from './WalletRow.scss'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './WalletRowPopOverOverrides.sc... Remove this comment to see the full error message import popOverThemeOverrides from './WalletRowPopOverOverrides.scss'; import LoadingSpinner from '../../widgets/LoadingSpinner'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../../assets/images/collaps... Remove this comment to see the full error message +// @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../../assets/images/collapse... Remove this comment to see the full error message import arrow from '../../../assets/images/collapse-arrow.inline.svg'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../../assets/images/hardwar... Remove this comment to see the full error message +// @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../../assets/images/hardware... Remove this comment to see the full error message import hardwareWalletsIcon from '../../../assets/images/hardware-wallet/connect-ic.inline.svg'; import { IS_RANKING_DATA_AVAILABLE, @@ -321,9 +319,7 @@ class WalletRow extends Component { className={styles.stakePoolTile} ref={this.stakePoolFirstTileRef} > -
+
{delegatedStakePool ? (
{
{nextPendingDelegatedStakePoolId ? ( -
+
{nextPendingDelegatedStakePool ? (
{nextPendingDelegatedStakePool.ticker} diff --git a/source/renderer/app/components/staking/delegation-center/helpers.tsx b/source/renderer/app/components/staking/delegation-center/helpers.tsx index 3c4437ae2a..64b683189b 100644 --- a/source/renderer/app/components/staking/delegation-center/helpers.tsx +++ b/source/renderer/app/components/staking/delegation-center/helpers.tsx @@ -1,7 +1,6 @@ import { get } from 'lodash'; import React from 'react'; import SVGInline from 'react-svg-inline'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './DelegationCenterHeader.scss'... Remove this comment to see the full error message import styles from './DelegationCenterHeader.scss'; // @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../../assets/images/delimet... Remove this comment to see the full error message import delimeterIcon from '../../../assets/images/delimeter.inline.svg'; diff --git a/source/renderer/app/components/staking/delegation-setup-wizard/DelegationStepsChooseStakePoolDialog.messages.ts b/source/renderer/app/components/staking/delegation-setup-wizard/DelegationStepsChooseStakePoolDialog.messages.ts index a5a3267e38..8f5dd9b69b 100644 --- a/source/renderer/app/components/staking/delegation-setup-wizard/DelegationStepsChooseStakePoolDialog.messages.ts +++ b/source/renderer/app/components/staking/delegation-setup-wizard/DelegationStepsChooseStakePoolDialog.messages.ts @@ -40,14 +40,14 @@ export const getMessages = () => { delegatedStakePoolLabel: { id: 'staking.delegationSetup.chooseStakePool.step.dialog.delegatedStakePoolLabel', defaultMessage: - '!!!You are already delegating {selectedWalletName} wallet to [{selectedPoolTicker}] stake pool. If you wish to re-delegate your stake, please select a different pool.', + '!!!You are already delegating {selectedWalletName} wallet to [{selectedPoolTicker}] stake pool. If you wish to re-delegate your stake, please select a different pool.', description: '"You are already delegating to stake pool" label on the delegation setup "choose stake pool" dialog.', }, delegatedStakePoolNextLabel: { id: 'staking.delegationSetup.chooseStakePool.step.dialog.delegatedStakePoolNextLabel', defaultMessage: - '!!!You are already pending delegation {selectedWalletName} wallet to [{selectedPoolTicker}] stake pool. If you wish to re-delegate your stake, please select a different pool.', + '!!!You are already pending delegation {selectedWalletName} wallet to [{selectedPoolTicker}] stake pool. If you wish to re-delegate your stake, please select a different pool.', description: '"You are already delegating to stake pool" label on the delegation setup "choose stake pool" dialog.', }, diff --git a/source/renderer/app/components/staking/delegation-setup-wizard/DelegationStepsChooseStakePoolDialog.scss b/source/renderer/app/components/staking/delegation-setup-wizard/DelegationStepsChooseStakePoolDialog.scss index d5ad51d830..6ea895951e 100644 --- a/source/renderer/app/components/staking/delegation-setup-wizard/DelegationStepsChooseStakePoolDialog.scss +++ b/source/renderer/app/components/staking/delegation-setup-wizard/DelegationStepsChooseStakePoolDialog.scss @@ -1,3 +1,5 @@ +@import '../stakingConfig'; + .delegationStepsChooseStakePoolDialogWrapper { :global { .StakePoolThumbnail_content { @@ -19,6 +21,10 @@ .Dialog_contentWrapper::-webkit-scrollbar-track { margin-top: -8px; } + + .ticker { + text-transform: uppercase; + } } .stakePoolsListWrapper { @@ -85,11 +91,10 @@ --theme-delegation-steps-choose-stake-pool-selected-ticker-color ); font-family: var(--font-semibold); - font-size: 14px; - letter-spacing: -0.5px; line-height: 1.57; margin-top: 8px; text-align: center; + @extend %stakePoolTicker; } .icon { diff --git a/source/renderer/app/components/staking/delegation-setup-wizard/DelegationStepsChooseStakePoolDialog.tsx b/source/renderer/app/components/staking/delegation-setup-wizard/DelegationStepsChooseStakePoolDialog.tsx index a6b98d3c9d..9200224db4 100644 --- a/source/renderer/app/components/staking/delegation-setup-wizard/DelegationStepsChooseStakePoolDialog.tsx +++ b/source/renderer/app/components/staking/delegation-setup-wizard/DelegationStepsChooseStakePoolDialog.tsx @@ -24,9 +24,7 @@ import { StakePoolsList } from '../stake-pools/StakePoolsList'; import { StakePoolsSearch } from '../stake-pools/StakePoolsSearch'; import { getFilteredStakePoolsList } from '../stake-pools/helpers'; import BackToTopButton from '../../widgets/BackToTopButton'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './DelegationSteps.scss' or its... Remove this comment to see the full error message import commonStyles from './DelegationSteps.scss'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './DelegationStepsChooseStakePo... Remove this comment to see the full error message import styles from './DelegationStepsChooseStakePoolDialog.scss'; import Wallet from '../../../domains/Wallet'; import ThumbSelectedPool from '../widgets/ThumbSelectedPool'; diff --git a/source/renderer/app/components/staking/delegation-setup-wizard/DelegationStepsChooseWalletDialog.tsx b/source/renderer/app/components/staking/delegation-setup-wizard/DelegationStepsChooseWalletDialog.tsx index aa21c2e533..c2f7a5541b 100644 --- a/source/renderer/app/components/staking/delegation-setup-wizard/DelegationStepsChooseWalletDialog.tsx +++ b/source/renderer/app/components/staking/delegation-setup-wizard/DelegationStepsChooseWalletDialog.tsx @@ -8,7 +8,6 @@ import { import classNames from 'classnames'; import { Stepper } from 'react-polymorph/lib/components/Stepper'; import { StepperSkin } from 'react-polymorph/lib/skins/simple/StepperSkin'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './DelegationSteps.scss' or its... Remove this comment to see the full error message import commonStyles from './DelegationSteps.scss'; // @ts-ignore ts-migrate(2307) FIXME: Cannot find module './DelegationStepsChooseWalletD... Remove this comment to see the full error message import styles from './DelegationStepsChooseWalletDialog.scss'; diff --git a/source/renderer/app/components/staking/delegation-setup-wizard/DelegationStepsConfirmationDialog.tsx b/source/renderer/app/components/staking/delegation-setup-wizard/DelegationStepsConfirmationDialog.tsx index 5a817f002a..b3fff1a5e0 100644 --- a/source/renderer/app/components/staking/delegation-setup-wizard/DelegationStepsConfirmationDialog.tsx +++ b/source/renderer/app/components/staking/delegation-setup-wizard/DelegationStepsConfirmationDialog.tsx @@ -8,9 +8,7 @@ import { Stepper } from 'react-polymorph/lib/components/Stepper'; import { StepperSkin } from 'react-polymorph/lib/skins/simple/StepperSkin'; import { Input } from 'react-polymorph/lib/components/Input'; import { InputSkin } from 'react-polymorph/lib/skins/simple/InputSkin'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './DelegationSteps.scss' or its... Remove this comment to see the full error message import commonStyles from './DelegationSteps.scss'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './DelegationStepsConfirmationD... Remove this comment to see the full error message import styles from './DelegationStepsConfirmationDialog.scss'; import DialogCloseButton from '../../widgets/DialogCloseButton'; import DialogBackButton from '../../widgets/DialogBackButton'; @@ -51,13 +49,16 @@ type Props = { oversaturationPercentage: number; }; +interface FormFields { + spendingPassword: string; +} + @observer class DelegationStepsConfirmationDialog extends Component { static contextTypes = { intl: intlShape.isRequired, }; - form = new ReactToolboxMobxForm( - // @ts-ignore ts-migrate(2554) FIXME: Expected 0 arguments, but got 2. + form = new ReactToolboxMobxForm( { fields: { spendingPassword: { @@ -102,7 +103,6 @@ class DelegationStepsConfirmationDialog extends Component { } ); submit = () => { - // @ts-ignore ts-migrate(2339) FIXME: Property 'submit' does not exist on type 'ReactToo... Remove this comment to see the full error message this.form.submit({ onSuccess: (form) => { const { selectedWallet } = this.props; @@ -136,7 +136,6 @@ class DelegationStepsConfirmationDialog extends Component { const isHardwareWallet = get(selectedWallet, 'isHardwareWallet'); const selectedPoolTicker = get(selectedPool, 'ticker'); const selectedPoolId = get(selectedPool, 'id'); - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const spendingPasswordField = form.$('spendingPassword'); const buttonLabel = !isSubmitting ? ( intl.formatMessage(messages.confirmButtonLabel) @@ -278,7 +277,6 @@ class DelegationStepsConfirmationDialog extends Component {
) : ( { closeButton={} >
-

- {intl.formatMessage(messages.description)} -

+

{intl.formatMessage(messages.description)}

{/*
-
{ - // @ts-ignore ts-migrate(2540) FIXME: Cannot assign to 'current' because it is a read-on... Remove this comment to see the full error message - this.stakingPageRef.current = ref; - }} - > +
{children}
diff --git a/source/renderer/app/components/staking/legacy/BlockGenerationInfo.tsx b/source/renderer/app/components/staking/legacy/BlockGenerationInfo.tsx index ce03be865c..1450d906d1 100644 --- a/source/renderer/app/components/staking/legacy/BlockGenerationInfo.tsx +++ b/source/renderer/app/components/staking/legacy/BlockGenerationInfo.tsx @@ -1,6 +1,5 @@ import React, { Component } from 'react'; import { observer } from 'mobx-react'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './BlockGenerationInfo.scss' or... Remove this comment to see the full error message import styles from './BlockGenerationInfo.scss'; @observer diff --git a/source/renderer/app/components/staking/legacy/Staking.tsx b/source/renderer/app/components/staking/legacy/Staking.tsx index 2c81f13dee..28946c20b1 100644 --- a/source/renderer/app/components/staking/legacy/Staking.tsx +++ b/source/renderer/app/components/staking/legacy/Staking.tsx @@ -3,7 +3,6 @@ import { observer } from 'mobx-react'; import BlockGenerationInfo from './BlockGenerationInfo'; import StakingSwitch from './StakingSwitch'; import StakingSystemState from './StakingSystemState'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './Staking.scss' or its corresp... Remove this comment to see the full error message import styles from './Staking.scss'; @observer diff --git a/source/renderer/app/components/staking/legacy/StakingChart.tsx b/source/renderer/app/components/staking/legacy/StakingChart.tsx index e38d647d94..d5af81d92e 100644 --- a/source/renderer/app/components/staking/legacy/StakingChart.tsx +++ b/source/renderer/app/components/staking/legacy/StakingChart.tsx @@ -2,7 +2,6 @@ import React, { Component } from 'react'; import { observer } from 'mobx-react'; import { BarChart, Bar, YAxis, XAxis, Cell, ReferenceLine } from 'recharts'; import StakingChartTooltip from './StakingChartTooltip'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './StakingChart.scss' or its co... Remove this comment to see the full error message import styles from './StakingChart.scss'; class CustomReferenceLine extends ReferenceLine { diff --git a/source/renderer/app/components/staking/legacy/StakingChartTooltip.tsx b/source/renderer/app/components/staking/legacy/StakingChartTooltip.tsx index 6853c279b5..2d1642e85f 100644 --- a/source/renderer/app/components/staking/legacy/StakingChartTooltip.tsx +++ b/source/renderer/app/components/staking/legacy/StakingChartTooltip.tsx @@ -3,7 +3,6 @@ import { observer } from 'mobx-react'; import { defineMessages, intlShape } from 'react-intl'; import moment from 'moment'; import StakingChartTooltipItem from './StakingChartTooltipItem'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './StakingChartTooltip.scss' or... Remove this comment to see the full error message import styles from './StakingChartTooltip.scss'; const dateFormat = 'YYYY-MM-DD-HH:mm'; diff --git a/source/renderer/app/components/staking/legacy/StakingChartTooltipItem.tsx b/source/renderer/app/components/staking/legacy/StakingChartTooltipItem.tsx index 90fdd1e367..3511d58920 100644 --- a/source/renderer/app/components/staking/legacy/StakingChartTooltipItem.tsx +++ b/source/renderer/app/components/staking/legacy/StakingChartTooltipItem.tsx @@ -1,6 +1,5 @@ import React, { Component } from 'react'; import { observer } from 'mobx-react'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './StakingChartTooltipItem.scss... Remove this comment to see the full error message import styles from './StakingChartTooltipItem.scss'; type Props = { diff --git a/source/renderer/app/components/staking/legacy/StakingSwitch.tsx b/source/renderer/app/components/staking/legacy/StakingSwitch.tsx index 1db81907ea..4a297c3281 100644 --- a/source/renderer/app/components/staking/legacy/StakingSwitch.tsx +++ b/source/renderer/app/components/staking/legacy/StakingSwitch.tsx @@ -3,7 +3,6 @@ import { observer } from 'mobx-react'; import { Checkbox } from 'react-polymorph/lib/components/Checkbox'; import { SwitchSkin } from 'react-polymorph/lib/skins/simple/SwitchSkin'; import { IDENTIFIERS } from 'react-polymorph/lib/themes/API'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './StakingSwitch.scss' or its c... Remove this comment to see the full error message import styles from './StakingSwitch.scss'; type Props = { diff --git a/source/renderer/app/components/staking/legacy/StakingSystemState.tsx b/source/renderer/app/components/staking/legacy/StakingSystemState.tsx index 14d546d7d7..f25886ea2f 100644 --- a/source/renderer/app/components/staking/legacy/StakingSystemState.tsx +++ b/source/renderer/app/components/staking/legacy/StakingSystemState.tsx @@ -1,7 +1,6 @@ import React, { Component } from 'react'; import { observer } from 'mobx-react'; import StakingSystemStateElement from './StakingSystemStateElement'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './StakingSystemState.scss' or ... Remove this comment to see the full error message import styles from './StakingSystemState.scss'; @observer diff --git a/source/renderer/app/components/staking/legacy/StakingSystemStateElement.tsx b/source/renderer/app/components/staking/legacy/StakingSystemStateElement.tsx index 20182c953a..00ae1b7cd5 100644 --- a/source/renderer/app/components/staking/legacy/StakingSystemStateElement.tsx +++ b/source/renderer/app/components/staking/legacy/StakingSystemStateElement.tsx @@ -1,6 +1,5 @@ import React, { Component } from 'react'; import { observer } from 'mobx-react'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './StakingSystemStateElement.sc... Remove this comment to see the full error message import styles from './StakingSystemStateElement.scss'; type Props = { diff --git a/source/renderer/app/components/staking/redeem-itn-rewards/LoadingOverlay.tsx b/source/renderer/app/components/staking/redeem-itn-rewards/LoadingOverlay.tsx index 42ac8b938c..de5dbbc5fa 100644 --- a/source/renderer/app/components/staking/redeem-itn-rewards/LoadingOverlay.tsx +++ b/source/renderer/app/components/staking/redeem-itn-rewards/LoadingOverlay.tsx @@ -1,5 +1,4 @@ import React, { Component } from 'react'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './LoadingOverlay.scss' or its ... Remove this comment to see the full error message import styles from './LoadingOverlay.scss'; type Props = {}; diff --git a/source/renderer/app/components/staking/redeem-itn-rewards/NoWalletsDialog.tsx b/source/renderer/app/components/staking/redeem-itn-rewards/NoWalletsDialog.tsx index 391ebf0a64..bc168465a2 100644 --- a/source/renderer/app/components/staking/redeem-itn-rewards/NoWalletsDialog.tsx +++ b/source/renderer/app/components/staking/redeem-itn-rewards/NoWalletsDialog.tsx @@ -4,7 +4,6 @@ import { defineMessages, intlShape } from 'react-intl'; import SVGInline from 'react-svg-inline'; import DialogCloseButton from '../../widgets/DialogCloseButton'; import Dialog from '../../widgets/Dialog'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './NoWalletsDialog.scss' or its... Remove this comment to see the full error message import styles from './NoWalletsDialog.scss'; // @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../../assets/images/sad-wal... Remove this comment to see the full error message import sadWalletImage from '../../../assets/images/sad-wallet.inline.svg'; diff --git a/source/renderer/app/components/staking/redeem-itn-rewards/RedemptionUnavailableDialog.tsx b/source/renderer/app/components/staking/redeem-itn-rewards/RedemptionUnavailableDialog.tsx index 1cf330277f..00bbc11250 100644 --- a/source/renderer/app/components/staking/redeem-itn-rewards/RedemptionUnavailableDialog.tsx +++ b/source/renderer/app/components/staking/redeem-itn-rewards/RedemptionUnavailableDialog.tsx @@ -6,7 +6,6 @@ import globalMessages from '../../../i18n/global-messages'; import DialogCloseButton from '../../widgets/DialogCloseButton'; import Dialog from '../../widgets/Dialog'; import LoadingSpinner from '../../widgets/LoadingSpinner'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './RedemptionUnavailableDialog.... Remove this comment to see the full error message import styles from './RedemptionUnavailableDialog.scss'; // @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../../assets/images/close-c... Remove this comment to see the full error message import closeCrossThin from '../../../assets/images/close-cross-thin.inline.svg'; diff --git a/source/renderer/app/components/staking/redeem-itn-rewards/Step1ConfigurationDialog.scss b/source/renderer/app/components/staking/redeem-itn-rewards/Step1ConfigurationDialog.scss index f151ee4ef1..c74e7a7a66 100644 --- a/source/renderer/app/components/staking/redeem-itn-rewards/Step1ConfigurationDialog.scss +++ b/source/renderer/app/components/staking/redeem-itn-rewards/Step1ConfigurationDialog.scss @@ -38,20 +38,6 @@ margin-bottom: 17px; } - .walletsDropdown { - &.disabled { - pointer-events: none; - :global { - .SelectOverrides_selectInput:after { - opacity: 0.5; - } - .SimpleFormField_inputWrapper { - opacity: 0.5; - } - } - } - } - .checkbox { margin-bottom: 20px; diff --git a/source/renderer/app/components/staking/redeem-itn-rewards/Step1ConfigurationDialog.tsx b/source/renderer/app/components/staking/redeem-itn-rewards/Step1ConfigurationDialog.tsx index 35e4769fef..bfdbe289d6 100644 --- a/source/renderer/app/components/staking/redeem-itn-rewards/Step1ConfigurationDialog.tsx +++ b/source/renderer/app/components/staking/redeem-itn-rewards/Step1ConfigurationDialog.tsx @@ -24,7 +24,6 @@ import { import DialogCloseButton from '../../widgets/DialogCloseButton'; import WalletsDropdown from '../../widgets/forms/WalletsDropdown'; import Dialog from '../../widgets/Dialog'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './Step1ConfigurationDialog.scs... Remove this comment to see the full error message import styles from './Step1ConfigurationDialog.scss'; import ReactToolboxMobxForm from '../../../utils/ReactToolboxMobxForm'; import globalMessages from '../../../i18n/global-messages'; @@ -143,8 +142,19 @@ type Props = { wallets: Array; }; +type State = { + wasRecoveryPhraseValidAtLeastOnce: boolean; +}; + +interface FormFields { + checkboxAcceptance1: string; + checkboxAcceptance2: string; + walletsDropdown: string; + recoveryPhrase: string; +} + @observer -class Step1ConfigurationDialog extends Component { +class Step1ConfigurationDialog extends Component { static contextTypes = { intl: intlShape.isRequired, }; @@ -152,9 +162,13 @@ class Step1ConfigurationDialog extends Component { error: null, recoveryPhrase: [], }; + + state = { + wasRecoveryPhraseValidAtLeastOnce: false, + }; + recoveryPhraseAutocomplete: Autocomplete; - form = new ReactToolboxMobxForm( - // @ts-ignore ts-migrate(2554) FIXME: Expected 0 arguments, but got 2. + form = new ReactToolboxMobxForm( { fields: { recoveryPhrase: { @@ -172,6 +186,16 @@ class Step1ConfigurationDialog extends Component { this.context.intl.formatMessage(messages.invalidRecoveryPhrase), ], }), + hooks: { + onChange: (field) => { + if ( + this.state.wasRecoveryPhraseValidAtLeastOnce === false && + field.isValid + ) { + this.setState({ wasRecoveryPhraseValidAtLeastOnce: true }); + } + }, + }, }, walletsDropdown: { type: 'select', @@ -198,7 +222,6 @@ class Step1ConfigurationDialog extends Component { } ); submit = () => { - // @ts-ignore ts-migrate(2339) FIXME: Property 'submit' does not exist on type 'ReactToo... Remove this comment to see the full error message this.form.submit({ onSuccess: () => this.props.onContinue(), }); @@ -207,11 +230,9 @@ class Step1ConfigurationDialog extends Component { get canSubmit() { const { isCalculatingReedemFees, wallet, error } = this.props; const { form } = this; - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const { checked: checkboxAcceptance1isChecked } = form.$( 'checkboxAcceptance1' ); - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const { checked: checkboxAcceptance2isChecked } = form.$( 'checkboxAcceptance2' ); @@ -221,7 +242,6 @@ class Step1ConfigurationDialog extends Component { !error && checkboxAcceptance1isChecked && checkboxAcceptance2isChecked && - // @ts-ignore ts-migrate(2339) FIXME: Property 'isValid' does not exist on type 'ReactTo... Remove this comment to see the full error message form.isValid ); } @@ -270,29 +290,21 @@ class Step1ConfigurationDialog extends Component { />

); - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const recoveryPhraseField = form.$('recoveryPhrase'); - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const walletsDropdownField = form.$('walletsDropdown'); - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const checkboxAcceptance1Field = form.$('checkboxAcceptance1'); - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const checkboxAcceptance2Field = form.$('checkboxAcceptance2'); const walletId = get(wallet, 'id', null); - const validRecoveryPhase = recoveryPhraseField.isValid; - const buttonClasses = classnames([ - 'primary', - isCalculatingReedemFees ? styles.isSubmitting : null, - ]); - const walletsDropdownClasses = classnames([ - styles.walletsDropdown, - !validRecoveryPhase ? styles.disabled : null, - ]); + const walletsDropdownDisabled = !( + recoveryPhraseField.isValid || + this.state.wasRecoveryPhraseValidAtLeastOnce + ); + const actions = { direction: 'column', items: [ { - className: buttonClasses, + className: 'primary', disabled: !this.canSubmit, primary: true, label: intl.formatMessage(messages.continueButtonLabel), @@ -372,7 +384,6 @@ class Step1ConfigurationDialog extends Component { />
{ value={walletId} getStakePoolById={() => {}} errorPosition="bottom" + disabled={walletsDropdownDisabled} />
{ static contextTypes = { @@ -85,8 +88,7 @@ class Step2ConfirmationDialog extends Component { static defaultProps = { error: null, }; - form = new ReactToolboxMobxForm( - // @ts-ignore ts-migrate(2554) FIXME: Expected 0 arguments, but got 2. + form = new ReactToolboxMobxForm( { fields: { spendingPassword: { @@ -104,12 +106,14 @@ class Step2ConfirmationDialog extends Component { ), value: '', validators: [ - ({ field }) => [ - isValidSpendingPassword(field.value), - this.context.intl.formatMessage( - globalMessages.invalidSpendingPassword - ), - ], + ({ field }) => { + return [ + isValidSpendingPassword(field.value), + this.context.intl.formatMessage( + globalMessages.invalidSpendingPassword + ), + ]; + }, ], }, }, @@ -125,7 +129,6 @@ class Step2ConfirmationDialog extends Component { } ); submit = () => { - // @ts-ignore ts-migrate(2339) FIXME: Property 'submit' does not exist on type 'ReactToo... Remove this comment to see the full error message this.form.submit({ onSuccess: (form) => { const { spendingPassword } = form.values(); @@ -161,13 +164,11 @@ class Step2ConfirmationDialog extends Component { ? amount : transactionFees; const { name: walletName } = wallet; - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const spendingPasswordField = form.$('spendingPassword'); const actions = { direction: 'column', items: [ { - className: isSubmitting ? styles.isSubmitting : null, // @ts-ignore ts-migrate(2339) FIXME: Property 'isValid' does not exist on type 'ReactTo... Remove this comment to see the full error message disabled: !form.isValid, primary: true, diff --git a/source/renderer/app/components/staking/redeem-itn-rewards/Step3FailureDialog.tsx b/source/renderer/app/components/staking/redeem-itn-rewards/Step3FailureDialog.tsx index fc38ade204..241e83f834 100644 --- a/source/renderer/app/components/staking/redeem-itn-rewards/Step3FailureDialog.tsx +++ b/source/renderer/app/components/staking/redeem-itn-rewards/Step3FailureDialog.tsx @@ -4,7 +4,6 @@ import { defineMessages, intlShape } from 'react-intl'; import SVGInline from 'react-svg-inline'; import DialogCloseButton from '../../widgets/DialogCloseButton'; import Dialog from '../../widgets/Dialog'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './Step3FailureDialog.scss' or ... Remove this comment to see the full error message import styles from './Step3FailureDialog.scss'; // @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../../assets/images/sad-wal... Remove this comment to see the full error message import sadWalletImage from '../../../assets/images/sad-wallet.inline.svg'; diff --git a/source/renderer/app/components/staking/redeem-itn-rewards/Step3SuccessDialog.tsx b/source/renderer/app/components/staking/redeem-itn-rewards/Step3SuccessDialog.tsx index 6167258985..efd7656bf6 100644 --- a/source/renderer/app/components/staking/redeem-itn-rewards/Step3SuccessDialog.tsx +++ b/source/renderer/app/components/staking/redeem-itn-rewards/Step3SuccessDialog.tsx @@ -5,7 +5,6 @@ import { defineMessages, intlShape, FormattedHTMLMessage } from 'react-intl'; import SVGInline from 'react-svg-inline'; import DialogCloseButton from '../../widgets/DialogCloseButton'; import Dialog from '../../widgets/Dialog'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './Step3SuccessDialog.scss' or ... Remove this comment to see the full error message import styles from './Step3SuccessDialog.scss'; import Wallet from '../../../domains/Wallet'; import { formattedWalletAmount } from '../../../utils/formatters'; diff --git a/source/renderer/app/components/staking/rewards/StakingRewards.scss b/source/renderer/app/components/staking/rewards/StakingRewards.scss index 09e8a9a981..ca18c26da9 100644 --- a/source/renderer/app/components/staking/rewards/StakingRewards.scss +++ b/source/renderer/app/components/staking/rewards/StakingRewards.scss @@ -180,6 +180,12 @@ visibility: visible; } } + + &.total, + &.unspent { + padding-right: 10px; + text-align: right; + } } td { @@ -209,12 +215,9 @@ .syncingProgress { position: absolute; - right: 10px; - top: 0; - + right: 22px; + top: 10px; :global .LoadingSpinner_component.LoadingSpinner_medium { - margin: 10px auto !important; - .LoadingSpinner_icon svg path { fill: var(--theme-loading-spinner-color); opacity: 0.3; @@ -273,16 +276,11 @@ &.rewardWallet { max-width: 200px; - min-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } - &.rewardAmount { - min-width: 250px; - } - &.rewardsAddress { min-width: 100px; @@ -301,7 +299,7 @@ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - width: 237px; + width: 200px; &:hover { cursor: pointer; @@ -325,6 +323,12 @@ } } } + + &.rewardTotal, + &.rewardUnspent { + padding-right: 23px; + text-align: right; + } } } } diff --git a/source/renderer/app/components/staking/rewards/StakingRewards.tsx b/source/renderer/app/components/staking/rewards/StakingRewards.tsx index c3f7013ccb..fbce0f2f4d 100644 --- a/source/renderer/app/components/staking/rewards/StakingRewards.tsx +++ b/source/renderer/app/components/staking/rewards/StakingRewards.tsx @@ -9,6 +9,7 @@ import { Button } from 'react-polymorph/lib/components/Button'; import { ButtonSkin } from 'react-polymorph/lib/skins/simple/ButtonSkin'; import CopyToClipboard from 'react-copy-to-clipboard'; import { DECIMAL_PLACES_IN_ADA } from '../../../config/numbersConfig'; +import { formattedWalletAmount } from '../../../utils/formatters'; import { bigNumberComparator, stringComparator, @@ -20,7 +21,6 @@ import sortIcon from '../../../assets/images/ascending.inline.svg'; // @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../../assets/images/downloa... Remove this comment to see the full error message import downloadIcon from '../../../assets/images/download-ic.inline.svg'; import type { Reward } from '../../../api/staking/types'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './StakingRewards.scss' or its ... Remove this comment to see the full error message import styles from './StakingRewards.scss'; import globalMessages from '../../../i18n/global-messages'; // @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../../assets/images/clipboa... Remove this comment to see the full error message @@ -57,10 +57,15 @@ const messages = defineMessages({ defaultMessage: '!!!Wallet', description: 'Table header "Wallet" label on staking rewards page', }, - tableHeaderReward: { - id: 'staking.rewards.tableHeader.reward', + tableHeaderRewardTotal: { + id: 'staking.rewards.tableHeader.total', defaultMessage: '!!!Total rewards earned (ADA)', - description: 'Table header "Reward" label on staking rewards page', + description: 'Table header "Total Reward" label on staking rewards page', + }, + tableHeaderRewardUnspent: { + id: 'staking.rewards.tableHeader.unspent', + defaultMessage: '!!!Unspent (ADA)', + description: 'Table header "Unspent" label on staking rewards page', }, tableHeaderRewardsAddress: { id: 'staking.rewards.tableHeader.rewardsAddress', @@ -93,7 +98,8 @@ const REWARD_FIELDS = { WALLET_NAME: 'wallet', IS_RESTORING: 'isRestoring', SYNCING_PROGRESS: 'syncingProgress', - REWARD: 'reward', + REWARD_TOTAL: 'total', + REWARD_UNSPENT: 'unspent', REWARDS_ADDRESS: 'rewardsAddress', }; const REWARD_ORDERS = { @@ -116,7 +122,7 @@ type State = { }; @observer -class StakingRewards extends Component { +export class StakingRewards extends Component { static contextTypes = { intl: intlShape.isRequired, }; @@ -140,23 +146,22 @@ class StakingRewards extends Component { ) => { const { onExportCsv } = this.props; const { intl } = this.context; - const exportedHeader = [ - ...availableTableHeaders.map((header) => header.title), - intl.formatMessage(messages.tableHeaderDate), - ]; - const date = new Date().toISOString(); + const exportedHeader = availableTableHeaders.map((header) => header.title); const exportedBody = sortedRewards.map((reward) => { const rewardWallet = get(reward, REWARD_FIELDS.WALLET_NAME); const isRestoring = get(reward, REWARD_FIELDS.IS_RESTORING); - const rewardAmount = get(reward, REWARD_FIELDS.REWARD).toFormat( + const rewardTotal = get(reward, REWARD_FIELDS.REWARD_TOTAL)?.toFixed( + DECIMAL_PLACES_IN_ADA + ); + const rewardUnspent = get(reward, REWARD_FIELDS.REWARD_UNSPENT)?.toFixed( DECIMAL_PLACES_IN_ADA ); const rewardsAddress = get(reward, REWARD_FIELDS.REWARDS_ADDRESS); return [ rewardWallet, rewardsAddress, - isRestoring ? '-' : rewardAmount, - date, + isRestoring ? '-' : rewardTotal, + isRestoring ? '-' : rewardUnspent, ]; }); const exportedContent = [exportedHeader, ...exportedBody]; @@ -169,9 +174,14 @@ class StakingRewards extends Component { const { rewards } = this.props; const { rewardsOrder, rewardsSortBy } = this.state; return rewards.slice().sort((rewardA: Reward, rewardB: Reward) => { - const rewardCompareResult = bigNumberComparator( - rewardA.reward, - rewardB.reward, + const totalCompareResult = bigNumberComparator( + rewardA.total, + rewardB.total, + rewardsOrder === REWARD_ORDERS.ASCENDING + ); + const unspentCompareResult = bigNumberComparator( + rewardA.unspent, + rewardB.unspent, rewardsOrder === REWARD_ORDERS.ASCENDING ); const walletNameCompareResult = stringComparator( @@ -185,40 +195,28 @@ class StakingRewards extends Component { rewardsOrder === REWARD_ORDERS.ASCENDING ); - if (rewardsSortBy === REWARD_FIELDS.REWARD) { - if (rewardCompareResult === 0 && walletAddressCompareResult === 0) { - return walletNameCompareResult; - } - - if (rewardCompareResult === 0 && walletNameCompareResult === 0) { - return walletAddressCompareResult; - } + if (rewardsSortBy === REWARD_FIELDS.REWARD_TOTAL) { + if (totalCompareResult !== 0) return totalCompareResult; + if (walletNameCompareResult !== 0) return walletNameCompareResult; + return walletAddressCompareResult; + } - return rewardCompareResult; + if (rewardsSortBy === REWARD_FIELDS.REWARD_UNSPENT) { + if (unspentCompareResult !== 0) return unspentCompareResult; + if (walletNameCompareResult !== 0) return walletNameCompareResult; + return walletAddressCompareResult; } if (rewardsSortBy === REWARD_FIELDS.WALLET_NAME) { - if (walletNameCompareResult === 0 && walletAddressCompareResult) { - return rewardCompareResult; - } - - if (rewardCompareResult === 0 && walletNameCompareResult === 0) { - return walletAddressCompareResult; - } - - return walletNameCompareResult; + if (walletNameCompareResult !== 0) return walletNameCompareResult; + if (totalCompareResult !== 0) return totalCompareResult; + return walletAddressCompareResult; } if (rewardsSortBy === REWARD_FIELDS.REWARDS_ADDRESS) { - if (walletAddressCompareResult === 0 && rewardCompareResult === 0) { - return walletNameCompareResult; - } - - if (walletAddressCompareResult === 0 && walletNameCompareResult === 0) { - return rewardCompareResult; - } - - return walletAddressCompareResult; + if (walletAddressCompareResult !== 0) return walletAddressCompareResult; + if (walletNameCompareResult !== 0) return walletNameCompareResult; + return totalCompareResult; } return 0; @@ -253,9 +251,15 @@ class StakingRewards extends Component { title: intl.formatMessage(messages.tableHeaderRewardsAddress), }, { - name: REWARD_FIELDS.REWARD, + name: REWARD_FIELDS.REWARD_TOTAL, title: `${intl.formatMessage( - messages.tableHeaderReward + messages.tableHeaderRewardTotal + )} (${intl.formatMessage(globalMessages.adaUnit)})`, + }, + { + name: REWARD_FIELDS.REWARD_UNSPENT, + title: `${intl.formatMessage( + messages.tableHeaderRewardUnspent )} (${intl.formatMessage(globalMessages.adaUnit)})`, }, ]; @@ -323,6 +327,7 @@ class StakingRewards extends Component { ]); return ( this.handleRewardsSort(tableHeader.name) @@ -346,9 +351,13 @@ class StakingRewards extends Component { reward, REWARD_FIELDS.SYNCING_PROGRESS ); - const rewardAmount = get( + const rewardTotal = get( reward, - REWARD_FIELDS.REWARD + REWARD_FIELDS.REWARD_TOTAL + ).toFormat(DECIMAL_PLACES_IN_ADA); + const rewardUnspent = get( + reward, + REWARD_FIELDS.REWARD_UNSPENT ).toFormat(DECIMAL_PLACES_IN_ADA); const rewardsAddress = get( reward, @@ -397,11 +406,9 @@ class StakingRewards extends Component {
)} - - {isRestoring ? ( - '-' - ) : ( - + + {!isRestoring && ( + )} {isRestoring && (
@@ -418,6 +425,13 @@ class StakingRewards extends Component {
)} + + {isRestoring ? ( + '-' + ) : ( + + )} + ); })} @@ -432,7 +446,7 @@ class StakingRewards extends Component { )}
-
+
@@ -459,5 +473,3 @@ class StakingRewards extends Component { }); }; } - -export default StakingRewards; diff --git a/source/renderer/app/components/staking/stake-pools/StakePools.tsx b/source/renderer/app/components/staking/stake-pools/StakePools.tsx index e06f0cc95e..746d0c5c3f 100644 --- a/source/renderer/app/components/staking/stake-pools/StakePools.tsx +++ b/source/renderer/app/components/staking/stake-pools/StakePools.tsx @@ -11,7 +11,6 @@ import { StakePoolsSearch } from './StakePoolsSearch'; import BackToTopButton from '../../widgets/BackToTopButton'; import LoadingSpinner from '../../widgets/LoadingSpinner'; import Wallet from '../../../domains/Wallet'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './StakePools.scss' or its corr... Remove this comment to see the full error message import styles from './StakePools.scss'; import { getFilteredStakePoolsList } from './helpers'; import { formattedNumber } from '../../../utils/formatters'; @@ -71,24 +70,26 @@ const messages = defineMessages({ }); const SELECTED_INDEX_TABLE = 'selectedIndexTable'; type Props = { - wallets: Array; currentLocale: string; - stakePoolsList: Array; - onOpenExternalLink: (...args: Array) => any; currentTheme: string; - updateDelegatingStake: (...args: Array) => any; - rankStakePools: (...args: Array) => any; - selectedDelegationWalletId?: string | null | undefined; - stake?: number | null | undefined; - onDelegate: (...args: Array) => any; - isLoading: boolean; + getStakePoolById: (...args: Array) => any; isFetching: boolean; + isListViewTooltipVisible?: boolean; + isLoading: boolean; isRanking: boolean; - stakePoolsDelegatingList: Array; - getStakePoolById: (...args: Array) => any; + maxDelegationFunds: number; + onDelegate: (...args: Array) => any; + onListViewVisited: () => void; + onOpenExternalLink: (...args: Array) => any; onSmashSettingsClick: (...args: Array) => any; + rankStakePools: (...args: Array) => any; + selectedDelegationWalletId?: string | null | undefined; smashServerUrl: string | null | undefined; - maxDelegationFunds: number; + stake?: number | null | undefined; + stakePoolsDelegatingList: Array; + stakePoolsList: Array; + updateDelegatingStake: (...args: Array) => any; + wallets: Array; }; type State = { search: string; @@ -135,12 +136,13 @@ class StakePools extends Component { isListView: false, }); }; - handleListView = () => + handleListView = () => { this.setState({ isGridView: false, isGridRewardsView: false, isListView: true, }); + }; handleSetListActive = (selectedList: string) => this.setState({ selectedList, @@ -169,9 +171,11 @@ class StakePools extends Component { selectedDelegationWalletId, stake, onOpenExternalLink, + onListViewVisited, currentTheme, - isLoading, isFetching, + isListViewTooltipVisible, + isLoading, isRanking, stakePoolsDelegatingList, getStakePoolById, @@ -269,7 +273,9 @@ class StakePools extends Component { onGridView={this.handleGridView} onGridRewardsView={this.handleGridRewardsView} onListView={this.handleListView} + onListViewVisited={onListViewVisited} isListView={isListView} + isListViewTooltipVisible={isListViewTooltipVisible} isGridView={isGridView} isGridRewardsView={isGridRewardsView} // @ts-ignore ts-migrate(2769) FIXME: No overload matches this call. @@ -284,23 +290,41 @@ class StakePools extends Component { - - {(stakePoolsScrollContext) => ( - - )} - + {isListView ? ( + + ) : ( + + {(stakePoolsScrollContext) => ( + + )} + + )} )} {isListView && ( diff --git a/source/renderer/app/components/staking/stake-pools/StakePoolsList.tsx b/source/renderer/app/components/staking/stake-pools/StakePoolsList.tsx index a3e98bf938..6740bae46d 100644 --- a/source/renderer/app/components/staking/stake-pools/StakePoolsList.tsx +++ b/source/renderer/app/components/staking/stake-pools/StakePoolsList.tsx @@ -5,7 +5,6 @@ import type { ElementRef } from 'react'; import { AutoSizer, List, WindowScroller } from 'react-virtualized'; import { Instance } from 'tippy.js'; import LoadingSpinner from '../../widgets/LoadingSpinner'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './StakePoolsList.scss' or its ... Remove this comment to see the full error message import styles from './StakePoolsList.scss'; import StakePool from '../../../domains/StakePool'; import { ThumbPool } from '../widgets/ThumbPool'; diff --git a/source/renderer/app/components/staking/stake-pools/StakePoolsRanking.tsx b/source/renderer/app/components/staking/stake-pools/StakePoolsRanking.tsx index 023d4b88b8..02ef5be5be 100644 --- a/source/renderer/app/components/staking/stake-pools/StakePoolsRanking.tsx +++ b/source/renderer/app/components/staking/stake-pools/StakePoolsRanking.tsx @@ -33,7 +33,7 @@ import type { DiscreetModeFeature } from '../../../features/discreet-mode'; import WalletsDropdown from '../../widgets/forms/WalletsDropdown'; import ButtonLink from '../../widgets/ButtonLink'; import { Slider } from '../../widgets/Slider'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './StakePoolsRanking.scss' or i... Remove this comment to see the full error message +import { StakingPageScrollContext } from '../layouts/StakingWithNavigation'; import styles from './StakePoolsRanking.scss'; const messages = defineMessages({ @@ -388,42 +388,50 @@ class StakePoolsRanking extends Component { /> ) : null}
-
-
-
-
-
-
- -
-
-
- - {shortNumber(CIRCULATING_SUPPLY)} - + + {({ scrollElementRef }) => ( +
+
+
+
+
+
+ scrollElementRef.current} + /> +
+
+
+ scrollElementRef.current} + > + {shortNumber(CIRCULATING_SUPPLY)} + +
+
+
-
-
-
+ )} +
); } diff --git a/source/renderer/app/components/staking/stake-pools/StakePoolsRankingLoader.scss b/source/renderer/app/components/staking/stake-pools/StakePoolsRankingLoader.scss index dc1d1e8cbf..1d6ee6ae1a 100644 --- a/source/renderer/app/components/staking/stake-pools/StakePoolsRankingLoader.scss +++ b/source/renderer/app/components/staking/stake-pools/StakePoolsRankingLoader.scss @@ -1,3 +1,5 @@ +@import '../../../themes/mixins/layers.scss'; + .component { align-items: center; background-color: var(--rp-modal-overlay-bg-color); @@ -8,5 +10,5 @@ position: fixed; right: 0; top: 134px; - z-index: 5000; + z-index: $loader-backdrop-z-index; } diff --git a/source/renderer/app/components/staking/stake-pools/StakePoolsRankingLoader.tsx b/source/renderer/app/components/staking/stake-pools/StakePoolsRankingLoader.tsx index a00da53aff..3c21d89019 100644 --- a/source/renderer/app/components/staking/stake-pools/StakePoolsRankingLoader.tsx +++ b/source/renderer/app/components/staking/stake-pools/StakePoolsRankingLoader.tsx @@ -1,7 +1,6 @@ import React, { Component } from 'react'; import { observer } from 'mobx-react'; import LoadingSpinner from '../../widgets/LoadingSpinner'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './StakePoolsRankingLoader.scss... Remove this comment to see the full error message import styles from './StakePoolsRankingLoader.scss'; @observer diff --git a/source/renderer/app/components/staking/stake-pools/StakePoolsSearch.messages.ts b/source/renderer/app/components/staking/stake-pools/StakePoolsSearch.messages.ts new file mode 100644 index 0000000000..0e53b54b10 --- /dev/null +++ b/source/renderer/app/components/staking/stake-pools/StakePoolsSearch.messages.ts @@ -0,0 +1,39 @@ +import { defineMessages } from 'react-intl'; + +export const messages = defineMessages({ + searchInputPlaceholder: { + id: 'staking.stakePools.search.searchInputPlaceholder', + defaultMessage: '!!!Search stake pools', + description: '"Delegating List Title" for the Stake Pools search.', + }, + delegatingListTitle: { + id: 'staking.stakePools.search.delegatingListTitle', + defaultMessage: '!!!Stake pools to which you are delegating', + description: '"delegatingListTitle" for the Stake Pools search.', + }, + listTitle: { + id: 'staking.stakePools.search.listTitle', + defaultMessage: '!!!Stake pools ({pools})', + description: '"listTitle" for the Stake Pools search.', + }, + gridIconTooltip: { + id: 'staking.stakePools.search.gridIconTooltip', + defaultMessage: '!!!Grid View', + description: '"gridIconTooltip" for the Stake Pools search.', + }, + gridRewardsIconTooltip: { + id: 'staking.stakePools.search.gridRewardsIconTooltip', + defaultMessage: '!!!Grid Rewards View', + description: '"gridRewardsIconTooltip" for the Stake Pools search.', + }, + listIconTooltip: { + id: 'staking.stakePools.search.listIconTooltip', + defaultMessage: '!!!List View', + description: '"listIconTooltip" for the Stake Pools search.', + }, + clearTooltip: { + id: 'staking.stakePools.search.clearTooltip', + defaultMessage: '!!!Clear', + description: '"clearTooltip" for the Stake Pools search.', + }, +}); diff --git a/source/renderer/app/components/staking/stake-pools/StakePoolsSearch.scss b/source/renderer/app/components/staking/stake-pools/StakePoolsSearch.scss index de493589ee..c37ab4404b 100644 --- a/source/renderer/app/components/staking/stake-pools/StakePoolsSearch.scss +++ b/source/renderer/app/components/staking/stake-pools/StakePoolsSearch.scss @@ -1,5 +1,8 @@ +@import '../stakingConfig'; + .component { background: transparent; + display: flex; padding: 20px 0 0; position: initial; transition: position 1s ease-out, top 1s ease-out; @@ -7,23 +10,33 @@ } .container { + flex: 1 1 auto; position: relative; } .searchIcon { bottom: 12.5px; + color: var(--theme-staking-stake-pools-search-icon-color); left: 20px; position: absolute; z-index: 1; svg { + opacity: 0.3; width: 15px; + g > g { fill: var(--theme-staking-stake-pools-search-icon-color); } } } +.searchIconFocus { + svg { + opacity: 0.7; + } +} + .searchInput { input { border-radius: 4px; @@ -49,14 +62,6 @@ line-height: 48px; position: absolute; right: 20px; - - &.inputExtrasSearch { - right: 110px; - - &.withGridRewardsView { - right: 145px; - } - } } .clearSearchButton { @@ -90,36 +95,38 @@ } .viewButtons { + @extend %contentBorderAndBackground; align-items: center; - bottom: 0.5px; display: flex; - height: 48px; + height: 50px; justify-content: space-between; line-height: 48px; - padding-left: 20px; - position: absolute; - right: 11px; + margin-left: 10px; + padding: 0 20px; + width: auto; - .separator { - color: var(--theme-staking-stake-pools-search-clear-button-color); - opacity: 0.2; - padding-right: 10px; - position: relative; - top: -2px; + & > span { + display: flex; + margin-right: 15px; + + &:last-child { + margin-right: 0; + } } button { border-radius: 3px; color: var(--theme-about-window-icon-close-button-color); cursor: pointer; - height: 28px; - margin: 0 2px; - width: 28px; + height: 15px; + width: 15px; &:hover { - background-color: var( - --theme-staking-stake-pools-search-clear-button-background-color - ); + svg { + > g > g { + opacity: 0.7; + } + } } svg { diff --git a/source/renderer/app/components/staking/stake-pools/StakePoolsSearch.tsx b/source/renderer/app/components/staking/stake-pools/StakePoolsSearch.tsx index c5fa652584..1224920fcb 100644 --- a/source/renderer/app/components/staking/stake-pools/StakePoolsSearch.tsx +++ b/source/renderer/app/components/staking/stake-pools/StakePoolsSearch.tsx @@ -1,11 +1,10 @@ -import React, { Component } from 'react'; +import React, { useRef, useState } from 'react'; import SVGInline from 'react-svg-inline'; -import { defineMessages, intlShape } from 'react-intl'; +import { injectIntl } from 'react-intl'; import { Input } from 'react-polymorph/lib/components/Input'; import { InputSkin } from 'react-polymorph/lib/skins/simple/InputSkin'; import { PopOver } from 'react-polymorph/lib/components/PopOver'; import classnames from 'classnames'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './StakePoolsSearch.scss' or it... Remove this comment to see the full error message import styles from './StakePoolsSearch.scss'; // @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../../assets/images/search.... Remove this comment to see the full error message import searchIcon from '../../../assets/images/search.inline.svg'; @@ -15,51 +14,17 @@ import closeIcon from '../../../assets/images/close-cross.inline.svg'; import gridIcon from '../../../assets/images/grid-ic.inline.svg'; // @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../../assets/images/grid-re... Remove this comment to see the full error message import gridRewardsIcon from '../../../assets/images/grid-rewards.inline.svg'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../../assets/images/list-ic... Remove this comment to see the full error message -import listIcon from '../../../assets/images/list-ic.inline.svg'; import { IS_GRID_REWARDS_VIEW_AVAILABLE } from '../../../config/stakingConfig'; +import type { Intl } from '../../../types/i18nTypes'; +import { messages } from './StakePoolsSearch.messages'; +import { StakePoolsSearchListViewButton } from './StakePoolsSearchListViewButton'; +import { StakingPageScrollContext } from '../layouts/StakingWithNavigation'; -const messages = defineMessages({ - searchInputPlaceholder: { - id: 'staking.stakePools.search.searchInputPlaceholder', - defaultMessage: '!!!Search stake pools', - description: '"Delegating List Title" for the Stake Pools search.', - }, - delegatingListTitle: { - id: 'staking.stakePools.search.delegatingListTitle', - defaultMessage: '!!!Stake pools to which you are delegating', - description: '"delegatingListTitle" for the Stake Pools search.', - }, - listTitle: { - id: 'staking.stakePools.search.listTitle', - defaultMessage: '!!!Stake pools ({pools})', - description: '"listTitle" for the Stake Pools search.', - }, - gridIconTooltip: { - id: 'staking.stakePools.search.gridIconTooltip', - defaultMessage: '!!!Grid View', - description: '"gridIconTooltip" for the Stake Pools search.', - }, - gridRewardsIconTooltip: { - id: 'staking.stakePools.search.gridRewardsIconTooltip', - defaultMessage: '!!!Grid Rewards View', - description: '"gridRewardsIconTooltip" for the Stake Pools search.', - }, - listIconTooltip: { - id: 'staking.stakePools.search.listIconTooltip', - defaultMessage: '!!!List View', - description: '"listIconTooltip" for the Stake Pools search.', - }, - clearTooltip: { - id: 'staking.stakePools.search.clearTooltip', - defaultMessage: '!!!Clear', - description: '"clearTooltip" for the Stake Pools search.', - }, -}); type Props = { label?: string; placeholder?: string; isListView?: boolean; + isListViewTooltipVisible?: boolean; isGridView?: boolean; isGridRewardsView?: boolean; onSearch: (...args: Array) => any; @@ -67,104 +32,133 @@ type Props = { onGridView?: (...args: Array) => any; onGridRewardsView?: (...args: Array) => any; onListView?: (...args: Array) => any; + onListViewVisited?: () => void; search: string; + intl: Intl; }; -export class StakePoolsSearch extends Component { - static contextTypes = { - intl: intlShape.isRequired, - }; - searchInput: Record | null | undefined = null; - autoSelectOnFocus = () => - this.searchInput ? this.searchInput.inputElement.current.select() : false; - get hasSearchClearButton() { - return this.props.search.length > 0; - } +function StakePoolsSearchComponent({ + label, + onClearSearch, + onSearch, + onGridView, + onGridRewardsView, + onListView, + onListViewVisited, + placeholder, + search, + isListView, + isListViewTooltipVisible, + isGridView, + isGridRewardsView, + intl, +}: Props) { + const searchInput = + useRef<{ inputElement: { current: HTMLInputElement } }>(null); + + const [isSearchInputFocused, setSearchInputFocused] = useState(false); - handleClearSearch = () => { - this.props.onClearSearch(); + const autoSelectOnFocus = () => { + searchInput?.current + ? searchInput.current.inputElement?.current?.select() + : false; - if (this.searchInput) { - this.searchInput.focus(); - } + setSearchInputFocused(true); }; - render() { - const { intl } = this.context; - const { - label, - onSearch, - onGridView, - onGridRewardsView, - onListView, - placeholder, - search, - isListView, - isGridView, - isGridRewardsView, - } = this.props; - const gridButtonClasses = classnames([ - styles.gridView, - isGridView ? styles.selected : null, - ]); - const gridRewardsButtonClasses = classnames([ - styles.gridRewardsView, - isGridRewardsView ? styles.selected : null, - ]); - const listButtonClasses = classnames([ - styles.listView, - isListView ? styles.selected : null, - ]); - const isBigSearchComponent = isListView || isGridView || isGridRewardsView; - const searchInputClases = classnames([ - styles.searchInput, - isBigSearchComponent ? styles.inputExtrasSearch : null, - IS_GRID_REWARDS_VIEW_AVAILABLE ? styles.withGridRewardsView : null, - ]); - const clearSearchClasses = classnames([ - styles.inputExtras, - isBigSearchComponent ? styles.inputExtrasSearch : null, - IS_GRID_REWARDS_VIEW_AVAILABLE ? styles.withGridRewardsView : null, - ]); - return ( -
-
- - { - this.searchInput = input; - }} - placeholder={ - placeholder || intl.formatMessage(messages.searchInputPlaceholder) - } - skin={InputSkin} - value={search} - maxLength={150} - onFocus={this.autoSelectOnFocus} - /> - {this.hasSearchClearButton && ( -
- - - -
- )} + const handleClearSearch = () => { + onClearSearch(); + searchInput?.current?.inputElement.current.focus(); + setSearchInputFocused(true); + }; + + const hasSearchClearButton = () => { + return search.length > 0; + }; + + const gridButtonClasses = classnames([ + styles.gridView, + isGridView ? styles.selected : null, + ]); + const gridRewardsButtonClasses = classnames([ + styles.gridRewardsView, + isGridRewardsView ? styles.selected : null, + ]); + const isBigSearchComponent = isListView || isGridView || isGridRewardsView; + const searchInputClasses = classnames([ + styles.searchInput, + isBigSearchComponent ? styles.inputExtrasSearch : null, + IS_GRID_REWARDS_VIEW_AVAILABLE ? styles.withGridRewardsView : null, + ]); + const clearSearchClasses = classnames([ + styles.inputExtras, + isBigSearchComponent ? styles.inputExtrasSearch : null, + IS_GRID_REWARDS_VIEW_AVAILABLE ? styles.withGridRewardsView : null, + ]); + return ( + + {({ scrollElementRef }) => ( +
+
+ + { + searchInput.current = input; + }} + onFocus={autoSelectOnFocus} + onBlur={() => setSearchInputFocused(false)} + placeholder={ + placeholder || + intl.formatMessage(messages.searchInputPlaceholder) + } + skin={InputSkin} + value={search} + maxLength={150} + /> + {hasSearchClearButton && ( +
+ + + +
+ )} +
+ {isBigSearchComponent && (
- | - + scrollElementRef.current} + popperOptions={{ + placement: 'top', + modifiers: [ + { + name: 'flip', + options: { + fallbackPlacements: ['bottom'], + }, + }, + ], + }} + > @@ -172,6 +166,18 @@ export class StakePoolsSearch extends Component { {IS_GRID_REWARDS_VIEW_AVAILABLE && ( scrollElementRef.current} + popperOptions={{ + placement: 'top', + modifiers: [ + { + name: 'flip', + options: { + fallbackPlacements: ['bottom', 'left'], + }, + }, + ], + }} > )} - - - +
)}
-
- ); - } + )} + + ); } + +export const StakePoolsSearch = injectIntl(StakePoolsSearchComponent); diff --git a/source/renderer/app/components/staking/stake-pools/StakePoolsSearchListViewButton.tsx b/source/renderer/app/components/staking/stake-pools/StakePoolsSearchListViewButton.tsx new file mode 100644 index 0000000000..3fdc75fca0 --- /dev/null +++ b/source/renderer/app/components/staking/stake-pools/StakePoolsSearchListViewButton.tsx @@ -0,0 +1,54 @@ +import React, { useState } from 'react'; +import SVGInline from 'react-svg-inline'; +import { injectIntl } from 'react-intl'; +import { PopOver } from 'react-polymorph/lib/components/PopOver'; +import styles from './StakePoolsSearch.scss'; +// @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../../assets/images/list-ic... Remove this comment to see the full error message +import listIcon from '../../../assets/images/list-ic.inline.svg'; +import type { Intl } from '../../../types/i18nTypes'; +import { messages } from './StakePoolsSearch.messages'; + +type Props = { + isListView?: boolean; + isListViewTooltipVisible?: boolean; + onClick?: () => void; + onListViewVisited?: () => void; + intl: Intl; + tooltipTarget?: HTMLDivElement; +}; + +function StakePoolsSearchListViewButtonComponent({ + onClick, + onListViewVisited, + isListView, + isListViewTooltipVisible, + intl, + tooltipTarget, +}: Props) { + const [visible, setVisible] = useState(false); + const isPopOverVisible = visible || isListViewTooltipVisible; + + return ( + tooltipTarget} + > + + + ); +} + +export const StakePoolsSearchListViewButton = injectIntl( + StakePoolsSearchListViewButtonComponent +); diff --git a/source/renderer/app/components/staking/stake-pools/StakePoolsTable.scss b/source/renderer/app/components/staking/stake-pools/StakePoolsTable.scss index 6424cede01..c87f455cea 100644 --- a/source/renderer/app/components/staking/stake-pools/StakePoolsTable.scss +++ b/source/renderer/app/components/staking/stake-pools/StakePoolsTable.scss @@ -1,5 +1,6 @@ @import '../../../themes/mixins/link'; @import '../../../themes/mixins/loading-spinner'; +@import '../../../themes/mixins/layers'; .component { .headerWrapper { @@ -105,7 +106,7 @@ padding: 0 20px; position: sticky; top: 0; - z-index: 999; + z-index: $sticky-header-z-index; tr { border: 0; @@ -113,7 +114,7 @@ justify-content: space-between; width: 100%; - .tooltipWithHTMLContent { + .tooltipWithHtmlContent { p { -webkit-box-orient: horizontal; display: block; @@ -218,7 +219,7 @@ -ms-user-select: none; user-select: none; - // Right alligned cells need this padding + // Right aligned cells need this padding // because of the sorting icon &:nth-child(7), &:nth-child(8) { @@ -227,6 +228,7 @@ .ticker { color: var(--theme-staking-stake-pool-tooltip-link-color); + text-transform: uppercase; } &:nth-child(2) { diff --git a/source/renderer/app/components/staking/stake-pools/StakePoolsTable.tsx b/source/renderer/app/components/staking/stake-pools/StakePoolsTable.tsx index 551007a019..ca95bf4e2a 100644 --- a/source/renderer/app/components/staking/stake-pools/StakePoolsTable.tsx +++ b/source/renderer/app/components/staking/stake-pools/StakePoolsTable.tsx @@ -4,7 +4,6 @@ import { orderBy } from 'lodash'; import classNames from 'classnames'; import { defineMessages, intlShape, FormattedHTMLMessage } from 'react-intl'; import { PopOver } from 'react-polymorph/lib/components/PopOver'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './StakePoolsTable.scss' or its... Remove this comment to see the full error message import styles from './StakePoolsTable.scss'; import StakePool from '../../../domains/StakePool'; import LoadingSpinner from '../../widgets/LoadingSpinner'; @@ -277,7 +276,7 @@ class StakePoolsTable extends Component { key="ranking" placement="bottom" content={ -
+
} @@ -390,11 +389,7 @@ class StakePoolsTable extends Component { /> - + { const sortIconClasses = classNames([ styles.sortIcon, isSorted ? styles.sorted : null, - isSorted && stakePoolsOrder === 'asc' ? styles.ascending : null, isSorted && styles[`${stakePoolsOrder}CurrentOrdering`], styles[`${defaultOrdering}DefaultOrdering`], ]); diff --git a/source/renderer/app/components/staking/widgets/PoolPopOver.tsx b/source/renderer/app/components/staking/widgets/PoolPopOver.tsx index 2e12dd5a49..592c3ea904 100644 --- a/source/renderer/app/components/staking/widgets/PoolPopOver.tsx +++ b/source/renderer/app/components/staking/widgets/PoolPopOver.tsx @@ -5,7 +5,6 @@ import { PopOver } from 'react-polymorph/lib/components/PopOver'; import { STAKE_POOL_TOOLTIP_HOVER_WAIT } from '../../../config/timingConfig'; import StakePool from '../../../domains/StakePool'; import TooltipPool from './TooltipPool'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './PoolPopOver.scss' or its cor... Remove this comment to see the full error message import styles from './PoolPopOver.scss'; /** diff --git a/source/renderer/app/components/staking/widgets/ThumbPool.tsx b/source/renderer/app/components/staking/widgets/ThumbPool.tsx index 7f5fc3b46b..1d8a81d560 100644 --- a/source/renderer/app/components/staking/widgets/ThumbPool.tsx +++ b/source/renderer/app/components/staking/widgets/ThumbPool.tsx @@ -1,7 +1,6 @@ import React, { useState } from 'react'; import classnames from 'classnames'; import { PoolPopOver } from './PoolPopOver'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './ThumbPool.scss' or its corre... Remove this comment to see the full error message import styles from './ThumbPool.scss'; import { getColorFromRange } from '../../../utils/colors'; import StakePool from '../../../domains/StakePool'; diff --git a/source/renderer/app/components/staking/widgets/ThumbPoolContent.scss b/source/renderer/app/components/staking/widgets/ThumbPoolContent.scss index de1193d99e..6ec229facb 100644 --- a/source/renderer/app/components/staking/widgets/ThumbPoolContent.scss +++ b/source/renderer/app/components/staking/widgets/ThumbPoolContent.scss @@ -1,3 +1,5 @@ +@import '../stakingConfig'; + .component { height: 69px; padding: 9px 0 0; @@ -15,11 +17,10 @@ .ticker { color: var(--theme-staking-stake-pool-ticker-color); font-family: var(--font-semibold); - font-size: 14px; - letter-spacing: -0.5px; line-height: 1; margin: 0 0 1px; text-align: center; + @extend %stakePoolTicker; } .ranking { diff --git a/source/renderer/app/components/staking/widgets/ThumbPoolContent.tsx b/source/renderer/app/components/staking/widgets/ThumbPoolContent.tsx index 82b9610bdd..08aea04366 100644 --- a/source/renderer/app/components/staking/widgets/ThumbPoolContent.tsx +++ b/source/renderer/app/components/staking/widgets/ThumbPoolContent.tsx @@ -7,7 +7,6 @@ import classnames from 'classnames'; import clockIcon from '../../../assets/images/clock-corner.inline.svg'; // @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../../assets/images/no-data... Remove this comment to see the full error message import noDataDashBigImage from '../../../assets/images/no-data-dash-big.inline.svg'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './ThumbPoolContent.scss' or it... Remove this comment to see the full error message import styles from './ThumbPoolContent.scss'; import { getColorFromRange, getSaturationColor } from '../../../utils/colors'; import StakePool from '../../../domains/StakePool'; diff --git a/source/renderer/app/components/staking/widgets/ThumbSelectedPool.scss b/source/renderer/app/components/staking/widgets/ThumbSelectedPool.scss index 10e85463ab..a1f8c742f9 100644 --- a/source/renderer/app/components/staking/widgets/ThumbSelectedPool.scss +++ b/source/renderer/app/components/staking/widgets/ThumbSelectedPool.scss @@ -38,6 +38,7 @@ letter-spacing: -0.5px; line-height: 1.57; text-align: center; + text-transform: uppercase; } .icon { diff --git a/source/renderer/app/components/staking/widgets/ThumbSelectedPool.tsx b/source/renderer/app/components/staking/widgets/ThumbSelectedPool.tsx index cc67c667a1..9adf88a691 100644 --- a/source/renderer/app/components/staking/widgets/ThumbSelectedPool.tsx +++ b/source/renderer/app/components/staking/widgets/ThumbSelectedPool.tsx @@ -2,7 +2,6 @@ import React, { Component } from 'react'; import { observer } from 'mobx-react'; import SVGInline from 'react-svg-inline'; import classnames from 'classnames'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './ThumbSelectedPool.scss' or i... Remove this comment to see the full error message import styles from './ThumbSelectedPool.scss'; import { getColorFromRange } from '../../../utils/colors'; // @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../../assets/images/check-w... Remove this comment to see the full error message diff --git a/source/renderer/app/components/staking/widgets/TooltipPool.scss b/source/renderer/app/components/staking/widgets/TooltipPool.scss index e3816b3bfd..3cfb562080 100644 --- a/source/renderer/app/components/staking/widgets/TooltipPool.scss +++ b/source/renderer/app/components/staking/widgets/TooltipPool.scss @@ -230,11 +230,11 @@ $tooltip-width: 280px; .ticker { color: var(--theme-staking-stake-pool-tooltip-text-color); display: inline-block; - font-size: 14px; line-height: 1.36; margin-bottom: 9px; opacity: 0.6; vertical-align: middle; + @extend %stakePoolTicker; } .retirement { @@ -367,7 +367,7 @@ $tooltip-width: 280px; } } - .tooltipWithHTMLContent { + .tooltipWithHtmlContent { p { -webkit-box-orient: horizontal; display: block; diff --git a/source/renderer/app/components/staking/widgets/TooltipPool.tsx b/source/renderer/app/components/staking/widgets/TooltipPool.tsx index 235bb7f8d0..c41ee80735 100644 --- a/source/renderer/app/components/staking/widgets/TooltipPool.tsx +++ b/source/renderer/app/components/staking/widgets/TooltipPool.tsx @@ -16,7 +16,6 @@ import SVGInline from 'react-svg-inline'; import { ButtonSkin } from 'react-polymorph/lib/skins/simple/ButtonSkin'; import { Link } from 'react-polymorph/lib/components/Link'; import { LinkSkin } from 'react-polymorph/lib/skins/simple/LinkSkin'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './TooltipPool.scss' or its cor... Remove this comment to see the full error message import styles from './TooltipPool.scss'; import StakePool from '../../../domains/StakePool'; // @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../../assets/images/close-c... Remove this comment to see the full error message @@ -298,11 +297,12 @@ class TooltipPool extends Component { { key: 'relativeStake', value: ( -
- - {/* @ts-ignore ts-migrate(2345) FIXME: Argument of type 'BigNumber' is not assignable to ... Remove this comment to see the full error message */} - {`${toFixedUserFormat(relativeStake, 2)}%`} - +
+ {`${toFixedUserFormat( + // @ts-ignore ts-migrate(2345) FIXME: Argument of type 'BigNumber' is not assignable to ... Remove this comment to see the full error message + relativeStake, + 2 + )}%`}
), }, @@ -326,7 +326,7 @@ class TooltipPool extends Component { { key: 'pledge', value: ( -
+
{formattedWalletAmount(pledge, true, false)} @@ -353,7 +353,7 @@ class TooltipPool extends Component { { key: 'producedBlocks', value: ( -
+
{toFixedUserFormat(producedBlocks, 0)} @@ -363,7 +363,7 @@ class TooltipPool extends Component { { key: 'potentialRewards', value: ( -
+
{isGridRewardsView && potentialRewards.isZero && potentialRewards.isZero() ? ( @@ -392,7 +392,7 @@ class TooltipPool extends Component { offset={[0, 10]} key={field.key} content={ -
+
@@ -486,7 +486,7 @@ class TooltipPool extends Component { +
{stakePoolAddressHoverCopy}
} diff --git a/source/renderer/app/components/static/About.scss b/source/renderer/app/components/static/About.scss index 6563b50953..54aed6e811 100644 --- a/source/renderer/app/components/static/About.scss +++ b/source/renderer/app/components/static/About.scss @@ -55,7 +55,7 @@ } .daedalusTitleVersion { - color: var(--theme-about-window-title-varsion-color); + color: var(--theme-about-window-title-version-color); display: flex; flex-direction: column; justify-content: center; diff --git a/source/renderer/app/components/static/About.tsx b/source/renderer/app/components/static/About.tsx index 137a1fe292..717da52728 100644 --- a/source/renderer/app/components/static/About.tsx +++ b/source/renderer/app/components/static/About.tsx @@ -5,13 +5,9 @@ import { Link } from 'react-polymorph/lib/components/Link'; import { LinkSkin } from 'react-polymorph/lib/skins/simple/LinkSkin'; import DialogCloseButton from '../widgets/DialogCloseButton'; import globalMessages from '../../i18n/global-messages'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './About.scss' or its correspon... Remove this comment to see the full error message import styles from './About.scss'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../assets/images/close-cros... Remove this comment to see the full error message import closeCrossThin from '../../assets/images/close-cross-thin.inline.svg'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../assets/images/daedalus-l... Remove this comment to see the full error message import daedalusIcon from '../../assets/images/daedalus-logo-loading-grey.inline.svg'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../assets/images/cardano-lo... Remove this comment to see the full error message import cardanoIcon from '../../assets/images/cardano-logo.inline.svg'; const messages = defineMessages({ @@ -33,13 +29,13 @@ const messages = defineMessages({ aboutContentDaedalusMembers: { id: 'static.about.content.daedalus.members', defaultMessage: - '!!!Alan McNicholas, Aleksandar Djordjevic, Alexander Rukin, Brian McKenna, Charles Hoskinson, Danilo Prates, Darko Mijić, Dominik Guzei, Elin Liu, Gabriela Ponce, Jane Wild, Jeremy Wood, Juli Sudi, Junko Oda, Laurie Wang, Manus McCole, Michael Bishop, Mior Sufian, Nikola Glumac, Piotr Stachyra, Rhys Bartels-Waller, Richard Wild, Robert Moore, Rodney Lorrimar, Sam Jeston, Samuel Leathers, Serge Kosyrev, Tatyana Valkevych, Tomas Vrana, Tomislav Horaček, Yakov Karavelov', + '!!!Alan McNicholas, Aleksandar Djordjevic, Alexander Rukin, Brian McKenna, Charles Hoskinson, Daniel Main, Danilo Prates, Darko Mijić, Dmitrii Gaico, Dominik Guzei, Elin Liu, Gabriela Ponce, Jane Wild, Jeremy Wood, Juli Sudi, Junko Oda, Laurie Wang, Lucas Araujo, Manus McCole, Marcin Mazurek, Michael Bishop, Michael Chappell, Mior Sufian, Nikola Glumac, Piotr Stachyra, Przemysław Włodek, Renan Ferreira, Rhys Bartels-Waller, Richard Wild, Robert Moore, Rodney Lorrimar, Sam Jeston, Samuel Leathers, Serge Kosyrev, Szymon Masłowski, Tatyana Valkevych, Tomas Vrana, Tomislav Horaček, Yakov Karavelov', description: 'About page daedalus team members', }, aboutContentCardanoMembers: { id: 'static.about.content.cardano.members', defaultMessage: - "!!!Alexander Sukhoverkhov, Alexander Vieth, Alexandre Rodrigues Baldé, Alfredo Di Napoli, Anastasiya Besman, Andrzej Rybczak, Ante Kegalj, Anton Belyy, Anupam Jain, Arseniy Seroka, Artyom Kazak, Carlos D'Agostino, Charles Hoskinson, Dan Friedman, Denis Shevchenko, Dmitry Kovanikov, Dmitry Mukhutdinov, Dmitry Nikulin, Domen Kožar, Duncan Coutts, Edsko de Vries, Eileen Fitzgerald, George Agapov, Hiroto Shioi, Ilya Lubimov, Ilya Peresadin, Ivan Gromakovskii, Jake Mitchell, Jane Wild, Jens Krause, Jeremy Wood, Joel Mislov Kunst, Jonn Mostovoy, Konstantin Ivanov, Kristijan Šarić, Lars Brünjes, Laurie Wang, Lionel Miller, Michael Bishop, Mikhail Volkhov, Niklas Hambüchen, Peter Gaži, Philipp Kant, Serge Kosyrev, Vincent Hanquez", + '!!!Alan McNicholas, Alejandro Garcia, Alexander Diemand, Alexander Vieth, Anatoli Ivanou, Andreas Triantafyllos, Ante Kegalj, Armando Santos, Ben Ford, Charles Hoskinson, Dan Friedman, Deepak Kapiswe, Denis Shevchenko, Dorin Solomon, Duncan Coutts, Edsko de Vries, Erik de Castro Lopo, Gerard Moroney, Hiroto Shioi, Jane Wild, Jean-Christophe Mincke, Jeremy Wood, Johannes Lund, Jordan Millar, Karl Knutsson, Kristijan Šarić, Lars Brünjes, Laurie Wang, Liz Bancroft, Luke Nadur, Marc Fontaine, Marcin Szamotulski, Matt Parsons, Matthias Benkort, Michael Bishop, Michael Hueschen, Moritz Angermann, Neil Davis, Niamh Ahern, Nicholas Clarke, Nicolas Di Prima, Noel Rimbert, Patrick Kelly, Pawel Jakubas, Peter Gaži, Peter Thompson, Philipp Kant, Piotr Stachyra, Ravi Patel, Richard Wild, Rob Cohen, Rodney Lorrimar, Ryan Lemmer, Samuel Leathers, Serge Kosyrev, Tatyana Valkevych, Tom Flynn, Vasileios Gkoumas, Vincent Hanquez', description: 'About page cardano team members', }, aboutCopyright: { @@ -130,7 +126,7 @@ export default class About extends Component {

{apiHeadline}

-
{apiMembers}
+
{apiMembers}
diff --git a/source/renderer/app/components/status/DaedalusDiagnostics.tsx b/source/renderer/app/components/status/DaedalusDiagnostics.tsx index dbc1e56407..71da9d39c7 100644 --- a/source/renderer/app/components/status/DaedalusDiagnostics.tsx +++ b/source/renderer/app/components/status/DaedalusDiagnostics.tsx @@ -13,25 +13,18 @@ import SVGInline from 'react-svg-inline'; import { ALLOWED_TIME_DIFFERENCE } from '../../config/timingConfig'; import globalMessages from '../../i18n/global-messages'; import DialogCloseButton from '../widgets/DialogCloseButton'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../assets/images/close-cros... Remove this comment to see the full error message import closeCrossThin from '../../assets/images/close-cross-thin.inline.svg'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../assets/images/clipboard-... Remove this comment to see the full error message import iconCopy from '../../assets/images/clipboard-ic.inline.svg'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../assets/images/sand-clock... Remove this comment to see the full error message import sandClockIcon from '../../assets/images/sand-clock-xs.inline.svg'; import LocalizableError from '../../i18n/LocalizableError'; -import { - formattedNumber, - formattedCpuModel, - formattedSize, -} from '../../utils/formatters'; +import { formattedNumber, formattedSize } from '../../utils/formatters'; import { CardanoNodeStates } from '../../../../common/types/cardano-node.types'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './DaedalusDiagnostics.scss' or... Remove this comment to see the full error message import styles from './DaedalusDiagnostics.scss'; import type { CardanoNodeState } from '../../../../common/types/cardano-node.types'; import type { SystemInfo } from '../../types/systemInfoTypes'; import type { CoreSystemInfo } from '../../types/coreSystemInfoTypes'; import type { TipInfo } from '../../api/network/types'; +import { ErrorType } from '../../domains/ApiError'; const messages = defineMessages({ systemInfo: { @@ -74,6 +67,43 @@ const messages = defineMessages({ defaultMessage: '!!!https://iohk.zendesk.com/hc', description: '"Support" link URL while disk space is unknown', }, + hasMetHardwareRequirementsLabel: { + id: 'daedalus.diagnostics.dialog.hasMetHardwareRequirementsStatus', + defaultMessage: '!!!Recommended system requirements status', + description: + 'Displayed on the left of the Recommended system requirements status row', + }, + hasMetHardwareRequirementsStatusLowValue: { + id: 'daedalus.diagnostics.dialog.hasMetHardwareRequirementsStatusLowValue', + defaultMessage: '!!!Low', + description: + 'Displayed on the right of the Recommended system requirements status row when hardware requirements are insufficient', + }, + hasMetHardwareRequirementsStatusGoodValue: { + id: 'daedalus.diagnostics.dialog.hasMetHardwareRequirementsStatusGoodValue', + defaultMessage: '!!!Good', + description: + 'Displayed on the right of the Recommended system requirements status row when hardware requirements are ok', + }, + hasMetHardwareRequirementsStatusLowTooltip: { + id: 'daedalus.diagnostics.dialog.hasMetHardwareRequirementsStatusLowTooltip', + defaultMessage: + '!!!Your system specifications do not meet Daedalus’ recommended hardware requirements. We suggest using a machine with at least 16 GB of RAM', + description: + 'Visible on hovering over Recommended system requirement status when status is Low', + }, + hasMetHardwareRequirementsStatusGoodTooltip: { + id: 'daedalus.diagnostics.dialog.hasMetHardwareRequirementsStatusGoodTooltip', + defaultMessage: + '!!!Your system specifications meet Daedalus’ recommended hardware requirements', + description: + 'Visible on hovering over Recommended system requirement status when status is Good', + }, + isRTSFlagsModeEnabled: { + id: 'daedalus.diagnostics.dialog.isRTSFlagsModeEnabled', + defaultMessage: '!!!RTS Flags Mode', + description: 'Indicates whether RTS Flags Mode is enabled or not', + }, coreInfo: { id: 'daedalus.diagnostics.dialog.coreInfo', defaultMessage: '!!!CORE INFO', @@ -301,13 +331,23 @@ const messages = defineMessages({ }, statusOn: { id: 'daedalus.diagnostics.dialog.statusOn', - defaultMessage: '!!!YES', - description: 'YES', + defaultMessage: '!!!Yes', + description: 'Yes', }, statusOff: { id: 'daedalus.diagnostics.dialog.statusOff', - defaultMessage: '!!!NO', - description: 'NO', + defaultMessage: '!!!No', + description: 'No', + }, + statusOnForUserSettings: { + id: 'daedalus.diagnostics.dialog.statusOnForUserSettings', + defaultMessage: '!!!On', + description: 'On', + }, + statusOffForUserSettings: { + id: 'daedalus.diagnostics.dialog.statusOffForUserSettings', + defaultMessage: '!!!Off', + description: 'Off', }, serviceUnreachable: { id: 'daedalus.diagnostics.dialog.serviceUnreachable', @@ -410,15 +450,17 @@ class DaedalusDiagnostics extends Component { } } - getSectionRow = (messageId: string, content?: Node) => ( -
-
- {this.context.intl.formatMessage(messages[messageId])} - {content} -
+ getSectionRow = (messageId: string, content?: Node) => { + return ( +
+
+ {this.context.intl.formatMessage(messages[messageId])} + {content} +
+
-
- ); + ); + }; getRow = (messageId: string, value: Node | boolean) => { const { intl } = this.context; const key = intl.formatMessage(messages[messageId]); @@ -483,11 +525,12 @@ class DaedalusDiagnostics extends Component { const { platform, platformVersion, - cpu: cpuInOriginalFormat, + cpu, ram, availableDiskSpace: availableDiskSpaceInOriginalFormat, + hasMetHardwareRequirements, + isRTSFlagsModeEnabled, } = systemInfo; - const cpu = formattedCpuModel(cpuInOriginalFormat); const availableDiskSpace = formattedSize( availableDiskSpaceInOriginalFormat ); @@ -508,7 +551,7 @@ class DaedalusDiagnostics extends Component { const { isNodeRestarting } = this.state; const isNTPServiceReachable = localTimeDifference != null; const connectionError = get(nodeConnectionError, 'values', '{}'); - const { message, code } = connectionError; + const { message, code } = connectionError as ErrorType; const unknownDiskSpaceSupportUrl = intl.formatMessage( messages.unknownDiskSpaceSupportUrl ); @@ -555,6 +598,37 @@ class DaedalusDiagnostics extends Component { /> ) )} + {getRow( + 'hasMetHardwareRequirementsLabel', + +
+ {intl.formatMessage( + hasMetHardwareRequirements + ? messages.hasMetHardwareRequirementsStatusGoodValue + : messages.hasMetHardwareRequirementsStatusLowValue + )} +
+
+ )} + {getRow( + 'isRTSFlagsModeEnabled', + intl.formatMessage( + isRTSFlagsModeEnabled + ? messages.statusOnForUserSettings + : messages.statusOffForUserSettings + ) + )}
{getSectionRow('coreInfo')} @@ -565,8 +639,8 @@ class DaedalusDiagnostics extends Component { {getRow( 'blankScreenFix', isBlankScreenFixActive - ? intl.formatMessage(messages.statusOn) - : intl.formatMessage(messages.statusOff) + ? intl.formatMessage(messages.statusOnForUserSettings) + : intl.formatMessage(messages.statusOffForUserSettings) )} {getRow( 'stateDirectoryPath', diff --git a/source/renderer/app/components/voting/VotingFooterLinks.tsx b/source/renderer/app/components/voting/VotingFooterLinks.tsx index 6a44498f77..60b76d2625 100644 --- a/source/renderer/app/components/voting/VotingFooterLinks.tsx +++ b/source/renderer/app/components/voting/VotingFooterLinks.tsx @@ -2,7 +2,6 @@ import React from 'react'; import { Link } from 'react-polymorph/lib/components/Link'; import { observer } from 'mobx-react'; import { defineMessages, intlShape, injectIntl } from 'react-intl'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './VotingFooterLinks.scss' or i... Remove this comment to see the full error message import styles from './VotingFooterLinks.scss'; type FooterLink = { diff --git a/source/renderer/app/components/voting/VotingNoWallets.tsx b/source/renderer/app/components/voting/VotingNoWallets.tsx index 1ba1bbf948..bf08ff8b65 100644 --- a/source/renderer/app/components/voting/VotingNoWallets.tsx +++ b/source/renderer/app/components/voting/VotingNoWallets.tsx @@ -4,11 +4,8 @@ import SVGInline from 'react-svg-inline'; import BigNumber from 'bignumber.js'; import { Button } from 'react-polymorph/lib/components/Button'; import { ButtonSkin } from 'react-polymorph/lib/skins/simple/ButtonSkin'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './VotingNoWallets.scss' or its... Remove this comment to see the full error message import styles from './VotingNoWallets.scss'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../assets/images/attention-... Remove this comment to see the full error message import icon from '../../assets/images/attention-big-thin.inline.svg'; -import { NEXT_VOTING_FUND_NUMBER } from '../../config/votingConfig'; const messages = defineMessages({ headLine: { @@ -33,6 +30,7 @@ const messages = defineMessages({ type Props = { onGoToCreateWalletClick: (...args: Array) => any; minVotingFunds: number; + nextFundNumber: number; }; export default class VotingNoWallets extends Component { static contextTypes = { @@ -41,13 +39,14 @@ export default class VotingNoWallets extends Component { render() { const { intl } = this.context; - const { onGoToCreateWalletClick, minVotingFunds } = this.props; + const { onGoToCreateWalletClick, minVotingFunds, nextFundNumber } = + this.props; return (

{intl.formatMessage(messages.headLine, { - nextVotingFundNumber: NEXT_VOTING_FUND_NUMBER, + nextVotingFundNumber: nextFundNumber, })}

diff --git a/source/renderer/app/components/voting/VotingRegistrationDialogWizard.tsx b/source/renderer/app/components/voting/VotingRegistrationDialogWizard.tsx index 66b11a6838..04242acf42 100644 --- a/source/renderer/app/components/voting/VotingRegistrationDialogWizard.tsx +++ b/source/renderer/app/components/voting/VotingRegistrationDialogWizard.tsx @@ -42,6 +42,7 @@ type Props = { onRestart: (...args: Array) => any; onExternalLinkClick: (...args: Array) => any; hwDeviceStatus: HwDeviceStatus; + nextFundNumber: number; }; @observer @@ -75,6 +76,7 @@ class VotingRegistrationDialogWizard extends Component { hwDeviceStatus, isTrezor, isHardwareWallet, + nextFundNumber, } = this.props; const selectedWalletId = get(selectedWallet, 'id', null); let content = null; @@ -93,6 +95,7 @@ class VotingRegistrationDialogWizard extends Component { onSelectWallet={onSelectWallet} isWalletAcceptable={isWalletAcceptable} getStakePoolById={getStakePoolById} + nextFundNumber={nextFundNumber} /> ); break; @@ -114,6 +117,7 @@ class VotingRegistrationDialogWizard extends Component { selectedWallet={selectedWallet} isTrezor={isTrezor} isHardwareWallet={isHardwareWallet} + nextFundNumber={nextFundNumber} /> ); break; @@ -130,6 +134,7 @@ class VotingRegistrationDialogWizard extends Component { transactionError={transactionError} onConfirm={onContinue} onRestart={onRestart} + nextFundNumber={nextFundNumber} /> ); break; @@ -141,6 +146,7 @@ class VotingRegistrationDialogWizard extends Component { onClose={onClose} stepsList={stepsList} activeStep={activeStep} + nextFundNumber={nextFundNumber} /> ); break; @@ -153,6 +159,7 @@ class VotingRegistrationDialogWizard extends Component { onDownloadPDF={onDownloadPDF} stepsList={stepsList} activeStep={activeStep} + nextFundNumber={nextFundNumber} /> ); break; diff --git a/source/renderer/app/components/voting/VotingUnavailable.tsx b/source/renderer/app/components/voting/VotingUnavailable.tsx index e615fa4a30..d2e4d72ab4 100644 --- a/source/renderer/app/components/voting/VotingUnavailable.tsx +++ b/source/renderer/app/components/voting/VotingUnavailable.tsx @@ -3,7 +3,6 @@ import { observer } from 'mobx-react'; import { FormattedHTMLMessage } from 'react-intl'; import globalMessages from '../../i18n/global-messages'; import LoadingSpinner from '../widgets/LoadingSpinner'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './VotingUnavailable.scss' or i... Remove this comment to see the full error message import styles from './VotingUnavailable.scss'; import { formattedNumber } from '../../utils/formatters'; @@ -11,18 +10,20 @@ type Props = { syncPercentage: number; }; -const VotingUnavailable = ({ syncPercentage }: Props) => ( -

- -
- +const VotingUnavailable = ({ syncPercentage }: Props) => { + return ( +
+ +
+ +
-
-); + ); +}; export default observer(VotingUnavailable); diff --git a/source/renderer/app/components/voting/voting-info/ApiError.messages.ts b/source/renderer/app/components/voting/voting-info/ApiError.messages.ts new file mode 100644 index 0000000000..ed16d80eda --- /dev/null +++ b/source/renderer/app/components/voting/voting-info/ApiError.messages.ts @@ -0,0 +1,20 @@ +import { defineMessages } from 'react-intl'; + +export const messages = defineMessages({ + title: { + id: 'voting.apiError.title', + defaultMessage: '!!!Catalyst API unavailable', + description: 'Title', + }, + description1: { + id: 'voting.apiError.description1', + defaultMessage: + '!!!Unable to communicate with the API that retrieves the Catalyst date information.', + description: 'Description 1', + }, + description2: { + id: 'voting.apiError.description2', + defaultMessage: '!!!Please, try again later.', + description: 'Description 2', + }, +}); diff --git a/source/renderer/app/components/voting/voting-info/ApiError.scss b/source/renderer/app/components/voting/voting-info/ApiError.scss new file mode 100644 index 0000000000..5d1a99280b --- /dev/null +++ b/source/renderer/app/components/voting/voting-info/ApiError.scss @@ -0,0 +1,24 @@ +@import '../votingConfig'; + +.root { + @extend %regularText; + align-items: start; + background-color: var(--theme-button-flat-background-color); + border-radius: 5px; + color: var(--theme-button-flat-text-color); + display: flex; + flex-direction: column; + padding: 20px; + width: 100%; + + .title { + font-family: var(--font-semibold); + font-size: 18px; + line-height: 1.33; + margin-bottom: 11px; + } + + .description2 { + font-family: var(--font-medium); + } +} diff --git a/source/renderer/app/components/voting/voting-info/ApiError.tsx b/source/renderer/app/components/voting/voting-info/ApiError.tsx new file mode 100644 index 0000000000..6999e10c84 --- /dev/null +++ b/source/renderer/app/components/voting/voting-info/ApiError.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { injectIntl } from 'react-intl'; +import type { Intl } from '../../../types/i18nTypes'; +import styles from './ApiError.scss'; +import { messages } from './ApiError.messages'; + +type Props = { + intl: Intl; +}; + +function ApiError({ intl }: Props) { + return ( +
+

{intl.formatMessage(messages.title)}

+ {intl.formatMessage(messages.description1)} + + {intl.formatMessage(messages.description2)} + +
+ ); +} + +export default injectIntl(ApiError); diff --git a/source/renderer/app/components/voting/voting-info/AppStore.tsx b/source/renderer/app/components/voting/voting-info/AppStore.tsx index 9a88c17128..3dfbb8f704 100644 --- a/source/renderer/app/components/voting/voting-info/AppStore.tsx +++ b/source/renderer/app/components/voting/voting-info/AppStore.tsx @@ -7,7 +7,6 @@ import downloadAppStoreIcon from '../../../assets/images/voting/download-app-sto // @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../../assets/images/voting/... Remove this comment to see the full error message import downloadPlayStoreIcon from '../../../assets/images/voting/download-play-store-icon-ic.inline.svg'; import type { Intl } from '../../../types/i18nTypes'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './AppStore.scss' or its corres... Remove this comment to see the full error message import styles from './AppStore.scss'; import { messages } from './AppStore.messages'; diff --git a/source/renderer/app/components/voting/voting-info/CurrentPhase.scss b/source/renderer/app/components/voting/voting-info/CurrentPhase.scss index 1d90208480..ba427e1344 100644 --- a/source/renderer/app/components/voting/voting-info/CurrentPhase.scss +++ b/source/renderer/app/components/voting/voting-info/CurrentPhase.scss @@ -12,7 +12,7 @@ .fundName { color: var(--theme-button-flat-text-color); - font-family: var(--font-bold); + font-family: var(--font-semibold); font-size: 18px; line-height: 1.33; margin-bottom: 11px; diff --git a/source/renderer/app/components/voting/voting-info/Headline.scss b/source/renderer/app/components/voting/voting-info/Headline.scss index 7e80851187..b7e1c742d1 100644 --- a/source/renderer/app/components/voting/voting-info/Headline.scss +++ b/source/renderer/app/components/voting/voting-info/Headline.scss @@ -4,7 +4,8 @@ .heading { @extend %accentText; font-family: var(--font-semibold); - font-size: 19px; + font-size: 18px; + letter-spacing: 2px; margin-bottom: 14px; text-align: center; text-transform: uppercase; diff --git a/source/renderer/app/components/voting/voting-info/Headline.tsx b/source/renderer/app/components/voting/voting-info/Headline.tsx index a670b353c9..904df4c6d5 100644 --- a/source/renderer/app/components/voting/voting-info/Headline.tsx +++ b/source/renderer/app/components/voting/voting-info/Headline.tsx @@ -4,7 +4,6 @@ import { injectIntl } from 'react-intl'; import { ExternalLinkButton } from '../../widgets/ExternalLinkButton'; import { VOTING_REWARD } from '../../../config/votingConfig'; import type { Intl } from '../../../types/i18nTypes'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './Headline.scss' or its corres... Remove this comment to see the full error message import styles from './Headline.scss'; import { messages } from './Headline.messages'; diff --git a/source/renderer/app/components/voting/voting-info/RegisterToVote.scss b/source/renderer/app/components/voting/voting-info/RegisterToVote.scss index dbdd57b02f..6b2f5dba14 100644 --- a/source/renderer/app/components/voting/voting-info/RegisterToVote.scss +++ b/source/renderer/app/components/voting/voting-info/RegisterToVote.scss @@ -7,7 +7,7 @@ padding: 20px; .title { - font-family: var(--font-bold); + font-family: var(--font-semibold); font-size: 18px; line-height: 1.33; } diff --git a/source/renderer/app/components/voting/voting-info/RegisterToVote.tsx b/source/renderer/app/components/voting/voting-info/RegisterToVote.tsx index d56267a43d..1c55f62f5c 100644 --- a/source/renderer/app/components/voting/voting-info/RegisterToVote.tsx +++ b/source/renderer/app/components/voting/voting-info/RegisterToVote.tsx @@ -2,10 +2,6 @@ import React, { useState } from 'react'; import { injectIntl } from 'react-intl'; import { Button } from 'react-polymorph/lib/components/Button'; import { Checkbox } from 'react-polymorph/lib/components/Checkbox'; -import { - VOTING_NEW_SNAPSHOT_DATE, - NEXT_VOTING_FUND_NUMBER, -} from '../../../config/votingConfig'; import { formattedDateTime, mapToLongDateTimeFormat, @@ -14,15 +10,15 @@ import type { Locale } from '../../../../../common/types/locales.types'; import type { Intl } from '../../../types/i18nTypes'; import { messages } from './RegisterToVote.messages'; import { messages as votingMessages } from './VotingInfo.messages'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './RegisterToVote.scss' or its ... Remove this comment to see the full error message import styles from './RegisterToVote.scss'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './VotingInfo.scss' or its corr... Remove this comment to see the full error message import votingStyles from './VotingInfo.scss'; +import type { CatalystFund } from '../../../api/voting/types'; type Props = { currentLocale: Locale; currentDateFormat: string; currentTimeFormat: string; + fundInfo: CatalystFund; intl: Intl; onRegisterToVoteClick: (...args: Array) => any; }; @@ -31,38 +27,41 @@ function RegisterToVote({ currentLocale, currentDateFormat, currentTimeFormat, + fundInfo, intl, onRegisterToVoteClick, }: Props) { const [step1, setStep1] = useState(false); const [step2, setStep2] = useState(false); const canRegister = step1 && step2; - const castEndDate = formattedDateTime(VOTING_NEW_SNAPSHOT_DATE, { - currentLocale, - ...mapToLongDateTimeFormat({ + const snapshotDate = formattedDateTime( + fundInfo.next.registrationSnapshotTime, + { currentLocale, - currentDateFormat, - currentTimeFormat, - }), - }); + ...mapToLongDateTimeFormat({ + currentLocale, + currentDateFormat, + currentTimeFormat, + }), + } + ); return (
{intl.formatMessage(votingMessages.fundName, { - votingFundNumber: NEXT_VOTING_FUND_NUMBER, + votingFundNumber: fundInfo.next.number, })} {intl.formatMessage(messages.dateLabel)} - {castEndDate} + {snapshotDate}
{intl.formatMessage(messages.stepsTitle)}
) => any; - intl: Intl; -}; +import type { PhaseIntlProps as Props } from './types'; function ResultsPhase({ currentLocale, currentDateFormat, currentTimeFormat, + fundInfo, onExternalLinkClick, intl, }: Props) { @@ -37,7 +24,7 @@ function ResultsPhase({ currentDateFormat, currentTimeFormat, }); - const endDate = formattedDateTime(VOTING_CAST_END_DATE, { + const endDate = formattedDateTime(fundInfo.current.endTime, { currentLocale, currentDateFormat: mappedFormats.currentDateFormat, currentTimeFormat: mappedFormats.currentTimeFormat, @@ -46,7 +33,7 @@ function ResultsPhase({

{intl.formatMessage(votingMessages.fundName, { - votingFundNumber: CURRENT_VOTING_FUND_NUMBER, + votingFundNumber: fundInfo.current.number, })}

diff --git a/source/renderer/app/components/voting/voting-info/SnapshotPhase.tsx b/source/renderer/app/components/voting/voting-info/SnapshotPhase.tsx index ecb5c87a91..3d73d1b86c 100644 --- a/source/renderer/app/components/voting/voting-info/SnapshotPhase.tsx +++ b/source/renderer/app/components/voting/voting-info/SnapshotPhase.tsx @@ -1,34 +1,20 @@ import React from 'react'; import { observer } from 'mobx-react'; import { injectIntl } from 'react-intl'; -import { - CURRENT_VOTING_FUND_NUMBER, - VOTING_SNAPSHOT_DATE, - VOTING_CAST_START_DATE, - VOTING_CAST_END_DATE, -} from '../../../config/votingConfig'; import { formattedDateTime, mapToLongDateTimeFormat, } from '../../../utils/formatters'; -import type { Locale } from '../../../../../common/types/locales.types'; -import type { Intl } from '../../../types/i18nTypes'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './CurrentPhase.scss' or its co... Remove this comment to see the full error message import styles from './CurrentPhase.scss'; import { messages } from './SnapshotPhase.messages'; import { messages as votingMessages } from './VotingInfo.messages'; - -type Props = { - currentLocale: Locale; - currentDateFormat: string; - currentTimeFormat: string; - intl: Intl; -}; +import type { PhaseIntlProps as Props } from './types'; function SnapshotPhase({ currentLocale, currentDateFormat, currentTimeFormat, + fundInfo, intl, }: Props) { const mappedFormats = mapToLongDateTimeFormat({ @@ -36,16 +22,19 @@ function SnapshotPhase({ currentDateFormat, currentTimeFormat, }); - const snapshotDate = formattedDateTime(VOTING_SNAPSHOT_DATE, { - currentLocale, - currentDateFormat: mappedFormats.currentDateFormat, - currentTimeFormat: mappedFormats.currentTimeFormat, - }); - const startDate = formattedDateTime(VOTING_CAST_START_DATE, { + const snapshotDate = formattedDateTime( + fundInfo.current.registrationSnapshotTime, + { + currentLocale, + currentDateFormat: mappedFormats.currentDateFormat, + currentTimeFormat: mappedFormats.currentTimeFormat, + } + ); + const startDate = formattedDateTime(fundInfo.current.startTime, { currentLocale, currentDateFormat: mappedFormats.currentDateFormat, }); - const endDate = formattedDateTime(VOTING_CAST_END_DATE, { + const endDate = formattedDateTime(fundInfo.current.endTime, { currentLocale, currentDateFormat: mappedFormats.currentDateFormat, }); @@ -53,7 +42,7 @@ function SnapshotPhase({

{intl.formatMessage(votingMessages.fundName, { - votingFundNumber: CURRENT_VOTING_FUND_NUMBER, + votingFundNumber: fundInfo.current.number, })}

diff --git a/source/renderer/app/components/voting/voting-info/TallyingPhase.tsx b/source/renderer/app/components/voting/voting-info/TallyingPhase.tsx index 19d4b4bc51..532b11ebcf 100644 --- a/source/renderer/app/components/voting/voting-info/TallyingPhase.tsx +++ b/source/renderer/app/components/voting/voting-info/TallyingPhase.tsx @@ -1,33 +1,20 @@ import React from 'react'; import { observer } from 'mobx-react'; import { injectIntl } from 'react-intl'; -import { - CURRENT_VOTING_FUND_NUMBER, - VOTING_RESULTS_DATE, - VOTING_CAST_END_DATE, -} from '../../../config/votingConfig'; import { formattedDateTime, mapToLongDateTimeFormat, } from '../../../utils/formatters'; -import type { Locale } from '../../../../../common/types/locales.types'; -import type { Intl } from '../../../types/i18nTypes'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './CurrentPhase.scss' or its co... Remove this comment to see the full error message import styles from './CurrentPhase.scss'; import { messages } from './TallyingPhase.messages'; import { messages as votingMessages } from './VotingInfo.messages'; - -type Props = { - currentLocale: Locale; - currentDateFormat: string; - currentTimeFormat: string; - intl: Intl; -}; +import type { PhaseIntlProps as Props } from './types'; function TallyingPhase({ currentLocale, currentDateFormat, currentTimeFormat, + fundInfo, intl, }: Props) { const mappedFormats = mapToLongDateTimeFormat({ @@ -35,11 +22,11 @@ function TallyingPhase({ currentDateFormat, currentTimeFormat, }); - const endDated = formattedDateTime(VOTING_CAST_END_DATE, { + const endDated = formattedDateTime(fundInfo.current.endTime, { currentLocale, currentDateFormat: mappedFormats.currentDateFormat, }); - const resultsDate = formattedDateTime(VOTING_RESULTS_DATE, { + const resultsDate = formattedDateTime(fundInfo.current.resultsTime, { currentLocale, currentDateFormat: mappedFormats.currentDateFormat, }); @@ -47,7 +34,7 @@ function TallyingPhase({

{intl.formatMessage(votingMessages.fundName, { - votingFundNumber: CURRENT_VOTING_FUND_NUMBER, + votingFundNumber: fundInfo.current.number, })}

diff --git a/source/renderer/app/components/voting/voting-info/VotingInfo.tsx b/source/renderer/app/components/voting/voting-info/VotingInfo.tsx index 9586d9516f..525099eae2 100644 --- a/source/renderer/app/components/voting/voting-info/VotingInfo.tsx +++ b/source/renderer/app/components/voting/voting-info/VotingInfo.tsx @@ -1,8 +1,6 @@ import React from 'react'; import { observer } from 'mobx-react'; import BorderedBox from '../../widgets/BorderedBox'; -import type { Locale } from '../../../../../common/types/locales.types'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './VotingInfo.scss' or its corr... Remove this comment to see the full error message import styles from './VotingInfo.scss'; import ResultsPhase from './ResultsPhase'; import SnapshotPhase from './SnapshotPhase'; @@ -11,22 +9,15 @@ import TallyingPhase from './TallyingPhase'; import Headline from './Headline'; import AppStore from './AppStore'; import RegisterToVote from './RegisterToVote'; -import { FundPhases } from '../../../stores/VotingStore'; -import type { FundPhase } from '../../../stores/VotingStore'; +import ApiError from './ApiError'; +import { FundPhase } from '../../../stores/VotingStore'; +import { VotingProps as Props, PhaseProps } from './types'; -type Props = { - currentLocale: Locale; - currentDateFormat: string; - currentTimeFormat: string; - fundPhase: FundPhase; - onRegisterToVoteClick: (...args: Array) => any; - onExternalLinkClick: (...args: Array) => any; -}; -const phaseToComponentMap = { - [FundPhases.SNAPSHOT]: SnapshotPhase, - [FundPhases.VOTING]: VotingPhase, - [FundPhases.TALLYING]: TallyingPhase, - [FundPhases.RESULTS]: ResultsPhase, +const phaseToComponentMap: { [key in FundPhase]: React.FC } = { + [FundPhase.SNAPSHOT]: SnapshotPhase, + [FundPhase.VOTING]: VotingPhase, + [FundPhase.TALLYING]: TallyingPhase, + [FundPhase.RESULTS]: ResultsPhase, }; const VotingInfo = ({ @@ -34,38 +25,46 @@ const VotingInfo = ({ currentDateFormat, currentTimeFormat, fundPhase, + fundInfo, onRegisterToVoteClick, onExternalLinkClick, }: Props) => { - const PhaseComponent = phaseToComponentMap[fundPhase || FundPhases.SNAPSHOT]; + const PhaseComponent = phaseToComponentMap[fundPhase]; return (

-
- -
- -
-
-
- -
+ {fundPhase === null && } + {fundPhase && ( + <> +
+ +
+ +
+
+
+ +
+ + )}
diff --git a/source/renderer/app/components/voting/voting-info/VotingPhase.tsx b/source/renderer/app/components/voting/voting-info/VotingPhase.tsx index f5eb9d964b..5135cb65ef 100644 --- a/source/renderer/app/components/voting/voting-info/VotingPhase.tsx +++ b/source/renderer/app/components/voting/voting-info/VotingPhase.tsx @@ -1,35 +1,21 @@ import React from 'react'; import { observer } from 'mobx-react'; import { injectIntl } from 'react-intl'; -import { - CURRENT_VOTING_FUND_NUMBER, - VOTING_CAST_START_DATE, - VOTING_CAST_END_DATE, -} from '../../../config/votingConfig'; import { formattedDateTime, mapToLongDateTimeFormat, } from '../../../utils/formatters'; -import type { Locale } from '../../../../../common/types/locales.types'; -import type { Intl } from '../../../types/i18nTypes'; import { messages } from './VotingPhase.messages'; import { messages as votingMessages } from './VotingInfo.messages'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './CurrentPhase.scss' or its co... Remove this comment to see the full error message import styles from './CurrentPhase.scss'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './VotingInfo.scss' or its corr... Remove this comment to see the full error message import votingStyles from './VotingInfo.scss'; - -type Props = { - currentLocale: Locale; - currentDateFormat: string; - currentTimeFormat: string; - intl: Intl; -}; +import type { PhaseIntlProps as Props } from './types'; function VotingPhase({ currentLocale, currentDateFormat, currentTimeFormat, + fundInfo, intl, }: Props) { const mappedFormats = mapToLongDateTimeFormat({ @@ -37,11 +23,11 @@ function VotingPhase({ currentDateFormat, currentTimeFormat, }); - const startDate = formattedDateTime(VOTING_CAST_START_DATE, { + const startDate = formattedDateTime(fundInfo.current.startTime, { currentLocale, currentDateFormat: mappedFormats.currentDateFormat, }); - const endDate = formattedDateTime(VOTING_CAST_END_DATE, { + const endDate = formattedDateTime(fundInfo.current.endTime, { currentLocale, currentDateFormat: mappedFormats.currentDateFormat, }); @@ -49,7 +35,7 @@ function VotingPhase({

{intl.formatMessage(votingMessages.fundName, { - votingFundNumber: CURRENT_VOTING_FUND_NUMBER, + votingFundNumber: fundInfo.current.number, })}

diff --git a/source/renderer/app/components/voting/voting-info/types.ts b/source/renderer/app/components/voting/voting-info/types.ts new file mode 100644 index 0000000000..3e5e8d6fd0 --- /dev/null +++ b/source/renderer/app/components/voting/voting-info/types.ts @@ -0,0 +1,21 @@ +import { FundPhase } from '../../../stores/VotingStore'; +import type { Intl } from '../../../types/i18nTypes'; +import type { CatalystFund } from '../../../api/voting/types'; +import type { Locale } from '../../../../../common/types/locales.types'; + +export type VotingProps = { + currentLocale: Locale; + currentDateFormat: string; + currentTimeFormat: string; + fundPhase: FundPhase; + fundInfo: CatalystFund; + onRegisterToVoteClick: (...args: Array) => any; + onExternalLinkClick: (...args: Array) => any; +}; + +export type PhaseProps = {} & Omit< + VotingProps, + 'fundPhase' | 'onRegisterToVoteClick' +>; + +export type PhaseIntlProps = { intl: Intl } & PhaseProps; diff --git a/source/renderer/app/components/voting/voting-registration-wizard-steps/VotingRegistrationStepsChooseWallet.tsx b/source/renderer/app/components/voting/voting-registration-wizard-steps/VotingRegistrationStepsChooseWallet.tsx index 234cd68dad..a184f4b30b 100644 --- a/source/renderer/app/components/voting/voting-registration-wizard-steps/VotingRegistrationStepsChooseWallet.tsx +++ b/source/renderer/app/components/voting/voting-registration-wizard-steps/VotingRegistrationStepsChooseWallet.tsx @@ -4,7 +4,6 @@ import BigNumber from 'bignumber.js'; import classNames from 'classnames'; import WalletsDropdown from '../../widgets/forms/WalletsDropdown'; import Wallet from '../../../domains/Wallet'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './VotingRegistrationStepsChoos... Remove this comment to see the full error message import styles from './VotingRegistrationStepsChooseWallet.scss'; import VotingRegistrationDialog from './widgets/VotingRegistrationDialog'; @@ -73,6 +72,7 @@ type Props = { selectedWalletId: string | null | undefined; isWalletAcceptable: (...args: Array) => any; getStakePoolById: (...args: Array) => any; + nextFundNumber: number; }; type State = { selectedWalletId: string | null | undefined; @@ -109,6 +109,7 @@ export default class VotingRegistrationStepsChooseWallet extends Component< isWalletAcceptable, numberOfStakePools, getStakePoolById, + nextFundNumber, } = this.props; const buttonLabel = intl.formatMessage(messages.continueButtonLabel); const selectedWallet: Wallet | null | undefined = wallets.find( @@ -165,6 +166,7 @@ export default class VotingRegistrationStepsChooseWallet extends Component< activeStep={activeStep} actions={actions} containerClassName={styles.component} + nextFundNumber={nextFundNumber} >

diff --git a/source/renderer/app/components/voting/voting-registration-wizard-steps/VotingRegistrationStepsConfirm.tsx b/source/renderer/app/components/voting/voting-registration-wizard-steps/VotingRegistrationStepsConfirm.tsx index 4bf86ac35b..4c1bb5a5fc 100644 --- a/source/renderer/app/components/voting/voting-registration-wizard-steps/VotingRegistrationStepsConfirm.tsx +++ b/source/renderer/app/components/voting/voting-registration-wizard-steps/VotingRegistrationStepsConfirm.tsx @@ -80,6 +80,7 @@ type Props = { | (LocalizableError | null | undefined); onConfirm: (...args: Array) => any; onRestart: (...args: Array) => any; + nextFundNumber: number; }; @observer @@ -100,6 +101,7 @@ class VotingRegistrationStepsConfirm extends Component { transactionConfirmations, transactionError, onClose, + nextFundNumber, } = this.props; const description = intl.formatMessage(messages.description); const descriptionRestart = ( @@ -154,6 +156,7 @@ class VotingRegistrationStepsConfirm extends Component { actions={actions} containerClassName={styles.component} hideSteps={!!transactionError} + nextFundNumber={nextFundNumber} > {transactionError ? ( diff --git a/source/renderer/app/components/voting/voting-registration-wizard-steps/VotingRegistrationStepsEnterPinCode.tsx b/source/renderer/app/components/voting/voting-registration-wizard-steps/VotingRegistrationStepsEnterPinCode.tsx index 72935565b7..793cadffd3 100644 --- a/source/renderer/app/components/voting/voting-registration-wizard-steps/VotingRegistrationStepsEnterPinCode.tsx +++ b/source/renderer/app/components/voting/voting-registration-wizard-steps/VotingRegistrationStepsEnterPinCode.tsx @@ -9,11 +9,7 @@ import { isValidRepeatPinCode, } from '../../../utils/validations'; import { FORM_VALIDATION_DEBOUNCE_WAIT } from '../../../config/timingConfig'; -import { - VOTING_REGISTRATION_PIN_CODE_LENGTH, - NEXT_VOTING_FUND_NUMBER, -} from '../../../config/votingConfig'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './VotingRegistrationStepsEnter... Remove this comment to see the full error message +import { VOTING_REGISTRATION_PIN_CODE_LENGTH } from '../../../config/votingConfig'; import styles from './VotingRegistrationStepsEnterPinCode.scss'; import VotingRegistrationDialog from './widgets/VotingRegistrationDialog'; @@ -65,15 +61,20 @@ type Props = { stepsList: Array; activeStep: number; onSetPinCode: (...args: Array) => any; + nextFundNumber: number; }; +interface FormFields { + pinCode: string[]; + repeatPinCode: string[]; +} + @observer class VotingRegistrationStepsEnterPinCode extends Component { static contextTypes = { intl: intlShape.isRequired, }; - form = new ReactToolboxMobxForm( - // @ts-ignore ts-migrate(2554) FIXME: Expected 0 arguments, but got 2. + form = new ReactToolboxMobxForm( { fields: { pinCode: { @@ -124,7 +125,6 @@ class VotingRegistrationStepsEnterPinCode extends Component { } ); submit = () => { - // @ts-ignore ts-migrate(2339) FIXME: Property 'submit' does not exist on type 'ReactToo... Remove this comment to see the full error message this.form.submit({ onSuccess: (form) => { const { pinCode } = form.values(); @@ -136,13 +136,11 @@ class VotingRegistrationStepsEnterPinCode extends Component { render() { const { form } = this; const { intl } = this.context; - const { onClose, stepsList, activeStep } = this.props; + const { onClose, stepsList, activeStep, nextFundNumber } = this.props; const buttonLabel = intl.formatMessage(messages.continueButtonLabel); const enterPinCodeLabel = intl.formatMessage(messages.enterPinCodeLabel); const repeatPinCodeLabel = intl.formatMessage(messages.repeatPinCodeLabel); - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const pinCodeField = form.$('pinCode'); - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const repeatPinCodeField = form.$('repeatPinCode'); const pinCodeFieldProps = pinCodeField.bind(); const repeatPinCodeFieldProps = repeatPinCodeField.bind(); @@ -150,7 +148,6 @@ class VotingRegistrationStepsEnterPinCode extends Component { { label: buttonLabel, onClick: this.submit, - // @ts-ignore ts-migrate(2339) FIXME: Property 'isValid' does not exist on type 'ReactTo... Remove this comment to see the full error message disabled: !form.isValid, primary: true, }, @@ -164,12 +161,13 @@ class VotingRegistrationStepsEnterPinCode extends Component { activeStep={activeStep} actions={actions} containerClassName={styles.component} + nextFundNumber={nextFundNumber} >

diff --git a/source/renderer/app/components/voting/voting-registration-wizard-steps/VotingRegistrationStepsQrCode.tsx b/source/renderer/app/components/voting/voting-registration-wizard-steps/VotingRegistrationStepsQrCode.tsx index ce48d1805d..d4bc826e47 100644 --- a/source/renderer/app/components/voting/voting-registration-wizard-steps/VotingRegistrationStepsQrCode.tsx +++ b/source/renderer/app/components/voting/voting-registration-wizard-steps/VotingRegistrationStepsQrCode.tsx @@ -5,7 +5,6 @@ import { set } from 'lodash'; import { observer } from 'mobx-react'; import { Checkbox } from 'react-polymorph/lib/components/Checkbox'; import VotingRegistrationDialog from './widgets/VotingRegistrationDialog'; -import { NEXT_VOTING_FUND_NUMBER } from '../../../config/votingConfig'; // @ts-ignore ts-migrate(2307) FIXME: Cannot find module './VotingRegistrationStepsQrCod... Remove this comment to see the full error message import styles from './VotingRegistrationStepsQrCode.scss'; @@ -68,6 +67,7 @@ type Props = { stepsList: Array; activeStep: number; qrCode: string | null | undefined; + nextFundNumber: number; }; type State = { isCheckbox1Accepted: boolean; @@ -96,14 +96,15 @@ class VotingRegistrationStepsQrCode extends Component { render() { const { intl } = this.context; const { isCheckbox1Accepted, isCheckbox2Accepted } = this.state; - const { stepsList, activeStep, qrCode, onDownloadPDF } = this.props; + const { stepsList, activeStep, qrCode, onDownloadPDF, nextFundNumber } = + this.props; const qrCodeTitle = intl.formatMessage(messages.qrCodeTitle); const qrCodeDescription1 = intl.formatMessage(messages.qrCodeDescription1); const qrCodeDescription2 = intl.formatMessage(messages.qrCodeDescription2); const qrCodeWarning = ; const checkbox1Label = intl.formatMessage(messages.checkbox1Label); const checkbox2Label = intl.formatMessage(messages.checkbox2Label, { - nextVotingFundNumber: NEXT_VOTING_FUND_NUMBER, + nextVotingFundNumber: nextFundNumber, }); const closeButtonLabel = intl.formatMessage(messages.closeButtonLabel); const saveAsPdfButtonLabel = intl.formatMessage( @@ -142,6 +143,7 @@ class VotingRegistrationStepsQrCode extends Component { actions={actions} containerClassName={styles.component} hideCloseButton={!areBothCheckboxesAccepted} + nextFundNumber={nextFundNumber} >
{qrCode && ( diff --git a/source/renderer/app/components/voting/voting-registration-wizard-steps/VotingRegistrationStepsRegister.tsx b/source/renderer/app/components/voting/voting-registration-wizard-steps/VotingRegistrationStepsRegister.tsx index dddb75284b..887721bc71 100644 --- a/source/renderer/app/components/voting/voting-registration-wizard-steps/VotingRegistrationStepsRegister.tsx +++ b/source/renderer/app/components/voting/voting-registration-wizard-steps/VotingRegistrationStepsRegister.tsx @@ -16,12 +16,10 @@ import ReactToolboxMobxForm from '../../../utils/ReactToolboxMobxForm'; import { formattedWalletAmount } from '../../../utils/formatters'; import { FORM_VALIDATION_DEBOUNCE_WAIT } from '../../../config/timingConfig'; import LocalizableError from '../../../i18n/LocalizableError'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './VotingRegistrationStepsRegis... Remove this comment to see the full error message import styles from './VotingRegistrationStepsRegister.scss'; import VotingRegistrationDialog from './widgets/VotingRegistrationDialog'; import Wallet, { HwDeviceStatuses } from '../../../domains/Wallet'; import HardwareWalletStatus from '../../hardware-wallet/HardwareWalletStatus'; -import { NEXT_VOTING_FUND_NUMBER } from '../../../config/votingConfig'; import type { HwDeviceStatus } from '../../../domains/Wallet'; const messages = defineMessages({ @@ -78,6 +76,7 @@ type Props = { transactionError?: LocalizableError | null | undefined; hwDeviceStatus: HwDeviceStatus; selectedWallet: Wallet | null | undefined; + nextFundNumber: number; isTrezor: boolean; isHardwareWallet: boolean; isSubmitting: boolean; @@ -87,13 +86,16 @@ type Props = { onExternalLinkClick: (...args: Array) => any; }; +interface FormFields { + spendingPassword: string; +} + @observer class VotingRegistrationStepsRegister extends Component { static contextTypes = { intl: intlShape.isRequired, }; - form = new ReactToolboxMobxForm( - // @ts-ignore ts-migrate(2554) FIXME: Expected 0 arguments, but got 2. + form = new ReactToolboxMobxForm( { fields: { spendingPassword: { @@ -133,7 +135,6 @@ class VotingRegistrationStepsRegister extends Component { } ); submit = () => { - // @ts-ignore ts-migrate(2339) FIXME: Property 'values' does not exist on type 'ReactToo... Remove this comment to see the full error message const { spendingPassword } = this.form.values(); this.props.onConfirm(spendingPassword); }; @@ -156,8 +157,8 @@ class VotingRegistrationStepsRegister extends Component { selectedWallet, isTrezor, isHardwareWallet, + nextFundNumber, } = this.props; - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const spendingPasswordField = form.$('spendingPassword'); const buttonLabel = intl.formatMessage(messages.continueButtonLabel); const learnMoreLinkUrl = intl.formatMessage(messages.learntMoreLinkUrl); @@ -185,12 +186,13 @@ class VotingRegistrationStepsRegister extends Component { actions={actions} onBack={onBack} containerClassName={styles.component} + nextFundNumber={nextFundNumber} >

diff --git a/source/renderer/app/components/voting/voting-registration-wizard-steps/widgets/ConfirmationDialog.tsx b/source/renderer/app/components/voting/voting-registration-wizard-steps/widgets/ConfirmationDialog.tsx index f607bd00d7..b818c91e86 100644 --- a/source/renderer/app/components/voting/voting-registration-wizard-steps/widgets/ConfirmationDialog.tsx +++ b/source/renderer/app/components/voting/voting-registration-wizard-steps/widgets/ConfirmationDialog.tsx @@ -3,7 +3,6 @@ import { observer } from 'mobx-react'; import classnames from 'classnames'; import { defineMessages, intlShape } from 'react-intl'; import Dialog from '../../../widgets/Dialog'; -import { NEXT_VOTING_FUND_NUMBER } from '../../../../config/votingConfig'; // @ts-ignore ts-migrate(2307) FIXME: Cannot find module './ConfirmationDialog.scss' or ... Remove this comment to see the full error message import styles from './ConfirmationDialog.scss'; @@ -35,6 +34,7 @@ const messages = defineMessages({ }, }); type Props = { + nextFundNumber: number; onConfirm: (...args: Array) => any; onCancel: (...args: Array) => any; }; @@ -47,12 +47,8 @@ class ConfirmationDialog extends Component { render() { const { intl } = this.context; - const { onConfirm, onCancel } = this.props; + const { nextFundNumber, onConfirm, onCancel } = this.props; const dialogClasses = classnames([styles.component, 'ConfirmDialog']); - const confirmButtonClasses = classnames([ - 'confirmButton', // 'attention', - styles.confirmButton, - ]); const actions = [ { className: 'cancelButton', @@ -60,7 +56,7 @@ class ConfirmationDialog extends Component { onClick: onCancel, }, { - className: confirmButtonClasses, + className: 'confirmButton', label: intl.formatMessage(messages.confirmButtonLabel), primary: true, onClick: onConfirm, @@ -70,7 +66,7 @@ class ConfirmationDialog extends Component { { >

{intl.formatMessage(messages.content, { - nextVotingFundNumber: NEXT_VOTING_FUND_NUMBER, + nextVotingFundNumber: nextFundNumber, })}

diff --git a/source/renderer/app/components/voting/voting-registration-wizard-steps/widgets/VotingRegistrationDialog.tsx b/source/renderer/app/components/voting/voting-registration-wizard-steps/widgets/VotingRegistrationDialog.tsx index cb41e79d2e..a193e99bfd 100644 --- a/source/renderer/app/components/voting/voting-registration-wizard-steps/widgets/VotingRegistrationDialog.tsx +++ b/source/renderer/app/components/voting/voting-registration-wizard-steps/widgets/VotingRegistrationDialog.tsx @@ -12,7 +12,6 @@ import Dialog from '../../../widgets/Dialog'; import DialogCloseButton from '../../../widgets/DialogCloseButton'; import DialogBackButton from '../../../widgets/DialogBackButton'; import type { DialogActions } from '../../../widgets/Dialog'; -import { NEXT_VOTING_FUND_NUMBER } from '../../../../config/votingConfig'; const messages = defineMessages({ dialogTitle: { @@ -37,6 +36,7 @@ type Props = { contentClassName?: string | null | undefined; hideCloseButton?: boolean; hideSteps?: boolean; + nextFundNumber: number; }; @observer @@ -61,6 +61,7 @@ class VotingRegistrationDialog extends Component { contentClassName, hideCloseButton, hideSteps, + nextFundNumber, } = this.props; const containerStyles = classnames([styles.container, containerClassName]); const contentStyles = classnames([styles.content, contentClassName]); @@ -77,7 +78,7 @@ class VotingRegistrationDialog extends Component { { static contextTypes = { @@ -112,8 +116,7 @@ class WalletCreateDialog extends Component { } walletNameInput: Input; - form = new ReactToolboxMobxForm( - // @ts-ignore ts-migrate(2554) FIXME: Expected 0 arguments, but got 2. + form = new ReactToolboxMobxForm( { fields: { walletName: { @@ -188,7 +191,6 @@ class WalletCreateDialog extends Component { this.setState({ isSubmitting: false, }); - // @ts-ignore ts-migrate(2339) FIXME: Property 'submit' does not exist on type 'ReactToo... Remove this comment to see the full error message this.form.submit({ onSuccess: (form) => { this.setState({ @@ -223,13 +225,9 @@ class WalletCreateDialog extends Component { styles.spendingPasswordField, currentLocale === 'ja-JP' ? styles.jpLangTooltipIcon : '', ]); - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const walletNameField = form.$('walletName'); - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const spendingPasswordField = form.$('spendingPassword'); - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const repeatedPasswordField = form.$('repeatPassword'); - // @ts-ignore ts-migrate(2339) FIXME: Property 'isValid' does not exist on type 'ReactTo... Remove this comment to see the full error message const canSubmit = !isSubmitting && form.isValid; const buttonLabel = !isSubmitting ? ( this.context.intl.formatMessage(messages.createPersonalWallet) diff --git a/source/renderer/app/components/wallet/WalletRestoreDialog.tsx b/source/renderer/app/components/wallet/WalletRestoreDialog.tsx index c46bef931c..6c7e7a3ff5 100644 --- a/source/renderer/app/components/wallet/WalletRestoreDialog.tsx +++ b/source/renderer/app/components/wallet/WalletRestoreDialog.tsx @@ -25,7 +25,6 @@ import { import globalMessages from '../../i18n/global-messages'; import LocalizableError from '../../i18n/LocalizableError'; import { FORM_VALIDATION_DEBOUNCE_WAIT } from '../../config/timingConfig'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './WalletRestoreDialog.scss' or... Remove this comment to see the full error message import styles from './WalletRestoreDialog.scss'; import { submitOnEnter } from '../../utils/form'; import { @@ -37,7 +36,6 @@ import { WALLET_RECOVERY_PHRASE_WORD_COUNT, YOROI_WALLET_RECOVERY_PHRASE_WORD_COUNT, } from '../../config/cryptoConfig'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../assets/images/info-icon.... Remove this comment to see the full error message import infoIconInline from '../../assets/images/info-icon.inline.svg'; import LoadingSpinner from '../widgets/LoadingSpinner'; @@ -209,6 +207,13 @@ type State = { walletType: string; }; +interface FormFields { + repeatPassword: string; + spendingPassword: string; + recoveryPhrase: string; + walletName: string; +} + @observer class WalletRestoreDialog extends Component { static contextTypes = { @@ -228,8 +233,7 @@ class WalletRestoreDialog extends Component { } } - form = new ReactToolboxMobxForm( - // @ts-ignore ts-migrate(2554) FIXME: Expected 0 arguments, but got 2. + form = new ReactToolboxMobxForm( { fields: { walletName: { @@ -327,7 +331,6 @@ class WalletRestoreDialog extends Component { } ); submit = () => { - // @ts-ignore ts-migrate(2339) FIXME: Property 'submit' does not exist on type 'ReactToo... Remove this comment to see the full error message this.form.submit({ onSuccess: (form) => { const { onSubmit } = this.props; @@ -350,17 +353,13 @@ class WalletRestoreDialog extends Component { resetForm = () => { const { form } = this; // Cancel all debounced field validations - // @ts-ignore ts-migrate(2339) FIXME: Property 'each' does not exist on type 'ReactToolb... Remove this comment to see the full error message form.each((field) => { field.debouncedValidation.cancel(); }); - // @ts-ignore ts-migrate(2339) FIXME: Property 'reset' does not exist on type 'ReactTool... Remove this comment to see the full error message form.reset(); - // @ts-ignore ts-migrate(2339) FIXME: Property 'showErrors' does not exist on type 'Reac... Remove this comment to see the full error message form.showErrors(false); }; resetMnemonics = () => { - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const recoveryPhraseField = this.form.$('recoveryPhrase'); recoveryPhraseField.debouncedValidation.cancel(); recoveryPhraseField.reset(); @@ -374,22 +373,14 @@ class WalletRestoreDialog extends Component { const { form } = this; const { walletType } = this.state; const { suggestedMnemonics, isSubmitting, error, onCancel } = this.props; - const dialogClasses = classnames([ - styles.component, - styles.dialogWithCertificateRestore, - 'WalletRestoreDialog', - ]); + const dialogClasses = classnames([styles.component, 'WalletRestoreDialog']); const walletNameFieldClasses = classnames([ 'walletName', styles.walletName, ]); - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const walletNameField = form.$('walletName'); - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const recoveryPhraseField = form.$('recoveryPhrase'); - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const spendingPasswordField = form.$('spendingPassword'); - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const repeatedPasswordField = form.$('repeatPassword'); const label = this.isCertificate() ? this.context.intl.formatMessage(messages.restorePaperWalletButtonLabel) diff --git a/source/renderer/app/components/wallet/WalletSendForm.scss b/source/renderer/app/components/wallet/WalletSendForm.scss index a3cb966490..de5a2aaade 100755 --- a/source/renderer/app/components/wallet/WalletSendForm.scss +++ b/source/renderer/app/components/wallet/WalletSendForm.scss @@ -52,9 +52,11 @@ } .estimatedFeeInput { + margin-bottom: 30px; + input { background-color: var(--rp-input-bg-color-disabled) !important; - border-color: var(--rp-input-bg-color-disabled) !important; + border-color: var(--rp-input-border-color-disabled) !important; font-family: var(--font-regular); pointer-events: none; @@ -62,13 +64,41 @@ font-family: var(--font-regular); } } + + &.withOffset { + margin-bottom: 20px; + } +} + +.minimumAmountNotice { + align-items: center; + background-color: var(--rp-input-bg-color-disabled); + border-radius: 4px; + color: var(--theme-button-flat-text-color); + display: flex; + font-family: var(--font-light); + font-style: italic; + line-height: 22px; + margin: 20px 0 30px 0; + padding: 14px 20px; + width: 100%; + + b { + font-family: var(--font-medium); + font-weight: normal; + } } .assetInput, .receiverInput, -.estimatedFeeInput, .depositInput { margin-bottom: 20px; +} + +.assetInput, +.receiverInput, +.estimatedFeeInput, +.depositInput { position: relative; :global { @@ -99,34 +129,6 @@ padding-left: 20px; position: absolute; right: 10px; - - .clearReceiverButton { - background-color: var( - --theme-staking-stake-pools-search-clear-button-background-color - ); - border-radius: 3px; - cursor: pointer; - height: 28px; - width: 28px; - - &:hover { - background-color: var( - --theme-staking-stake-pools-search-clear-button-hover-background-color - ); - } - - .clearReceiverIcon { - > svg { - position: relative; - top: 0.5px; - width: 10px; - - > g > g { - fill: var(--theme-staking-stake-pools-search-clear-button-color); - } - } - } - } } &.sameReceiverInput { @@ -136,6 +138,24 @@ } } +.adaInput { + position: relative; + + .clearAdaContainer { + align-items: center; + bottom: 0; + display: flex; + height: 48px; + padding-left: 20px; + position: absolute; + right: 50px; + } + + .dividerContainer { + margin: 0px; + } +} + .estimatedFeeInput, .depositInput { :global { @@ -189,9 +209,15 @@ color: var(--theme-input-placeholder-color); font-family: var(--font-regular); font-size: 14px; + height: 20px; line-height: 1.36; - padding-bottom: 10px; - padding-top: 10px; + margin: 10px 0; + + & > span { + align-items: center; + display: inline-flex; + height: 100%; + } } .addAssetButton { @@ -201,6 +227,7 @@ height: 20px; letter-spacing: 0.5px; line-height: 1.2; + margin-right: 6px; margin-top: 0; padding: 0 8px; text-transform: uppercase; diff --git a/source/renderer/app/components/wallet/WalletSendForm.spec.tsx b/source/renderer/app/components/wallet/WalletSendForm.spec.tsx new file mode 100644 index 0000000000..ca61fbf5f1 --- /dev/null +++ b/source/renderer/app/components/wallet/WalletSendForm.spec.tsx @@ -0,0 +1,418 @@ +import React, { useState } from 'react'; +import { addLocaleData } from 'react-intl'; +import BigNumber from 'bignumber.js'; +import { Provider as MobxProvider } from 'mobx-react'; +import faker from 'faker'; +import { + render, + fireEvent, + screen, + cleanup, + within, + waitForElementToBeRemoved, +} from '@testing-library/react'; +import '@testing-library/jest-dom'; +import en from 'react-intl/locale-data/en'; +// Assets and helpers +import { TestDecorator } from '../../../../../tests/_utils/TestDecorator'; +import { NUMBER_OPTIONS } from '../../config/profileConfig'; +import { DiscreetModeFeatureProvider } from '../../features/discreet-mode'; +import { BrowserLocalStorageBridge } from '../../features/local-storage'; +import { HwDeviceStatuses } from '../../domains/Wallet'; +import WalletTokenPicker from './tokens/wallet-token-picker/WalletTokenPicker'; +import WalletSendForm from './WalletSendForm'; + +describe('wallet/Wallet Send Form', () => { + beforeEach(() => addLocaleData([...en])); + afterEach(cleanup); + const currencyMaxFractionalDigits = 6; + + function createAssets(index: number) { + const id = `${faker.random.uuid()}:${index}`; + return { + policyId: id, + assetName: faker.internet.domainWord(), + uniqueId: id, + fingerprint: faker.random.uuid(), + quantity: new BigNumber(faker.finance.amount()), + decimals: 0, + recommendedDecimals: null, + metadata: { + name: id, + ticker: faker.finance.currencyCode(), + description: '', + }, + }; + } + + const assets = [createAssets(0), createAssets(1)]; + + function SetupWallet({ + calculateTransactionFee, + currentNumberFormat = NUMBER_OPTIONS[0].value, + }: { + calculateTransactionFee: (...args: Array) => any; + currentNumberFormat?: string; + }) { + const [tokenPickerOpen, setTokenPickerOpen] = useState(false); + + return ( + + + + + true} + onSubmit={jest.fn()} + isDialogOpen={(dialog) => + dialog === WalletTokenPicker && tokenPickerOpen + } + isRestoreActive={false} + hwDeviceStatus={HwDeviceStatuses.READY} + isHardwareWallet={false} + isLoadingAssets={false} + onExternalLinkClick={jest.fn()} + hasAssets + selectedAsset={null} + onUnsetActiveAsset={() => {}} + isAddressFromSameWallet={false} + tokenFavorites={{}} + walletName={faker.name.firstName()} + onTokenPickerDialogClose={() => setTokenPickerOpen(false)} + onTokenPickerDialogOpen={() => setTokenPickerOpen(true)} + /> + + + + + ); + } + + function enterReceiverAddress() { + const address = + 'addr_test1qrjzmxr4x7vhlusn05fd4lt7cs6dy8wtcv6vaf9lff7m9yqkw6whlsg36t3laez562llhkvfy5tny4p9y8zrspe48vgsea3q6m'; + const receiverAddress = screen.getByPlaceholderText('Paste an address'); + fireEvent.change(receiverAddress, { + target: { + value: address, + }, + }); + } + + function getInput(label: string) { + return screen.getByLabelText(label); + } + + async function findInput(label: string) { + return screen.findByLabelText(label); + } + + async function addToken(value = 1, tokenIndex = 0) { + const addTokenButton = await screen.findByText('+ Add a token'); + fireEvent.click(addTokenButton); + const tokenPicker = await screen.findByTestId('WalletTokenPicker'); + const tokenCheckbox = tokenPicker.querySelectorAll( + 'input[type="checkbox"]' + )[tokenIndex]; + fireEvent.click(tokenCheckbox); + const addTokenPickerButton = await screen.findByText('Add'); + fireEvent.click(addTokenPickerButton); + + const { uniqueId } = assets[tokenIndex]; + + const token = await screen.findByTestId(`assetInput:${uniqueId}`); + fireEvent.change(token, { + target: { + value, + }, + }); + return async () => { + fireEvent.mouseEnter(token); + const removeTokenButton = await screen.findByTestId( + `removeAsset:${uniqueId}` + ); + fireEvent.click(removeTokenButton); + }; + } + + async function waitForMinimumAdaRequiredMsg(minimumAda = 2) { + const minimumAdaRequiredMsg = screen.getByTestId('minimumAdaRequiredMsg'); + await within(minimumAdaRequiredMsg).findByText( + `a minimum of ${minimumAda} ADA required` + ); + } + + function assertAdaInput(amount: number) { + const adaInput = getInput('Ada'); + expect(adaInput).toHaveValue( + new BigNumber(amount).toFormat(currencyMaxFractionalDigits) + ); + } + + function createTransactionFeeMock(times: number, minimumAda: number) { + const mock = jest.fn().mockResolvedValue({ + fee: new BigNumber(1), + minimumAda: new BigNumber(1), + }); + Array.from({ + length: times, + }).forEach(() => { + // @ts-ignore + mock.mockResolvedValueOnce({ + fee: new BigNumber(1), + minimumAda: new BigNumber(minimumAda), + }); + }); + return mock; + } + + const MINIMUM_AMOUNT_UPDATED_MESSAGE_TEST_ID = + 'WalletSendForm::minimumAmountNotice::updated'; + + function assertMinimumAmountNoticeMessage(minimumAda: number) { + expect( + screen.getByTestId(MINIMUM_AMOUNT_UPDATED_MESSAGE_TEST_ID) + ).toHaveTextContent( + `Note: the ada field was automatically updated because this transaction requires a minimum of ${minimumAda} ADA.` + ); + } + + test('should update Ada input field to minimum required and restore to original value when tokens are removed', async () => { + expect.assertions(4); + + const minimumAda = 2; + const calculateTransactionFeeMock = createTransactionFeeMock(1, minimumAda); + + render( + + ); + + enterReceiverAddress(); + + const removeToken1 = await addToken(); + + await waitForMinimumAdaRequiredMsg(); + assertAdaInput(minimumAda); + + const minimumAmountNoticeTestId = + 'WalletSendForm::minimumAmountNotice::updated'; + + expect(screen.getByTestId(minimumAmountNoticeTestId)).toHaveTextContent( + `Note: the ada field was automatically updated because this transaction requires a minimum of ${minimumAda} ADA.` + ); + + await removeToken1(); + + await waitForMinimumAdaRequiredMsg(1); + + assertAdaInput(1); + + expect(screen.queryByTestId(minimumAmountNoticeTestId)).toBeInTheDocument(); + }); + + test('should display an update button when Ada input field is less than minimum required', async () => { + expect.assertions(3); + const minimumAda = 2; + const calculateTransactionFeeMock = createTransactionFeeMock(2, minimumAda); + render( + + ); + enterReceiverAddress(); + await addToken(); + await waitForMinimumAdaRequiredMsg(); + assertAdaInput(minimumAda); + const lowerAdaValue = 1.5; + const adaInput = getInput('Ada'); + fireEvent.change(adaInput, { + target: { + value: lowerAdaValue, + }, + }); + const minimumAdaRequiredMsg = screen.getByTestId('minimumAdaRequiredMsg'); + const updateButton = await within(minimumAdaRequiredMsg).findByText( + 'UPDATE' + ); + assertAdaInput(lowerAdaValue); + fireEvent.click(updateButton); + await waitForElementToBeRemoved(updateButton); + assertAdaInput(minimumAda); + }); + + test('should favour user Ada input instead of minimum required when the value is greater than the minimum one', async () => { + expect.assertions(2); + const minimumAda = 2.5; + const calculateTransactionFeeMock = createTransactionFeeMock(2, minimumAda); + render( + + ); + enterReceiverAddress(); + const userAdaAmount = 1.5; + const adaInput = await findInput('Ada'); + fireEvent.change(adaInput, { + target: { + value: userAdaAmount, + }, + }); + const removeToken1 = await addToken(); + await waitForMinimumAdaRequiredMsg(minimumAda); + assertAdaInput(minimumAda); + await removeToken1(); + const minimumAmountNotice = await screen.findByTestId( + 'WalletSendForm::minimumAmountNotice::restored' + ); + expect(minimumAmountNotice).toHaveTextContent( + `Note: the ada field was automatically updated to ${userAdaAmount} ADA because now it fulfills the minimum amount of 1 ADA for the transaction.` + ); + }); + + test('should remove message when user entry is higher than minimum amount', async () => { + expect.assertions(3); + const minimumAda = 2.5; + const calculateTransactionFeeMock = createTransactionFeeMock(2, minimumAda); + render( + + ); + enterReceiverAddress(); + const userAdaAmount = 1.5; + const adaInput = await findInput('Ada'); + fireEvent.change(adaInput, { + target: { + value: userAdaAmount, + }, + }); + const removeToken1 = await addToken(); + await waitForMinimumAdaRequiredMsg(minimumAda); + assertAdaInput(minimumAda); + await removeToken1(); + const minimumAmountNoticeTestId = + 'WalletSendForm::minimumAmountNotice::restored'; + const minimumAmountNotice = await screen.findByTestId( + minimumAmountNoticeTestId + ); + expect(minimumAmountNotice).toBeInTheDocument(); + fireEvent.change(adaInput, { + target: { + value: userAdaAmount + 1, + }, + }); + expect( + screen.queryByTestId(minimumAmountNoticeTestId) + ).not.toBeInTheDocument(); + }); + + test('should not display any minimum amount notice message when ada input is greater than minimum amount', async () => { + expect.assertions(2); + const calculateTransactionFeeMock = jest.fn().mockResolvedValue({ + fee: new BigNumber(1), + minimumAda: new BigNumber(2), + }); + render( + + ); + enterReceiverAddress(); + const userAdaAmount = 3.5; + const adaInput = await findInput('Ada'); + fireEvent.change(adaInput, { + target: { + value: userAdaAmount, + }, + }); + await addToken(); + await waitForMinimumAdaRequiredMsg(); + assertAdaInput(userAdaAmount); + await expect( + screen.findByTestId('WalletSendForm::minimumAmountNotice::restored') + ).rejects.toBeTruthy(); + }); + + test('should apply minimum fee to ada field when user has removed the previous update', async () => { + expect.assertions(3); + const minimumAda = 2; + const calculateTransactionFeeMock = createTransactionFeeMock(4, minimumAda); + render( + + ); + enterReceiverAddress(); + const removeToken1 = await addToken(); + await waitForMinimumAdaRequiredMsg(); + assertAdaInput(minimumAda); + const adaInput = await findInput('Ada'); + fireEvent.change(adaInput, { + target: { + value: '', + }, + }); + await removeToken1(); + expect(adaInput).toHaveValue(''); + await addToken(); + await waitForMinimumAdaRequiredMsg(); + assertAdaInput(minimumAda); + }); + + test('should format ada input field using numeric format profile', async () => { + expect.assertions(1); + const minimumAda = 2; + const calculateTransactionFeeMock = createTransactionFeeMock(4, minimumAda); + render( + + ); + enterReceiverAddress(); + await addToken(); + await waitForMinimumAdaRequiredMsg(); + const adaInput = getInput('Ada'); + expect(adaInput).toHaveValue(`${minimumAda},000000`); + }); + + test('should calculate transaction fee even when one of the assets are empty', async () => { + expect.assertions(2); + const minimumAda = 2; + const calculateTransactionFeeMock = createTransactionFeeMock(4, minimumAda); + render( + + ); + enterReceiverAddress(); + await addToken(0); + await waitForMinimumAdaRequiredMsg(1); + expect(getInput('Ada')).toHaveValue(''); + await addToken(minimumAda, 1); + await waitForMinimumAdaRequiredMsg(); + assertAdaInput(minimumAda); + }); + + test('should keep transaction fee when assets are removed and ada field is untouched', async () => { + expect.assertions(3); + + const fee = 2; + const minimumAda = 1; + const calculateTransactionFeeMock = createTransactionFeeMock(1, fee); + + render( + + ); + + enterReceiverAddress(); + + const removeToken = await addToken(); + await waitForMinimumAdaRequiredMsg(); + + assertAdaInput(fee); + + await removeToken(); + + await waitForMinimumAdaRequiredMsg(minimumAda); + + assertAdaInput(minimumAda); + assertMinimumAmountNoticeMessage(minimumAda); + }); +}); diff --git a/source/renderer/app/components/wallet/WalletSendForm.tsx b/source/renderer/app/components/wallet/WalletSendForm.tsx index 4f5cd77211..853687d1dd 100755 --- a/source/renderer/app/components/wallet/WalletSendForm.tsx +++ b/source/renderer/app/components/wallet/WalletSendForm.tsx @@ -4,7 +4,7 @@ import type { Node } from 'react'; import type { Field } from 'mobx-react-form'; import { observer } from 'mobx-react'; import { intlShape, FormattedHTMLMessage } from 'react-intl'; -import { filter, get, indexOf, omit, map, without } from 'lodash'; +import { filter, get, indexOf, omit, map, without, isEmpty } from 'lodash'; import BigNumber from 'bignumber.js'; import classNames from 'classnames'; import SVGInline from 'react-svg-inline'; @@ -17,10 +17,7 @@ import BorderedBox from '../widgets/BorderedBox'; import LoadingSpinner from '../widgets/LoadingSpinner'; import ReadOnlyInput from '../widgets/forms/ReadOnlyInput'; import { FormattedHTMLMessageWithLink } from '../widgets/FormattedHTMLMessageWithLink'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../assets/images/question-m... Remove this comment to see the full error message import questionMarkIcon from '../../assets/images/question-mark.inline.svg'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../assets/images/close-cros... Remove this comment to see the full error message -import closeIcon from '../../assets/images/close-cross.inline.svg'; import globalMessages from '../../i18n/global-messages'; import messages from './send-form/messages'; import { messages as apiErrorMessages } from '../../api/errors'; @@ -35,14 +32,25 @@ import { NUMBER_FORMATS } from '../../../../common/types/number.types'; import AssetInput from './send-form/AssetInput'; import WalletSendAssetsConfirmationDialog from './send-form/WalletSendAssetsConfirmationDialog'; import WalletSendConfirmationDialogContainer from '../../containers/wallet/dialogs/WalletSendConfirmationDialogContainer'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './WalletSendForm.scss' or its ... Remove this comment to see the full error message import styles from './WalletSendForm.scss'; import Asset from '../../domains/Asset'; import type { HwDeviceStatus } from '../../domains/Wallet'; import type { AssetToken, ApiTokens } from '../../api/assets/types'; +import type { ReactIntlMessage } from '../../types/i18nTypes'; import { DiscreetWalletAmount } from '../../features/discreet-mode'; +import WalletTokenPicker from './tokens/wallet-token-picker/WalletTokenPicker'; +import { ClearButton } from './widgets/ClearButton'; +import { Divider } from './widgets/Divider'; messages.fieldIsRequired = globalMessages.fieldIsRequired; +type AdaInputState = 'restored' | 'updated' | 'reset' | 'none'; +// @ts-ignore ts-migrate(2304) FIXME: Cannot find name 'EnumMap'. +const AdaInputStateType: EnumMap = { + Restored: 'restored', + Updated: 'updated', + None: 'none', + Reset: 'reset', +}; type Props = { currencyMaxIntegerDigits: number; currencyMaxFractionalDigits: number; @@ -60,11 +68,22 @@ type Props = { isRestoreActive: boolean; isHardwareWallet: boolean; hwDeviceStatus: HwDeviceStatus; - onOpenDialogAction: (...args: Array) => any; + onSubmit: (...args: Array) => any; onUnsetActiveAsset: (...args: Array) => any; onExternalLinkClick: (...args: Array) => any; isAddressFromSameWallet: boolean; + tokenFavorites: Record; + walletName: string; + onTokenPickerDialogOpen: (...args: Array) => any; + onTokenPickerDialogClose: (...args: Array) => any; }; + +interface FormFields { + receiver: string; + adaAmount: string; + [assets: string]: string; +} + type State = { formFields: { receiver: { @@ -75,14 +94,17 @@ type State = { }; }; minimumAda: BigNumber; + adaAmountInputTrack: BigNumber; feeCalculationRequestQue: number; transactionFee: BigNumber; transactionFeeError: (string | null | undefined) | (Node | null | undefined); - showRemoveAssetButton: Record; selectedAssetUniqueIds: Array; isResetButtonDisabled: boolean; isReceiverAddressValid: boolean; + isReceiverAddressValidOnce: boolean; isTransactionFeeCalculated: boolean; + isCalculatingTransactionFee: boolean; + adaInputState: AdaInputState; }; @observer @@ -90,25 +112,21 @@ class WalletSendForm extends Component { static contextTypes = { intl: intlShape.isRequired, }; - // @ts-ignore ts-migrate(2416) FIXME: Property 'state' in type 'WalletSendForm' is not a... Remove this comment to see the full error message state = { - formFields: {}, + formFields: {} as State['formFields'], minimumAda: new BigNumber(0), + adaAmountInputTrack: new BigNumber(0), feeCalculationRequestQue: 0, transactionFee: new BigNumber(0), transactionFeeError: null, - showRemoveAssetButton: {}, selectedAssetUniqueIds: [], isResetButtonDisabled: true, isReceiverAddressValid: false, + isReceiverAddressValidOnce: false, isTransactionFeeCalculated: false, + isCalculatingTransactionFee: false, + adaInputState: AdaInputStateType.None, }; - // We need to track the fee calculation state in order to disable - // the "Submit" button as soon as either receiver or amount field changes. - // This is required as we are using debounced validation and we need to - // disable the "Submit" button as soon as the value changes and then wait for - // the validation to end in order to see if the button should be enabled or not. - _isCalculatingTransactionFee = false; // We need to track the mounted state in order to avoid calling // setState promise handling code after the component was already unmounted: // Read more: https://facebook.github.io/react/blog/2015/12/16/ismounted-antipattern.html @@ -174,7 +192,7 @@ class WalletSendForm extends Component { return allAssets.find((asset) => asset.uniqueId === uniqueId); }; focusableFields: Record = {}; - addFocusableField = (field: Field | null | undefined) => { + addFocusableField = (field: Input | null | undefined) => { if (field) { const { name: fieldName } = field.props; this.focusableFields[fieldName] = field; @@ -189,49 +207,46 @@ class WalletSendForm extends Component { } }; handleSubmitOnEnter = (event: KeyboardEvent): void => { - if (event.target instanceof HTMLInputElement && event.key === 'Enter') - this.handleOnSubmit(); + if (event.target instanceof HTMLInputElement && event.key === 'Enter') { + setTimeout(() => { + this.handleOnSubmit(); + }, FORM_VALIDATION_DEBOUNCE_WAIT); + } }; handleOnSubmit = () => { if (this.isDisabled()) { return; } - - this.props.onOpenDialogAction({ - dialog: WalletSendAssetsConfirmationDialog, - }); + this.props.onSubmit(); }; handleOnReset = () => { // Cancel all debounced field validations - // @ts-ignore ts-migrate(2339) FIXME: Property 'each' does not exist on type 'ReactToolb... Remove this comment to see the full error message this.form.each((field) => { field.debouncedValidation.cancel(); }); - // @ts-ignore ts-migrate(2339) FIXME: Property 'reset' does not exist on type 'ReactTool... Remove this comment to see the full error message this.form.reset(); - // @ts-ignore ts-migrate(2339) FIXME: Property 'showErrors' does not exist on type 'Reac... Remove this comment to see the full error message this.form.showErrors(false); this.clearReceiverFieldValue(); this.clearAdaAmountFieldValue(); this.updateFormFields(true); this.setState({ minimumAda: new BigNumber(0), - showRemoveAssetButton: {}, + adaAmountInputTrack: new BigNumber(0), isResetButtonDisabled: true, + adaInputState: AdaInputStateType.None, + isReceiverAddressValidOnce: false, }); }; clearReceiverFieldValue = () => { - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const receiverField = this.form.$('receiver'); if (receiverField) { - receiverField.clear(); + receiverField.onChange(''); this.setReceiverValidity(false); this.focusField(receiverField); } }; clearAdaAmountFieldValue = () => { - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const adaAmountField = this.form.$('adaAmount'); if (adaAmountField) { @@ -247,7 +262,6 @@ class WalletSendForm extends Component { this.resetTransactionFee(); }; updateFormFields = (resetFormFields: boolean, uniqueId?: string) => { - // @ts-ignore ts-migrate(2339) FIXME: Property 'fields' does not exist on type 'ReactToo... Remove this comment to see the full error message const formFields = this.form.fields; const receiverField = formFields.get('receiver'); const adaAmountField = formFields.get('adaAmount'); @@ -265,7 +279,6 @@ class WalletSendForm extends Component { }, }); } else if (uniqueId) { - // @ts-ignore ts-migrate(2339) FIXME: Property 'receiver' does not exist on type '{}'. const { assetFields, assetsDropdown } = this.state.formFields.receiver; const assetField = formFields.get(`asset_${uniqueId}`); @@ -292,13 +305,15 @@ class WalletSendForm extends Component { } }; hasReceiverValue = () => { - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const receiverField = this.form.$('receiver'); return receiverField.value.length > 0; }; + hasAdaAmountValue = () => { + const adaAmountField = this.form.$('adaAmount'); + return adaAmountField.value.length > 0; + }; isAddressFromSameWallet = () => { const { isAddressFromSameWallet } = this.props; - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const receiverField = this.form.$('receiver'); return ( this.hasReceiverValue() && @@ -307,12 +322,11 @@ class WalletSendForm extends Component { ); }; isDisabled = () => - this._isCalculatingTransactionFee || + this.state.isCalculatingTransactionFee || !this.state.isTransactionFeeCalculated || - // @ts-ignore ts-migrate(2339) FIXME: Property 'isValid' does not exist on type 'ReactTo... Remove this comment to see the full error message - !this.form.isValid; - form = new ReactToolboxMobxForm( - // @ts-ignore ts-migrate(2554) FIXME: Expected 0 arguments, but got 2. + !this.form.isValid || + this.form.validating; + form = new ReactToolboxMobxForm( { fields: { receiver: { @@ -343,7 +357,7 @@ class WalletSendForm extends Component { const isAdaAmountValid = adaAmountField.isValid; if (isValid && isAdaAmountValid) { - this.calculateTransactionFee(); + await this.calculateTransactionFee(); } else { this.resetTransactionFee(); } @@ -381,7 +395,7 @@ class WalletSendForm extends Component { ); if (isValid) { - this.calculateTransactionFee(); + await this.calculateTransactionFee(); } else { this.resetTransactionFee(); } @@ -416,9 +430,10 @@ class WalletSendForm extends Component { setReceiverValidity(isValid: boolean) { if (this._isMounted) { - this.setState({ + this.setState(({ isReceiverAddressValidOnce }) => ({ isReceiverAddressValid: isValid, - }); + isReceiverAddressValidOnce: isValid || isReceiverAddressValidOnce, + })); } } @@ -426,37 +441,40 @@ class WalletSendForm extends Component { currentFeeCalculationRequestQue: number, prevFeeCalculationRequestQue: number ) => currentFeeCalculationRequestQue - prevFeeCalculationRequestQue === 1; - calculateTransactionFee = async () => { - const { form } = this; - const emptyAssetFieldValue = '0'; - const hasEmptyAssetFields = - this.selectedAssetsAmounts.includes(emptyAssetFieldValue); - - // @ts-ignore ts-migrate(2339) FIXME: Property 'isValid' does not exist on type 'ReactTo... Remove this comment to see the full error message - if (!form.isValid || hasEmptyAssetFields) { - // @ts-ignore ts-migrate(2339) FIXME: Property 'showErrors' does not exist on type 'Reac... Remove this comment to see the full error message - form.showErrors(true); + validateEmptyAssets = () => { + return this.selectedAssets + .filter((_, index) => { + const quantity = new BigNumber(this.selectedAssetsAmounts[index]); + return quantity.isZero(); + }) + .forEach(({ uniqueId }) => { + this.form.$(`asset_${uniqueId}`).validate({ + showErrors: true, + }); + }); + }; + calculateTransactionFee = async (shouldUpdateMinimumAdaAmount = false) => { + if (!this.state.isReceiverAddressValid) { return; } - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message + this.validateEmptyAssets(); + const { form } = this; const receiverField = form.$('receiver'); const receiver = receiverField.value; - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const adaAmountField = form.$('adaAmount'); const adaAmount = formattedAmountToLovelace(adaAmountField.value); // @ts-ignore ts-migrate(2322) FIXME: Type '{ policy_id: string; asset_name: string; qua... Remove this comment to see the full error message - const assets: ApiTokens = filter( - this.selectedAssets.map(({ policyId, assetName }, index) => { + const assets: ApiTokens = this.selectedAssets + .map(({ policyId, assetName }, index) => { const quantity = new BigNumber(this.selectedAssetsAmounts[index]); return { policy_id: policyId, asset_name: assetName, quantity, // BigNumber or number - prevent parsing a BigNumber to Number (Integer) because of JS number length limitation }; - }), - 'quantity' - ); + }) + .filter(({ quantity }: { quantity: BigNumber }) => quantity.gt(0)); const { selectedAssetUniqueIds, feeCalculationRequestQue: prevFeeCalculationRequestQue, @@ -464,12 +482,11 @@ class WalletSendForm extends Component { this.setState((prevState) => ({ feeCalculationRequestQue: prevState.feeCalculationRequestQue + 1, isTransactionFeeCalculated: false, - transactionFee: new BigNumber(0), transactionFeeError: null, + isCalculatingTransactionFee: true, })); try { - this._isCalculatingTransactionFee = true; const { fee, minimumAda } = await this.props.calculateTransactionFee( receiver, adaAmount, @@ -481,16 +498,26 @@ class WalletSendForm extends Component { this.isLatestTransactionFeeRequest( this.state.feeCalculationRequestQue, prevFeeCalculationRequestQue - ) && - !this.selectedAssetsAmounts.includes(emptyAssetFieldValue) + ) ) { - this._isCalculatingTransactionFee = false; - this.setState({ + const minimumAdaValue = minimumAda || new BigNumber(0); + const adaAmountValue = new BigNumber(adaAmountField.value || 0); + const nextState = { isTransactionFeeCalculated: true, - minimumAda: minimumAda || new BigNumber(0), + minimumAda: minimumAdaValue, transactionFee: fee, transactionFeeError: null, - }); + isCalculatingTransactionFee: false, + adaInputState: this.state.adaInputState, + }; + + if (shouldUpdateMinimumAdaAmount) { + const adaInputState = await this.checkAdaInputState(minimumAdaValue); + nextState.adaInputState = adaInputState; + this.trySetMinimumAdaAmount(adaInputState, minimumAdaValue); + } + + this.setState(nextState); } } catch (error) { if ( @@ -504,6 +531,11 @@ class WalletSendForm extends Component { let transactionFeeError; let localizableError = error; let values; + let nextState = { + isCalculatingTransactionFee: false, + isTransactionFeeCalculated: false, + transactionFee: new BigNumber(0), + }; if (error.id === 'api.errors.utxoTooSmall') { const minimumAda = get(error, 'values.minimumAda'); @@ -515,9 +547,23 @@ class WalletSendForm extends Component { values = { minimumAda, }; - this.setState({ - minimumAda: new BigNumber(minimumAda), - }); + + if (shouldUpdateMinimumAdaAmount) { + const minimumAdaValue = new BigNumber(minimumAda); + const adaInputState = await this.checkAdaInputState( + minimumAdaValue + ); + this.trySetMinimumAdaAmount(adaInputState, minimumAdaValue); + this.setState({ + ...nextState, + adaInputState, + minimumAda: new BigNumber(minimumAda), + }); + return; + } + + // @ts-ignore ts-migrate(2322) FIXME: Type '{ minimumAda: BigNumber; isCalculatingTransa... Remove this comment to see the full error message + nextState = { ...nextState, minimumAda: new BigNumber(minimumAda) }; } } @@ -534,41 +580,102 @@ class WalletSendForm extends Component { ); } - this._isCalculatingTransactionFee = false; - this.setState({ - isTransactionFeeCalculated: false, - transactionFee: new BigNumber(0), - transactionFeeError, - }); + this.setState({ ...nextState, transactionFeeError }); } } }; + checkAdaInputState = async ( + minimumAda: BigNumber + ): Promise => { + const { adaAmountInputTrack, selectedAssetUniqueIds, adaInputState } = + this.state; + + if ( + adaAmountInputTrack.gte(minimumAda) && + adaInputState === AdaInputStateType.Updated + ) { + return AdaInputStateType.Restored; + } + + if (adaAmountInputTrack.lt(minimumAda)) { + const isValid = await this.props.validateAmount( + formattedAmountToNaturalUnits(minimumAda.toString()) + ); + + if (!isValid) { + return AdaInputStateType.None; + } + + return AdaInputStateType.Updated; + } + + return AdaInputStateType.None; + }; + trySetMinimumAdaAmount = ( + adaInputState: AdaInputState, + minimumAda: BigNumber + ) => { + const { formFields } = this.state; + const { adaAmount: adaAmountField } = formFields.receiver; + + switch (adaInputState) { + case 'updated': + adaAmountField.onChange(minimumAda.toString()); + break; + + case 'restored': + case 'reset': + adaAmountField.onChange(this.state.adaAmountInputTrack.toString()); + break; + + case 'none': + default: + } + }; + updateAdaAmount = async () => { + const { minimumAda } = this.state; + const formattedMinimumAda = minimumAda.toString(); + const isValid = await this.props.validateAmount( + formattedAmountToNaturalUnits(formattedMinimumAda) + ); + + if (!isValid) { + return; + } + + this.form.$('adaAmount').onChange(formattedMinimumAda); + this.setState({ + adaInputState: AdaInputStateType.None, + adaAmountInputTrack: minimumAda, + }); + }; + onAdaAmountFieldChange = (value: string) => { + const { formFields } = this.state; + const { adaAmount: adaAmountField } = formFields.receiver; + adaAmountField.onChange(value != null ? value : ''); + const adaAmount = new BigNumber(value != null ? value : 0); + this.setState({ + adaAmountInputTrack: adaAmount, + adaInputState: AdaInputStateType.None, + }); + }; + isAdaAmountLessThanMinimumRequired = () => { + const adaAmountField = this.form.$('adaAmount'); + const adaAmount = new BigNumber(adaAmountField.value || 0); + return adaAmount.lt(this.state.minimumAda); + }; resetTransactionFee() { if (this._isMounted) { - this._isCalculatingTransactionFee = false; this.setState({ isTransactionFeeCalculated: false, transactionFee: new BigNumber(0), transactionFeeError: null, + isCalculatingTransactionFee: false, }); } } - showRemoveAssetButton = (uniqueId: string) => { - const { showRemoveAssetButton } = this.state; - showRemoveAssetButton[uniqueId] = true; - this.setState({ - showRemoveAssetButton, - }); - }; - hideRemoveAssetButton = (uniqueId: string) => { - const { showRemoveAssetButton } = this.state; - showRemoveAssetButton[uniqueId] = false; - this.setState({ - showRemoveAssetButton, - }); - }; addAssetRow = (uniqueId: string) => { this.addAssetFields(uniqueId); this.updateFormFields(false, uniqueId); @@ -582,36 +689,34 @@ class WalletSendForm extends Component { }; removeAssetRow = (uniqueId: string) => { const { formFields, selectedAssetUniqueIds } = this.state; - // @ts-ignore ts-migrate(2339) FIXME: Property 'receiver' does not exist on type '{}'. const { receiver } = formFields; const assetFields = omit(receiver.assetFields, uniqueId); const assetsDropdown = omit(receiver.assetsDropdown, uniqueId); - this.setState({ - selectedAssetUniqueIds: without(selectedAssetUniqueIds, uniqueId), - formFields: { - ...formFields, - receiver: { ...receiver, assetFields, assetsDropdown }, + this.setState( + { + selectedAssetUniqueIds: without(selectedAssetUniqueIds, uniqueId), + formFields: { + ...formFields, + receiver: { ...receiver, assetFields, assetsDropdown }, + }, }, - }); - this.removeAssetFields(uniqueId); - setTimeout(() => { - this.calculateTransactionFee(); - }); + async () => { + this.removeAssetFields(uniqueId); + await this.calculateTransactionFee(true); + } + ); }; addAssetFields = (uniqueId: string) => { const newAsset = `asset_${uniqueId}`; - // @ts-ignore ts-migrate(2339) FIXME: Property 'add' does not exist on type 'ReactToolbo... Remove this comment to see the full error message this.form.add({ name: newAsset, value: null, key: newAsset, }); this.form - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message .$(newAsset) .set('label', this.context.intl.formatMessage(messages.assetLabel)); this.form - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message .$(newAsset) .set( 'placeholder', @@ -619,7 +724,6 @@ class WalletSendForm extends Component { this.props.currencyMaxFractionalDigits )}` ); - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message this.form.$(newAsset).set('validators', [ async ({ field }) => { const { value } = field; @@ -651,7 +755,7 @@ class WalletSendForm extends Component { const isValid = isValidAmount && isValidRange; if (isValid) { - this.calculateTransactionFee(); + await this.calculateTransactionFee(true); } else { this.resetTransactionFee(); } @@ -663,21 +767,17 @@ class WalletSendForm extends Component { }, ]); const assetsDropdown = `assetsDropdown_${uniqueId}`; - // @ts-ignore ts-migrate(2339) FIXME: Property 'add' does not exist on type 'ReactToolbo... Remove this comment to see the full error message this.form.add({ name: assetsDropdown, value: null, key: assetsDropdown, }); - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message this.form.$(assetsDropdown).set('type', 'select'); }; removeAssetFields = (uniqueId: string) => { const assetFieldToDelete = `asset_${uniqueId}`; - // @ts-ignore ts-migrate(2339) FIXME: Property 'del' does not exist on type 'ReactToolbo... Remove this comment to see the full error message this.form.del(assetFieldToDelete); const assetsDropdownFieldToDelete = `assetsDropdown_${uniqueId}`; - // @ts-ignore ts-migrate(2339) FIXME: Property 'del' does not exist on type 'ReactToolbo... Remove this comment to see the full error message this.form.del(assetsDropdownFieldToDelete); }; onChangeAsset = async (currentUniqueId: string, newUniqueId: string) => { @@ -700,34 +800,39 @@ class WalletSendForm extends Component { this.removeAssetRow(currentUniqueId); this.resetTransactionFee(); }; + getMinimumAdaValue = () => { + const { minimumAda } = this.state; + return minimumAda.isZero() + ? TRANSACTION_MIN_ADA_VALUE + : minimumAda.toFormat(); + }; renderReceiverRow = (): Node => { const { intl } = this.context; const { formFields, - minimumAda, transactionFeeError, selectedAssetUniqueIds, - isReceiverAddressValid, + isReceiverAddressValidOnce, } = this.state; - const { currencyMaxFractionalDigits, walletAmount } = this.props; + const { + currencyMaxFractionalDigits, + walletAmount, + onTokenPickerDialogOpen, + } = this.props; + const { adaAmount: adaAmountField, receiver: receiverField, assetFields, - assetsDropdown, - // @ts-ignore ts-migrate(2339) FIXME: Property 'receiver' does not exist on type '{}'. } = formFields.receiver; const assetsSeparatorBasicHeight = 140; const assetsSeparatorCalculatedHeight = selectedAssetUniqueIds.length ? assetsSeparatorBasicHeight * (selectedAssetUniqueIds.length + 1) - 40 * selectedAssetUniqueIds.length : assetsSeparatorBasicHeight; - const minimumAdaValue = minimumAda.isZero() - ? TRANSACTION_MIN_ADA_VALUE - : minimumAda.toFormat(); + const minimumAdaValue = this.getMinimumAdaValue(); const addAssetButtonClasses = classNames([ styles.addAssetButton, - !this.hasAvailableAssets ? styles.disabled : null, 'primary', ]); const receiverFieldClasses = classNames([ @@ -772,25 +877,14 @@ class WalletSendForm extends Component { /> {this.hasReceiverValue() && (
- - - +
)}
- {this.hasReceiverValue() && isReceiverAddressValid && ( + {isReceiverAddressValidOnce && ( <>
{
{intl.formatMessage(globalMessages.adaUnit)}
- { - this.addFocusableField(field); - }} - className="adaAmount" - value={adaAmountField.value} - bigNumberFormat={this.getCurrentNumberFormat()} - decimalPlaces={currencyMaxFractionalDigits} - numberLocaleOptions={{ - minimumFractionDigits: currencyMaxFractionalDigits, - }} - onChange={(value) => { - adaAmountField.onChange(value); - }} - currency={globalMessages.adaUnit} - error={adaAmountField.error || transactionFeeError} - onKeyPress={this.handleSubmitOnEnter} - allowSigns={false} - autoFocus={this._isAutoFocusEnabled} - /> -
- - {intl.formatMessage(messages.minAdaRequired, { - minimumAda: minimumAdaValue, - })} - +
+ { + this.addFocusableField(field); + }} + className="adaAmount" + bigNumberFormat={this.getCurrentNumberFormat()} + decimalPlaces={currencyMaxFractionalDigits} + numberLocaleOptions={{ + minimumFractionDigits: currencyMaxFractionalDigits, + }} + onChange={this.onAdaAmountFieldChange} + currency={globalMessages.adaUnit} + error={adaAmountField.error || transactionFeeError} + onKeyPress={this.handleSubmitOnEnter} + allowSigns={false} + autoFocus={this._isAutoFocusEnabled} + /> + {this.hasAdaAmountValue() && ( +
+ +
+ +
+
+ )} +
+
+ {this.isAdaAmountLessThanMinimumRequired() ? ( + <> +
@@ -895,6 +1012,16 @@ class WalletSendForm extends Component {
); }; + renderMinimumAmountNotice = (message: ReactIntlMessage, values: {}) => { + return ( +
+ +
+ ); + }; render() { const { form } = this; @@ -905,19 +1032,22 @@ class WalletSendForm extends Component { transactionFeeError, isResetButtonDisabled, isTransactionFeeCalculated, + selectedAssetUniqueIds, } = this.state; const { + assets, currencyMaxFractionalDigits, hwDeviceStatus, isHardwareWallet, isDialogOpen, isRestoreActive, onExternalLinkClick, + tokenFavorites, + walletName, + onTokenPickerDialogClose, } = this.props; - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const receiverField = form.$('receiver'); const receiver = receiverField.value; - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const adaAmountField = form.$('adaAmount'); const adaAmount = new BigNumber(adaAmountField.value || 0); let fees = '0'; @@ -932,6 +1062,13 @@ class WalletSendForm extends Component { styles.calculatingFeesSpinnerButton, styles.spinning, ]); + const estimatedFeeInputClasses = classNames({ + [styles.estimatedFeeInput]: true, + [styles.withOffset]: + this.state.adaInputState === AdaInputStateType.Updated || + this.state.adaInputState === AdaInputStateType.Restored, + }); + const minimumAdaValue = this.getMinimumAdaValue(); return (
{isRestoreActive ? ( @@ -944,9 +1081,8 @@ class WalletSendForm extends Component { ) : (
- {/* @ts-ignore ts-migrate(2339) FIXME: Property 'receiver' does not exist on type '{}'. */} {formFields.receiver && this.renderReceiverRow()} -
+
{ } isSet /> - {this._isCalculatingTransactionFee && ( + {this.state.isCalculatingTransactionFee && (
{
)}
+ {this.state.adaInputState === AdaInputStateType.Updated && + this.renderMinimumAmountNotice(messages.minimumAmountNotice, { + minimumAda: minimumAdaValue, + })} + {this.state.adaInputState === AdaInputStateType.Restored && + this.renderMinimumAmountNotice(messages.restoredAdaAmount, { + minimumAda: minimumAdaValue, + adaAmount: adaAmountField.value, + })}
); } diff --git a/source/renderer/app/components/wallet/backup-recovery/WalletBackupPrivacyWarningDialog.tsx b/source/renderer/app/components/wallet/backup-recovery/WalletBackupPrivacyWarningDialog.tsx index 87c3470a24..2ee5b83a93 100644 --- a/source/renderer/app/components/wallet/backup-recovery/WalletBackupPrivacyWarningDialog.tsx +++ b/source/renderer/app/components/wallet/backup-recovery/WalletBackupPrivacyWarningDialog.tsx @@ -9,7 +9,6 @@ import DialogCloseButton from '../../widgets/DialogCloseButton'; import WalletRecoveryInstructions from './WalletRecoveryInstructions'; import globalMessages from '../../../i18n/global-messages'; import { WALLET_RECOVERY_PHRASE_WORD_COUNT } from '../../../config/cryptoConfig'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './WalletBackupPrivacyWarningDi... Remove this comment to see the full error message import styles from './WalletBackupPrivacyWarningDialog.scss'; const messages = defineMessages({ diff --git a/source/renderer/app/components/wallet/backup-recovery/WalletRecoveryPhraseDisplayDialog.tsx b/source/renderer/app/components/wallet/backup-recovery/WalletRecoveryPhraseDisplayDialog.tsx index 4376b7735d..6479ce126e 100644 --- a/source/renderer/app/components/wallet/backup-recovery/WalletRecoveryPhraseDisplayDialog.tsx +++ b/source/renderer/app/components/wallet/backup-recovery/WalletRecoveryPhraseDisplayDialog.tsx @@ -7,7 +7,6 @@ import DialogCloseButton from '../../widgets/DialogCloseButton'; import Dialog from '../../widgets/Dialog'; import WalletRecoveryInstructions from './WalletRecoveryInstructions'; import globalMessages from '../../../i18n/global-messages'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './WalletRecoveryPhraseDisplayD... Remove this comment to see the full error message import styles from './WalletRecoveryPhraseDisplayDialog.scss'; import { WALLET_RECOVERY_PHRASE_WORD_COUNT } from '../../../config/cryptoConfig'; import LoadingSpinner from '../../widgets/LoadingSpinner'; diff --git a/source/renderer/app/components/wallet/backup-recovery/WalletRecoveryPhraseEntryDialog.tsx b/source/renderer/app/components/wallet/backup-recovery/WalletRecoveryPhraseEntryDialog.tsx index b77715c2f9..33b091bdb0 100644 --- a/source/renderer/app/components/wallet/backup-recovery/WalletRecoveryPhraseEntryDialog.tsx +++ b/source/renderer/app/components/wallet/backup-recovery/WalletRecoveryPhraseEntryDialog.tsx @@ -21,7 +21,6 @@ import DialogBackButton from '../../widgets/DialogBackButton'; import Dialog from '../../widgets/Dialog'; import WalletRecoveryInstructions from './WalletRecoveryInstructions'; import globalMessages from '../../../i18n/global-messages'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './WalletRecoveryPhraseEntryDia... Remove this comment to see the full error message import styles from './WalletRecoveryPhraseEntryDialog.scss'; import LoadingSpinner from '../../widgets/LoadingSpinner'; @@ -95,13 +94,16 @@ type Props = { onFinishBackup: (...args: Array) => any; }; +interface FormFields { + recoveryPhrase: string; +} + @observer class WalletRecoveryPhraseEntryDialog extends Component { static contextTypes = { intl: intlShape.isRequired, }; - form = new ReactToolboxMobxForm( - // @ts-ignore ts-migrate(2554) FIXME: Expected 0 arguments, but got 2. + form = new ReactToolboxMobxForm( { fields: { recoveryPhrase: { @@ -151,7 +153,6 @@ class WalletRecoveryPhraseEntryDialog extends Component { onCancelBackup, onFinishBackup, } = this.props; - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const recoveryPhraseField = form.$('recoveryPhrase'); const dialogClasses = classnames([ styles.component, diff --git a/source/renderer/app/components/wallet/backup-recovery/WalletRecoveryPhraseMnemonic.tsx b/source/renderer/app/components/wallet/backup-recovery/WalletRecoveryPhraseMnemonic.tsx index eb69ce6ede..6113699c5b 100644 --- a/source/renderer/app/components/wallet/backup-recovery/WalletRecoveryPhraseMnemonic.tsx +++ b/source/renderer/app/components/wallet/backup-recovery/WalletRecoveryPhraseMnemonic.tsx @@ -1,6 +1,5 @@ import React, { Component } from 'react'; import { observer } from 'mobx-react'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './WalletRecoveryPhraseMnemonic... Remove this comment to see the full error message import styles from './WalletRecoveryPhraseMnemonic.scss'; type Props = { diff --git a/source/renderer/app/components/wallet/file-import/WalletFileImportDialog.tsx b/source/renderer/app/components/wallet/file-import/WalletFileImportDialog.tsx index caffc8b84f..57e1ee9131 100644 --- a/source/renderer/app/components/wallet/file-import/WalletFileImportDialog.tsx +++ b/source/renderer/app/components/wallet/file-import/WalletFileImportDialog.tsx @@ -15,7 +15,6 @@ import { } from '../../../utils/validations'; import globalMessages from '../../../i18n/global-messages'; import LocalizableError from '../../../i18n/LocalizableError'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './WalletFileImportDialog.scss'... Remove this comment to see the full error message import styles from './WalletFileImportDialog.scss'; import { FORM_VALIDATION_DEBOUNCE_WAIT } from '../../../config/timingConfig'; @@ -80,13 +79,19 @@ type Props = { error: LocalizableError | null | undefined; }; +interface FormFields { + walletFilePath: string; + walletName: string; + spendingPassword: string; + repeatPassword: string; +} + @observer class WalletFileImportDialog extends Component { static contextTypes = { intl: intlShape.isRequired, }; - form = new ReactToolboxMobxForm( - // @ts-ignore ts-migrate(2554) FIXME: Expected 0 arguments, but got 2. + form = new ReactToolboxMobxForm( { fields: { walletFilePath: { @@ -122,7 +127,7 @@ class WalletFileImportDialog extends Component { ), value: '', validators: [ - () => + () => { // const repeatPasswordField = form.$('repeatPassword'); // if (repeatPasswordField.value.length > 0) { // repeatPasswordField.validate({ showErrors: true }); @@ -133,7 +138,8 @@ class WalletFileImportDialog extends Component { // globalMessages.invalidSpendingPassword // ), // ]; - [true], // @API TODO - missing API v2 endpoint and password declaration + return [true]; // @API TODO - missing API v2 endpoint and password declaration + }, ], }, repeatPassword: { @@ -144,7 +150,7 @@ class WalletFileImportDialog extends Component { ), value: '', validators: [ - () => + () => { // const spendingPassword = form.$('spendingPassword').value; // if (spendingPassword.length === 0) return [true]; // return [ @@ -153,7 +159,8 @@ class WalletFileImportDialog extends Component { // globalMessages.invalidRepeatPassword // ), // ]; - [true], // @API TODO - missing API v2 endpoint and password declaration + return [true]; // @API TODO - missing API v2 endpoint and password declaration + }, ], }, }, @@ -169,7 +176,6 @@ class WalletFileImportDialog extends Component { } ); submit = () => { - // @ts-ignore ts-migrate(2339) FIXME: Property 'submit' does not exist on type 'ReactToo... Remove this comment to see the full error message this.form.submit({ onSuccess: (form) => { const { walletFilePath, spendingPassword, walletName } = form.values(); @@ -188,7 +194,6 @@ class WalletFileImportDialog extends Component { const { intl } = this.context; const { form } = this; const { isSubmitting, error, onClose } = this.props; - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const walletFilePath = form.$('walletFilePath'); const dialogClasses = classnames([ styles.component, diff --git a/source/renderer/app/components/wallet/layouts/WalletWithNavigation.tsx b/source/renderer/app/components/wallet/layouts/WalletWithNavigation.tsx index fb3aaed931..47045c8016 100644 --- a/source/renderer/app/components/wallet/layouts/WalletWithNavigation.tsx +++ b/source/renderer/app/components/wallet/layouts/WalletWithNavigation.tsx @@ -4,7 +4,6 @@ import type { Node } from 'react'; import classnames from 'classnames'; import { observer } from 'mobx-react'; import WalletNavigation from '../navigation/WalletNavigation'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './WalletWithNavigation.scss' o... Remove this comment to see the full error message import styles from './WalletWithNavigation.scss'; import NotResponding from '../not-responding/NotResponding'; import SetWalletPassword from '../settings/SetWalletPassword'; diff --git a/source/renderer/app/components/wallet/not-responding/NotResponding.tsx b/source/renderer/app/components/wallet/not-responding/NotResponding.tsx index b0ae19f4a0..94422e95fe 100644 --- a/source/renderer/app/components/wallet/not-responding/NotResponding.tsx +++ b/source/renderer/app/components/wallet/not-responding/NotResponding.tsx @@ -7,7 +7,6 @@ import { Link } from 'react-polymorph/lib/components/Link'; import { LinkSkin } from 'react-polymorph/lib/skins/simple/LinkSkin'; // @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../../assets/images/not-res... Remove this comment to see the full error message import icon from '../../../assets/images/not-responding.inline.svg'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './NotResponding.scss' or its c... Remove this comment to see the full error message import styles from './NotResponding.scss'; type Props = { diff --git a/source/renderer/app/components/wallet/paper-wallet-certificate/CompletionDialog.tsx b/source/renderer/app/components/wallet/paper-wallet-certificate/CompletionDialog.tsx index 4b703488d2..5d6d965f16 100644 --- a/source/renderer/app/components/wallet/paper-wallet-certificate/CompletionDialog.tsx +++ b/source/renderer/app/components/wallet/paper-wallet-certificate/CompletionDialog.tsx @@ -1,7 +1,6 @@ import React, { Component } from 'react'; import { observer } from 'mobx-react'; import QRCode from 'qrcode.react'; -import classnames from 'classnames'; import { defineMessages, intlShape } from 'react-intl'; import CopyToClipboard from 'react-copy-to-clipboard'; import SVGInline from 'react-svg-inline'; @@ -9,7 +8,6 @@ import { Link } from 'react-polymorph/lib/components/Link'; import { LinkSkin } from 'react-polymorph/lib/skins/simple/LinkSkin'; import Dialog from '../../widgets/Dialog'; import { getNetworkExplorerUrl } from '../../../utils/network'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './CompletionDialog.scss' or it... Remove this comment to see the full error message import styles from './CompletionDialog.scss'; // @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../../assets/images/clipboa... Remove this comment to see the full error message import iconCopy from '../../../assets/images/clipboard-ic.inline.svg'; @@ -114,7 +112,6 @@ class CompletionDialog extends Component { const { onClose, walletCertificateAddress, onOpenExternalLink, network } = this.props; const { showCopyNotification } = this.state; - const dialogClasses = classnames([styles.component, 'completionDialog']); const actions = [ { className: 'finishButton', @@ -139,14 +136,12 @@ class CompletionDialog extends Component { : '#000'; return (
-

- {intl.formatMessage(messages.subtitle)} -

+

{intl.formatMessage(messages.subtitle)}

{intl.formatMessage(messages.linkInstructions)}

diff --git a/source/renderer/app/components/wallet/paper-wallet-certificate/ConfirmationDialog.tsx b/source/renderer/app/components/wallet/paper-wallet-certificate/ConfirmationDialog.tsx index a5585c0037..4f45a54a75 100644 --- a/source/renderer/app/components/wallet/paper-wallet-certificate/ConfirmationDialog.tsx +++ b/source/renderer/app/components/wallet/paper-wallet-certificate/ConfirmationDialog.tsx @@ -3,7 +3,6 @@ import { observer } from 'mobx-react'; import classnames from 'classnames'; import { defineMessages, intlShape } from 'react-intl'; import Dialog from '../../widgets/Dialog'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './ConfirmationDialog.scss' or ... Remove this comment to see the full error message import styles from './ConfirmationDialog.scss'; const messages = defineMessages({ @@ -55,11 +54,7 @@ class ConfirmationDialog extends Component { const { intl } = this.context; const { onConfirm, onCancel } = this.props; const dialogClasses = classnames([styles.component, 'ConfirmDialog']); - const confirmButtonClasses = classnames([ - 'confirmButton', - 'attention', - styles.confirmButton, - ]); + const confirmButtonClasses = classnames(['confirmButton', 'attention']); const actions = [ { className: 'cancelButton', diff --git a/source/renderer/app/components/wallet/paper-wallet-certificate/InstructionsDialog.tsx b/source/renderer/app/components/wallet/paper-wallet-certificate/InstructionsDialog.tsx index 4ee7b33d71..53f78259f0 100644 --- a/source/renderer/app/components/wallet/paper-wallet-certificate/InstructionsDialog.tsx +++ b/source/renderer/app/components/wallet/paper-wallet-certificate/InstructionsDialog.tsx @@ -8,7 +8,6 @@ import Dialog from '../../widgets/Dialog'; import DialogCloseButton from '../../widgets/DialogCloseButton'; import { getNetworkExplorerUrl } from '../../../utils/network'; import LocalizableError from '../../../i18n/LocalizableError'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './InstructionsDialog.scss' or ... Remove this comment to see the full error message import styles from './InstructionsDialog.scss'; import { handleFormErrors } from '../../../utils/ReactToolboxMobxForm'; import { @@ -129,7 +128,6 @@ class InstructionsDialog extends Component { const { intl } = this.context; const { onClose, onPrint, inProgress, onOpenExternalLink, network, error } = this.props; - const dialogClasses = classnames([styles.component, 'instructionsDialog']); const printButtonClasses = classnames([ 'printButton', inProgress ? styles.submitButtonSpinning : null, @@ -156,7 +154,7 @@ class InstructionsDialog extends Component { ); return ( { const { intl } = this.context; const { onContinue, onClose } = this.props; const { isPrintedCorrectly, isReadable, isScannable } = this.state; - const dialogClasses = classnames([styles.component, 'printDialog']); const certificatePrintedCheckboxClasses = classnames([ 'printedCheckbox', styles.checkbox, @@ -121,7 +119,7 @@ class PrintDialog extends Component { ]; return ( { const { intl } = this.context; const { securePasswordConfirmed } = this.state; const { additionalMnemonics, onContinue, onClose } = this.props; - const dialogClasses = classnames([ - styles.component, - 'SecuringPasswordDialog', - ]); const actions = [ { className: 'continueButton', @@ -84,7 +79,7 @@ class SecuringPasswordDialog extends Component { ]; return ( ) => any; }; +interface FormFields { + recoveryPhrase: string; +} + @observer class VerificationDialog extends Component { static contextTypes = { @@ -116,8 +119,7 @@ class VerificationDialog extends Component { })); }; recoveryPhraseAutocomplete: Autocomplete; - form = new ReactToolboxMobxForm( - // @ts-ignore ts-migrate(2554) FIXME: Expected 0 arguments, but got 2. + form = new ReactToolboxMobxForm( { fields: { recoveryPhrase: { @@ -183,7 +185,6 @@ class VerificationDialog extends Component { } ); submit = () => { - // @ts-ignore ts-migrate(2339) FIXME: Property 'submit' does not exist on type 'ReactToo... Remove this comment to see the full error message this.form.submit({ onSuccess: (form) => { const { recoveryPhrase } = form.values(); @@ -198,13 +199,10 @@ class VerificationDialog extends Component { const { form } = this; const autocomplete = this.recoveryPhraseAutocomplete; // Cancel all debounced field validations - // @ts-ignore ts-migrate(2339) FIXME: Property 'each' does not exist on type 'ReactToolb... Remove this comment to see the full error message form.each((field) => { field.debouncedValidation.cancel(); }); - // @ts-ignore ts-migrate(2339) FIXME: Property 'reset' does not exist on type 'ReactTool... Remove this comment to see the full error message form.reset(); - // @ts-ignore ts-migrate(2339) FIXME: Property 'showErrors' does not exist on type 'Reac... Remove this comment to see the full error message form.showErrors(false); // Autocomplete has to be reset manually autocomplete.clear(); @@ -225,7 +223,6 @@ class VerificationDialog extends Component { const { suggestedMnemonics, onClose } = this.props; const { storingConfirmed, recoveringConfirmed, isRecoveryPhraseValid } = this.state; - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const recoveryPhraseField = form.$('recoveryPhrase'); const dialogClasses = classnames([styles.dialog, 'verificationDialog']); const storingUnderstandanceCheckboxClasses = classnames([ diff --git a/source/renderer/app/components/wallet/receive/AddressActions.tsx b/source/renderer/app/components/wallet/receive/AddressActions.tsx index b06b259b19..a402d9d1f2 100644 --- a/source/renderer/app/components/wallet/receive/AddressActions.tsx +++ b/source/renderer/app/components/wallet/receive/AddressActions.tsx @@ -4,11 +4,10 @@ import classnames from 'classnames'; import CopyToClipboard from 'react-copy-to-clipboard'; import { defineMessages, intlShape } from 'react-intl'; import SVGInline from 'react-svg-inline'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './AddressActions.scss' or its ... Remove this comment to see the full error message import styles from './AddressActions.scss'; // @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../../assets/images/qr-code... Remove this comment to see the full error message import iconQR from '../../../assets/images/qr-code.inline.svg'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../../assets/images/clipboa... Remove this comment to see the full error message +// @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../../assets/images/clipboard... Remove this comment to see the full error message import iconCopy from '../../../assets/images/clipboard-ic.inline.svg'; import WalletAddress from '../../../domains/WalletAddress'; diff --git a/source/renderer/app/components/wallet/receive/AddressRandom.tsx b/source/renderer/app/components/wallet/receive/AddressRandom.tsx index 30a8bdcff2..7715bd070e 100644 --- a/source/renderer/app/components/wallet/receive/AddressRandom.tsx +++ b/source/renderer/app/components/wallet/receive/AddressRandom.tsx @@ -2,7 +2,6 @@ import React, { Component } from 'react'; import { observer } from 'mobx-react'; import classnames from 'classnames'; import AddressActions from './AddressActions'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './AddressRandom.scss' or its c... Remove this comment to see the full error message import styles from './AddressRandom.scss'; import WalletAddress from '../../../domains/WalletAddress'; diff --git a/source/renderer/app/components/wallet/receive/AddressSequential.tsx b/source/renderer/app/components/wallet/receive/AddressSequential.tsx index ed2dc3c04e..68db531500 100644 --- a/source/renderer/app/components/wallet/receive/AddressSequential.tsx +++ b/source/renderer/app/components/wallet/receive/AddressSequential.tsx @@ -2,7 +2,6 @@ import React, { Component } from 'react'; import { observer } from 'mobx-react'; import classnames from 'classnames'; import AddressActions from './AddressActions'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './AddressSequential.scss' or i... Remove this comment to see the full error message import styles from './AddressSequential.scss'; import WalletAddress from '../../../domains/WalletAddress'; diff --git a/source/renderer/app/components/wallet/receive/VirtualAddressesList.tsx b/source/renderer/app/components/wallet/receive/VirtualAddressesList.tsx index 266d010ec5..b7ccd7cff0 100644 --- a/source/renderer/app/components/wallet/receive/VirtualAddressesList.tsx +++ b/source/renderer/app/components/wallet/receive/VirtualAddressesList.tsx @@ -3,7 +3,6 @@ import { throttle } from 'lodash'; import { observer } from 'mobx-react'; import { AutoSizer, List } from 'react-virtualized'; import WalletAddress from '../../../domains/WalletAddress'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './VirtualAddressesList.scss' o... Remove this comment to see the full error message import styles from './VirtualAddressesList.scss'; type Props = { diff --git a/source/renderer/app/components/wallet/receive/WalletReceiveDialog.tsx b/source/renderer/app/components/wallet/receive/WalletReceiveDialog.tsx index 03c662cac4..a0af533832 100644 --- a/source/renderer/app/components/wallet/receive/WalletReceiveDialog.tsx +++ b/source/renderer/app/components/wallet/receive/WalletReceiveDialog.tsx @@ -17,11 +17,9 @@ import Dialog from '../../widgets/Dialog'; import DialogCloseButton from '../../widgets/DialogCloseButton'; import WalletAddress from '../../../domains/WalletAddress'; import globalMessages from '../../../i18n/global-messages'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './WalletReceiveDialog.scss' or... Remove this comment to see the full error message import styles from './WalletReceiveDialog.scss'; import ReactToolboxMobxForm from '../../../utils/ReactToolboxMobxForm'; import HardwareWalletStatus from '../../hardware-wallet/HardwareWalletStatus'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../../assets/images/clipboa... Remove this comment to see the full error message import iconCopy from '../../../assets/images/clipboard-ic.inline.svg'; import { HW_SHELLEY_CONFIG } from '../../../config/hardwareWalletsConfig'; import { hardenedPathToDerivationPath } from '../../../utils/hardwareWalletUtils'; @@ -158,6 +156,10 @@ type State = { isReverifying: boolean; }; +interface FormFields { + noteInput: string; +} + @observer class WalletReceiveDialog extends Component { static contextTypes = { @@ -168,8 +170,7 @@ class WalletReceiveDialog extends Component { isInvalidAddressConfirmed: false, isReverifying: false, }; - // @ts-ignore ts-migrate(2554) FIXME: Expected 0 arguments, but got 1. - form = new ReactToolboxMobxForm({ + form = new ReactToolboxMobxForm({ fields: { noteInput: { value: '', @@ -179,7 +180,6 @@ class WalletReceiveDialog extends Component { }, }); submit = () => { - // @ts-ignore ts-migrate(2339) FIXME: Property 'submit' does not exist on type 'ReactToo... Remove this comment to see the full error message this.form.submit({ onSuccess: (form) => { const { noteInput } = form.values(); @@ -187,6 +187,7 @@ class WalletReceiveDialog extends Component { onDownloadPDF(noteInput); }, onError: (err) => { + // @ts-ignore Argument of type 'MobxReactForm' is not assignable to parameter of type 'string'. throw new Error(err); }, }); @@ -271,14 +272,9 @@ class WalletReceiveDialog extends Component { isReverifying, } = this.state; const { intl } = this.context; - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const noteInputField = this.form.$('noteInput'); const deviceType = isHardwareWallet && isTrezor ? 'Trezor' : 'Ledger'; const isSubmitting = false; - const buttonClasses = classnames([ - 'attention', - isSubmitting ? styles.isSubmitting : null, - ]); const supportButtonLabel = !isSubmitting ? ( intl.formatMessage(messages.supportRequestButtonLabel) ) : ( @@ -294,7 +290,7 @@ class WalletReceiveDialog extends Component { ); actions = [ { - className: buttonClasses, + className: 'attention', label: supportButtonLabel, onClick: onSupportRequestClick.bind(this, supportRequestLinkUrl), disabled: !isInvalidAddressConfirmed, @@ -497,7 +493,7 @@ class WalletReceiveDialog extends Component {

{intl.formatMessage(messages.invalidAddressWarningTitle)}

-

+

{intl.formatMessage( messages.invalidAddressWarningDescription, { diff --git a/source/renderer/app/components/wallet/receive/WalletReceiveRandom.tsx b/source/renderer/app/components/wallet/receive/WalletReceiveRandom.tsx index 55a862bf8c..5dd35f968b 100644 --- a/source/renderer/app/components/wallet/receive/WalletReceiveRandom.tsx +++ b/source/renderer/app/components/wallet/receive/WalletReceiveRandom.tsx @@ -14,12 +14,10 @@ import ReactToolboxMobxForm from '../../../utils/ReactToolboxMobxForm'; import { submitOnEnter } from '../../../utils/form'; import BorderedBox from '../../widgets/BorderedBox'; import TinySwitch from '../../widgets/forms/TinySwitch'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../../assets/images/clipboa... Remove this comment to see the full error message import iconCopy from '../../../assets/images/clipboard-ic.inline.svg'; import globalMessages from '../../../i18n/global-messages'; import LocalizableError from '../../../i18n/LocalizableError'; import { VirtualAddressesList } from './VirtualAddressesList'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './WalletReceiveRandom.scss' or... Remove this comment to see the full error message import styles from './WalletReceiveRandom.scss'; import AddressRandom from './AddressRandom'; import WalletAddress from '../../../domains/WalletAddress'; @@ -83,6 +81,10 @@ type Props = { onToggleUsedAddresses: (...args: Array) => any; }; +interface FormFields { + spendingPassword: string; +} + @observer class WalletReceiveRandom extends Component { static contextTypes = { @@ -93,8 +95,7 @@ class WalletReceiveRandom extends Component { const { onToggleUsedAddresses } = this.props; onToggleUsedAddresses(); }; - form = new ReactToolboxMobxForm( - // @ts-ignore ts-migrate(2554) FIXME: Expected 0 arguments, but got 2. + form = new ReactToolboxMobxForm( { fields: { spendingPassword: { @@ -141,7 +142,6 @@ class WalletReceiveRandom extends Component { /> ); submit = () => { - // @ts-ignore ts-migrate(2339) FIXME: Property 'submit' does not exist on type 'ReactToo... Remove this comment to see the full error message this.form.submit({ onSuccess: (form) => { const { spendingPassword } = form.values(); @@ -189,7 +189,6 @@ class WalletReceiveRandom extends Component { walletHasPassword ? styles.submitWithPasswordButton : styles.submitButton, isSubmitting ? styles.spinning : null, ]); - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const passwordField = form.$('spendingPassword'); const canSubmit = !isSubmitting && passwordField.value; const generateAddressForm = ( diff --git a/source/renderer/app/components/wallet/receive/WalletReceiveSequential.tsx b/source/renderer/app/components/wallet/receive/WalletReceiveSequential.tsx index 2a5f0f559b..a0e52e846b 100644 --- a/source/renderer/app/components/wallet/receive/WalletReceiveSequential.tsx +++ b/source/renderer/app/components/wallet/receive/WalletReceiveSequential.tsx @@ -7,7 +7,6 @@ import TinySwitch from '../../widgets/forms/TinySwitch'; import WalletAddress from '../../../domains/WalletAddress'; import globalMessages from '../../../i18n/global-messages'; import { VirtualAddressesList } from './VirtualAddressesList'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './WalletReceiveSequential.scss... Remove this comment to see the full error message import styles from './WalletReceiveSequential.scss'; import AddressSequential from './AddressSequential'; @@ -160,7 +159,7 @@ class WalletReceiveSequential extends Component {

-
+

{intl.formatMessage(messages.instructionsTitle)}

diff --git a/source/renderer/app/components/wallet/send-form/AssetInput.scss b/source/renderer/app/components/wallet/send-form/AssetInput.scss index 14e2eaeac9..59c5c0be36 100644 --- a/source/renderer/app/components/wallet/send-form/AssetInput.scss +++ b/source/renderer/app/components/wallet/send-form/AssetInput.scss @@ -2,7 +2,7 @@ @import '../../../themes/mixins/error-message'; .component { - position: relative; + display: flex; .amountTokenTotal { color: var(--theme-input-right-floating-text-color); @@ -14,147 +14,112 @@ top: 2px; } + .amountValue { + &:before { + content: ' '; + } + } + .assetItem { margin-bottom: 20px; } .removeAssetButton { - border-radius: 5px; - box-sizing: border-box; + align-items: center; + border-radius: 3px; + color: var(--theme-staking-stake-pools-search-clear-button-color); cursor: pointer; - display: inline-block; - font-family: var(--font-semibold); - font-size: 10px; - font-weight: 500; - height: 20px; - letter-spacing: 0.5px; - line-height: 20px; - margin: 1px 0 0 10px; - padding: 0 8px; - text-transform: uppercase; - vertical-align: top; - width: auto; + display: flex; + height: 28px; + justify-content: center; + width: 28px; } .rightContent { + align-items: center; bottom: 0; display: inline-flex; height: 48px; position: absolute; - right: 0; + right: 10px; + top: 34px; } - .clearAssetContainer { - bottom: 0.5px; - display: flex; - float: right; - height: 48px; - justify-content: flex-end; - line-height: 48px; - padding-left: 20px; + .inputBlock { + flex: 1 1 auto; position: relative; - right: 10px; - width: 50px; + } +} - .clearAssetButton { +.clearAssetContainer { + align-items: center; + display: flex; + height: 48px; + + .clearAssetButton { + background-color: var( + --theme-staking-stake-pools-search-clear-button-background-color + ); + border-radius: 3px; + cursor: pointer; + height: 28px; + width: 28px; + + &:hover { background-color: var( - --theme-staking-stake-pools-search-clear-button-background-color + --theme-staking-stake-pools-search-clear-button-hover-background-color ); - border-radius: 3px; - cursor: pointer; - height: 28px; - width: 28px; - - &:hover { - background-color: var( - --theme-staking-stake-pools-search-clear-button-hover-background-color - ); - } + } - .clearReceiverIcon { - > svg { - position: relative; - top: 0.5px; - width: 10px; + .clearReceiverIcon { + > svg { + position: relative; + top: 0.5px; + width: 10px; - > g > g { - fill: var(--theme-staking-stake-pools-search-clear-button-color); - } + > g > g { + fill: var(--theme-staking-stake-pools-search-clear-button-color); } } } } - .separator { - background: var(--rp-input-border-color); - height: 20px; - left: 15px; - position: relative; - top: 15px; - width: 1px; + & + .ticker { + margin-left: 10px; } +} - .assetsDropdownWrapper { - display: flex; - position: relative; - width: auto; - - > div { - width: 100%; - } - - .assetsDropdown { - input { - border: none; - height: 48px; - } - - :global { - .SimpleSelect_selectInput.SelectOverrides_selectInput - .WalletsDropdownOption_component { - max-width: 100%; - } - - .SimpleSelect_selectInput.SelectOverrides_selectInput - .WalletsDropdownOption_selected - .WalletsDropdownOption_topRow { - justify-content: flex-end; - } - - .SimpleSelect_selectInput.SelectOverrides_selectInput - .WalletsDropdownOption_label { - .Asset_content { - margin-right: 24.5px; - } - } - - .ItemDropdownOption_selected { - .ItemDropdownOption_label { - margin-top: 5px; - } - .ItemDropdownOption_detail { - display: none !important; - } - } +.divider { + background-color: var(--theme-input-placeholder-color); + height: 20px; + margin: 0 20px; + opacity: 0.2; + width: 1px; +} - .SimpleOptions_option { - padding: 6px 15px; - } +.ticker { + color: var(--theme-input-placeholder-color); + font-family: var(--font-regular); + font-size: 16px; + font-weight: 300; + margin-right: 10px; + text-transform: uppercase; + z-index: 1; +} - .OptionsOverrides_options { - left: auto !important; - min-width: 289px; - right: 41px !important; - } +.removeAssetBlock { + align-items: center; + display: flex; + height: 48px; + margin: 34px 11px 0 11px; +} - .SimpleBubble_root:not(.SimpleBubble_openUpward) .SimpleBubble_bubble { - border-radius: 2px; - } +.removeIcon { + align-items: center; + display: flex; + justify-content: center; - .SelectOverrides_selectInput .SimpleInput_customValueBlock { - background: transparent; - } - } - } + & > svg { + height: 2px; } } diff --git a/source/renderer/app/components/wallet/send-form/AssetInput.tsx b/source/renderer/app/components/wallet/send-form/AssetInput.tsx index 2fdefa6074..d6a4b8ee1d 100644 --- a/source/renderer/app/components/wallet/send-form/AssetInput.tsx +++ b/source/renderer/app/components/wallet/send-form/AssetInput.tsx @@ -2,38 +2,33 @@ import React, { Component } from 'react'; import { observer } from 'mobx-react'; import type { Field } from 'mobx-react-form'; import { intlShape } from 'react-intl'; -import { get, orderBy } from 'lodash'; +import { get } from 'lodash'; import classNames from 'classnames'; import SVGInline from 'react-svg-inline'; import { NumericInput } from 'react-polymorph/lib/components/NumericInput'; import { PopOver } from 'react-polymorph/lib/components/PopOver'; import AmountInputSkin from '../skins/AmountInputSkin'; -import AssetsDropdown from '../../widgets/forms/AssetsDropdown'; // @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../../assets/images/close-c... Remove this comment to see the full error message import closeIcon from '../../../assets/images/close-cross.inline.svg'; +// @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../../assets/images/remove.... Remove this comment to see the full error message +import removeIcon from '../../../assets/images/remove.inline.svg'; import type { NumberFormat } from '../../../../../common/types/number.types'; -import type { AssetToken } from '../../../api/assets/types'; import { DiscreetTokenWalletAmount } from '../../../features/discreet-mode'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './AssetInput.scss' or its corr... Remove this comment to see the full error message +import Asset from '../../assets/Asset'; +import { Divider } from '../widgets/Divider'; +import { ClearButton } from '../widgets/ClearButton'; import styles from './AssetInput.scss'; import messages from './messages'; type Props = { uniqueId: string; - index: number; getAssetByUniqueId: (...args: Array) => any; - availableAssets: Array; assetFields: Record; - assetsDropdown: Record; addFocusableField: (...args: Array) => any; - removeAssetButtonVisible: Record; - showRemoveAssetButton: (...args: Array) => any; - hideRemoveAssetButton: (...args: Array) => any; currentNumberFormat: NumberFormat; removeAssetRow: (...args: Array) => any; handleSubmitOnEnter: (...args: Array) => any; clearAssetFieldValue: (...args: Array) => any; - onChangeAsset: (...args: Array) => any; autoFocus: boolean; }; const INPUT_FIELD_PADDING_DELTA = 10; @@ -52,7 +47,9 @@ class AssetInput extends Component { this.rightContentRef = React.createRef(); } - hasAssetValue = (asset: Field) => get(asset, 'value', false); + hasAssetValue = (asset: Field) => { + return get(asset, 'value', false); + }; generateInputFieldStyle = () => { const { current: rightContentDom } = this.rightContentRef; @@ -72,20 +69,13 @@ class AssetInput extends Component { const { intl } = this.context; const { uniqueId, - index, getAssetByUniqueId, - availableAssets, assetFields, - assetsDropdown, addFocusableField, - removeAssetButtonVisible, - showRemoveAssetButton, - hideRemoveAssetButton, currentNumberFormat, removeAssetRow, handleSubmitOnEnter, clearAssetFieldValue, - onChangeAsset, autoFocus, } = this.props; const asset = getAssetByUniqueId(uniqueId); @@ -96,120 +86,91 @@ class AssetInput extends Component { const { quantity, metadata, decimals } = asset; const ticker = get(metadata, 'ticker', null); - const sortedAssets = orderBy( - [asset, ...availableAssets], - 'uniqueId', - 'asc' - ); const assetField = assetFields[uniqueId]; - const assetsDropdownField = assetsDropdown[uniqueId]; const inputFieldStyle = this.generateInputFieldStyle(); return ( -
showRemoveAssetButton(uniqueId)} - onMouseLeave={() => hideRemoveAssetButton(uniqueId)} - onMouseEnter={() => showRemoveAssetButton(uniqueId)} - onFocus={() => { - // jsx-a11y/mouse-events-have-key-events - }} - className={styles.component} - > - {quantity.isPositive() && ( -
- {intl.formatMessage(messages.ofLabel)} - {` `} - -
- )} - addFocusableField(field)} - placeholder={ - decimals - ? `0${currentNumberFormat.decimalSeparator}${'0'.repeat( - decimals - )}` - : '0' - } - className={styles.assetItem} - label={ - <> - {`${intl.formatMessage(messages.assetLabel)} #${index + 1}`} - {removeAssetButtonVisible[uniqueId] && ( - removeAssetRow(uniqueId)} - > - {intl.formatMessage(messages.removeLabel)} - - )} - - } - bigNumberFormat={decimals ? currentNumberFormat : null} - decimalPlaces={decimals} - numberLocaleOptions={{ - minimumFractionDigits: decimals, - }} - onChange={(value) => { - assetField.onChange(value); - }} - currency={ticker} - value={assetField.value} - error={assetField.error} - skin={AmountInputSkin} - style={inputFieldStyle} - onKeyPress={(evt: React.KeyboardEvent) => { - if (decimals === 0) { - const { charCode } = evt; +
+
+ {quantity.isPositive() && ( +
+ {intl.formatMessage(messages.ofLabel)} + + + +
+ )} + addFocusableField(field)} + placeholder={ + decimals + ? `0${currentNumberFormat.decimalSeparator}${'0'.repeat( + decimals + )}` + : '0' + } + className={styles.assetItem} + label={} + data-testid={`assetInput:${uniqueId}`} + bigNumberFormat={decimals ? currentNumberFormat : null} + decimalPlaces={decimals} + numberLocaleOptions={{ + minimumFractionDigits: decimals, + }} + onChange={(value) => { + assetField.onChange(value); + }} + currency={ticker} + value={assetField.value} + error={assetField.error} + skin={AmountInputSkin} + style={inputFieldStyle} + onKeyPress={(evt: React.KeyboardEvent) => { + if (decimals === 0) { + const { charCode } = evt; - if (charCode === 190 || charCode === 110 || charCode === 46) { - evt.persist(); - evt.preventDefault(); - evt.stopPropagation(); + if (charCode === 190 || charCode === 110 || charCode === 46) { + evt.persist(); + evt.preventDefault(); + evt.stopPropagation(); + } } - } - handleSubmitOnEnter(evt); - }} - allowSigns={false} - autoFocus={autoFocus} - /> -
- {this.hasAssetValue(assetField) && ( -
- - - -
-
- )} -
- + /> +
+ )} + {ticker ? ( + <> + + {ticker} + + ) : null}
+
+ removeAssetRow(uniqueId)} + data-testid={`removeAsset:${uniqueId}`} + > + + +
); } diff --git a/source/renderer/app/components/wallet/send-form/WalletSendAssetsConfirmationDialog.messages.ts b/source/renderer/app/components/wallet/send-form/WalletSendAssetsConfirmationDialog.messages.ts index 319ace9ef8..3970c87590 100644 --- a/source/renderer/app/components/wallet/send-form/WalletSendAssetsConfirmationDialog.messages.ts +++ b/source/renderer/app/components/wallet/send-form/WalletSendAssetsConfirmationDialog.messages.ts @@ -90,19 +90,19 @@ export const getMessages = () => { unformattedAmountLabel: { id: 'wallet.send.confirmationDialog.unformattedAmountLabel', defaultMessage: '!!!unformatted amount', - description: 'Label for "unformated amount"', + description: 'Label for "unformatted amount"', }, unformattedAmountMessageForSoftwareWallets: { id: 'wallet.send.confirmationDialog.unformattedAmountMessageForSoftwareWallets', defaultMessage: '!!!Native assets may specify a number of decimal places, as defined in the Cardano token registry. Daedalus uses this information to format the amount that is being sent in the transaction.

The native token unformatted amount is the amount without these decimal places. Please ensure that you verify both amounts, as some wallet software may not yet use the Cardano token registry.', - description: 'Message for "unformated amount"', + description: 'Message for "unformatted amount"', }, unformattedAmountMessageForHardwareWallets: { id: 'wallet.send.confirmationDialog.unformattedAmountMessageForHardwareWallets', defaultMessage: '!!!Native assets may specify a number of decimal places, as defined in the Cardano token registry. Daedalus uses this information to format the amount that is being sent in the transaction.

The native token unformatted amount is the amount without these decimal places. Please ensure that you verify both amounts, as some wallet software may not yet use the Cardano token registry.

The native token unformatted amount will be displayed on the hardware wallet device during transaction confirmation.', - description: 'Message for "unformated amount"', + description: 'Message for "unformatted amount"', }, emptyingWarning: { id: 'wallet.send.confirmationDialog.emptyingWarning', diff --git a/source/renderer/app/components/wallet/send-form/WalletSendAssetsConfirmationDialog.tsx b/source/renderer/app/components/wallet/send-form/WalletSendAssetsConfirmationDialog.tsx index b9a0be98ca..c0aee20300 100644 --- a/source/renderer/app/components/wallet/send-form/WalletSendAssetsConfirmationDialog.tsx +++ b/source/renderer/app/components/wallet/send-form/WalletSendAssetsConfirmationDialog.tsx @@ -14,9 +14,7 @@ import ReactToolboxMobxForm from '../../../utils/ReactToolboxMobxForm'; import Dialog from '../../widgets/Dialog'; import DialogCloseButton from '../../widgets/DialogCloseButton'; import LocalizableError from '../../../i18n/LocalizableError'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './WalletSendAssetsConfirmation... Remove this comment to see the full error message import styles from './WalletSendAssetsConfirmationDialog.scss'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../../assets/images/questio... Remove this comment to see the full error message import questionMarkIcon from '../../../assets/images/question-mark.inline.svg'; import { FORM_VALIDATION_DEBOUNCE_WAIT } from '../../../config/timingConfig'; import { submitOnEnter } from '../../../utils/form'; @@ -62,6 +60,12 @@ type State = { assetsAmounts: Array; areTermsAccepted: boolean; }; + +interface FormFields { + passphrase: string; + flightCandidateCheckbox: string; +} + const messages = getMessages(); @observer @@ -87,8 +91,7 @@ class WalletSendAssetsConfirmationDialog extends Component { }); } - form = new ReactToolboxMobxForm( - // @ts-ignore ts-migrate(2554) FIXME: Expected 0 arguments, but got 2. + form = new ReactToolboxMobxForm( { fields: { passphrase: { @@ -132,13 +135,15 @@ class WalletSendAssetsConfirmationDialog extends Component { } ); submit = () => { - // @ts-ignore ts-migrate(2339) FIXME: Property 'submit' does not exist on type 'ReactToo... Remove this comment to see the full error message this.form.submit({ onSuccess: (form) => { const { selectedAssets, assetsAmounts } = this.state; const { receiver, amount, amountToNaturalUnits, isHardwareWallet } = this.props; const { passphrase } = form.values(); + const hasAssetsRemainingAfterTransaction = + this.props.selectedAssets.length < + this.props.allAvailableTokens?.length; const transactionData = { receiver, amount: amountToNaturalUnits(amount), @@ -146,6 +151,7 @@ class WalletSendAssetsConfirmationDialog extends Component { isHardwareWallet, assets: selectedAssets, assetsAmounts, + hasAssetsRemainingAfterTransaction, }; this.props.onSubmit(transactionData); }, @@ -153,13 +159,13 @@ class WalletSendAssetsConfirmationDialog extends Component { }); }; handleSubmitOnEnter = (event: KeyboardEvent) => - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message (this.props.isHardwareWallet || this.form.$('passphrase').isValid) && + this.props.error?.id !== + 'api.errors.NotEnoughFundsForTransactionFeesErrorWithTokens' && submitOnEnter(this.submit, event); renderConfirmationElement = ( isHardwareWallet: boolean ): React.ReactElement, any> | null => { - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const passphraseField = this.form.$('passphrase'); const { areTermsAccepted } = this.state; const { hwDeviceStatus, isFlight, onExternalLinkClick, wallet, isTrezor } = @@ -222,9 +228,7 @@ class WalletSendAssetsConfirmationDialog extends Component { const { form } = this; const { intl } = this.context; const { selectedAssets, areTermsAccepted, assetsAmounts } = this.state; - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const passphraseField = form.$('passphrase'); - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const flightCandidateCheckboxField = form.$('flightCandidateCheckbox'); const { onCancel, @@ -259,6 +263,8 @@ class WalletSendAssetsConfirmationDialog extends Component { primary: true, className: 'confirmButton', disabled: + error?.id === + 'api.errors.NotEnoughFundsForTransactionFeesErrorWithTokens' || (!isHardwareWallet && !passphraseField.isValid) || (isHardwareWallet && hwDeviceStatus !== @@ -273,7 +279,6 @@ class WalletSendAssetsConfirmationDialog extends Component { let errorElement = null; if (error) { - // @ts-ignore ts-migrate(2339) FIXME: Property 'values' does not exist on type 'Localiza... Remove this comment to see the full error message const errorHasLink = !!error.values.linkLabel; errorElement = errorHasLink ? ( { onExternalLinkClick={onExternalLinkClick} /> ) : ( - intl.formatMessage(error) + ); } diff --git a/source/renderer/app/components/wallet/send-form/WalletSendConfirmationDialog.tsx b/source/renderer/app/components/wallet/send-form/WalletSendConfirmationDialog.tsx index 3525e88e5b..d73a08796e 100644 --- a/source/renderer/app/components/wallet/send-form/WalletSendConfirmationDialog.tsx +++ b/source/renderer/app/components/wallet/send-form/WalletSendConfirmationDialog.tsx @@ -11,7 +11,6 @@ import ReactToolboxMobxForm from '../../../utils/ReactToolboxMobxForm'; import Dialog from '../../widgets/Dialog'; import DialogCloseButton from '../../widgets/DialogCloseButton'; import LocalizableError from '../../../i18n/LocalizableError'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './WalletSendConfirmationDialog... Remove this comment to see the full error message import styles from './WalletSendConfirmationDialog.scss'; import { FORM_VALIDATION_DEBOUNCE_WAIT } from '../../../config/timingConfig'; import { submitOnEnter } from '../../../utils/form'; @@ -48,6 +47,12 @@ type Props = { type State = { areTermsAccepted: boolean; }; + +interface FormFields { + flightCandidateCheckbox: string; + passphrase: string; +} + const messages = getMessages(); @observer @@ -58,8 +63,7 @@ class WalletSendConfirmationDialog extends Component { state = { areTermsAccepted: false, }; - form = new ReactToolboxMobxForm( - // @ts-ignore ts-migrate(2554) FIXME: Expected 0 arguments, but got 2. + form = new ReactToolboxMobxForm( { fields: { passphrase: { @@ -103,17 +107,19 @@ class WalletSendConfirmationDialog extends Component { } ); submit = () => { - // @ts-ignore ts-migrate(2339) FIXME: Property 'submit' does not exist on type 'ReactToo... Remove this comment to see the full error message this.form.submit({ onSuccess: (form) => { const { receiver, amount, amountToNaturalUnits, isHardwareWallet } = this.props; const { passphrase } = form.values(); + const hasAssetsRemainingAfterTransaction = + this.props.allAvailableTokens?.length > 0; const transactionData = { receiver, amount: amountToNaturalUnits(amount), passphrase, isHardwareWallet, + hasAssetsRemainingAfterTransaction, }; this.props.onSubmit(transactionData); }, @@ -121,13 +127,13 @@ class WalletSendConfirmationDialog extends Component { }); }; handleSubmitOnEnter = (event: KeyboardEvent) => - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message (this.props.isHardwareWallet || this.form.$('passphrase').isValid) && + this.props.error?.id !== + 'api.errors.NotEnoughFundsForTransactionFeesErrorWithTokens' && submitOnEnter(this.submit, event); renderConfirmationElement = ( isHardwareWallet: boolean ): React.ReactElement, any> | null | undefined => { - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const passphraseField = this.form.$('passphrase'); const { areTermsAccepted } = this.state; const { hwDeviceStatus, isFlight, onExternalLinkClick, wallet, isTrezor } = @@ -174,9 +180,7 @@ class WalletSendConfirmationDialog extends Component { const { form } = this; const { intl } = this.context; const { areTermsAccepted } = this.state; - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const passphraseField = form.$('passphrase'); - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const flightCandidateCheckboxField = form.$('flightCandidateCheckbox'); const { onCancel, @@ -210,6 +214,8 @@ class WalletSendConfirmationDialog extends Component { primary: true, className: 'confirmButton', disabled: + error?.id === + 'api.errors.NotEnoughFundsForTransactionFeesErrorWithTokens' || (!isHardwareWallet && !passphraseField.isValid) || (isHardwareWallet && hwDeviceStatus !== @@ -220,7 +226,6 @@ class WalletSendConfirmationDialog extends Component { let errorElement = null; if (error) { - // @ts-ignore ts-migrate(2339) FIXME: Property 'values' does not exist on type 'Localiza... Remove this comment to see the full error message const errorHasLink = !!error.values.linkLabel; errorElement = errorHasLink ? ( { onExternalLinkClick={onExternalLinkClick} /> ) : ( - intl.formatMessage(error) + ); } diff --git a/source/renderer/app/components/wallet/send-form/messages.ts b/source/renderer/app/components/wallet/send-form/messages.ts index 553dd422a4..b927aa0317 100644 --- a/source/renderer/app/components/wallet/send-form/messages.ts +++ b/source/renderer/app/components/wallet/send-form/messages.ts @@ -131,4 +131,28 @@ export default defineMessages({ description: 'Label for the "Calculating fees" message for amount input field.', }, + updateAdaAmountButton: { + id: 'wallet.send.form.asset.updateAdaAmountButton', + defaultMessage: '!!!UPDATE', + description: + 'Label for the "UPDATE" button responsible to set minimum amount required for transaction .', + }, + updateAdaAmountDescription: { + id: 'wallet.send.form.updateAdaAmountDescription', + defaultMessage: '!!!to the minimum of {minimumAda} ADA required', + description: + 'Description for the "UPDATE" button when ADA amount is less than required ', + }, + minimumAmountNotice: { + id: 'wallet.send.form.minimumAmountNote', + defaultMessage: + '!!!Note: the ada field was automatically updated because this transaction requires a minimum of {minimumAda} ADA.', + description: 'Minimum amount update notice', + }, + restoredAdaAmount: { + id: 'wallet.send.form.restoredAdaAmount', + defaultMessage: + '!!!Note: the ada field was automatically updated to {adaAmount} ADA because now it fulfills the minimum amount of {minimumAda} ADA for the transaction.', + description: 'Restored ada amount to original user input', + }, }); diff --git a/source/renderer/app/components/wallet/settings/ChangeSpendingPasswordDialog.scss b/source/renderer/app/components/wallet/settings/ChangeSpendingPasswordDialog.scss index 52d0de0afd..0456b3afba 100644 --- a/source/renderer/app/components/wallet/settings/ChangeSpendingPasswordDialog.scss +++ b/source/renderer/app/components/wallet/settings/ChangeSpendingPasswordDialog.scss @@ -75,6 +75,10 @@ } } + .repeatedPassword { + width: 100%; + } + .error { @include error-message; margin-top: 27px; diff --git a/source/renderer/app/components/wallet/settings/ChangeSpendingPasswordDialog.tsx b/source/renderer/app/components/wallet/settings/ChangeSpendingPasswordDialog.tsx index f0f3148224..3a6a8717c8 100644 --- a/source/renderer/app/components/wallet/settings/ChangeSpendingPasswordDialog.tsx +++ b/source/renderer/app/components/wallet/settings/ChangeSpendingPasswordDialog.tsx @@ -16,7 +16,6 @@ import { import globalMessages from '../../../i18n/global-messages'; import LocalizableError from '../../../i18n/LocalizableError'; import { PasswordInput } from '../../widgets/forms/PasswordInput'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './ChangeSpendingPasswordDialog... Remove this comment to see the full error message import styles from './ChangeSpendingPasswordDialog.scss'; import { FORM_VALIDATION_DEBOUNCE_WAIT } from '../../../config/timingConfig'; import { submitOnEnter } from '../../../utils/form'; @@ -99,6 +98,12 @@ type Props = { currentLocale: string; }; +interface FormFields { + currentPassword: string; + spendingPassword: string; + repeatPassword: string; +} + @observer class ChangeSpendingPasswordDialog extends Component { static defaultProps = { @@ -109,8 +114,7 @@ class ChangeSpendingPasswordDialog extends Component { static contextTypes = { intl: intlShape.isRequired, }; - form = new ReactToolboxMobxForm( - // @ts-ignore ts-migrate(2554) FIXME: Expected 0 arguments, but got 2. + form = new ReactToolboxMobxForm( { fields: { currentPassword: { @@ -199,7 +203,6 @@ class ChangeSpendingPasswordDialog extends Component { } ); submit = () => { - // @ts-ignore ts-migrate(2339) FIXME: Property 'submit' does not exist on type 'ReactToo... Remove this comment to see the full error message this.form.submit({ onSuccess: (form) => { const { currentPassword, spendingPassword } = form.values(); @@ -243,13 +246,9 @@ class ChangeSpendingPasswordDialog extends Component { currentLocale === 'ja-JP' ? styles.jpLangTooltipIcon : '', ]); const newPasswordClasses = classnames(['newPassword', styles.newPassword]); - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const currentPasswordField = form.$('currentPassword'); - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const newPasswordField = form.$('spendingPassword'); - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const repeatedPasswordField = form.$('repeatPassword'); - // @ts-ignore ts-migrate(2339) FIXME: Property 'isValid' does not exist on type 'ReactTo... Remove this comment to see the full error message const canSubmit = !isSubmitting && form.isValid; const currentPasswordError = // @ts-ignore ts-migrate(2339) FIXME: Property 'code' does not exist on type 'Localizabl... Remove this comment to see the full error message diff --git a/source/renderer/app/components/wallet/settings/DelegateWalletButton.tsx b/source/renderer/app/components/wallet/settings/DelegateWalletButton.tsx index 5c44398d20..eff16106fa 100644 --- a/source/renderer/app/components/wallet/settings/DelegateWalletButton.tsx +++ b/source/renderer/app/components/wallet/settings/DelegateWalletButton.tsx @@ -1,7 +1,6 @@ import React, { Component } from 'react'; import { defineMessages, intlShape } from 'react-intl'; import { Button } from 'react-polymorph/lib/components/Button'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './DelegateWalletButton.scss' o... Remove this comment to see the full error message import styles from './DelegateWalletButton.scss'; const messages = defineMessages({ diff --git a/source/renderer/app/components/wallet/settings/DeleteWallet.tsx b/source/renderer/app/components/wallet/settings/DeleteWallet.tsx index 52374a146c..5eb4816a74 100644 --- a/source/renderer/app/components/wallet/settings/DeleteWallet.tsx +++ b/source/renderer/app/components/wallet/settings/DeleteWallet.tsx @@ -3,7 +3,6 @@ import type { Node } from 'react'; import React from 'react'; import { observer } from 'mobx-react'; import { defineMessages, injectIntl, intlShape } from 'react-intl'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './WalletSettings.scss' or its ... Remove this comment to see the full error message import styles from './WalletSettings.scss'; import type { ReactIntlMessage } from '../../../types/i18nTypes'; import BorderedBox from '../../widgets/BorderedBox'; diff --git a/source/renderer/app/components/wallet/settings/DeleteWalletConfirmation.tsx b/source/renderer/app/components/wallet/settings/DeleteWalletConfirmation.tsx index 4a6b2b37d2..d5822f9f62 100644 --- a/source/renderer/app/components/wallet/settings/DeleteWalletConfirmation.tsx +++ b/source/renderer/app/components/wallet/settings/DeleteWalletConfirmation.tsx @@ -4,7 +4,6 @@ import { CheckboxSkin } from 'react-polymorph/lib/skins/simple/CheckboxSkin'; import { Input } from 'react-polymorph/lib/components/Input'; import { InputSkin } from 'react-polymorph/lib/skins/simple/InputSkin'; import { submitOnEnter } from '../../../utils/form'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './DeleteWalletConfirmationDial... Remove this comment to see the full error message import styles from './DeleteWalletConfirmationDialog.scss'; type Props = { diff --git a/source/renderer/app/components/wallet/settings/ExportWalletToFileDialog.tsx b/source/renderer/app/components/wallet/settings/ExportWalletToFileDialog.tsx index d79b0e3ed2..675e6cd70e 100644 --- a/source/renderer/app/components/wallet/settings/ExportWalletToFileDialog.tsx +++ b/source/renderer/app/components/wallet/settings/ExportWalletToFileDialog.tsx @@ -65,6 +65,10 @@ type State = { exportType: ExportType; }; +interface FormFields { + spendingPassword: string; +} + @observer class ExportWalletToFileDialog extends Component { static contextTypes = { @@ -82,8 +86,7 @@ class ExportWalletToFileDialog extends Component { // onChangeExportType(exportType: ExportType) { // this.setState({ exportType }); // } - form = new ReactToolboxMobxForm( - // @ts-ignore ts-migrate(2554) FIXME: Expected 0 arguments, but got 2. + form = new ReactToolboxMobxForm( { fields: { spendingPassword: { @@ -96,7 +99,7 @@ class ExportWalletToFileDialog extends Component { ), value: '', validators: [ - () => + () => { // if (field.value === '') { // return [ // false, @@ -105,7 +108,8 @@ class ExportWalletToFileDialog extends Component { // ), // ]; // } - [true], + return [true]; + }, ], }, }, @@ -121,7 +125,6 @@ class ExportWalletToFileDialog extends Component { } ); submit = () => { - // @ts-ignore ts-migrate(2339) FIXME: Property 'submit' does not exist on type 'ReactToo... Remove this comment to see the full error message this.form.submit({ onSuccess: async (form) => { const { spendingPassword } = form.values(); diff --git a/source/renderer/app/components/wallet/settings/ICOPublicKeyBox.tsx b/source/renderer/app/components/wallet/settings/ICOPublicKeyBox.tsx index ec2bec5458..88ea4169d6 100644 --- a/source/renderer/app/components/wallet/settings/ICOPublicKeyBox.tsx +++ b/source/renderer/app/components/wallet/settings/ICOPublicKeyBox.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { defineMessages, injectIntl, intlShape } from 'react-intl'; import BorderedBox from '../../widgets/BorderedBox'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './WalletSettings.scss' or its ... Remove this comment to see the full error message import styles from './WalletSettings.scss'; import PublicKeyQRCodeDialog from './ICOPublicKeyQRCodeDialog'; import ICOPublicKeyDialog from './ICOPublicKeyDialog'; diff --git a/source/renderer/app/components/wallet/settings/ICOPublicKeyDialog.tsx b/source/renderer/app/components/wallet/settings/ICOPublicKeyDialog.tsx index 09769e3b3f..215f99c510 100644 --- a/source/renderer/app/components/wallet/settings/ICOPublicKeyDialog.tsx +++ b/source/renderer/app/components/wallet/settings/ICOPublicKeyDialog.tsx @@ -3,7 +3,6 @@ import { observer } from 'mobx-react'; import { defineMessages, intlShape } from 'react-intl'; import { Input } from 'react-polymorph/lib/components/Input'; import vjf from 'mobx-react-form/lib/validators/VJF'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './PublicKeyDialog.scss' or its... Remove this comment to see the full error message import styles from './PublicKeyDialog.scss'; import ReactToolboxMobxForm from '../../../utils/ReactToolboxMobxForm'; import { submitOnEnter } from '../../../utils/form'; @@ -41,6 +40,10 @@ type Props = { walletName: string; }; +interface FormFields { + spendingPassword: string; +} + @observer class ICOPublicKeyDialog extends Component { static contextTypes = { @@ -55,8 +58,7 @@ class ICOPublicKeyDialog extends Component { } } - form = new ReactToolboxMobxForm( - // @ts-ignore ts-migrate(2554) FIXME: Expected 0 arguments, but got 2. + form = new ReactToolboxMobxForm( { fields: { spendingPassword: { @@ -96,7 +98,6 @@ class ICOPublicKeyDialog extends Component { } ); submit = () => { - // @ts-ignore ts-migrate(2339) FIXME: Property 'submit' does not exist on type 'ReactToo... Remove this comment to see the full error message this.form.submit({ onSuccess: (form) => { const { spendingPassword } = form.values(); @@ -113,14 +114,12 @@ class ICOPublicKeyDialog extends Component { const { intl } = this.context; const { onClose, error, walletName } = this.props; const { form } = this; - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const spendingPasswordField = form.$('spendingPassword'); const actions = [ { label: intl.formatMessage(messages.buttonLabel), onClick: this.submit, primary: true, - // @ts-ignore ts-migrate(2339) FIXME: Property 'isValid' does not exist on type 'ReactTo... Remove this comment to see the full error message disabled: !this.form.isValid, }, ]; @@ -131,14 +130,12 @@ class ICOPublicKeyDialog extends Component { actions={actions} closeOnOverlayClick onClose={onClose} - className={styles.dialog} closeButton={} >
{intl.formatMessage(messages.description)}
= defineMessages({ dialogTitle: { id: 'wallet.settings.icoPublicKey', defaultMessage: '!!!ICO Public Key', diff --git a/source/renderer/app/components/wallet/settings/ICOPublicKeyQRCodeDialog.tsx b/source/renderer/app/components/wallet/settings/ICOPublicKeyQRCodeDialog.tsx index c04a3dc3d2..6fa6b8db22 100644 --- a/source/renderer/app/components/wallet/settings/ICOPublicKeyQRCodeDialog.tsx +++ b/source/renderer/app/components/wallet/settings/ICOPublicKeyQRCodeDialog.tsx @@ -9,7 +9,6 @@ import DialogCloseButton from '../../widgets/DialogCloseButton'; import Dialog from '../../widgets/Dialog'; // @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../../assets/images/clipboa... Remove this comment to see the full error message import iconCopy from '../../../assets/images/clipboard-ic.inline.svg'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './PublicKeyQRCodeDialog.scss' ... Remove this comment to see the full error message import styles from './PublicKeyQRCodeDialog.scss'; import globalMessages from '../../../i18n/global-messages'; import { messages } from './ICOPublicKeyQRCodeDialog.messages'; @@ -58,7 +57,7 @@ const ICOPublicKeyQRCodeDialog = observer((props: Props) => { className={styles.dialog} closeButton={} > -
+
{ static contextTypes = { intl: intlShape.isRequired, }; - form = new ReactToolboxMobxForm( - // @ts-ignore ts-migrate(2554) FIXME: Expected 0 arguments, but got 2. + form = new ReactToolboxMobxForm( { fields: { confirmUnsupportChecked: { @@ -206,15 +210,12 @@ class UndelegateWalletConfirmationDialog extends Component { confirmationDisabled = () => { const { form } = this; const { fees, isSubmitting, hwDeviceStatus, selectedWallet } = this.props; - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const { isValid: unsupportCheckboxIsValid } = form.$( 'confirmUnsupportChecked' ); - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const { isValid: ineligibleCheckboxIsValid } = form.$( 'confirmIneligibleChecked' ); - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const { isValid: passphraseIsValid } = form.$('passphrase'); const isHardwareWallet = get(selectedWallet, 'isHardwareWallet'); @@ -237,7 +238,6 @@ class UndelegateWalletConfirmationDialog extends Component { return false; } - // @ts-ignore ts-migrate(2339) FIXME: Property 'submit' does not exist on type 'ReactToo... Remove this comment to see the full error message return this.form.submit({ onSuccess: (form) => { const { selectedWallet, onConfirm } = this.props; @@ -273,11 +273,8 @@ class UndelegateWalletConfirmationDialog extends Component { render() { const { form } = this; const { intl } = this.context; - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const unsupportCheckboxField = form.$('confirmUnsupportChecked'); - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const ineligibleCheckboxField = form.$('confirmIneligibleChecked'); - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const passphraseField = form.$('passphrase'); const { selectedWallet, diff --git a/source/renderer/app/components/wallet/settings/UndelegateWalletSuccessDialog.tsx b/source/renderer/app/components/wallet/settings/UndelegateWalletSuccessDialog.tsx index e61562c68e..cb1005bec7 100644 --- a/source/renderer/app/components/wallet/settings/UndelegateWalletSuccessDialog.tsx +++ b/source/renderer/app/components/wallet/settings/UndelegateWalletSuccessDialog.tsx @@ -4,7 +4,6 @@ import { defineMessages, intlShape, FormattedHTMLMessage } from 'react-intl'; import SVGInline from 'react-svg-inline'; import DialogCloseButton from '../../widgets/DialogCloseButton'; import Dialog from '../../widgets/Dialog'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './UndelegateWalletSuccessDialo... Remove this comment to see the full error message import styles from './UndelegateWalletSuccessDialog.scss'; import globalMessages from '../../../i18n/global-messages'; // @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../../assets/images/untada.... Remove this comment to see the full error message diff --git a/source/renderer/app/components/wallet/settings/UnpairWallet.tsx b/source/renderer/app/components/wallet/settings/UnpairWallet.tsx index 9be5fa70e6..7031cd1fa7 100644 --- a/source/renderer/app/components/wallet/settings/UnpairWallet.tsx +++ b/source/renderer/app/components/wallet/settings/UnpairWallet.tsx @@ -8,7 +8,6 @@ import { injectIntl, intlShape, } from 'react-intl'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './WalletSettings.scss' or its ... Remove this comment to see the full error message import styles from './WalletSettings.scss'; import type { ReactIntlMessage } from '../../../types/i18nTypes'; import BorderedBox from '../../widgets/BorderedBox'; diff --git a/source/renderer/app/components/wallet/settings/WalletPublicKeyBox.tsx b/source/renderer/app/components/wallet/settings/WalletPublicKeyBox.tsx index cfd58e8d15..f7a5383317 100644 --- a/source/renderer/app/components/wallet/settings/WalletPublicKeyBox.tsx +++ b/source/renderer/app/components/wallet/settings/WalletPublicKeyBox.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { defineMessages, injectIntl } from 'react-intl'; import BorderedBox from '../../widgets/BorderedBox'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './WalletSettings.scss' or its ... Remove this comment to see the full error message import styles from './WalletSettings.scss'; import WalletPublicKeyQRCodeDialog from './WalletPublicKeyQRCodeDialog'; import PublicKeyDialog from './WalletPublicKeyDialog'; diff --git a/source/renderer/app/components/wallet/settings/WalletPublicKeyDialog.tsx b/source/renderer/app/components/wallet/settings/WalletPublicKeyDialog.tsx index 82de24086c..8a72c6f025 100644 --- a/source/renderer/app/components/wallet/settings/WalletPublicKeyDialog.tsx +++ b/source/renderer/app/components/wallet/settings/WalletPublicKeyDialog.tsx @@ -3,7 +3,6 @@ import { observer } from 'mobx-react'; import { defineMessages, intlShape } from 'react-intl'; import { Input } from 'react-polymorph/lib/components/Input'; import vjf from 'mobx-react-form/lib/validators/VJF'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './PublicKeyDialog.scss' or its... Remove this comment to see the full error message import styles from './PublicKeyDialog.scss'; import ReactToolboxMobxForm from '../../../utils/ReactToolboxMobxForm'; import { submitOnEnter } from '../../../utils/form'; @@ -40,6 +39,10 @@ type Props = { walletName: string; }; +interface FormFields { + spendingPassword: string; +} + @observer class WalletPublicKeyDialog extends Component { static contextTypes = { @@ -54,8 +57,7 @@ class WalletPublicKeyDialog extends Component { } } - form = new ReactToolboxMobxForm( - // @ts-ignore ts-migrate(2554) FIXME: Expected 0 arguments, but got 2. + form = new ReactToolboxMobxForm( { fields: { spendingPassword: { @@ -95,7 +97,6 @@ class WalletPublicKeyDialog extends Component { } ); submit = () => { - // @ts-ignore ts-migrate(2339) FIXME: Property 'submit' does not exist on type 'ReactToo... Remove this comment to see the full error message this.form.submit({ onSuccess: (form) => { const { spendingPassword } = form.values(); @@ -112,14 +113,12 @@ class WalletPublicKeyDialog extends Component { const { intl } = this.context; const { onClose, error, walletName } = this.props; const { form } = this; - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const spendingPasswordField = form.$('spendingPassword'); const actions = [ { label: intl.formatMessage(messages.buttonLabel), onClick: this.submit, primary: true, - // @ts-ignore ts-migrate(2339) FIXME: Property 'isValid' does not exist on type 'ReactTo... Remove this comment to see the full error message disabled: !this.form.isValid, }, ]; @@ -130,14 +129,12 @@ class WalletPublicKeyDialog extends Component { actions={actions} closeOnOverlayClick onClose={onClose} - className={styles.dialog} closeButton={} >
{intl.formatMessage(messages.description)}
{ className={styles.dialog} closeButton={} > -
+
{ const { intl } = this.context; const { onContinue, onClose, walletName } = this.props; const { safetyAgreement } = this.state; - const isSubmitting = false; const actions = [ { - className: isSubmitting ? styles.isSubmitting : null, label: intl.formatMessage(messages.recoveryPhraseStep1Button), primary: true, onClick: onContinue, diff --git a/source/renderer/app/components/wallet/settings/WalletRecoveryPhraseStep2Dialog.tsx b/source/renderer/app/components/wallet/settings/WalletRecoveryPhraseStep2Dialog.tsx index a98be12c7f..90e7d5ec6c 100644 --- a/source/renderer/app/components/wallet/settings/WalletRecoveryPhraseStep2Dialog.tsx +++ b/source/renderer/app/components/wallet/settings/WalletRecoveryPhraseStep2Dialog.tsx @@ -13,7 +13,6 @@ import { } from '../../../utils/validations'; import DialogCloseButton from '../../widgets/DialogCloseButton'; import Dialog from '../../widgets/Dialog'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './WalletRecoveryPhraseStepDial... Remove this comment to see the full error message import styles from './WalletRecoveryPhraseStepDialogs.scss'; import globalMessages from '../../../i18n/global-messages'; @@ -70,6 +69,10 @@ type State = { isVerifying: boolean; }; +interface FormFields { + recoveryPhrase: string; +} + @observer class WalletRecoveryPhraseStep2Dialog extends Component { static contextTypes = { @@ -78,8 +81,7 @@ class WalletRecoveryPhraseStep2Dialog extends Component { state = { isVerifying: false, }; - form = new ReactToolboxMobxForm( - // @ts-ignore ts-migrate(2554) FIXME: Expected 0 arguments, but got 2. + form = new ReactToolboxMobxForm( { fields: { recoveryPhrase: { @@ -113,7 +115,6 @@ class WalletRecoveryPhraseStep2Dialog extends Component { const { intl } = this.context; const { onClose, onContinue, expectedWordCount, walletName } = this.props; const { isVerifying } = this.state; - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const recoveryPhraseField = form.$('recoveryPhrase'); const { length: enteredWordCount } = recoveryPhraseField.value; const canSubmit = diff --git a/source/renderer/app/components/wallet/settings/WalletRecoveryPhraseStep4Dialog.tsx b/source/renderer/app/components/wallet/settings/WalletRecoveryPhraseStep4Dialog.tsx index 308ef7ba26..9f1dade81f 100644 --- a/source/renderer/app/components/wallet/settings/WalletRecoveryPhraseStep4Dialog.tsx +++ b/source/renderer/app/components/wallet/settings/WalletRecoveryPhraseStep4Dialog.tsx @@ -6,7 +6,6 @@ import { Link } from 'react-polymorph/lib/components/Link'; import { LinkSkin } from 'react-polymorph/lib/skins/simple/LinkSkin'; import DialogCloseButton from '../../widgets/DialogCloseButton'; import Dialog from '../../widgets/Dialog'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './WalletRecoveryPhraseStepDial... Remove this comment to see the full error message import styles from './WalletRecoveryPhraseStepDialogs.scss'; export const messages = defineMessages({ diff --git a/source/renderer/app/components/wallet/settings/WalletRecoveryPhraseVerificationWidget.tsx b/source/renderer/app/components/wallet/settings/WalletRecoveryPhraseVerificationWidget.tsx index 623a021270..e1e3be6f1c 100644 --- a/source/renderer/app/components/wallet/settings/WalletRecoveryPhraseVerificationWidget.tsx +++ b/source/renderer/app/components/wallet/settings/WalletRecoveryPhraseVerificationWidget.tsx @@ -14,7 +14,6 @@ import iconOk from '../../../assets/images/recovery-phrase-verification-ok.inlin import iconWarning from '../../../assets/images/recovery-phrase-verification-warning.inline.svg'; // @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../../assets/images/recover... Remove this comment to see the full error message import iconNotification from '../../../assets/images/recovery-phrase-verification-notification.inline.svg'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './WalletRecoveryPhraseVerifica... Remove this comment to see the full error message import styles from './WalletRecoveryPhraseVerificationWidget.scss'; import { RECOVERY_PHRASE_VERIFICATION_STATUSES as statuses, @@ -246,7 +245,7 @@ class WalletRecoveryPhraseVerificationWidget extends Component { return (

{intl.formatMessage(messages.title)}

-
+
{intl.formatMessage(messages.description, { wordCount, })} @@ -272,7 +271,6 @@ class WalletRecoveryPhraseVerificationWidget extends Component { { !isWalletNameConfirmationCorrect); // @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. const handleSubmit = React.useCallback(() => !isDisabled && onContinue()); - const buttonClasses = classnames([ - 'attention', - isSubmitting ? styles.isSubmitting : null, - ]); const buttonLabel = !isSubmitting ? ( `${intl.formatMessage(messages.confirmButtonLabel)} ${countdownDisplay}` ) : ( @@ -83,7 +78,7 @@ const WalletSettingsRemoveConfirmationDialog = observer((props: Props) => { onClick: onCancel, }, { - className: buttonClasses, + className: 'attention', label: buttonLabel, onClick: onContinue, disabled: isDisabled, diff --git a/source/renderer/app/components/wallet/skins/AmountInputSkin.tsx b/source/renderer/app/components/wallet/skins/AmountInputSkin.tsx index c87763707a..1e8bdcfb16 100644 --- a/source/renderer/app/components/wallet/skins/AmountInputSkin.tsx +++ b/source/renderer/app/components/wallet/skins/AmountInputSkin.tsx @@ -2,7 +2,6 @@ import React, { Component } from 'react'; import { defineMessages, intlShape } from 'react-intl'; import BigNumber from 'bignumber.js'; import { InputSkin } from 'react-polymorph/lib/skins/simple/InputSkin'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './AmountInputSkin.scss' or its... Remove this comment to see the full error message import styles from './AmountInputSkin.scss'; export const messages = defineMessages({ diff --git a/source/renderer/app/components/wallet/summary/WalletSummary.tsx b/source/renderer/app/components/wallet/summary/WalletSummary.tsx index ee658ba589..2f5fb3e439 100644 --- a/source/renderer/app/components/wallet/summary/WalletSummary.tsx +++ b/source/renderer/app/components/wallet/summary/WalletSummary.tsx @@ -1,12 +1,13 @@ import React, { Component } from 'react'; import { observer } from 'mobx-react'; import { defineMessages, intlShape } from 'react-intl'; +import type { Reward } from '../../../api/staking/types'; import Wallet from '../../../domains/Wallet'; import type { Currency } from '../../../types/currencyTypes'; import WalletSummaryHeader from './WalletSummaryHeader'; import WalletSummaryCurrency from './WalletSummaryCurrency'; import type { AssetToken } from '../../../api/assets/types'; -import WalletTokensList from '../tokens/WalletTokensList'; +import WalletTokensList from '../tokens/wallet-tokens-list/WalletTokensList'; import { MAX_TOKENS_ON_SUMMARY_PAGE } from '../../../config/numbersConfig'; const messages = defineMessages({ @@ -18,6 +19,7 @@ const messages = defineMessages({ }); type Props = { wallet: Wallet; + reward: Reward; numberOfRecentTransactions: number; numberOfTransactions?: number; numberOfPendingTransactions: number; @@ -50,6 +52,7 @@ class WalletSummary extends Component { render() { const { wallet, + reward, numberOfPendingTransactions, numberOfRecentTransactions, numberOfTransactions, @@ -78,6 +81,7 @@ class WalletSummary extends Component { <> svg { - height: 7px; - margin-top: 7px; - width: 12px; - - path { - fill: var(--theme-widgets-asset-token-text-color); - } - } - .header:hover & { - opacity: 0.5; - } - &.isExpanded { - transform: rotate(180deg); - } -} -.content { - display: none; - margin: 13px 12px 0; -} -.footer { - border-top: 1px solid var(--theme-wallet-settings-section-separator-color); - color: var(--theme-widgets-asset-token-text-color); - display: flex; - justify-content: space-between; - margin-top: 20px; - padding-top: 20px; - - dl { - line-height: 1; - padding: 9px 0 0 8px; - width: calc(100% - 264px); - } - - dt { - display: inline-block; - font-family: var(--font-medium); - font-size: 12px; - line-height: 1.33; - text-align: right; - white-space: nowrap; - width: 67px; - } - - dd { - display: inline-flex; - font-family: var(--font-light); - font-size: 12px; - line-height: 16px; - margin-left: 10px; - white-space: normal; - width: calc(100% - 100px); - word-break: break-all; - word-wrap: break-word; - } - - .footerButtons { - margin: auto 0; - white-space: nowrap; - .button { - height: 36px; - margin-left: 12px; - width: 120px; - } - .settingsButton { - svg { - height: 9px; - margin-left: 6px; - width: 11px; - } - } - } -} diff --git a/source/renderer/app/components/wallet/summary/WalletSummaryCurrency.scss b/source/renderer/app/components/wallet/summary/WalletSummaryCurrency.scss index 6098cf0be8..5aeec02f42 100644 --- a/source/renderer/app/components/wallet/summary/WalletSummaryCurrency.scss +++ b/source/renderer/app/components/wallet/summary/WalletSummaryCurrency.scss @@ -32,7 +32,7 @@ font-family: var(--font-bold); font-size: 16px; line-height: 1; - margin-bottom: 5.5px; + margin-bottom: 8.5px; user-select: text; word-break: break-all; diff --git a/source/renderer/app/components/wallet/summary/WalletSummaryCurrency.tsx b/source/renderer/app/components/wallet/summary/WalletSummaryCurrency.tsx index 0b4c8a6e30..63d9a61a6b 100644 --- a/source/renderer/app/components/wallet/summary/WalletSummaryCurrency.tsx +++ b/source/renderer/app/components/wallet/summary/WalletSummaryCurrency.tsx @@ -4,10 +4,9 @@ import moment from 'moment'; import { defineMessages, intlShape } from 'react-intl'; import SVGInline from 'react-svg-inline'; import classnames from 'classnames'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../../assets/images/currenc... Remove this comment to see the full error message +// @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../../assets/images/currency... Remove this comment to see the full error message import currencySettingsIcon from '../../../assets/images/currency-settings-ic.inline.svg'; import globalMessages from '../../../i18n/global-messages'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './WalletSummaryCurrency.scss' ... Remove this comment to see the full error message import styles from './WalletSummaryCurrency.scss'; import Wallet from '../../../domains/Wallet'; import { formattedWalletCurrencyAmount } from '../../../utils/formatters'; @@ -92,10 +91,7 @@ class WalletSummaryCurrency extends Component {
{/* @ts-ignore ts-migrate(2741) FIXME: Property 'replacer' is missing in type '{ children... Remove this comment to see the full error message */} {currencyWalletAmount} - - {' '} - {currencyWalletAmountSymbol} - + {currencyWalletAmountSymbol}
1 {intl.formatMessage(globalMessages.adaUnit)} = {currencyRate || '–'}{' '} diff --git a/source/renderer/app/components/wallet/summary/WalletSummaryHeader.scss b/source/renderer/app/components/wallet/summary/WalletSummaryHeader.scss index d361922982..807e20d2c8 100644 --- a/source/renderer/app/components/wallet/summary/WalletSummaryHeader.scss +++ b/source/renderer/app/components/wallet/summary/WalletSummaryHeader.scss @@ -14,12 +14,16 @@ } .walletContent { - align-items: center; display: flex; flex-direction: row; justify-content: space-between; } +.currency { + min-width: 187px; + padding-top: 6px; +} + .walletName { color: var(--theme-bordered-box-text-color); font-family: var(--font-regular); @@ -40,28 +44,28 @@ word-break: break-all; .currencyCode { - font-size: 16px; font-size: 16px; margin-left: 6px; opacity: 0.7; } } +.rewards { + @extend %smallText; + height: auto; +} + +.rewardsAmount { + font-family: var(--font-medium); +} + .transactionsCountWrapper { - .numberOfTransactions, - .numberOfPendingTransactions { + .numberOfTransactions { @extend %smallText; - - span:first-child { + font-family: var(--font-light); + span { font-family: var(--font-medium); - opacity: 0.7; } - - span:last-child { - font-family: var(--font-light); - opacity: 1; - } - &.isLoadingNumberOfTransactions { @include animated-ellipsis($width: 16px); } diff --git a/source/renderer/app/components/wallet/summary/WalletSummaryHeader.tsx b/source/renderer/app/components/wallet/summary/WalletSummaryHeader.tsx index b02c7a5bb9..b40ba89679 100644 --- a/source/renderer/app/components/wallet/summary/WalletSummaryHeader.tsx +++ b/source/renderer/app/components/wallet/summary/WalletSummaryHeader.tsx @@ -2,31 +2,27 @@ import React, { Component } from 'react'; // @ts-ignore ts-migrate(2305) FIXME: Module '"react"' has no exported member 'Node'. import type { Node } from 'react'; import { observer } from 'mobx-react'; -import { defineMessages, intlShape } from 'react-intl'; +import { defineMessages, FormattedHTMLMessage, intlShape } from 'react-intl'; import classnames from 'classnames'; +import type { Reward } from '../../../api/staking/types'; import globalMessages from '../../../i18n/global-messages'; import BorderedBox from '../../widgets/BorderedBox'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './WalletSummaryHeader.scss' or... Remove this comment to see the full error message import styles from './WalletSummaryHeader.scss'; import Wallet from '../../../domains/Wallet'; import { formattedWalletAmount } from '../../../utils/formatters'; import { DiscreetValue } from '../../../features/discreet-mode'; +import WalletSummaryHeaderRewards from './WalletSummaryHeaderRewards'; const messages = defineMessages({ transactionsLabel: { id: 'wallet.summary.header.transactionsLabel', - defaultMessage: '!!!Number of transactions', + defaultMessage: '!!!{total} transactions, {pending} pending', description: '"Number of transactions" label on Wallet summary header page', }, - pendingTransactionsLabel: { - id: 'wallet.summary.header.pendingTransactionsLabel', - defaultMessage: '!!!Number of pending transactions', - description: - '"Number of pending transactions" label on Wallet summary header page', - }, }); type Props = { wallet: Wallet; + reward: Reward; numberOfRecentTransactions: number; numberOfTransactions?: number; numberOfPendingTransactions: number; @@ -43,6 +39,7 @@ class WalletSummaryHeader extends Component { render() { const { wallet, + reward, numberOfPendingTransactions, numberOfRecentTransactions, numberOfTransactions, @@ -56,15 +53,18 @@ class WalletSummaryHeader extends Component { styles.numberOfTransactions, isLoadingAllTransactions ? styles.isLoadingNumberOfTransactions : null, ]); - const numberOfPendingTransactionsStyles = classnames([ - styles.numberOfPendingTransactions, - ]); const walletNameStyles = classnames([styles.walletName]); const walletAmountStyles = classnames([styles.walletAmount]); const isRestoreActive = wallet.isRestoring; const walletAmount = isRestoreActive - ? '-' + ? '–' : formattedWalletAmount(wallet.amount, false); + const totalTransactions = isRestoreActive + ? '–' + : numberOfTransactions ?? numberOfRecentTransactions; + const pendingTransactions = isRestoreActive + ? '–' + : numberOfPendingTransactions; return (
@@ -78,28 +78,32 @@ class WalletSummaryHeader extends Component { {intl.formatMessage(globalMessages.adaUnit)}
+ {!wallet.isLegacy && ( +
+ +
+ )} {!isLoadingTransactions && (
-
- - {intl.formatMessage(messages.pendingTransactionsLabel)} - - :  - {numberOfPendingTransactions} -
- - {intl.formatMessage(messages.transactionsLabel)} - - :  - - {numberOfTransactions || numberOfRecentTransactions} - +
)}
- {currency} +
{currency}
diff --git a/source/renderer/app/components/wallet/summary/WalletSummaryHeaderRewards.scss b/source/renderer/app/components/wallet/summary/WalletSummaryHeaderRewards.scss new file mode 100644 index 0000000000..a46295ef8a --- /dev/null +++ b/source/renderer/app/components/wallet/summary/WalletSummaryHeaderRewards.scss @@ -0,0 +1,35 @@ +.component { + font-family: var(--font-light); + :global(.amount) { + font-family: var(--font-medium); + } +} + +.questionMark { + height: 19px; + margin-left: 4px; + width: 10px; + + :global { + .SVGInline { + svg { + height: 10px; + width: 10px; + path { + fill: var(--theme-bordered-box-text-color); + opacity: 0.3; + } + } + } + } + + &:hover :global { + .SVGInline { + svg { + path { + opacity: 0.5; + } + } + } + } +} diff --git a/source/renderer/app/components/wallet/summary/WalletSummaryHeaderRewards.spec.ts b/source/renderer/app/components/wallet/summary/WalletSummaryHeaderRewards.spec.ts new file mode 100644 index 0000000000..511833b26d --- /dev/null +++ b/source/renderer/app/components/wallet/summary/WalletSummaryHeaderRewards.spec.ts @@ -0,0 +1,32 @@ +import BigNumber from 'bignumber.js'; +import { + discreetRewardsAmount, + getFormattedRewardAmount, +} from './WalletSummaryHeaderRewards'; + +describe('getFormattedRewardAmount', () => { + it('should render integer amounts without decimal places', () => { + expect(getFormattedRewardAmount(new BigNumber(1))).toEqual('1 ADA'); + }); + it('should render fraction amounts with a single decimal place', () => { + expect(getFormattedRewardAmount(new BigNumber(0.512))).toEqual('0.5 ADA'); + }); + it('should indicate values below 0.1', () => { + expect(getFormattedRewardAmount(new BigNumber(0.05))).toEqual('< 0.1 ADA'); + }); +}); +describe('discreetRewardsAmount', () => { + const symbol = '***'; + it('should replace the amount with a dash while restoring', () => { + const replacer = discreetRewardsAmount(true); + expect(replacer(true, symbol, new BigNumber(0))).toEqual('–'); + }); + it('should replace the amount with a discreet value', () => { + const replacer = discreetRewardsAmount(); + expect(replacer(true, symbol, new BigNumber(0))).toEqual('*** ADA'); + }); + it('should return the formatted ADA value', () => { + const replacer = discreetRewardsAmount(); + expect(replacer(false, symbol, new BigNumber(0.5))).toEqual('0.5 ADA'); + }); +}); diff --git a/source/renderer/app/components/wallet/summary/WalletSummaryHeaderRewards.tsx b/source/renderer/app/components/wallet/summary/WalletSummaryHeaderRewards.tsx new file mode 100644 index 0000000000..214d2ffed9 --- /dev/null +++ b/source/renderer/app/components/wallet/summary/WalletSummaryHeaderRewards.tsx @@ -0,0 +1,91 @@ +import BigNumber from 'bignumber.js'; +import React from 'react'; +import { observer } from 'mobx-react'; +import { + defineMessages, + FormattedHTMLMessage, + FormattedMessage, +} from 'react-intl'; +import { PopOver } from 'react-polymorph/lib/components/PopOver'; +import SVGInline from 'react-svg-inline'; +// @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../../assets/images/question... Remove this comment to see the full error message +import questionMarkIcon from '../../../assets/images/question-mark.inline.svg'; +import { useDiscreetModeFeature } from '../../../features'; +import type { ReplacerFn } from '../../../features/discreet-mode/types'; +import { formattedWalletAmount } from '../../../utils/formatters'; +// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './WalletSummaryHeaderRewards.s... Remove this comment to see the full error message +import styles from './WalletSummaryHeaderRewards.scss'; + +const messages = defineMessages({ + rewards: { + id: 'wallet.summary.header.rewardsSummary', + defaultMessage: '!!!{total} rewards earned, {unspent} unspent rewards', + description: 'Headline for the Decentralisation notification.', + }, + unspendableTooltip: { + id: 'wallet.summary.header.unspendableTooltip', + defaultMessage: `!!!All the ada in this wallet is in the rewards account. + Since transaction fees cannot be paid with rewards, please send 2 or + more ada to this wallet to cover transaction fees.`, + description: + 'Tooltip describing that rewards are unspendable on the Wallet summary header', + }, +}); +export function getFormattedRewardAmount(amount: BigNumber): string { + return amount.isGreaterThan(0) && amount.isLessThan(0.1) + ? '< 0.1 ADA' + : formattedWalletAmount(amount, true, false, 'ADA', 1); +} +export function discreetRewardsAmount(isRestoring = false): ReplacerFn { + return (isDiscreetMode, symbol, value) => { + if (isRestoring) { + return '–'; + } + + if (!isDiscreetMode) { + return getFormattedRewardAmount(value); + } + + return `${symbol} ADA`; + }; +} +export type WalletSummaryHeaderRewardsProps = { + total: BigNumber; + unspent: BigNumber; + walletAmount: BigNumber; + isRestoring: boolean; +}; + +function WalletSummaryHeaderRewards(props: WalletSummaryHeaderRewardsProps) { + const discreetModeFeature = useDiscreetModeFeature(); + const { isRestoring, total, unspent, walletAmount } = props; + return ( +
+ + {walletAmount.isGreaterThan(0) && walletAmount.isEqualTo(unspent) && ( + } + > + + + + + )} +
+ ); +} + +export default observer(WalletSummaryHeaderRewards); diff --git a/source/renderer/app/components/wallet/tokens/WalletToken.scss b/source/renderer/app/components/wallet/tokens/WalletToken.scss deleted file mode 100644 index 057d93a8a8..0000000000 --- a/source/renderer/app/components/wallet/tokens/WalletToken.scss +++ /dev/null @@ -1,197 +0,0 @@ -.component { - overflow: hidden; - &:not(:last-of-type) { - border-bottom: 1px solid rgba(94, 96, 102, 0.15); - } - &.isExpanded:not(.removing) { - background-color: var(--theme-tokens-list-header-expanded-background-color); - padding-bottom: 20px; - .header { - background-color: var( - --theme-tokens-list-header-expanded-background-color - ); - &:hover { - background-color: var( - --theme-tokens-list-header-expanded-background-color-hover - ); - } - } - .content { - display: block; - } - } - &.inserting { - animation: insertAnimation 0.3s; - } - &.removing { - animation: removingAnimation 0.3s; - border-bottom: 0 none; - height: 0; - } -} -@keyframes insertAnimation { - 0% { - height: 0; - } - 100% { - height: 36px; - } -} -@keyframes removingAnimation { - 0% { - height: 36px; - } - 100% { - height: 0; - } -} - -.header { - cursor: pointer; - display: flex; - justify-content: space-between; - padding: 7px 42px 7px 39px; - position: relative; - .favoriteIcon { - border-radius: 3px; - cursor: pointer; - height: 22px; - left: 8px; - opacity: 0.3; - padding-top: 2px; - position: absolute; - top: 7px; - transition: opacity 0.25s; - width: 23px; - z-index: 1; - svg { - height: 14px; - width: 15px; - } - &.isFavorite { - opacity: 0.7; - } - &:not(.isFavorite) { - path { - stroke: var(--theme-button-flat-text-color-disabled); - } - } - &:hover { - opacity: 0.7; - &.isFavorite { - opacity: 1; - } - } - } - .asset { - max-width: calc(100% - 90px); - } - .assetAmount { - color: var(--theme-widgets-asset-token-text-color); - font-family: var(--font-regular); - font-size: 12px; - letter-spacing: 0.4px; - line-height: 1.33; - margin: 3px 0; - max-width: 80px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - &:hover { - background: var(--theme-button-flat-background-color-hover); - } -} -.leftContent { - max-width: 75%; -} -.rightContent { - max-width: 25%; -} -.arrow { - bottom: 10px; - height: 22px; - opacity: 0.3; - position: absolute; - right: 12px; - text-align: center; - top: 7px; - width: 12px; - z-index: 1; - - > svg { - height: 12px; - margin-top: 5px; - width: 12px; - - path { - fill: var(--theme-widgets-asset-token-text-color); - } - } - .header:hover & { - opacity: 0.5; - } - &.isExpanded { - transform: rotate(180deg); - } -} -.content { - display: none; - margin: 13px 12px 0; -} -.footer { - border-top: 1px solid var(--theme-wallet-settings-section-separator-color); - color: var(--theme-widgets-asset-token-text-color); - display: flex; - justify-content: space-between; - margin-top: 20px; - padding-top: 20px; - - dl { - line-height: 1; - padding: 9px 0 0 8px; - width: calc(100% - 264px); - } - - dt { - display: inline-block; - font-family: var(--font-medium); - font-size: 12px; - line-height: 1.33; - text-align: right; - white-space: nowrap; - width: 70px; - } - - dd { - display: inline-flex; - font-family: var(--font-light); - font-size: 12px; - line-height: 16px; - margin-left: 10px; - white-space: normal; - width: calc(100% - 100px); - word-break: break-all; - word-wrap: break-word; - } - - .footerButtons { - margin: auto 0; - white-space: nowrap; - .button { - height: 36px; - margin-left: 12px; - width: 120px; - &.disabled { - opacity: 0.4; - } - } - .settingsButton { - svg { - height: 9px; - margin-left: 6px; - width: 11px; - } - } - } -} diff --git a/source/renderer/app/components/wallet/tokens/WalletNoTokens.scss b/source/renderer/app/components/wallet/tokens/wallet-no-tokens/WalletNoTokens.scss similarity index 91% rename from source/renderer/app/components/wallet/tokens/WalletNoTokens.scss rename to source/renderer/app/components/wallet/tokens/wallet-no-tokens/WalletNoTokens.scss index 5fa8ece952..3682724ca6 100644 --- a/source/renderer/app/components/wallet/tokens/WalletNoTokens.scss +++ b/source/renderer/app/components/wallet/tokens/wallet-no-tokens/WalletNoTokens.scss @@ -1,4 +1,4 @@ -@import '../../../themes/mixins/animations'; +@import '../../../../themes/mixins/animations'; .numberOfAssets { color: var(--theme-transactions-list-group-date-color); diff --git a/source/renderer/app/components/wallet/tokens/wallet-no-tokens/WalletNoTokens.tsx b/source/renderer/app/components/wallet/tokens/wallet-no-tokens/WalletNoTokens.tsx new file mode 100644 index 0000000000..e1802c69ea --- /dev/null +++ b/source/renderer/app/components/wallet/tokens/wallet-no-tokens/WalletNoTokens.tsx @@ -0,0 +1,83 @@ +import React, { Component } from 'react'; +import { observer } from 'mobx-react'; +import { defineMessages, intlShape } from 'react-intl'; +import BorderedBox from '../../../widgets/BorderedBox'; +import styles from './WalletNoTokens.scss'; +import { ExternalLinkButton } from '../../../widgets/ExternalLinkButton'; + +const messages = defineMessages({ + tokensTitle: { + id: 'wallet.summary.assets.tokensTitle', + defaultMessage: '!!!Tokens', + description: 'Number of tokens title on Wallet summary assets page', + }, + learnMoreTextTop: { + id: 'wallet.summary.noTokens.learnMore.textTop', + defaultMessage: '!!!Want to find out more about native tokens?', + description: '"Learn more" text in the Wallets Summary No Tokens component', + }, + learnMoreTextBottom: { + id: 'wallet.summary.noTokens.learnMore.textBottom', + defaultMessage: '!!!Start by visiting the IOHK blog for a useful primer.', + description: '"Learn more" text in the Wallets Summary No Tokens component', + }, + learnMoreLinkLabel: { + id: 'wallet.summary.noTokens.learnMore.linkLabel', + defaultMessage: '!!!Learn more', + description: + '"Learn more" label or button in the Wallets Summary No Tokens component', + }, + learnMoreLinkUrl: { + id: 'wallet.summary.noTokens.learnMore.linkUrl', + defaultMessage: + '!!!https://iohk.io/en/blog/posts/2021/02/04/native-tokens-to-bring-new-utility-to-life-on-cardano/', + description: + '"Learn more" link URL in the Wallets Summary No Tokens component', + }, +}); +type Props = { + isLoadingAssets: boolean; + onExternalLinkClick: (...args: Array) => any; + numberOfAssets: number; +}; + +@observer +class WalletSummaryNoTokens extends Component { + static contextTypes = { + intl: intlShape.isRequired, + }; + + render() { + const { isLoadingAssets, onExternalLinkClick, numberOfAssets } = this.props; + const { intl } = this.context; + return ( + <> + {!isLoadingAssets && ( +
+ {intl.formatMessage(messages.tokensTitle)} ({numberOfAssets}) +
+ )} +
+ +
+
+

{intl.formatMessage(messages.learnMoreTextTop)}

+

{intl.formatMessage(messages.learnMoreTextBottom)}

+
+ + onExternalLinkClick( + intl.formatMessage(messages.learnMoreLinkUrl) + ) + } + /> +
+
+
+ + ); + } +} + +export default WalletSummaryNoTokens; diff --git a/source/renderer/app/components/wallet/tokens/wallet-token-picker/WalletTokenPicker.messages.ts b/source/renderer/app/components/wallet/tokens/wallet-token-picker/WalletTokenPicker.messages.ts new file mode 100644 index 0000000000..50a6ef638a --- /dev/null +++ b/source/renderer/app/components/wallet/tokens/wallet-token-picker/WalletTokenPicker.messages.ts @@ -0,0 +1,55 @@ +import { defineMessages } from 'react-intl'; + +export const messages = defineMessages({ + title: { + id: 'wallet.token.picker.title', + defaultMessage: '!!!Add tokens', + description: 'Token picker title', + }, + allTokensLabel: { + id: 'wallet.token.picker.allTokensLabel', + defaultMessage: '!!!All tokens', + description: 'Label for all tokens option', + }, + favoriteTokensLabel: { + id: 'wallet.token.picker.favoriteTokensLabel', + defaultMessage: '!!!Favorites', + description: 'Label for favorite tokens option', + }, + checkAllLabel: { + id: 'wallet.token.picker.checkAllLabel', + defaultMessage: '!!!Select all', + description: 'Label for select all button label', + }, + checkedCountLabel: { + id: 'wallet.token.picker.checkedCountLabel', + defaultMessage: '!!!{checkedCount} out of {maxTokens} tokens.', + description: 'Label of selected tokens count', + }, + cancelButtonLabel: { + id: 'wallet.token.picker.cancelButtonLabel', + defaultMessage: '!!!Cancel', + description: 'Label of cancel button', + }, + addButtonLabel: { + id: 'wallet.token.picker.addButtonLabel', + defaultMessage: '!!!Add', + description: 'Label of add button', + }, + clearAll: { + id: 'wallet.token.picker.clearAll', + defaultMessage: '!!!Clear selection', + description: 'Label of clear selection button', + }, + noResults: { + id: 'wallet.token.picker.noResults', + defaultMessage: '!!!Results do not match search query', + description: 'Text for no results', + }, + maxTokensWarning: { + id: 'wallet.token.picker.maxTokensWarning', + defaultMessage: + '!!!You have already reached a maximum of {maxTokens} tokens for your transaction. To add another token you need to remove one from a list.', + description: 'Max tokens warning', + }, +}); diff --git a/source/renderer/app/components/wallet/tokens/wallet-token-picker/WalletTokenPicker.scss b/source/renderer/app/components/wallet/tokens/wallet-token-picker/WalletTokenPicker.scss new file mode 100644 index 0000000000..2f25f60902 --- /dev/null +++ b/source/renderer/app/components/wallet/tokens/wallet-token-picker/WalletTokenPicker.scss @@ -0,0 +1,226 @@ +.root { + display: flex; + flex-direction: column; + width: 100%; + + .toolbar { + border-bottom: 1px solid + var(--theme-tokens-picker-toolbar-border-bottom-color); + display: flex; + margin: 0 15px; + padding: 20px 0; + transition-duration: 0.2s; + transition-property: box-shadow border-color; + transition-timing-function: ease-in-out; + } + + .search { + padding: 0 15px 0px 15px; + } + + .scrollNotTop { + border-bottom-color: transparent; + } + + .toolbarContainer { + box-shadow: 0 10px 10px -10px var(--theme-tokens-picker-toolbar-box-shadow); + } + + .filterSelect { + font-family: var(--font-medium); + font-size: 16px; + line-height: 1.38; + + :global { + .SimpleOptions_selectedOption:after { + border: none; + } + + .SimpleOptions_option { + padding: 6px 12px; + } + + .SimpleInput_customValueWrapper .SimpleInput_input { + border: none; + height: auto; + padding: 0; + width: 110%; + } + + .SimpleInput_customValueBlock { + height: auto; + padding-right: 10px; + } + + .SelectOverrides_selectInput { + align-items: center; + display: flex; + position: relative; + + &:after { + bottom: 0; + left: 0; + margin-top: 1px; + position: relative; + } + + &:hover:after { + background-color: var(--rp-select-arrow-bg-color-open); + } + } + + .SimpleBubble_bubble { + border: none; + left: -12px; + min-width: initial; + } + + .ScrollbarsCustom-Content { + padding: 0 !important; + } + + .ScrollbarsCustom, + .ScrollbarsCustom-Wrapper, + .ScrollbarsCustom-Scroller { + position: relative !important; + } + } + } + + .filterCounter { + opacity: 0.5; + } + + .filterOption { + font-family: var(--font-regular); + font-size: 14px; + line-height: 1.43; + white-space: nowrap; + } + + .count { + font-family: var(--font-light); + font-size: 14px; + line-height: 1.43; + margin-left: auto; + } + + .toggleAllButton { + background: var(--theme-widgets-asset-token-fingerprint-background-color); + border-radius: 3px; + color: var(--theme-widgets-asset-token-text-color); + cursor: pointer; + font-family: var(--font-regular); + font-size: 14px; + line-height: 1.43; + margin-left: 6px; + padding: 0 6px; + white-space: nowrap; + + &:disabled { + cursor: initial; + opacity: 0.3; + } + } + + .noResults { + display: inline-block; + font-family: var(--font-regular); + font-size: 16px; + line-height: 1.38; + margin-left: 30px; + margin-top: 30px; + } + + .list { + margin-right: -2px; + max-height: 450px; + overflow-y: scroll; + padding-left: 15px; + padding-right: 12px; + + &::-webkit-scrollbar { + width: 5px; + } + + &::-webkit-scrollbar-button { + display: inline; + } + + &::-webkit-scrollbar-thumb { + border: none; + } + } + + .listItem { + display: flex; + + &:first-of-type { + padding-top: 6px; + } + + &:not(:last-of-type) { + .token { + border-bottom: 1px solid var(--theme-tokens-picker-token-separator); + } + } + } + + .checkbox { + align-self: flex-start; + margin-right: 20px; + margin-top: 14px; + } + + :global(.SimpleCheckbox_disabled) { + opacity: 0.3; + } + + .token { + flex: 1; + + &:global(.WalletToken_isExpanded) { + padding-bottom: 14px; + } + } + + .tokenHeader { + padding-bottom: 14px; + padding-top: 14px; + + :global(.WalletTokenHeader_asset) { + margin-left: 12px; + } + } + + .tokenFooter { + margin-top: 14px; + padding-top: 5px; + } +} + +.dialog { + margin-left: -15px; + margin-right: -15px; + + :global { + .Dialog_content { + display: flex; + width: 100%; + } + + .DialogCloseButton_component { + right: 15px; + } + + .Dialog_contentWrapper { + display: flex; + overflow-y: hidden; + } + + .Dialog_actions { + padding-left: 15px; + padding-right: 15px; + } + } +} diff --git a/source/renderer/app/components/wallet/tokens/wallet-token-picker/WalletTokenPicker.stories.tsx b/source/renderer/app/components/wallet/tokens/wallet-token-picker/WalletTokenPicker.stories.tsx new file mode 100644 index 0000000000..61b3a7b423 --- /dev/null +++ b/source/renderer/app/components/wallet/tokens/wallet-token-picker/WalletTokenPicker.stories.tsx @@ -0,0 +1,76 @@ +import React from 'react'; +import { storiesOf } from '@storybook/react'; +import { withKnobs } from '@storybook/addon-knobs'; +import BigNumber from 'bignumber.js'; +import StoryDecorator from '../../../../../../../storybook/stories/_support/StoryDecorator'; +import StoryProvider from '../../../../../../../storybook/stories/_support/StoryProvider'; +import { generateHash } from '../../../../../../../storybook/stories/_support/utils'; +import WalletTokenPicker from './WalletTokenPicker'; + +const assets = [ + { + policyId: generateHash(), + assetName: '546f6b656e31', + assetNameASCII: 'Token1', + quantity: new BigNumber(10), + fingerprint: generateHash(), + recommendedDecimals: null, + uniqueId: generateHash(), + metadata: { + name: 'MakerDAO', + ticker: 'DAI', + description: 'Test description', + unit: { + name: 'DAI', + decimals: 6, + }, + url: 'http://example.com', + logo: '', + }, + }, + { + policyId: generateHash(), + assetName: '546f6b656e32', + assetNameASCII: 'Token2', + quantity: new BigNumber(20), + fingerprint: generateHash(), + recommendedDecimals: null, + uniqueId: generateHash(), + }, + { + policyId: generateHash(), + assetName: '546f6b656e33', + assetNameASCII: 'Token3', + quantity: new BigNumber(30), + fingerprint: generateHash(), + recommendedDecimals: null, + uniqueId: generateHash(), + }, + { + policyId: generateHash(), + assetName: '546f6b656e34', + assetNameASCII: 'Token4', + quantity: new BigNumber(30), + fingerprint: generateHash(), + recommendedDecimals: null, + uniqueId: generateHash(), + }, +]; +storiesOf('Wallets|Tokens', module) + .addDecorator((story) => ( + + {story()} + + )) + .addDecorator(withKnobs) + .add('WalletTokenPicker', () => ( + {}} + onCancel={() => {}} + /> + )); diff --git a/source/renderer/app/components/wallet/tokens/wallet-token-picker/WalletTokenPicker.tsx b/source/renderer/app/components/wallet/tokens/wallet-token-picker/WalletTokenPicker.tsx new file mode 100644 index 0000000000..eed8ec4a2a --- /dev/null +++ b/source/renderer/app/components/wallet/tokens/wallet-token-picker/WalletTokenPicker.tsx @@ -0,0 +1,174 @@ +import React, { useMemo } from 'react'; +import { observer } from 'mobx-react'; +import { injectIntl } from 'react-intl'; +import classNames from 'classnames'; +import { Select } from 'react-polymorph/lib/components/Select'; +import Dialog from '../../../widgets/Dialog'; +import WalletToken from '../wallet-token/WalletToken'; +import WalletTokensSearch from '../wallet-tokens-search/WalletTokensSearch'; +import WalletTokenPickerCheckbox from './WalletTokenPickerCheckbox'; +import DialogCloseButton from '../../../widgets/DialogCloseButton'; +import styles from './WalletTokenPicker.scss'; +import { messages } from './WalletTokenPicker.messages'; +import { + filterSelectOptions, + getToggleAllLabel, + getTokenCounterText, +} from './helpers'; +import { useFilters, useCheckboxes, useScrollPosition } from './hooks'; +import { MAX_TOKENS, ScrollPositionEnum } from './const'; +import type { Props } from './types'; + +const WalletTokenPicker = ({ + intl, + assets, + walletName, + tokenFavorites, + previouslyCheckedIds = [], + onAdd, + onCancel, +}: Props) => { + const { onScroll, scrollPosition } = useScrollPosition(); + const { + searchValue, + setSearchValue, + currentAssets, + filterOption, + setFilterOption, + } = useFilters({ + assets, + tokenFavorites, + }); + const { + checkboxes, + totalCheckedCount, + checkedIds, + previouslyCheckedIdsSet, + isMaxTotalCount, + isToggleAllDisabled, + isClearAllMode, + toggleAllFn, + toggleCheckbox, + } = useCheckboxes({ + assets, + currentAssets, + previouslyCheckedIds, + }); + const scrollNotTop = scrollPosition !== ScrollPositionEnum.TOP; + const toolbarStyles = classNames( + styles.toolbar, + scrollNotTop && styles.scrollNotTop + ); + const toolbarContainerStyles = classNames( + scrollNotTop && styles.toolbarContainer + ); + const actions = useMemo( + () => [ + { + label: intl.formatMessage(messages.cancelButtonLabel), + onClick: onCancel, + }, + { + label: intl.formatMessage(messages.addButtonLabel), + primary: true, + disabled: !checkedIds.length, + onClick: () => onAdd(checkedIds), + }, + ], + [intl, checkedIds, onAdd] + ); + return ( + } + > +
+
+ +
+ +
+
+ setSearchInputFocused(true)} + onBlur={() => setSearchInputFocused(false)} + onChange={onSearch} + value={searchValue} + placeholder={intl.formatMessage(messages.placeholder)} + /> + {!!searchValue.length && ( + + )} +
+ ); +}; + +export default injectIntl(observer(WalletTokensSearch)); diff --git a/source/renderer/app/components/wallet/tokens/WalletTokens.scss b/source/renderer/app/components/wallet/tokens/wallet-tokens/WalletTokens.scss similarity index 84% rename from source/renderer/app/components/wallet/tokens/WalletTokens.scss rename to source/renderer/app/components/wallet/tokens/wallet-tokens/WalletTokens.scss index 44c23718b3..7c1bb29b5a 100644 --- a/source/renderer/app/components/wallet/tokens/WalletTokens.scss +++ b/source/renderer/app/components/wallet/tokens/wallet-tokens/WalletTokens.scss @@ -1,6 +1,11 @@ .component { padding: 20px 0; + + .searchContainer { + margin: 0 20px 20px; + } } + .syncing { padding: 10px 20px; .syncingText { diff --git a/source/renderer/app/components/wallet/tokens/wallet-tokens/WalletTokens.tsx b/source/renderer/app/components/wallet/tokens/wallet-tokens/WalletTokens.tsx new file mode 100644 index 0000000000..26344814b0 --- /dev/null +++ b/source/renderer/app/components/wallet/tokens/wallet-tokens/WalletTokens.tsx @@ -0,0 +1,158 @@ +import React, { useState, useCallback, useMemo } from 'react'; +import { intlShape, injectIntl, defineMessages } from 'react-intl'; +import { observer } from 'mobx-react'; +import styles from './WalletTokens.scss'; +import Wallet from '../../../../domains/Wallet'; +import WalletTokensList from '../wallet-tokens-list/WalletTokensList'; +import WalletTokensSearch from '../wallet-tokens-search/WalletTokensSearch'; +import LoadingSpinner from '../../../widgets/LoadingSpinner'; +import type { AssetToken } from '../../../../api/assets/types'; +import { TOGGLE_TOKEN_FAVORITE_TIMEOUT } from '../../../../config/timingConfig'; + +const messages = defineMessages({ + favoritesListTitle: { + id: 'wallet.tokens.list.favorites.title', + defaultMessage: '!!!Favorites', + description: 'Favorites list title label', + }, + tokensListTitle: { + id: 'wallet.tokens.list.tokens.title', + defaultMessage: '!!!Tokens', + description: 'Favorites list title label', + }, + syncingMessage: { + id: 'wallet.send.form.syncingTransactionsMessage', + defaultMessage: + '!!!The balance and transaction history of this wallet is being synced with the blockchain.', + description: + 'Syncing transactions message shown during async wallet restore in the wallet send form.', + }, +}); +type Props = { + assets: Array; + currentLocale: string; + intl: intlShape.isRequired; + isLoadingAssets: boolean; + onAssetSettings: (...args: Array) => any; + onCopyAssetParam: (...args: Array) => any; + onExternalLinkClick: (...args: Array) => any; + onOpenAssetSend: (...args: Array) => any; + onToggleFavorite: (...args: Array) => any; + tokenFavorites: Record; + wallet: Wallet; +}; +const WalletTokens = observer((props: Props) => { + const [searchValue, setSearchValue] = useState(''); + const [insertingAssetUniqueId, setInsertingAssetUniqueId] = useState< + string | null | undefined + >(null); + const [removingAssetUniqueId, setRemovingAssetUniqueId] = useState< + string | null | undefined + >(null); + const { + assets, + intl, + tokenFavorites, + onToggleFavorite, + isLoadingAssets, + ...listProps + } = props; + const { isRestoring } = props.wallet; + const hasTokens = assets.length || isLoadingAssets; + const favoriteTokensList = useMemo( + () => assets.filter(({ uniqueId }) => tokenFavorites[uniqueId]), + [assets, tokenFavorites, searchValue] + ); + + /** + * + * This function adds a `inserting` or `removing` + * state before actually proceeding with these actions + * so the UI element insertion/removal can be animated, + * preventing undesirable jumps in the tokens list + * + */ + const handleToggleFavorite = useCallback( + async ({ + uniqueId, + isFavorite, + }: { + uniqueId: string; + isFavorite: boolean; + }) => { + if (insertingAssetUniqueId || removingAssetUniqueId) { + return; + } + + if (isFavorite) { + // It's removing favorite + // We need to wait for the element to be removed, before updating the favorites list + setRemovingAssetUniqueId(uniqueId); + setTimeout(async () => { + await onToggleFavorite({ + uniqueId, + isFavorite, + }); + setTimeout(() => setRemovingAssetUniqueId(null), 500); + }, TOGGLE_TOKEN_FAVORITE_TIMEOUT); + } else { + // It's inserting favorite + // We update the favorites list straight away + setInsertingAssetUniqueId(uniqueId); + await onToggleFavorite({ + uniqueId, + isFavorite, + }); + setTimeout(() => { + setInsertingAssetUniqueId(null); + }, TOGGLE_TOKEN_FAVORITE_TIMEOUT); + } + }, + [insertingAssetUniqueId, removingAssetUniqueId] + ); + + if (isRestoring) { + return ( +
+ +

+ {intl.formatMessage(messages.syncingMessage)} +

+
+ ); + } + + return ( +
+ {hasTokens && ( +
+ +
+ )} + {!!favoriteTokensList.length && ( + + )} + +
+ ); +}); +export default injectIntl(WalletTokens); diff --git a/source/renderer/app/components/wallet/transactions/CancelTransactionButton.tsx b/source/renderer/app/components/wallet/transactions/CancelTransactionButton.tsx index bc952fb856..0ffc389120 100644 --- a/source/renderer/app/components/wallet/transactions/CancelTransactionButton.tsx +++ b/source/renderer/app/components/wallet/transactions/CancelTransactionButton.tsx @@ -2,7 +2,6 @@ import React, { Component } from 'react'; import { defineMessages, intlShape } from 'react-intl'; import { Button } from 'react-polymorph/lib/components/Button'; import { ButtonSkin } from 'react-polymorph/lib/skins/simple/ButtonSkin'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './CancelTransactionButton.scss... Remove this comment to see the full error message import styles from './CancelTransactionButton.scss'; const messages = defineMessages({ diff --git a/source/renderer/app/components/wallet/transactions/CancelTransactionConfirmationDialog.tsx b/source/renderer/app/components/wallet/transactions/CancelTransactionConfirmationDialog.tsx index 904c8248bc..bb729b04f1 100644 --- a/source/renderer/app/components/wallet/transactions/CancelTransactionConfirmationDialog.tsx +++ b/source/renderer/app/components/wallet/transactions/CancelTransactionConfirmationDialog.tsx @@ -3,7 +3,6 @@ import { observer } from 'mobx-react'; import classnames from 'classnames'; import { defineMessages, intlShape } from 'react-intl'; import Dialog from '../../widgets/Dialog'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './CancelTransactionConfirmatio... Remove this comment to see the full error message import styles from './CancelTransactionConfirmationDialog.scss'; const messages = defineMessages({ @@ -16,7 +15,7 @@ const messages = defineMessages({ content1: { id: 'cancel.transaction.confirmation.dialog.content1', defaultMessage: - '!!!This transaction was submitted to the Cardano network some time ago, but hasn’t been finalized yet. You can try to cancel the transaction now to release the pending funds, but there is a chance that the transaction will be finalized regardless. In this case, the transaction will reappear in your wallet as a completed transaction.', + '!!!This transaction was submitted to the Cardano network some time ago, but has not been finalized yet. You can try to cancel the transaction now to release the pending funds, but there is a chance that the transaction will be finalized regardless. In this case, the transaction will reappear in your wallet as a completed transaction.', description: 'Content for the pending transaction cancellation confirmation dialog.', }, @@ -59,7 +58,6 @@ class CancelTransactionConfirmationDialog extends Component { const confirmButtonClasses = classnames([ 'confirmButton', 'attention', - styles.confirmButton, isSubmitting ? styles.isSubmitting : null, ]); const actions = [ diff --git a/source/renderer/app/components/wallet/transactions/FilterButton.tsx b/source/renderer/app/components/wallet/transactions/FilterButton.tsx index 97b7f25d44..4dc66489fc 100644 --- a/source/renderer/app/components/wallet/transactions/FilterButton.tsx +++ b/source/renderer/app/components/wallet/transactions/FilterButton.tsx @@ -7,7 +7,6 @@ import TinyButton from '../../widgets/forms/TinyButton'; // @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../../assets/images/filter-... Remove this comment to see the full error message import filterIcon from '../../../assets/images/filter-dis-ic.inline.svg'; import globalMessages from '../../../i18n/global-messages'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './FilterButton.scss' or its co... Remove this comment to see the full error message import styles from './FilterButton.scss'; type Props = { diff --git a/source/renderer/app/components/wallet/transactions/FilterDialog.tsx b/source/renderer/app/components/wallet/transactions/FilterDialog.tsx index 3fab3b79ff..6b133e9f93 100644 --- a/source/renderer/app/components/wallet/transactions/FilterDialog.tsx +++ b/source/renderer/app/components/wallet/transactions/FilterDialog.tsx @@ -32,7 +32,6 @@ import TinyInput from '../../widgets/forms/TinyInput'; import TinyDatePicker from '../../widgets/forms/TinyDatePicker'; import TinyButton from '../../widgets/forms/TinyButton'; import globalMessages from '../../../i18n/global-messages'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './FilterDialog.scss' or its co... Remove this comment to see the full error message import styles from './FilterDialog.scss'; const messages = defineMessages({ @@ -117,6 +116,16 @@ export type FilterDialogProps = { }; type Props = FilterDialogProps & InjectedProps; +interface FormFields { + incomingChecked: boolean; + outgoingChecked: boolean; + dateRange: string; + fromDate: string; + toDate: string; + fromAmount: string; + toAmount: string; +} + @observer class FilterDialog extends Component { static contextTypes = { @@ -126,7 +135,7 @@ class FilterDialog extends Component { label: string; value: string; }>; - form: ReactToolboxMobxForm; + form: ReactToolboxMobxForm; popoverTippyInstance: ElementRef = createRef(); constructor(props: Props, context: Record) { @@ -165,8 +174,7 @@ class FilterDialog extends Component { value: DateRangeTypes.CUSTOM, }, ]; - // @ts-ignore ts-migrate(2554) FIXME: Expected 0 arguments, but got 1. - this.form = new ReactToolboxMobxForm({ + this.form = new ReactToolboxMobxForm({ fields: { incomingChecked: { type: 'checkbox', @@ -208,13 +216,11 @@ class FilterDialog extends Component { field: 'incomingChecked' | 'outgoingChecked', value: boolean ) => { - // @ts-ignore ts-migrate(2339) FIXME: Property 'select' does not exist on type 'ReactToo... Remove this comment to see the full error message this.form.select(field).set(value); if (value === false) { const otherFieldName = field === 'incomingChecked' ? 'outgoingChecked' : 'incomingChecked'; - // @ts-ignore ts-migrate(2339) FIXME: Property 'select' does not exist on type 'ReactToo... Remove this comment to see the full error message const otherField = this.form.select(otherFieldName); if (otherField.value === false) { @@ -235,23 +241,16 @@ class FilterDialog extends Component { incomingChecked, outgoingChecked, } = filterOptions; - // @ts-ignore ts-migrate(2339) FIXME: Property 'select' does not exist on type 'ReactToo... Remove this comment to see the full error message this.form.select('dateRange').set(dateRange); - // @ts-ignore ts-migrate(2339) FIXME: Property 'select' does not exist on type 'ReactToo... Remove this comment to see the full error message this.form.select('fromDate').set(fromDate); - // @ts-ignore ts-migrate(2339) FIXME: Property 'select' does not exist on type 'ReactToo... Remove this comment to see the full error message this.form.select('toDate').set(toDate); this.form - // @ts-ignore ts-migrate(2339) FIXME: Property 'select' does not exist on type 'ReactToo... Remove this comment to see the full error message .select('fromAmount') .set(reset ? fromAmount : this.getFromAmountValue(fromAmount)); this.form - // @ts-ignore ts-migrate(2339) FIXME: Property 'select' does not exist on type 'ReactToo... Remove this comment to see the full error message .select('toAmount') .set(reset ? toAmount : this.getToAmountValue(toAmount)); - // @ts-ignore ts-migrate(2339) FIXME: Property 'select' does not exist on type 'ReactToo... Remove this comment to see the full error message this.form.select('incomingChecked').set(incomingChecked); - // @ts-ignore ts-migrate(2339) FIXME: Property 'select' does not exist on type 'ReactToo... Remove this comment to see the full error message this.form.select('outgoingChecked').set(outgoingChecked); }; getFromAmountValue = (fromAmount?: string) => { @@ -281,7 +280,6 @@ class FilterDialog extends Component { isFormValuesEqualTo = ( comparedFilterOptions: TransactionFilterOptionsType ) => { - // @ts-ignore ts-migrate(2339) FIXME: Property 'fields' does not exist on type 'ReactToo... Remove this comment to see the full error message const formFieldNames = Object.keys(this.form.fields.toJSON()); return isEqual( this.getComposedFormValues(), @@ -289,7 +287,6 @@ class FilterDialog extends Component { ); }; getComposedFormValues = () => { - // @ts-ignore ts-migrate(2339) FIXME: Property 'values' does not exist on type 'ReactToo... Remove this comment to see the full error message const formValues = this.form.values(); return { ...formValues, @@ -298,7 +295,6 @@ class FilterDialog extends Component { }; }; handleSubmit = () => { - // @ts-ignore ts-migrate(2339) FIXME: Property 'submit' does not exist on type 'ReactToo... Remove this comment to see the full error message this.form.submit({ onSuccess: () => { const { onFilter } = this.props; @@ -317,10 +313,10 @@ class FilterDialog extends Component { this.popoverTippyInstance.current.hide(); } }; - isValidFromDate = (date: Record) => - date.isSameOrBefore(moment().endOf('day')); + isValidFromDate = (date: Record) => { + return date.isSameOrBefore(moment().endOf('day')); + }; isValidToDate = (date: Record) => { - // @ts-ignore ts-migrate(2339) FIXME: Property 'values' does not exist on type 'ReactToo... Remove this comment to see the full error message const { fromDate } = this.form.values(); return ( date.isSameOrBefore(moment().endOf('day')) && @@ -329,9 +325,7 @@ class FilterDialog extends Component { }; renderTypeField = () => { const { form } = this; - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const incomingCheckboxField = form.$('incomingChecked'); - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const outgoingCheckboxField = form.$('outgoingChecked'); return (
@@ -358,9 +352,7 @@ class FilterDialog extends Component { }; renderDateRangeField = () => { const { intl } = this.context; - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const dateRangeFieldBindProps = this.form.$('dateRange').bind(); - // @ts-ignore ts-migrate(2339) FIXME: Property 'values' does not exist on type 'ReactToo... Remove this comment to see the full error message const { fromDate, toDate } = this.form.values(); return (
@@ -372,9 +364,7 @@ class FilterDialog extends Component { fromDate, toDate, }); - // @ts-ignore ts-migrate(2339) FIXME: Property 'select' does not exist on type 'ReactToo... Remove this comment to see the full error message this.form.select('fromDate').set(calculatedDateRange.fromDate); - // @ts-ignore ts-migrate(2339) FIXME: Property 'select' does not exist on type 'ReactToo... Remove this comment to see the full error message this.form.select('toDate').set(calculatedDateRange.toDate); }} placeholder={intl.formatMessage(messages.selectTimeRange)} @@ -388,21 +378,17 @@ class FilterDialog extends Component { const { intl } = this.context; const { locale, dateFormat } = this.props; const { invalidFields } = validateFilterForm(this.getComposedFormValues()); - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const fromDateFieldBindProps = form.$('fromDate').bind(); - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const toDateFieldBindProps = form.$('toDate').bind(); const fromDateLocaleClassName = locale === 'ja-JP' ? styles.japaneseFromDateInput : null; const toDateLocaleClassName = locale === 'ja-JP' ? styles.japaneseToDateInput : null; const fromDateClassNames = classNames([ - styles.dateRangeInput, styles.fromDateInput, fromDateLocaleClassName, ]); const toDateClassNames = classNames([ - styles.dateRangeInput, styles.toDateInput, toDateLocaleClassName, ]); @@ -414,13 +400,11 @@ class FilterDialog extends Component { {...fromDateFieldBindProps} onChange={(...args) => { fromDateFieldBindProps.onChange(...args); - // @ts-ignore ts-migrate(2339) FIXME: Property 'select' does not exist on type 'ReactToo... Remove this comment to see the full error message this.form.select('dateRange').set(DateRangeTypes.CUSTOM); }} label={intl.formatMessage(globalMessages.rangeFrom)} pickerPanelPosition="left" closeOnSelect - // @ts-ignore ts-migrate(2339) FIXME: Property 'select' does not exist on type 'ReactToo... Remove this comment to see the full error message onReset={() => form.select('fromDate').set('')} isValidDate={this.isValidFromDate} locale={locale} @@ -432,13 +416,11 @@ class FilterDialog extends Component { {...toDateFieldBindProps} onChange={(...args) => { toDateFieldBindProps.onChange(...args); - // @ts-ignore ts-migrate(2339) FIXME: Property 'select' does not exist on type 'ReactToo... Remove this comment to see the full error message this.form.select('dateRange').set(DateRangeTypes.CUSTOM); }} label={intl.formatMessage(globalMessages.rangeTo)} pickerPanelPosition="right" closeOnSelect - // @ts-ignore ts-migrate(2339) FIXME: Property 'select' does not exist on type 'ReactToo... Remove this comment to see the full error message onReset={() => form.select('toDate').set('')} isValidDate={this.isValidToDate} locale={locale} @@ -455,9 +437,7 @@ class FilterDialog extends Component { const { intl } = this.context; const { locale, numberFormat } = this.props; const { invalidFields } = validateFilterForm(this.getComposedFormValues()); - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const fromAmountField = form.$('fromAmount'); - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const toAmountField = form.$('toAmount'); const fromAmountLocaleClassName = locale === 'ja-JP' ? styles.japaneseFromAmountInput : null; diff --git a/source/renderer/app/components/wallet/transactions/FilterResultInfo.tsx b/source/renderer/app/components/wallet/transactions/FilterResultInfo.tsx index 9c43399109..8b2e154ef6 100644 --- a/source/renderer/app/components/wallet/transactions/FilterResultInfo.tsx +++ b/source/renderer/app/components/wallet/transactions/FilterResultInfo.tsx @@ -1,7 +1,6 @@ import React, { Component } from 'react'; import { observer } from 'mobx-react'; import { defineMessages, FormattedHTMLMessage } from 'react-intl'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './FilterResultInfo.scss' or it... Remove this comment to see the full error message import styles from './FilterResultInfo.scss'; const messages = defineMessages({ diff --git a/source/renderer/app/components/wallet/transactions/Transaction.tsx b/source/renderer/app/components/wallet/transactions/Transaction.tsx index fe26205f94..4a8dc093f9 100644 --- a/source/renderer/app/components/wallet/transactions/Transaction.tsx +++ b/source/renderer/app/components/wallet/transactions/Transaction.tsx @@ -8,7 +8,6 @@ import { Link } from 'react-polymorph/lib/components/Link'; import { LinkSkin } from 'react-polymorph/lib/skins/simple/LinkSkin'; import CancelTransactionButton from './CancelTransactionButton'; import { TransactionMetadataView } from './metadata/TransactionMetadataView'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './Transaction.scss' or its cor... Remove this comment to see the full error message import styles from './Transaction.scss'; import TransactionTypeIcon from './TransactionTypeIcon'; // @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../../assets/images/collaps... Remove this comment to see the full error message @@ -617,7 +616,7 @@ export default class Transaction extends Component { {data.type === TransactionTypes.EXPEND && !data.fee.isZero() && ( <>

{intl.formatMessage(messages.transactionFee)}

-
+
{formattedWalletAmount(data.fee, false)}  @@ -631,7 +630,7 @@ export default class Transaction extends Component { {!data.deposit.isZero() && ( <>

{intl.formatMessage(messages.deposit)}

-
+
{
); - renderFailedIcon = () => ( - - ); + renderFailedIcon = () => { + return ( + + ); + }; renderIcon = (icon: string) => { let iconType; diff --git a/source/renderer/app/components/wallet/transactions/WalletNoTransactions.tsx b/source/renderer/app/components/wallet/transactions/WalletNoTransactions.tsx index 495e8b8b0d..a50f6ed8af 100644 --- a/source/renderer/app/components/wallet/transactions/WalletNoTransactions.tsx +++ b/source/renderer/app/components/wallet/transactions/WalletNoTransactions.tsx @@ -1,6 +1,5 @@ import React, { Component } from 'react'; import { observer } from 'mobx-react'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './WalletNoTransactions.scss' o... Remove this comment to see the full error message import styles from './WalletNoTransactions.scss'; type Props = { diff --git a/source/renderer/app/components/wallet/transactions/WalletTransactions.tsx b/source/renderer/app/components/wallet/transactions/WalletTransactions.tsx index 415815d446..98ceb01980 100644 --- a/source/renderer/app/components/wallet/transactions/WalletTransactions.tsx +++ b/source/renderer/app/components/wallet/transactions/WalletTransactions.tsx @@ -11,7 +11,6 @@ import { formattedWalletAmount } from '../../../utils/formatters'; import { getNumberOfFilterDimensionsApplied } from '../../../utils/transaction'; import { WalletTransaction } from '../../../domains/WalletTransaction'; import Wallet from '../../../domains/Wallet'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './WalletTransactions.scss' or ... Remove this comment to see the full error message import styles from './WalletTransactions.scss'; import type { TransactionFilterOptionsType } from '../../../stores/TransactionsStore'; diff --git a/source/renderer/app/components/wallet/transactions/WalletTransactionsHeader.tsx b/source/renderer/app/components/wallet/transactions/WalletTransactionsHeader.tsx index 83e32aea69..0cfdbb4935 100644 --- a/source/renderer/app/components/wallet/transactions/WalletTransactionsHeader.tsx +++ b/source/renderer/app/components/wallet/transactions/WalletTransactionsHeader.tsx @@ -6,10 +6,9 @@ import SVGInline from 'react-svg-inline'; import FilterButton from './FilterButton'; import FilterDialog from './FilterDialog'; import type { FilterDialogProps } from './FilterDialog'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './WalletTransactionsHeader.scs... Remove this comment to see the full error message import styles from './WalletTransactionsHeader.scss'; import TinyButton from '../../widgets/forms/TinyButton'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../../assets/images/downloa... Remove this comment to see the full error message +// @ts-ignore ts-migrate(2307) FIXME: Cannot find module '../../../assets/images/download... Remove this comment to see the full error message import downloadIcon from '../../../assets/images/download-icon.inline.svg'; export const messages = defineMessages({ diff --git a/source/renderer/app/components/wallet/transactions/WalletTransactionsList.tsx b/source/renderer/app/components/wallet/transactions/WalletTransactionsList.tsx index f0a17bb0f4..8df7a1fc3b 100644 --- a/source/renderer/app/components/wallet/transactions/WalletTransactionsList.tsx +++ b/source/renderer/app/components/wallet/transactions/WalletTransactionsList.tsx @@ -7,7 +7,6 @@ import { Button } from 'react-polymorph/lib/components/Button'; import { ButtonSkin } from 'react-polymorph/lib/skins/simple/ButtonSkin'; import { defineMessages, intlShape } from 'react-intl'; import moment from 'moment'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './WalletTransactionsList.scss'... Remove this comment to see the full error message import styles from './WalletTransactionsList.scss'; import Transaction from './Transaction'; import { WalletTransaction } from '../../../domains/WalletTransaction'; diff --git a/source/renderer/app/components/wallet/transactions/WalletTransactionsSearch.tsx b/source/renderer/app/components/wallet/transactions/WalletTransactionsSearch.tsx index a261a0c44c..6be7f1b23f 100644 --- a/source/renderer/app/components/wallet/transactions/WalletTransactionsSearch.tsx +++ b/source/renderer/app/components/wallet/transactions/WalletTransactionsSearch.tsx @@ -3,7 +3,6 @@ import { observer } from 'mobx-react'; import { defineMessages, intlShape } from 'react-intl'; import { Input } from 'react-polymorph/lib/components/Input'; import { InputSkin } from 'react-polymorph/lib/skins/simple/InputSkin'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './WalletTransactionsSearch.scs... Remove this comment to see the full error message import styles from './WalletTransactionsSearch.scss'; const messages = defineMessages({ @@ -30,7 +29,6 @@ class WalletTransactionsSearch extends Component { return (
+
    {value.list.map( ( v, @@ -47,7 +46,7 @@ function ListView({ value }: { value: MetadataList }) { function MapView({ value }: { value: MetadataMap }) { return ( -
      +
        {value.map.map( ( v, diff --git a/source/renderer/app/components/wallet/transactions/metadata/TransactionMetadataView.scss b/source/renderer/app/components/wallet/transactions/metadata/TransactionMetadataView.scss index 499195c6c3..bdf7e06209 100644 --- a/source/renderer/app/components/wallet/transactions/metadata/TransactionMetadataView.scss +++ b/source/renderer/app/components/wallet/transactions/metadata/TransactionMetadataView.scss @@ -7,59 +7,3 @@ } } } - -/** - * NOTE: These styles are currently not used because we simply - * JSON.stringify the metadata for transactions. This can be - * used as the basis for a more sophisticated implementation - * later on: - */ - -//.list { -// &::before { -// content: '['; -// display: inline; -// } -// &::after { -// content: ']'; -// display: inline; -// } -// li::after { -// content: ','; -// display: inline; -// } -// li:last-child::after { -// content: ''; -// } -// li { -// padding-left: 10px; -// } -// li > * { -// display: inline-block; -// } -//} -// -//.map { -// vertical-align: top; -// &::before { -// content: '{'; -// display: inline; -// } -// &::after { -// content: '}'; -// display: inline; -// } -// li::after { -// content: ','; -// display: inline; -// } -// li:last-child::after { -// content: ''; -// } -// li { -// padding-left: 10px; -// } -// li > * { -// display: inline-block; -// } -//} diff --git a/source/renderer/app/components/wallet/transactions/metadata/TransactionMetadataView.tsx b/source/renderer/app/components/wallet/transactions/metadata/TransactionMetadataView.tsx index eed024f15c..b307fac0d9 100644 --- a/source/renderer/app/components/wallet/transactions/metadata/TransactionMetadataView.tsx +++ b/source/renderer/app/components/wallet/transactions/metadata/TransactionMetadataView.tsx @@ -5,7 +5,6 @@ import type { MetadataValue, TransactionMetadata, } from '../../../../types/TransactionMetadata'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './TransactionMetadataView.scss... Remove this comment to see the full error message import styles from './TransactionMetadataView.scss'; function flattenMetadata(data: MetadataValue) { diff --git a/source/renderer/app/components/wallet/transactions/render-strategies/SimpleTransactionList.tsx b/source/renderer/app/components/wallet/transactions/render-strategies/SimpleTransactionList.tsx index 20f064db9d..a9b37c9b9f 100644 --- a/source/renderer/app/components/wallet/transactions/render-strategies/SimpleTransactionList.tsx +++ b/source/renderer/app/components/wallet/transactions/render-strategies/SimpleTransactionList.tsx @@ -5,7 +5,6 @@ import { observer } from 'mobx-react'; import type { Row } from '../types'; import type { ScrollContextType } from '../WalletTransactionsList'; import { WalletTransactionsListScrollContext } from '../WalletTransactionsList'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './SimpleTransactionList.scss' ... Remove this comment to see the full error message import styles from './SimpleTransactionList.scss'; type Props = { diff --git a/source/renderer/app/components/wallet/transactions/render-strategies/VirtualTransactionList.tsx b/source/renderer/app/components/wallet/transactions/render-strategies/VirtualTransactionList.tsx index 1b0326b06d..55b0d0c242 100644 --- a/source/renderer/app/components/wallet/transactions/render-strategies/VirtualTransactionList.tsx +++ b/source/renderer/app/components/wallet/transactions/render-strategies/VirtualTransactionList.tsx @@ -10,7 +10,6 @@ import type { ScrollContextType } from '../WalletTransactionsList'; import { WalletTransactionsListScrollContext } from '../WalletTransactionsList'; import type { Row } from '../types'; import { TransactionInfo, TransactionsGroup } from '../types'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './VirtualTransactionList.scss'... Remove this comment to see the full error message import styles from './VirtualTransactionList.scss'; type Props = { @@ -231,7 +230,7 @@ class VirtualTransactionList extends Component { }; updateVisibleExpandedTxRowHeights = () => { const expandedTxMap = this.props.getExpandedTransactions(); - // This is needed because a spreaded Map results in an array of [key, value] + // This is needed because a spread Map results in an array of [key, value] const expandedTxArray = [...expandedTxMap].map((mapValue) => mapValue[1]); const visibleExpandedTx = expandedTxArray.filter((tx) => { const index = this.findIndexForTx(tx); diff --git a/source/renderer/app/components/wallet/transfer-funds/TransferFundsStep1Dialog.tsx b/source/renderer/app/components/wallet/transfer-funds/TransferFundsStep1Dialog.tsx index 5a66573ed2..d904694f01 100644 --- a/source/renderer/app/components/wallet/transfer-funds/TransferFundsStep1Dialog.tsx +++ b/source/renderer/app/components/wallet/transfer-funds/TransferFundsStep1Dialog.tsx @@ -2,7 +2,6 @@ import React, { Component } from 'react'; import { defineMessages, intlShape } from 'react-intl'; import DialogCloseButton from '../../widgets/DialogCloseButton'; import Dialog from '../../widgets/Dialog'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './TransferFundsStep1Dialog.scs... Remove this comment to see the full error message import styles from './TransferFundsStep1Dialog.scss'; import Wallet from '../../../domains/Wallet'; import WalletsDropdown from '../../widgets/forms/WalletsDropdown'; diff --git a/source/renderer/app/components/wallet/transfer-funds/TransferFundsStep2Dialog.tsx b/source/renderer/app/components/wallet/transfer-funds/TransferFundsStep2Dialog.tsx index fa08de6d46..cf3428f7d1 100644 --- a/source/renderer/app/components/wallet/transfer-funds/TransferFundsStep2Dialog.tsx +++ b/source/renderer/app/components/wallet/transfer-funds/TransferFundsStep2Dialog.tsx @@ -11,7 +11,6 @@ import { LinkSkin } from 'react-polymorph/lib/skins/simple/LinkSkin'; import DialogCloseButton from '../../widgets/DialogCloseButton'; import DialogBackButton from '../../widgets/DialogBackButton'; import Dialog from '../../widgets/Dialog'; -// @ts-ignore ts-migrate(2307) FIXME: Cannot find module './TransferFundsStep2Dialog.scs... Remove this comment to see the full error message import styles from './TransferFundsStep2Dialog.scss'; import ReactToolboxMobxForm from '../../../utils/ReactToolboxMobxForm'; import { FORM_VALIDATION_DEBOUNCE_WAIT } from '../../../config/timingConfig'; @@ -94,13 +93,16 @@ type Props = { error?: LocalizableError | null | undefined; }; +interface FormFields { + spendingPassword: string; +} + @observer class TransferFundsStep2Dialog extends Component { static contextTypes = { intl: intlShape.isRequired, }; - form = new ReactToolboxMobxForm( - // @ts-ignore ts-migrate(2554) FIXME: Expected 0 arguments, but got 2. + form = new ReactToolboxMobxForm( { fields: { spendingPassword: { @@ -136,7 +138,6 @@ class TransferFundsStep2Dialog extends Component { } ); submit = () => { - // @ts-ignore ts-migrate(2339) FIXME: Property 'submit' does not exist on type 'ReactToo... Remove this comment to see the full error message this.form.submit({ onSuccess: (form) => { const { spendingPassword } = form.values(); @@ -146,7 +147,6 @@ class TransferFundsStep2Dialog extends Component { }); }; handleSubmitOnEnter = (event: KeyboardEvent) => - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message this.form.$('spendingPassword').isValid && submitOnEnter(this.submit, event); @@ -177,7 +177,6 @@ class TransferFundsStep2Dialog extends Component { sourceWalletAmount, false ); - // @ts-ignore ts-migrate(2339) FIXME: Property '$' does not exist on type 'ReactToolboxM... Remove this comment to see the full error message const spendingPasswordField = this.form.$('spendingPassword'); const buttonClasses = classnames([ 'confirmButton', @@ -254,7 +253,6 @@ class TransferFundsStep2Dialog extends Component {
{ > { tick={(props: TickProps) => ( )} - className={styles.xAxis} y={0} >