diff --git a/.eslintrc.json b/.eslintrc.json index 3fafb98ad..97a57d5b7 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,52 +1,52 @@ { - "parser": "@typescript-eslint/parser", - "parserOptions": { - "project": "./tsconfig.eslint.json" - }, - "plugins": ["@typescript-eslint", "prettier", "simple-import-sort"], - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/eslint-recommended", - "plugin:@typescript-eslint/recommended-requiring-type-checking", - "plugin:@typescript-eslint/recommended", - "plugin:prettier/recommended", - "prettier/@typescript-eslint" - ], - "rules": { - "@typescript-eslint/prefer-string-starts-ends-with": "off", - "@typescript-eslint/no-this-alias": "off", - "@typescript-eslint/await-thenable": "off", - "@typescript-eslint/ban-ts-ignore": "off", - "@typescript-eslint/camelcase": "off", - "@typescript-eslint/explicit-function-return-type": "off", - "@typescript-eslint/explicit-member-accessibility": ["off"], - "@typescript-eslint/interface-name-prefix": "off", - "@typescript-eslint/member-ordering": ["error"], - "@typescript-eslint/no-empty-function": "off", - "@typescript-eslint/no-empty-interface": "off", - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-inferrable-types": "off", - "@typescript-eslint/no-misused-promises": "off", - "@typescript-eslint/no-namespace": "off", - "@typescript-eslint/no-non-null-assertion": "off", - "@typescript-eslint/no-unnecessary-condition": "off", - "@typescript-eslint/no-unused-vars": "off", - "@typescript-eslint/no-var-requires": "off", - "@typescript-eslint/prefer-regexp-exec": "off", - "@typescript-eslint/require-await": "off", - "@typescript-eslint/unbound-method": "off", - "no-async-promise-executor": "off", - "no-empty": "off", - "no-inferrable-types": "off", - "no-prototype-builtins": "off", - "prefer-const": [ - "error", - { - "destructuring": "all" - } - ], - "prettier/prettier": "error", - "require-atomic-updates": "off", - "simple-import-sort/sort": "error" - } + "parser": "@typescript-eslint/parser", + "parserOptions": { + "project": "./tsconfig.eslint.json" + }, + "plugins": ["@typescript-eslint", "prettier", "simple-import-sort", "html"], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended-requiring-type-checking", + "plugin:@typescript-eslint/recommended", + "plugin:prettier/recommended", + "prettier/@typescript-eslint" + ], + "rules": { + "@typescript-eslint/prefer-string-starts-ends-with": "off", + "@typescript-eslint/no-this-alias": "off", + "@typescript-eslint/await-thenable": "off", + "@typescript-eslint/ban-ts-ignore": "off", + "@typescript-eslint/camelcase": "off", + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/explicit-member-accessibility": ["off"], + "@typescript-eslint/interface-name-prefix": "off", + "@typescript-eslint/member-ordering": ["error"], + "@typescript-eslint/no-empty-function": "off", + "@typescript-eslint/no-empty-interface": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-inferrable-types": "off", + "@typescript-eslint/no-misused-promises": "off", + "@typescript-eslint/no-namespace": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/no-unnecessary-condition": "off", + "@typescript-eslint/no-unused-vars": "off", + "@typescript-eslint/no-var-requires": "off", + "@typescript-eslint/prefer-regexp-exec": "off", + "@typescript-eslint/require-await": "off", + "@typescript-eslint/unbound-method": "off", + "no-async-promise-executor": "off", + "no-empty": "off", + "no-inferrable-types": "off", + "no-prototype-builtins": "off", + "prefer-const": [ + "error", + { + "destructuring": "all" + } + ], + "prettier/prettier": "error", + "require-atomic-updates": "off", + "simple-import-sort/sort": "error" + } } diff --git a/package-lock.json b/package-lock.json index 8eec38e7e..8111f2d4f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -462,6 +462,11 @@ } } }, + "@angular/animations": { + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-9.0.6.tgz", + "integrity": "sha512-LNtzUrrjqLTlZyhuAEV0sdEV0yi52Ih/p+ozCr/ivhTSSemcPbniTBbJlFZO4NJ2BuS2iEXkXwZs3mm8Fvx5Sg==" + }, "@angular/cli": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-9.0.5.tgz", @@ -3776,6 +3781,17 @@ "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==", "dev": true }, + "@jest/types": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", + "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^13.0.0" + } + }, "@ngtools/webpack": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-9.0.5.tgz", @@ -3866,6 +3882,15 @@ "fastq": "^1.6.0" } }, + "@phenomnomnominal/tsquery": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@phenomnomnominal/tsquery/-/tsquery-3.0.0.tgz", + "integrity": "sha512-SW8lKitBHWJ9fAYkJ9kJivuctwNYCh3BUxLdH0+XiR1GPBiu+7qiZzh8p8jqlj1LgVC1TbvfNFroaEsmYlL8Iw==", + "dev": true, + "requires": { + "esquery": "^1.0.1" + } + }, "@samverschueren/stream-to-observable": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz", @@ -4017,6 +4042,37 @@ } } }, + "@sheerun/mutationobserver-shim": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.3.tgz", + "integrity": "sha512-DetpxZw1fzPD5xUBrIAoplLChO2VB8DlL5Gg+I1IR9b2wPqYIca2WSUxL5g1vLeR4MsQq1NeWriXAVffV+U1Fw==", + "dev": true + }, + "@testing-library/angular": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@testing-library/angular/-/angular-8.2.0.tgz", + "integrity": "sha512-vOd4VdIW5x1dUfQyM93+916s9MWWXVg01BHsnMIizIvaQN0y+C79HXJJzTShamXNh3hTB6TnIFmLOx12Dh/PsA==", + "dev": true, + "requires": { + "@phenomnomnominal/tsquery": "^3.0.0", + "@testing-library/dom": "^5.1.0", + "tslib": "^1.9.0", + "tslint": "^5.16.0" + } + }, + "@testing-library/dom": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-5.6.1.tgz", + "integrity": "sha512-Y1T2bjtvQMewffn1CJ28kpgnuvPYKsBcZMagEH0ppfEMZPDc8AkkEnTk4smrGZKw0cblNB3lhM2FMnpfLExlHg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.5.5", + "@sheerun/mutationobserver-shim": "^0.3.2", + "aria-query": "3.0.0", + "pretty-format": "^24.8.0", + "wait-for-expect": "^1.2.0" + } + }, "@tootallnate/once": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.0.0.tgz", @@ -4108,6 +4164,31 @@ "integrity": "sha1-kU2r1QVG2bAUKAbkLHK8fCt+B4c=", "dev": true }, + "@types/istanbul-lib-coverage": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz", + "integrity": "sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg==", + "dev": true + }, + "@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.1.tgz", + "integrity": "sha512-UpYjBi8xefVChsCoBpKShdxTllC9pwISirfoZsUa2AAdQg/Jd2KQGtSbw+ya7GPo7x/wAPlH6JBhKhAsXUEZNA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*", + "@types/istanbul-lib-report": "*" + } + }, "@types/jasmine": { "version": "3.5.8", "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.5.8.tgz", @@ -4346,6 +4427,21 @@ "source-map": "^0.6.1" } }, + "@types/yargs": { + "version": "13.0.8", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.8.tgz", + "integrity": "sha512-XAvHLwG7UQ+8M4caKIH0ZozIOYay5fQkAgyIXegXT9jPtdIGdhga+sUEdAr1CiG46aB+c64xQEYyEzlwWVTNzA==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz", + "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==", + "dev": true + }, "@typescript-eslint/eslint-plugin": { "version": "2.22.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.22.0.tgz", @@ -4920,6 +5016,16 @@ "integrity": "sha1-7L0W+JSbFXGDcRsb2jNPN4QBhas=", "dev": true }, + "aria-query": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz", + "integrity": "sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w=", + "dev": true, + "requires": { + "ast-types-flow": "0.0.7", + "commander": "^2.11.0" + } + }, "ark-ts": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/ark-ts/-/ark-ts-0.4.1.tgz", @@ -5098,6 +5204,12 @@ "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", "dev": true }, + "ast-types-flow": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", + "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=", + "dev": true + }, "astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", @@ -5741,6 +5853,12 @@ "resolved": "https://registry.npmjs.org/bufio/-/bufio-1.0.6.tgz", "integrity": "sha512-mjYZFRHmI9bk3Oeexu0rWjHFY+w6hGLabdmwSFzq+EFr4MHHsNOYduDVdYl71NG5pTPL7GGzUCMk9cYuV34/Qw==" }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, "builtin-status-codes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", @@ -8041,6 +8159,15 @@ "get-stdin": "^6.0.0" } }, + "eslint-plugin-html": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-html/-/eslint-plugin-html-6.0.0.tgz", + "integrity": "sha512-PQcGippOHS+HTbQCStmH5MY1BF2MaU8qW/+Mvo/8xTa/ioeMXdSP+IiaBw2+nh0KEMfYQKuTz1Zo+vHynjwhbg==", + "dev": true, + "requires": { + "htmlparser2": "^3.10.1" + } + }, "eslint-plugin-prettier": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.2.tgz", @@ -13757,6 +13884,26 @@ "fast-diff": "^1.1.2" } }, + "pretty-format": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz", + "integrity": "sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==", + "dev": true, + "requires": { + "@jest/types": "^24.9.0", + "ansi-regex": "^4.0.0", + "ansi-styles": "^3.2.0", + "react-is": "^16.8.4" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + } + } + }, "pretty-hrtime": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", @@ -14273,6 +14420,12 @@ } } }, + "react-is": { + "version": "16.13.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.0.tgz", + "integrity": "sha512-GFMtL0vHkiBv9HluwNZTggSn/sCyEt9n02aM0dSAjGGyqyNlAyftYm4phPxdvCigG15JreC5biwxCgTAJZ7yAA==", + "dev": true + }, "read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -16793,6 +16946,44 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz", "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==" }, + "tslint": { + "version": "5.20.1", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.20.1.tgz", + "integrity": "sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^4.0.1", + "glob": "^7.1.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.8.0", + "tsutils": "^2.29.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, "tty-browserify": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", @@ -17325,6 +17516,12 @@ "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", "dev": true }, + "wait-for-expect": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/wait-for-expect/-/wait-for-expect-1.3.0.tgz", + "integrity": "sha512-8fJU7jiA96HfGPt+P/UilelSAZfhMBJ52YhKzlmZQvKEZU2EcD1GQ0yqGB6liLdHjYtYAoGVigYwdxr5rktvzA==", + "dev": true + }, "watchpack": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", diff --git a/package.json b/package.json index cfa856c7f..855ecdc9b 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ }, "private": true, "dependencies": { + "@angular/animations": "^9.0.6", "@angular/common": "~9.0.5", "@angular/compiler": "^9.0.5", "@angular/core": "~9.0.5", @@ -103,6 +104,7 @@ "@angular/language-service": "~9.0.5", "@ionic/angular-toolkit": "^2.2.0", "@ionic/lab": "^3.1.1", + "@testing-library/angular": "^8.2.0", "@types/bcryptjs": "^2.4.2", "@types/bip39": "^3.0.0", "@types/bytebuffer": "^5.0.40", @@ -118,6 +120,7 @@ "codecov": "^3.6.5", "eslint": "^6.8.0", "eslint-config-prettier": "^6.10.0", + "eslint-plugin-html": "^6.0.0", "eslint-plugin-prettier": "^3.1.2", "eslint-plugin-simple-import-sort": "^5.0.1", "husky": "^4.2.3", diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 846ce8fb3..8881ca903 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,6 +1,7 @@ import { HttpClient, HttpClientModule } from "@angular/common/http"; import { NgModule } from "@angular/core"; import { BrowserModule, HammerModule } from "@angular/platform-browser"; +import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { RouteReuseStrategy } from "@angular/router"; import { Keyboard } from "@ionic-native/keyboard/ngx"; import { Network } from "@ionic-native/network/ngx"; @@ -31,6 +32,7 @@ export function createTranslateLoader(http: HttpClient) { IonicModule.forRoot(), IonicStorageModule.forRoot(), BrowserModule, + BrowserAnimationsModule, HttpClientModule, TranslateModule.forRoot({ loader: { diff --git a/src/app/components/input-currency/input-currency.component.html b/src/app/components/input-currency/input-currency.component.html index 172a9734e..9afd20ff8 100644 --- a/src/app/components/input-currency/input-currency.component.html +++ b/src/app/components/input-currency/input-currency.component.html @@ -1,9 +1,11 @@ diff --git a/src/app/components/input-currency/input-currency.component.scss b/src/app/components/input-currency/input-currency.component.scss new file mode 100644 index 000000000..a07c63ee2 --- /dev/null +++ b/src/app/components/input-currency/input-currency.component.scss @@ -0,0 +1,6 @@ +:host { + .c-input-currency--relaxed { + --padding-top: 12px; + --padding-bottom: 12px; + } +} diff --git a/src/app/components/input-currency/input-currency.component.ts b/src/app/components/input-currency/input-currency.component.ts index ca8ac554a..5f2a35265 100644 --- a/src/app/components/input-currency/input-currency.component.ts +++ b/src/app/components/input-currency/input-currency.component.ts @@ -15,7 +15,7 @@ import { import { ARKTOSHI_DP } from "@/app/app.constants"; import BigNumber, { SafeBigNumber } from "@/utils/bignumber"; -export interface IInputCurrencyOutput { +export interface InputCurrencyOutput { display: string; value: BigNumber; satoshi: BigNumber; @@ -24,6 +24,7 @@ export interface IInputCurrencyOutput { @Component({ selector: "input-currency", templateUrl: "input-currency.component.html", + styleUrls: ["input-currency.component.scss"], providers: [ { provide: NG_VALUE_ACCESSOR, @@ -34,10 +35,10 @@ export interface IInputCurrencyOutput { ], }) export class InputCurrencyComponent implements OnInit, ControlValueAccessor { - public formControl = new FormControl(""); + public formControl: FormControl; @Output() - public updated = new EventEmitter(); + public inputCurrencyUpdate = new EventEmitter(); @Input() public name: string; @@ -48,6 +49,9 @@ export class InputCurrencyComponent implements OnInit, ControlValueAccessor { @Input() public fractionDigits = ARKTOSHI_DP; + @Input() + public isRelaxed = false; + public isDisabled: boolean; public input: (value: BigNumber) => void; @@ -76,6 +80,10 @@ export class InputCurrencyComponent implements OnInit, ControlValueAccessor { } ngOnInit() { + this.formControl = new FormControl({ + value: 0, + disabled: this.isDisabled, + }); this.formControl.valueChanges.subscribe((value: string) => { const formatted = this.format(value); this.input(formatted.value); @@ -89,14 +97,14 @@ export class InputCurrencyComponent implements OnInit, ControlValueAccessor { private format(value: string) { const sanitized = this.sanitizeInput(value); this.formControl.setValue(sanitized.display, { emitEvent: false }); - this.updated.emit(sanitized); + this.inputCurrencyUpdate.emit(sanitized); return sanitized; } /** * Copied from https://github.com/LedgerHQ/ledger-live-desktop with changes */ - private sanitizeInput(input: string): IInputCurrencyOutput { + private sanitizeInput(input: string): InputCurrencyOutput { const numbers = "0123456789"; const separatos = ".,"; diff --git a/src/app/components/input-currency/input-currency.module.ts b/src/app/components/input-currency/input-currency.module.ts index 4f24611d9..510a1f494 100644 --- a/src/app/components/input-currency/input-currency.module.ts +++ b/src/app/components/input-currency/input-currency.module.ts @@ -3,12 +3,13 @@ import { ReactiveFormsModule } from "@angular/forms"; import { IonicModule } from "@ionic/angular"; import { SharedModule } from "@/app/shared.module"; +import { DirectivesModule } from "@/directives/directives.module"; import { InputCurrencyComponent } from "./input-currency.component"; @NgModule({ declarations: [InputCurrencyComponent], - imports: [IonicModule, ReactiveFormsModule, SharedModule], + imports: [IonicModule, SharedModule, DirectivesModule], exports: [InputCurrencyComponent], }) export class InputCurrencyComponentModule {} diff --git a/src/app/components/input-fee/input-fee.component.html b/src/app/components/input-fee/input-fee.component.html index 68a634557..59bf93900 100644 --- a/src/app/components/input-fee/input-fee.component.html +++ b/src/app/components/input-fee/input-fee.component.html @@ -1,88 +1,81 @@ - - - Fee +
+ + {{ "INPUT_FEE.MIN" | translate }} + + - - {{ symbol }} - - - - - - - + {{ "INPUT_FEE.AVG" | translate }} + + + {{ "INPUT_FEE.MAX" | translate }} + +
- - {{ warningMessage }} + + {{ "TRANSACTIONS_PAGE.FEE" | translate }} - - {{ errorMessage }} - + - - INPUT_FEE.STATIC_FEE - + + + +

