diff --git a/__tests__/components/AmountInput.spec.ts b/__tests__/components/AmountInput.spec.ts index 3c89e273d..2418013e1 100644 --- a/__tests__/components/AmountInput.spec.ts +++ b/__tests__/components/AmountInput.spec.ts @@ -12,6 +12,7 @@ import { StandardValidationRules } from '@/core/validation/StandardValidationRul import { MaxDecimalsValidator, PositiveDecimalNumberValidator, MaxRelativeAmountValidator } from '@/core/validation/validators'; import { appConfig } from '@/config'; import { NetworkConfigurationModel } from '@/core/database/entities/NetworkConfigurationModel'; +import { StartsWithZeroValidator } from '@/core/validation/validators/StartsWithZeroValidator'; StandardValidationRules.register(); appConfig.constants.DECIMAL_SEPARATOR = '.'; @@ -27,6 +28,10 @@ extend('positiveDecimal', { validate: (value) => PositiveDecimalNumberValidator.validate(value), message: () => i18n.t('positive_decimal_error', { decimalSeparator: appConfig.constants.DECIMAL_SEPARATOR }).toString(), }); +extend('startsWithZero', { + validate: (value) => StartsWithZeroValidator.validate(value), + message: () => i18n.t('amount_value_cannot_start_with_zero').toString(), +}); extend('maxRelativeAmount', { validate: (value, { maxMosaicAtomicUnits, maxMosaicDivisibility }: any) => { const maxRelativeAmount = diff --git a/src/core/validation/CustomValidationRules.ts b/src/core/validation/CustomValidationRules.ts index bc0627d4a..b7fd5e97a 100644 --- a/src/core/validation/CustomValidationRules.ts +++ b/src/core/validation/CustomValidationRules.ts @@ -22,6 +22,7 @@ import { ProfileModel } from '@/core/database/entities/ProfileModel'; import { AccountService } from '@/services/AccountService'; import { NetworkConfigurationModel } from '@/core/database/entities/NetworkConfigurationModel'; import { Values } from 'vue-i18n'; +import { StartsWithZeroValidator } from '@/core/validation/validators/StartsWithZeroValidator'; // TODO CustomValidationRules needs to be created when the network configuration is resolved, UI // needs to use the resolved CustomValidationRules @@ -82,6 +83,10 @@ export class CustomValidationRules { validate: (value) => PositiveDecimalNumberValidator.validate(value), message: () => i18n.t('positive_decimal_error', { decimalSeparator: DECIMAL_SEPARATOR }).toString(), }); + extend('startsWithZero', { + validate: (value) => StartsWithZeroValidator.validate(value), + message: () => i18n.t('amount_value_cannot_start_with_zero').toString(), + }); extend('addressOrAlias', { validate: async (value) => { diff --git a/src/core/validation/ValidationRuleset.ts b/src/core/validation/ValidationRuleset.ts index 6db1c3f4a..724d62f6d 100644 --- a/src/core/validation/ValidationRuleset.ts +++ b/src/core/validation/ValidationRuleset.ts @@ -33,7 +33,10 @@ export const createValidationRuleSet = ({ address: 'required|address|addressNetworkType:currentProfile', profilePassword: 'required|profilePassword', addressOrAlias: 'required|addressOrAlias|addressOrAliasNetworkType:currentProfile', - amount: `positiveDecimal|maxDecimals:${maxMosaicDivisibility}|maxRelativeAmount:${[maxMosaicAtomicUnits, maxMosaicDivisibility]}`, + amount: `positiveDecimal|startsWithZero|maxDecimals:${maxMosaicDivisibility}|maxRelativeAmount:${[ + maxMosaicAtomicUnits, + maxMosaicDivisibility, + ]}`, confirmPassword: 'required|confirmPassword:@newPassword', divisibility: 'required|min_value:0|max_value:6|integer', duration: `required|min_value:0|max_value:${maxMosaicDuration}`, diff --git a/src/core/validation/validators/StartsWithZeroValidator.ts b/src/core/validation/validators/StartsWithZeroValidator.ts new file mode 100644 index 000000000..0cd21ad83 --- /dev/null +++ b/src/core/validation/validators/StartsWithZeroValidator.ts @@ -0,0 +1,29 @@ +/* + * Copyright 2020 NEM (https://nem.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + * + */ +import { Validator, staticImplements } from './Validator'; + +@staticImplements() +export class StartsWithZeroValidator { + /** + * Validates amount value to not starts with 0 in case of value > 1 + * @static + * @param {*} value + * @returns {boolean} + */ + public static validate(value: any): boolean { + return parseInt(value) > 0 ? !value.startsWith('0') : true; + } +} diff --git a/src/language/en-US.json b/src/language/en-US.json index 1b8d440db..8e50c7ab4 100644 --- a/src/language/en-US.json +++ b/src/language/en-US.json @@ -1132,5 +1132,6 @@ "transaction_cosignature_warning_proceed": "I understand and wish to proceed.", "lock_hash_algorithm": "Hash Algorithm", "not_enough_balance": "Not enough balance", - "use_max_value": "Use maximum value" + "use_max_value": "Use maximum value", + "amount_value_cannot_start_with_zero": "Amount value cannot start with 0" } diff --git a/src/language/ja-JP.json b/src/language/ja-JP.json index 52f9f0fd6..ea9d1bf5b 100644 --- a/src/language/ja-JP.json +++ b/src/language/ja-JP.json @@ -1132,5 +1132,6 @@ "transaction_cosignature_warning_proceed": "確認しましたので署名に同意します", "lock_hash_algorithm": "ハッシュアルゴリズム", "not_enough_balance": "バランスが悪い", - "use_max_value": "最大値を使用" + "use_max_value": "最大値を使用", + "amount_value_cannot_start_with_zero": "金額の値を0から始めることはできません" } diff --git a/src/language/ru-RU.json b/src/language/ru-RU.json index 9cdcfa25f..327a340bc 100644 --- a/src/language/ru-RU.json +++ b/src/language/ru-RU.json @@ -1132,5 +1132,6 @@ "transaction_cosignature_warning_proceed": "Я понимаю и хочу продолжить.", "lock_hash_algorithm": "алгоритм хеширования", "not_enough_balance": "не хватает баланса", - "use_max_value": "Используйте максимальное значение" + "use_max_value": "Используйте максимальное значение", + "amount_value_cannot_start_with_zero": "Значение суммы не может начинаться с 0" } diff --git a/src/language/zh-CN.json b/src/language/zh-CN.json index ca14e9ac4..7344795cb 100644 --- a/src/language/zh-CN.json +++ b/src/language/zh-CN.json @@ -1132,5 +1132,6 @@ "transaction_cosignature_warning_proceed": "我理解并请继续。", "lock_hash_algorithm": "哈希算法", "not_enough_balance": "余额不足", - "use_maximum_value": "使用最大值" + "use_maximum_value": "使用最大值", + "amount_value_cannot_start_with_zero": "金额值不能以 0 开头" } diff --git a/src/store/Account.ts b/src/store/Account.ts index 61dfed49e..710a77b0a 100644 --- a/src/store/Account.ts +++ b/src/store/Account.ts @@ -349,6 +349,7 @@ export default { if (!address) { throw new Error('Address must be provided when calling account/SET_CURRENT_SIGNER!'); } + const isOffline: boolean = rootGetters['network/isOfflineMode']; const currentProfile: ProfileModel = rootGetters['profile/currentProfile']; const currentAccount: AccountModel = getters.currentAccount; const previousSignerAddress: Address = getters.currentSignerAddress; @@ -426,14 +427,15 @@ export default { dispatch('mosaic/SIGNER_CHANGED', {}, { root: true }); dispatch('transaction/SIGNER_CHANGED', {}, { root: true }); dispatch('metadata/SIGNER_CHANGED', {}, { root: true }); - dispatch('harvesting/SET_CURRENT_SIGNER_HARVESTING_MODEL', currentSignerAddress.plain(), { root: true }); - const networkType = rootGetters['network/networkType']; - const nodeService = new NodeService(); - const nodes = await nodeService.getNodesFromStatisticService(networkType); - if (nodes && nodes.length && navigator.onLine) { - dispatch('harvesting/LOAD_HARVESTED_BLOCKS_STATS', {}, { root: true }); + if (!isOffline) { + dispatch('harvesting/SET_CURRENT_SIGNER_HARVESTING_MODEL', currentSignerAddress.plain(), { root: true }); + const networkType = rootGetters['network/networkType']; + const nodeService = new NodeService(); + const nodes = await nodeService.getNodesFromStatisticService(networkType); + if (nodes && nodes.length && navigator.onLine) { + dispatch('harvesting/LOAD_HARVESTED_BLOCKS_STATS', {}, { root: true }); + } } - if (unsubscribeWS) { if (previousSignerAddress) { await dispatch('UNSUBSCRIBE', previousSignerAddress); diff --git a/src/views/forms/FormMosaicSupplyChangeTransaction/FormMosaicSupplyChangeTransactionTs.ts b/src/views/forms/FormMosaicSupplyChangeTransaction/FormMosaicSupplyChangeTransactionTs.ts index 52a866514..0ea8e47db 100644 --- a/src/views/forms/FormMosaicSupplyChangeTransaction/FormMosaicSupplyChangeTransactionTs.ts +++ b/src/views/forms/FormMosaicSupplyChangeTransaction/FormMosaicSupplyChangeTransactionTs.ts @@ -44,7 +44,8 @@ import { MosaicModel } from '@/core/database/entities/MosaicModel'; // @ts-ignore import MosaicSelector from '@/components/MosaicSelector/MosaicSelector.vue'; import { Formatters } from '@/core/utils/Formatters'; - +import { NetworkConfigurationModel } from '@/core/database/entities/NetworkConfigurationModel'; +import { MosaicService } from '@/services/MosaicService'; @Component({ components: { ValidationObserver, @@ -60,7 +61,14 @@ import { Formatters } from '@/core/utils/Formatters'; MaxFeeAndSubmit, MosaicSelector, }, - computed: { ...mapGetters({ mosaics: 'mosaic/mosaics', holdMosaics: 'mosaic/holdMosaics' }) }, + computed: { + ...mapGetters({ + mosaics: 'mosaic/mosaics', + holdMosaics: 'mosaic/holdMosaics', + currentHeight: 'network/currentHeight', + networkConfiguration: 'network/networkConfiguration', + }), + }, }) export class FormMosaicSupplyChangeTransactionTs extends FormTransactionBase { /** @@ -110,6 +118,15 @@ export class FormMosaicSupplyChangeTransactionTs extends FormTransactionBase { * @protected * @var {Record} */ + /** + * Current network block height + */ + private currentHeight: number; + /** + * Network Configs + * @protected + */ + public networkConfiguration: NetworkConfigurationModel; protected formItems = { mosaicHexId: null, action: null, @@ -248,6 +265,14 @@ export class FormMosaicSupplyChangeTransactionTs extends FormTransactionBase { get ownedTargetHexIds(): string[] { return this.holdMosaics .filter((m) => m.ownerRawPlain === this.currentAccount.address && m.supplyMutable) + .filter((entry) => { + const expiration = MosaicService.getExpiration( + entry, + this.currentHeight, + this.networkConfiguration.blockGenerationTargetTime, + ); + return expiration !== 'expired'; + }) .map(({ mosaicIdHex }) => mosaicIdHex); } diff --git a/src/views/forms/FormOfflineTransferTransaction/FormOfflineTransferTransactionTs.ts b/src/views/forms/FormOfflineTransferTransaction/FormOfflineTransferTransactionTs.ts index 1dd7e727e..4aa49373e 100644 --- a/src/views/forms/FormOfflineTransferTransaction/FormOfflineTransferTransactionTs.ts +++ b/src/views/forms/FormOfflineTransferTransaction/FormOfflineTransferTransactionTs.ts @@ -139,7 +139,7 @@ export class FormOfflineTransferTransactionTs extends Vue { * Hook called when the page is mounted * @return {void} */ - public created() { + public async created() { // filter out invalid profiles this.profiles = this.profileService.getProfiles().filter((p) => p.accounts.length > 0); @@ -155,7 +155,7 @@ export class FormOfflineTransferTransactionTs extends Vue { // accounts available, iterate to first profile this.formItems.currentProfileName = this.profiles[0].profileName; - this.onProfileNameChange(); + await this.onProfileNameChange(); } /**