+ INPUT_FEE.STATIC_FEE +

+ +

+ {{ "INPUT_FEE.LOW_FEE_NOTICE" | translate }} +

- - - - - {{ "INPUT_FEE.MIN" | translate }} - - - - - {{ "INPUT_FEE.AVERAGE" | translate }} - - - - - {{ "INPUT_FEE.MAX" | translate }} - - - - -
+

+ {{ "INPUT_FEE.ADVANCED_NOTICE" | translate }} +

diff --git a/src/app/components/input-fee/input-fee.component.pcss b/src/app/components/input-fee/input-fee.component.pcss new file mode 100644 index 000000000..5651d2e3d --- /dev/null +++ b/src/app/components/input-fee/input-fee.component.pcss @@ -0,0 +1,28 @@ +:host { + @apply w-full; + + ion-range { + bottom: -0.5rem; + --bar-height: 3px; + --height: 1rem; + --knob-size: 1.5rem; + --knob-handle-size: 1.5rem; + --bar-background: var(--ion-color-light-shade); + } + + ion-item { + --padding-start: 0; + } + + .c-input-fee__hint { + @apply py-2 text-sm italic; + } + + .c-input-fee__hint--info { + color: var(--ion-color-medium-shade); + } + + .c-input-fee__hint--warn { + color: var(--ion-color-warning-shade); + } +} diff --git a/src/app/components/input-fee/input-fee.component.scss b/src/app/components/input-fee/input-fee.component.scss deleted file mode 100644 index d0258fbfb..000000000 --- a/src/app/components/input-fee/input-fee.component.scss +++ /dev/null @@ -1,30 +0,0 @@ -:host { - ion-item { - --padding-start: 0; - } - - .input-fee--warning .input-fee__label { - color: #de751f; - } - - .input-fee--danger .input-fee__label { - color: var(--ion-color-danger); - } - - .input-fee__helper { - padding-top: 2px; - padding-bottom: 5px; - white-space: normal !important; - overflow: visible !important; - } - - .input-fee__helper--warning { - @extend .input-fee__helper; - color: #de751f; - } - - .input-fee__helper--danger { - @extend .input-fee__helper; - color: var(--ion-color-danger); - } -} diff --git a/src/app/components/input-fee/input-fee.component.spec.ts b/src/app/components/input-fee/input-fee.component.spec.ts new file mode 100644 index 000000000..0a353fcb4 --- /dev/null +++ b/src/app/components/input-fee/input-fee.component.spec.ts @@ -0,0 +1,187 @@ +import { CommonModule } from "@angular/common"; +import { FormGroupDirective, ReactiveFormsModule } from "@angular/forms"; +import { IonicModule } from "@ionic/angular"; +import { TranslateModule } from "@ngx-translate/core"; +import { render } from "@testing-library/angular"; +import { BigNumber } from "bignumber.js"; + +import { PipesModule } from "@/pipes/pipes.module"; + +import { InputCurrencyComponentModule } from "../input-currency/input-currency.module"; +import { InputFeeComponent } from "./input-fee.component"; + +function createComponent(props?: Partial) { + return render(InputFeeComponent, { + imports: [ + IonicModule, + TranslateModule.forRoot(), + InputCurrencyComponentModule, + PipesModule, + CommonModule, + ReactiveFormsModule, + ], + componentProperties: { + ...props, + }, + providers: [FormGroupDirective], + }); +} + +describe("Input Fee Component", () => { + it("should create", async () => { + const component = await createComponent(); + expect(component.fixture.componentInstance.isStatic).toBeTrue(); + }); + + describe("Initial value", () => { + it("should set last fee by default if specified", async () => { + const component = await createComponent({ + last: 15.2, + min: 5, + avg: 20, + max: 30, + }); + const inputControl = + component.fixture.componentInstance.inputControl; + expect(BigNumber.isBigNumber(inputControl.value)).toBeTrue(); + expect(inputControl.value.isEqualTo("0.000000152")).toBeTrue(); + }); + + it("should set avg fee by default if the last was not specified", async () => { + const component = await createComponent({ + min: 5, + avg: 20, + max: 30, + }); + const inputControl = + component.fixture.componentInstance.inputControl; + expect(BigNumber.isBigNumber(inputControl.value)).toBeTrue(); + expect(inputControl.value.isEqualTo("0.0000002")).toBeTrue(); + }); + + it("should set max fee by default if the last and avg was not specified", async () => { + const component = await createComponent({ + min: 5, + max: 30, + }); + const inputControl = + component.fixture.componentInstance.inputControl; + expect(BigNumber.isBigNumber(inputControl.value)).toBeTrue(); + expect(inputControl.value.isEqualTo("0.0000003")).toBeTrue(); + }); + }); + + describe("Controls", () => { + it("should hide the controls if max is equals to min", async () => { + const component = await createComponent({ + min: 1, + max: 1, + }); + expect(() => + component.getByTestId("c-input-fee__controls"), + ).toThrowError(); + }); + + it("should change value by clicking on the min button", async () => { + const component = await createComponent({ + min: 11.1, + max: 20, + }); + component.click( + component.getByTestId("c-input-fee__controls__min"), + ); + expect( + component.fixture.componentInstance.inputControl.value.isEqualTo( + "0.000000111", + ), + ); + }); + + it("should change value by clicking on the avg button", async () => { + const component = await createComponent({ + avg: 11.1, + max: 20, + }); + component.click( + component.getByTestId("c-input-fee__controls__avg"), + ); + expect( + component.fixture.componentInstance.inputControl.value.isEqualTo( + "0.000000111", + ), + ); + }); + + it("should change value by clicking on the max button", async () => { + const component = await createComponent({ + max: 11.1, + }); + component.click( + component.getByTestId("c-input-fee__controls__max"), + ); + expect( + component.fixture.componentInstance.inputControl.value.isEqualTo( + "0.000000111", + ), + ); + }); + }); + + describe("Range", () => { + it("should show the range if max is greater than min", async () => { + const component = await createComponent({ + min: 10, + max: 20, + }); + expect(component.fixture.componentInstance.hasRange).toBeTrue(); + }); + + it("should change value by updating the range", async () => { + const component = await createComponent({ + min: 10, + max: 20, + }); + const { + rangeControl, + inputControl, + min, + } = component.fixture.componentInstance; + rangeControl.setValue(min); + expect(inputControl.value.isEqualTo("0.0000001")).toBeTrue(); + }); + }); + + describe("Minimum limit", () => { + it("should set limitMin to min if it is different from avg", async () => { + const component = await createComponent({ + min: 100, + avg: 200, + }); + expect(component.fixture.componentInstance.limitMin).toBe(100); + }); + + it("should set limitMin to min if it is different from max", async () => { + const component = await createComponent({ + min: 100, + max: 200, + }); + expect(component.fixture.componentInstance.limitMin).toBe(100); + }); + + it("should set limitMin to 1 arktoshi if min is equal to avg", async () => { + const component = await createComponent({ + min: 100, + avg: 100, + }); + expect(component.fixture.componentInstance.limitMin).toBe(1); + }); + + it("should set limitMin to 1 arktoshi if min is equal to max", async () => { + const component = await createComponent({ + min: 100, + max: 100, + }); + expect(component.fixture.componentInstance.limitMin).toBe(1); + }); + }); +}); diff --git a/src/app/components/input-fee/input-fee.component.ts b/src/app/components/input-fee/input-fee.component.ts index 5ecdb8904..e5804a110 100644 --- a/src/app/components/input-fee/input-fee.component.ts +++ b/src/app/components/input-fee/input-fee.component.ts @@ -1,30 +1,20 @@ -import { - Component, - EventEmitter, - Input, - OnDestroy, - OnInit, - Output, -} from "@angular/core"; +import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core"; import { ControlContainer, FormControl, + FormGroup, FormGroupDirective, } from "@angular/forms"; -import { TranslateService } from "@ngx-translate/core"; -import { TransactionType } from "ark-ts"; -import { Subscription } from "rxjs"; -import { switchMap } from "rxjs/operators"; -import { FeeStatistic } from "@/models/stored-network"; -import { ArkApiProvider } from "@/services/ark-api/ark-api"; +import { ARKTOSHI_DP } from "@/app/app.constants"; +import BigNumber, { SafeBigNumber } from "@/utils/bignumber"; -import { ArkUtility } from "../../utils/ark-utility"; +import { InputCurrencyOutput } from "../input-currency/input-currency.component"; @Component({ selector: "input-fee", templateUrl: "input-fee.component.html", - styleUrls: ["input-fee.component.scss"], + styleUrls: ["input-fee.component.pcss"], viewProviders: [ { provide: ControlContainer, @@ -32,160 +22,89 @@ import { ArkUtility } from "../../utils/ark-utility"; }, ], }) -export class InputFeeComponent implements OnInit, OnDestroy { +export class InputFeeComponent implements OnInit { + @Output() + public inputFeeUpdate = new EventEmitter(); + @Input() - public transactionType: number; + public parent = new FormGroup({}); - @Output() - public change: EventEmitter = new EventEmitter(); + // Arktoshi + @Input() + public min = 1; - @Output() - public error: EventEmitter = new EventEmitter(); - - public step: number; - public v1Fee: number; - public v2Fee: FeeStatistic; - public rangeFee: number; - public min: number; - public max: number; + // Arktoshi + @Input() public avg: number; - public symbol: string; - public isStaticFee = false; - public warningMessage: string; - public errorMessage: string; - public limitFee: number; - public subscription: Subscription; - - constructor( - private arkApiProvider: ArkApiProvider, - private translateService: TranslateService, - private parentForm: FormGroupDirective, - ) { - this.step = 1; - this.min = this.step; - this.symbol = this.arkApiProvider.network.symbol; - } - ngOnInit() { - this.parentForm.form.addControl("fee", new FormControl("fee")); - this.parentForm.form.addControl( - "feeRange", - new FormControl("feeRange"), - ); - this.parentForm.form.controls.fee.valueChanges.subscribe(value => - this.onInputText(value), - ); - this.parentForm.form.controls.feeRange.valueChanges.subscribe(value => - this.onInputRange(value), - ); - this.prepareFeeStatistics(); - } + // Arktoshi + @Input() + public max = 1; - public get maxArktoshi() { - return ArkUtility.subToUnit(this.max); - } + // Arktoshi + @Input() + public last: number; - public prepareFeeStatistics() { - this.subscription = this.arkApiProvider.fees - .pipe( - switchMap(fees => { - switch (Number(this.transactionType)) { - case TransactionType.SendArk: - this.v1Fee = fees.send; - break; - case TransactionType.Vote: - this.v1Fee = fees.vote; - break; - case TransactionType.CreateDelegate: - this.v1Fee = fees.delegate; - break; - } - - this.max = this.v1Fee; - this.avg = this.v1Fee; - this.limitFee = this.max * 10; - this.setRangeFee(this.avg); - - return this.arkApiProvider.feeStatistics; - }), - ) - .subscribe(fees => { - this.v2Fee = fees.find( - fee => fee.type === Number(this.transactionType), - ); - if (!this.v2Fee || this.v2Fee.fees.avgFee > this.max) { - this.isStaticFee = true; - return; - } - if (this.v2Fee.fees.maxFee > this.max) { - this.max = this.v2Fee.fees.maxFee; - } - this.avg = this.v2Fee.fees.avgFee; - this.setRangeFee(this.avg); - }); - } + @Input() + public isStatic = true; - public setRangeFee(value: number) { - this.parentForm.form.controls.feeRange.setValue(value); - this.emitChange(); - } + public hasRange = false; + public rangeControl = new FormControl(0); + public inputControl = new FormControl(0); + public limitMin = 1; - public onInputRange(rangeFee?: number) { - this.rangeFee = rangeFee; - const fee = ArkUtility.subToUnit(rangeFee); + // Arktoshi + public currentFee: number; - this.parentForm.form.controls.fee.setValue(fee, { - emitEvent: false, - }); + constructor() {} - const translateParams = { - symbol: this.symbol, - fee: ArkUtility.subToUnit(this.limitFee), - }; - - this.translateService - .get( - [ - "INPUT_FEE.ERROR.MORE_THAN_MAXIMUM", - "INPUT_FEE.LOW_FEE_NOTICE", - "INPUT_FEE.ADVANCED_NOTICE", - ], - translateParams, - ) - .subscribe(translation => { - this.errorMessage = null; - this.warningMessage = null; - - if (this.avg > rangeFee) { - this.warningMessage = - translation["INPUT_FEE.LOW_FEE_NOTICE"]; - } else if (rangeFee > this.limitFee) { - this.errorMessage = - translation["INPUT_FEE.ERROR.MORE_THAN_MAXIMUM"]; - } else if (rangeFee > this.max) { - this.warningMessage = - translation["INPUT_FEE.ADVANCED_NOTICE"]; - } - this.error.next(!!this.errorMessage || !fee.length); + ngOnInit() { + this.parent.addControl("fee", this.inputControl); + this.inputControl.valueChanges.subscribe((value: BigNumber) => { + // The range value should be in arktoshi + this.currentFee = value.shiftedBy(ARKTOSHI_DP).toNumber(); + this.rangeControl.setValue(this.currentFee, { + emitEvent: false, }); - } - - public onInputText(fee?: string) { - const arktoshi = parseInt(ArkUtility.unitToSub(fee)); + }); - this.parentForm.form.controls.feeRange.setValue(arktoshi, { - emitEvent: false, + this.rangeControl.valueChanges.subscribe((value: number) => { + this.inputControl.markAsDirty(); + this.currentFee = value; + this.setInputValue(value, false); }); - this.emitChange(); + // Initial value + this.setInputValue(this.last || this.avg || this.max); + this.checkRange(); + } + + public handleClickButton(value: number) { + this.setInputValue(value); + this.inputControl.markAsDirty(); } - public emitChange() { - const rangeFee = this.parentForm.form.get("feeRange"); - this.change.next(rangeFee.value); + public emitUpdate(output: InputCurrencyOutput) { + this.inputFeeUpdate.emit(output); } - ngOnDestroy() { - this.subscription.unsubscribe(); + private checkRange() { + // If the minimum input value is equal to the maximum or average + // Set the minimum to 1 arktoshi + if (this.min === this.max || this.min === this.avg) { + this.limitMin = 1; + } else { + this.limitMin = this.min; + } + // Hide range if the minimum is equal to maximum + this.hasRange = this.max > this.limitMin; + } + + private setInputValue(value: number, emitEvent = true) { + // The input value should be in human + const satoshi = new SafeBigNumber(value).shiftedBy(ARKTOSHI_DP * -1); + this.inputControl.setValue(satoshi, { + emitEvent, + }); } } diff --git a/src/app/components/input-fee/input-fee.module.ts b/src/app/components/input-fee/input-fee.module.ts index 5ef57bee3..c62fc5c4c 100644 --- a/src/app/components/input-fee/input-fee.module.ts +++ b/src/app/components/input-fee/input-fee.module.ts @@ -1,8 +1,9 @@ -import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; -import { FormsModule, ReactiveFormsModule } from "@angular/forms"; import { IonicModule } from "@ionic/angular"; -import { TranslateModule } from "@ngx-translate/core"; + +import { SharedModule } from "@/app/shared.module"; +import { DirectivesModule } from "@/directives/directives.module"; +import { PipesModule } from "@/pipes/pipes.module"; import { InputCurrencyComponentModule } from "../input-currency/input-currency.module"; import { InputFeeComponent } from "./input-fee.component"; @@ -11,11 +12,10 @@ import { InputFeeComponent } from "./input-fee.component"; declarations: [InputFeeComponent], imports: [ IonicModule, - FormsModule, - ReactiveFormsModule, + SharedModule, InputCurrencyComponentModule, - TranslateModule, - CommonModule, + DirectivesModule, + PipesModule, ], exports: [InputFeeComponent], }) diff --git a/src/app/directives/directives.module.ts b/src/app/directives/directives.module.ts index cab7455d5..ca4e6cd03 100644 --- a/src/app/directives/directives.module.ts +++ b/src/app/directives/directives.module.ts @@ -2,6 +2,7 @@ import { NgModule } from "@angular/core"; import { HeaderScrollerDirective } from "./header-scroller/header-scroller"; import { MarketNetOnlyDirective } from "./marketnet-only/marketnet-only"; +import { PasteClipboardValueDirective } from "./paste-clipboard-value/paste-clipboard-value"; import { ValueMaskOnBlurDirective } from "./value-mask-on-blur/value-mask-on-blur"; @NgModule({ @@ -9,12 +10,14 @@ import { ValueMaskOnBlurDirective } from "./value-mask-on-blur/value-mask-on-blu MarketNetOnlyDirective, HeaderScrollerDirective, ValueMaskOnBlurDirective, + PasteClipboardValueDirective, ], imports: [], exports: [ MarketNetOnlyDirective, HeaderScrollerDirective, ValueMaskOnBlurDirective, + PasteClipboardValueDirective, ], }) export class DirectivesModule {} diff --git a/src/app/directives/paste-clipboard-value/paste-clipboard-value.ts b/src/app/directives/paste-clipboard-value/paste-clipboard-value.ts new file mode 100644 index 000000000..55276d09b --- /dev/null +++ b/src/app/directives/paste-clipboard-value/paste-clipboard-value.ts @@ -0,0 +1,17 @@ +import { Directive, HostListener, Input } from "@angular/core"; +import { NgControl } from "@angular/forms"; + +@Directive({ + selector: "[appPasteClipboardValue]", +}) +export class PasteClipboardValueDirective { + constructor(private model: NgControl) {} + + @HostListener("paste", ["$event"]) + onPaste(input: ClipboardEvent) { + const value = input?.clipboardData?.getData("text"); + if (value) { + this.model.control.setValue(value); + } + } +} diff --git a/src/app/pages/delegates/delegate-detail/delegate-detail.html b/src/app/pages/delegates/delegate-detail/delegate-detail.html index b5935ce19..45c5f0491 100644 --- a/src/app/pages/delegates/delegate-detail/delegate-detail.html +++ b/src/app/pages/delegates/delegate-detail/delegate-detail.html @@ -64,9 +64,11 @@

diff --git a/src/app/pages/delegates/delegate-detail/delegate-detail.ts b/src/app/pages/delegates/delegate-detail/delegate-detail.ts index 29f27b0c2..dd82a8c48 100644 --- a/src/app/pages/delegates/delegate-detail/delegate-detail.ts +++ b/src/app/pages/delegates/delegate-detail/delegate-detail.ts @@ -1,4 +1,4 @@ -import { Component } from "@angular/core"; +import { Component, OnDestroy, OnInit } from "@angular/core"; import { FormGroup } from "@angular/forms"; import { Clipboard } from "@ionic-native/clipboard/ngx"; import { @@ -10,7 +10,10 @@ import { import { TranslateService } from "@ngx-translate/core"; import { Delegate, Network, TransactionType } from "ark-ts"; import lodash from "lodash"; +import { Subject } from "rxjs"; +import { takeUntil } from "rxjs/operators"; +import { InputCurrencyOutput } from "@/components/input-currency/input-currency.component"; import { Wallet } from "@/models/wallet"; import { ArkApiProvider } from "@/services/ark-api/ark-api"; import { ToastProvider } from "@/services/toast/toast"; @@ -22,7 +25,7 @@ import { UserDataService } from "@/services/user-data/user-data.interface"; styleUrls: ["delegate-detail.pcss"], providers: [Clipboard], }) -export class DelegateDetailPage { +export class DelegateDetailPage implements OnInit, OnDestroy { public delegate: Delegate; public qraddress = '{a: ""}'; public currentNetwork: Network; @@ -31,6 +34,9 @@ export class DelegateDetailPage { public transactionType = TransactionType.Vote; public fee: number; public voteForm = new FormGroup({}); + public nodeFees: any; + + private unsubscriber$: Subject = new Subject(); constructor( public navCtrl: NavController, @@ -55,6 +61,20 @@ export class DelegateDetailPage { } } + ngOnInit() { + this.arkApiProvider + .prepareFeesByType(TransactionType.Vote) + .pipe(takeUntil(this.unsubscriber$)) + .subscribe(data => { + this.nodeFees = data; + }); + } + + ngOnDestroy() { + this.unsubscriber$.next(); + this.unsubscriber$.complete(); + } + isSameDelegate() { if ( this.currentWallet && @@ -125,8 +145,8 @@ export class DelegateDetailPage { } } - onInputFee(fee) { - this.fee = fee; + onInputFee(output: InputCurrencyOutput) { + this.fee = output.satoshi.toNumber(); } unvote() { diff --git a/src/app/pages/transaction/transaction-send/transaction-send.html b/src/app/pages/transaction/transaction-send/transaction-send.html index b1f89ea67..8b0018a32 100644 --- a/src/app/pages/transaction/transaction-send/transaction-send.html +++ b/src/app/pages/transaction/transaction-send/transaction-send.html @@ -45,9 +45,12 @@ diff --git a/src/app/pages/transaction/transaction-send/transaction-send.ts b/src/app/pages/transaction/transaction-send/transaction-send.ts index 87c0b0409..ee6e374af 100644 --- a/src/app/pages/transaction/transaction-send/transaction-send.ts +++ b/src/app/pages/transaction/transaction-send/transaction-send.ts @@ -14,6 +14,7 @@ import { takeUntil } from "rxjs/operators"; import * as constants from "@/app/app.constants"; import { ConfirmTransactionComponent } from "@/components/confirm-transaction/confirm-transaction"; +import { InputCurrencyOutput } from "@/components/input-currency/input-currency.component"; import { PinCodeComponent } from "@/components/pin-code/pin-code"; import { QRScannerComponent } from "@/components/qr-scanner/qr-scanner"; import { WalletPickerModal } from "@/components/wallet-picker/wallet-picker.modal"; @@ -65,6 +66,7 @@ export class TransactionSendPage implements OnInit, OnDestroy { currentWallet: Wallet; currentNetwork: StoredNetwork; + nodeFees: any; fee: number; hasFeeError = false; hasSent = false; @@ -227,6 +229,13 @@ export class TransactionSendPage implements OnInit, OnDestroy { } ngOnInit(): void { + this.arkApiProvider + .prepareFeesByType(TransactionType.SendArk) + .pipe(takeUntil(this.unsubscriber$)) + .subscribe(data => { + this.nodeFees = data; + }); + this.hasNotSent(); this.pinCode.close.pipe(takeUntil(this.unsubscriber$)).subscribe(() => { @@ -260,8 +269,8 @@ export class TransactionSendPage implements OnInit, OnDestroy { this.unsubscriber$.complete(); } - public onFeeChange(newFee: number) { - this.fee = newFee; + public onFeeChange(output: InputCurrencyOutput) { + this.fee = output.satoshi.toNumber(); if (this.sendAllEnabled) { this.sendAll(); diff --git a/src/app/services/ark-api/ark-api.ts b/src/app/services/ark-api/ark-api.ts index b9c066b03..a574d2593 100644 --- a/src/app/services/ark-api/ark-api.ts +++ b/src/app/services/ark-api/ark-api.ts @@ -5,8 +5,25 @@ import * as arkts from "ark-ts"; import arktsConfig from "ark-ts/config"; import lodash from "lodash"; import moment from "moment"; -import { EMPTY, Observable, of, Subject, throwError } from "rxjs"; -import { catchError, expand, finalize, switchMap, tap } from "rxjs/operators"; +import { + EMPTY, + forkJoin, + Observable, + of, + Subject, + throwError, + zip, +} from "rxjs"; +import { + catchError, + expand, + finalize, + map, + mergeMap, + mergeMapTo, + switchMap, + tap, +} from "rxjs/operators"; import * as constants from "@/app/app.constants"; import { @@ -401,6 +418,50 @@ export class ArkApiProvider { return this.client.getDelegateByPublicKey(publicKey); } + public prepareFeesByType( + type: number, + ): Observable { + return zip(this.fees, this.feeStatistics).pipe( + map(([respStatic, respDynamic]) => { + const feeNameMap = { + 0: "send", + 1: "secondsignature", + 2: "delegate", + 3: "vote", + }; + const feeStatic = Number(respStatic[feeNameMap[type]]); + + let max = feeStatic; + let avg = feeStatic; + let min = 1; + let isStatic = true; + + const feeDynamic = respDynamic.find(item => item.type === type); + + if (feeDynamic) { + isStatic = feeDynamic.fees.avgFee > max; + + if (!isStatic) { + if (feeDynamic.fees.maxFee > max) { + max = feeDynamic.fees.maxFee; + } + + avg = feeDynamic.fees.avgFee; + min = feeDynamic.fees.minFee; + } + } + + return { + type, + isStatic, + min, + avg, + max, + }; + }), + ); + } + private isSuccessfulResponse(response) { const { data, errors } = response; const anyDuplicate = diff --git a/src/app/utils/http-utils.ts b/src/app/utils/http-utils.ts deleted file mode 100644 index b6de2b92f..000000000 --- a/src/app/utils/http-utils.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { HttpParams } from "@angular/common/http"; -import { isNil, isPlainObject } from "lodash"; - -export class HttpUtils { - static buildQueryParams(source: object): HttpParams { - let target: HttpParams = new HttpParams(); - Object.keys(source).forEach((key: string) => { - let value: any = source[key]; - if (isNil(value)) { - return; - } - if (isPlainObject(value)) { - value = JSON.stringify(value); - } else { - value = value.toString(); - } - target = target.append(key, value); - }); - return target; - } -} diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 9bf8538f5..2cfd2a9c7 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -160,6 +160,7 @@ }, "INPUT_FEE": { "MIN": "Min", + "AVG": "Avg", "AVERAGE": "Average", "MAX": "Max", "LOW_FEE_NOTICE": "Transactions with low fees may never get confirmed", diff --git a/src/theme/variables.scss b/src/theme/variables.scss index aea973900..87a9b0ce4 100644 --- a/src/theme/variables.scss +++ b/src/theme/variables.scss @@ -37,28 +37,28 @@ --ion-color-tertiary-tint: #7e57ff; /** success **/ - --ion-color-success: #10dc60; - --ion-color-success-rgb: 16, 220, 96; + --ion-color-success: #48bb78; + --ion-color-success-rgb: 72, 187, 120; --ion-color-success-contrast: #ffffff; --ion-color-success-contrast-rgb: 255, 255, 255; - --ion-color-success-shade: #0ec254; - --ion-color-success-tint: #28e070; + --ion-color-success-shade: #3fa56a; + --ion-color-success-tint: #5ac286; /** warning **/ - --ion-color-warning: #ffce00; - --ion-color-warning-rgb: 255, 206, 0; - --ion-color-warning-contrast: #ffffff; - --ion-color-warning-contrast-rgb: 255, 255, 255; - --ion-color-warning-shade: #e0b500; - --ion-color-warning-tint: #ffd31a; + --ion-color-warning: #ecc94b; + --ion-color-warning-rgb: 236, 201, 75; + --ion-color-warning-contrast: #000000; + --ion-color-warning-contrast-rgb: 0, 0, 0; + --ion-color-warning-shade: #d0b142; + --ion-color-warning-tint: #eece5d; /** danger **/ - --ion-color-danger: #f04141; - --ion-color-danger-rgb: 245, 61, 61; - --ion-color-danger-contrast: #ffffff; - --ion-color-danger-contrast-rgb: 255, 255, 255; - --ion-color-danger-shade: #d33939; - --ion-color-danger-tint: #f25454; + --ion-color-danger: #f56565; + --ion-color-danger-rgb: 245, 101, 101; + --ion-color-danger-contrast: #000000; + --ion-color-danger-contrast-rgb: 0, 0, 0; + --ion-color-danger-shade: #d85959; + --ion-color-danger-tint: #f67474; /** dark **/ --ion-color-dark: #222428;