diff --git a/changes/ana_emoney-format-rewards b/changes/ana_emoney-format-rewards new file mode 100644 index 0000000000..952173c053 --- /dev/null +++ b/changes/ana_emoney-format-rewards @@ -0,0 +1 @@ +[Added] [#3559](https://github.com/cosmos/lunie/pull/3559) Now rewards for multidenom networks are displayed in TmBalance and staking denom is especified in validators' components @Bitcoinera \ No newline at end of file diff --git a/src/ActionModal/components/ActionModal.vue b/src/ActionModal/components/ActionModal.vue index 07155f8cba..d415b51d78 100644 --- a/src/ActionModal/components/ActionModal.vue +++ b/src/ActionModal/components/ActionModal.vue @@ -936,7 +936,7 @@ export default { .action-modal-footer .tm-form-group .tm-form-group__field { display: flex; align-items: center; - justify-items: space-between; + justify-content: flex-end; } .action-modal-footer .tm-form-group .tm-form-group__field .tertiary { diff --git a/src/components/common/TmBalance.vue b/src/components/common/TmBalance.vue index cd1f03218e..4e16bbe77c 100644 --- a/src/components/common/TmBalance.vue +++ b/src/components/common/TmBalance.vue @@ -34,39 +34,76 @@
-
-
-

Available {{ stakingDenom }}

-

- {{ - overview.liquidStake | bigFigureOrShortDecimals | noBlanks - }} -

-
- -
-

Total Rewards

-

- +{{ - overview.totalRewards | bigFigureOrShortDecimals | noBlanks - }} -

-
-
-
-

{{ balance.denom }}

-

- {{ balance.amount | bigFigureOrShortDecimals }} -

+
+
+

Available {{ stakingDenom }}

+

+ {{ + overview.liquidStake | bigFigureOrShortDecimals | noBlanks + }} +

+
+

+ +{{ + overview.totalRewards + | bigFigureOrShortDecimals + | noBlanks + }} +

+
+
+ +
+

Total Rewards

+

+ +{{ + overview.totalRewards + | bigFigureOrShortDecimals + | noBlanks + }} +

+
+
+ +
+
+
+

+ {{ balance.denom }} +

+

+ {{ balance.amount | bigFigureOrShortDecimals }} +

+
+
+

+ +{{ + calculateTotalRewardsDenom(balance.denom) + | bigFigureOrShortDecimals + }} +

+
+
@@ -136,6 +173,7 @@ export default { selectedTokenFiatValue: `Tokens Total Fiat Value`, selectedFiatCurrency: `EUR`, // EUR is our default fiat currency showTutorial: false, + rewards: [], cosmosTokensTutorial: { fullguide: `https://lunie.io/guides/how-to-get-tokens/`, background: `red`, @@ -183,19 +221,10 @@ export default { readyToWithdraw() { return this.overview.totalRewards > 0 }, - formattedBalances() { - return this.balances - .filter(balance => !balance.denom.includes(this.stakingDenom)) - .map( - balance => - (balance = { - denom: balance.denom - .charAt(0) - .toLowerCase() - .concat(balance.denom.slice(-3)), - amount: parseFloat(balance.amount).toFixed(2) - }) - ) + filteredMultiDenomBalances() { + return this.balances.filter( + balance => !balance.denom.includes(this.stakingDenom) + ) }, concatBalances() { let balancesArray = [] @@ -233,6 +262,17 @@ export default { } else { return [this.stakingDenom] } + }, + isMultiDenomNetwork() { + if (this.balances && this.balances.length > 0) { + return this.balances.find( + balance => balance.denom !== this.stakingDenom + ) + ? true + : false + } else { + return false + } } }, methods: { @@ -247,6 +287,17 @@ export default { }, hideTutorial() { this.showTutorial = false + }, + calculateTotalRewardsDenom(denom) { + if (this.overview.rewards && this.overview.rewards.length > 0) { + let rewardsAccumulator = 0 + this.overview.rewards + .filter(reward => reward.denom === denom) + .forEach(reward => { + rewardsAccumulator += parseFloat(reward.amount) + }) + return rewardsAccumulator + } } }, apollo: { @@ -257,6 +308,10 @@ export default { totalRewards liquidStake totalStake + rewards { + amount + denom + } } } `, @@ -310,7 +365,8 @@ export default { } return { ...data.overview, - totalRewards: Number(data.overview.totalRewards) + totalRewards: Number(data.overview.totalRewards), + rewards: data.overview.rewards } }, /* istanbul ignore next */ @@ -408,7 +464,6 @@ export default { .values-container { position: relative; - padding: 1rem 2rem; } .values-container h2 { @@ -430,29 +485,9 @@ export default { padding-right: 2.5rem; } -.tokens-div { - position: absolute; - right: 1.25rem; - top: 3.5rem; -} - -.tokens-div > .col { - margin-right: 1rem; -} - -.token-denom { - font-size: 12px; - float: left; -} - -.token-balance { - font-weight: bold; -} - -.currency-selector.tm-form-group { - position: absolute; - right: 1.25rem; - top: -0.7rem; +p.rewards { + color: var(--success); + font-size: var(--s); } .rewards h2 { @@ -460,17 +495,29 @@ export default { font-size: var(--m); } +.rewards.multi-denom h2 { + font-size: 12px; +} + .available-atoms h2 { font-size: var(--m); line-height: 20px; } .upper-header { + padding: 0 2rem; display: flex; align-items: center; justify-content: space-between; } +.lower-header { + padding: 2rem; + align-items: normal; + flex-direction: row; + justify-content: space-between; +} + .button-container { display: flex; align-items: center; @@ -478,6 +525,7 @@ export default { width: 100%; border-bottom: 1px solid var(--bc-dim); border-top: 1px solid var(--bc-dim); + margin-top: 1rem; margin-bottom: 2rem; } @@ -490,8 +538,8 @@ export default { flex-direction: row; } -.small-container { - padding-top: 1rem; +.row div { + white-space: nowrap; } .open-tutorial { @@ -535,85 +583,61 @@ export default { flex-direction: column-reverse; } - .values-container { - flex-direction: column; - width: 100%; - } - .values-container .total-atoms__value { font-size: 28px; font-weight: 500; line-height: 32px; } - .available-atoms, - .rewards { - padding: 0; - } - .total-atoms { padding: 1rem 0; text-align: center; } - .tutorial-button { - margin: 0 auto 1rem auto; - } - - .scroll { - display: flex; - width: 90vw; - overflow-x: auto; - /* Make it smooth scrolling on iOS devices */ - -webkit-overflow-scrolling: touch; - } - - .scroll-item { - width: 100%; - } - - /* This doesn't work */ - /* .scroll > .scroll-item { - flex: 0 0 auto; - } */ - - .scroll > .row > div { - margin-right: 3rem; + .single-denom-rewards { + justify-content: center; + text-align: center; } - .tokens-div { - position: inherit; - margin: 0; - top: 0; + .single-denom-rewards .rewards { + padding-right: 0; } - .token-denom { - float: none; + .single-denom-rewards .available-atoms { + padding-right: 4rem; } - .currency-selector.tm-form-group { - width: 40px; - right: 2.5rem; + .tutorial-button { + margin: 0 auto 1rem auto; } .button-container { width: 100%; padding: 1rem; border-top: 1px solid var(--bc); + margin-top: 0rem; } .button-container button { width: 50%; } - .small-container { - display: flex; - justify-content: space-evenly; - padding: 1rem 0; - text-align: center; - } .tutorial-container { padding-right: 1rem; } } + +@media screen and (max-width: 1023px) { + .scroll { + display: flex; + width: 100vw; + overflow-x: auto; + /* Make it smooth scrolling on iOS devices */ + -webkit-overflow-scrolling: touch; + } + + .scroll-item { + width: 100%; + } +} diff --git a/src/components/staking/LiValidator.vue b/src/components/staking/LiValidator.vue index edd797badf..56d19af4b4 100644 --- a/src/components/staking/LiValidator.vue +++ b/src/components/staking/LiValidator.vue @@ -42,8 +42,18 @@

{{ delegation.amount | bigFigureOrShortDecimals }}

-
- +{{ rewards.amount | bigFigureOrShortDecimals }} +
+ +{{ + filterStakingDenomReward() | bigFigureOrShortDecimals + }}
@@ -85,26 +95,39 @@ export default { type: Object, required: true }, + /* istanbul ignore next */ delegation: { type: Object, default: () => ({}) }, + /* istanbul ignore next */ rewards: { - type: Object, + type: Array, default: () => ({}) }, index: { type: Number, required: true }, + /* istanbul ignore next */ showOnMobile: { type: String, - /* istanbul ignore next */ default: () => "returns" + }, + stakingDenom: { + type: String, + default: "" } }, methods: { - bigFigureOrPercent + bigFigureOrPercent, + bigFigureOrShortDecimals, + filterStakingDenomReward() { + const stakingDenomRewards = this.rewards.filter( + reward => reward.denom === this.stakingDenom + ) + return stakingDenomRewards[0].amount + } } } diff --git a/src/components/staking/PageValidator.vue b/src/components/staking/PageValidator.vue index 835aa21fab..ddcbe69a61 100644 --- a/src/components/staking/PageValidator.vue +++ b/src/components/staking/PageValidator.vue @@ -61,7 +61,7 @@

{{ delegation.amount | fullDecimals }}

- +{{ rewards.amount | fullDecimals | noBlanks }} + +{{ filterStakingDenomReward() | noBlanks }}
@@ -259,6 +259,8 @@ export default { error: false, loaded: false, showTutorial: false, + isMostRelevantRewardSelected: false, + mostRelevantReward: ``, cosmosStakingTutorial: { fullguide: `https://lunie.io/guides/how-cosmos-staking-works/`, background: `blue`, @@ -314,17 +316,21 @@ export default { }, methods: { shortDecimals, + fullDecimals, atoms, percent, fromNow, noBlanks, moment, + /* istanbul ignore next */ onDelegation() { this.$refs.delegationModal.open() }, + /* istanbul ignore next */ onUndelegation() { this.$refs.undelegationModal.open() }, + /* istanbul ignore next */ isBlankField(field, alternateFilter) { return field ? alternateFilter(field) : noBlanks(field) }, @@ -336,6 +342,14 @@ export default { }, handleIntercom() { this.$store.dispatch(`displayMessenger`) + }, + filterStakingDenomReward() { + if (this.rewards && this.rewards.length > 0) { + const stakingDenomRewards = this.rewards.filter( + reward => reward.denom === this.stakingDenom + ) + return stakingDenomRewards[0].amount + } } }, apollo: { @@ -355,18 +369,19 @@ export default { } } `, + /* istanbul ignore next */ skip() { - /* istanbul ignore next */ return !this.userAddress }, + /* istanbul ignore next */ variables() { - /* istanbul ignore next */ return { networkId: this.network, delegatorAddress: this.userAddress, operatorAddress: this.$route.params.validator } }, + /* istanbul ignore next */ update(result) { if (!result.delegation) { return { @@ -393,57 +408,79 @@ export default { operatorAddress: $operatorAddress ) { amount + denom } } `, + /* istanbul ignore next */ skip() { - /* istanbul ignore next */ return !this.userAddress }, + /* istanbul ignore next */ variables() { - /* istanbul ignore next */ return { networkId: this.network, delegatorAddress: this.userAddress, operatorAddress: this.$route.params.validator } }, + /* istanbul ignore next */ update(result) { - /* istanbul ignore next */ return result.rewards && result.rewards.length > 0 - ? result.rewards[0] + ? result.rewards : { amount: 0 } } }, validator: { query: ValidatorProfile, + /* istanbul ignore next */ variables() { - /* istanbul ignore next */ return { networkId: this.network, operatorAddress: this.$route.params.validator } }, + /* istanbul ignore next */ update(result) { if (!result.validator) return {} - /* istanbul ignore next */ this.loaded = true - /* istanbul ignore next */ return { ...result.validator, statusDetailed: getStatusText(result.validator.statusDetailed) } } }, + stakingDenom: { + query: gql` + query Network($networkId: String!) { + network(id: $networkId) { + id + stakingDenom + } + } + `, + /* istanbul ignore next */ + variables() { + return { + networkId: this.network + } + }, + /* istanbul ignore next */ + update(data) { + if (!data.network) return "" + return data.network.stakingDenom + } + }, $subscribe: { blockAdded: { + /* istanbul ignore next */ variables() { - /* istanbul ignore next */ return { networkId: this.network } }, + /* istanbul ignore next */ query() { return gql` subscription($networkId: String!) { @@ -454,26 +491,26 @@ export default { } ` }, + /* istanbul ignore next */ result() { - /* istanbul ignore next */ this.$apollo.queries.rewards.refetch() } }, userTransactionAdded: { + /* istanbul ignore next */ variables() { - /* istanbul ignore next */ return { networkId: this.network, address: this.userAddress } }, + /* istanbul ignore next */ skip() { - /* istanbul ignore next */ return !this.userAddress }, query: UserTransactionAdded, + /* istanbul ignore next */ result({ data }) { - /* istanbul ignore next */ if (data.userTransactionAdded.success) { this.$apollo.queries.delegation.refetch() } @@ -620,10 +657,12 @@ span { border-color: var(--success); } -.validator-status-detailed { +.validator-status-detailed, +.no-img-info { display: block; - margin-top: 0.4rem; + margin-top: 1rem; font-size: 0.8rem; + color: var(--dim); } @media screen and (max-width: 425px) { diff --git a/src/components/staking/TableValidators.vue b/src/components/staking/TableValidators.vue index abb36d3895..8ae7d8ed46 100644 --- a/src/components/staking/TableValidators.vue +++ b/src/components/staking/TableValidators.vue @@ -21,6 +21,7 @@ :delegation="getDelegation(validator)" :rewards="getRewards(validator)" :show-on-mobile="showOnMobile" + :staking-denom="stakingDenom" /> @@ -60,7 +61,8 @@ export default { property: `expectedReturns`, order: `desc` }, - showing: 15 + showing: 15, + stakingDenom: "" }), computed: { ...mapGetters([`address`, `network`]), @@ -123,9 +125,15 @@ export default { ) }, getRewards({ operatorAddress }) { - return this.rewards.find( - ({ validator }) => validator.operatorAddress === operatorAddress - ) + if (this.rewards) { + return ( + this.rewards + /* istanbul ignore next */ + .filter( + ({ validator }) => validator.operatorAddress === operatorAddress + ) + ) + } } }, apollo: { @@ -137,29 +145,56 @@ export default { operatorAddress } amount + denom } } `, + /* istanbul ignore next */ skip() { return !this.address }, + /* istanbul ignore next */ variables() { return { networkId: this.network, delegatorAddress: this.address } }, + /* istanbul ignore next */ update: result => { return result.rewards || [] } }, + stakingDenom: { + query: gql` + query Network($networkId: String!) { + network(id: $networkId) { + id + stakingDenom + } + } + `, + /* istanbul ignore next */ + variables() { + return { + networkId: this.network + } + }, + /* istanbul ignore next */ + update(data) { + if (!data.network) return "" + return data.network.stakingDenom + } + }, $subscribe: { blockAdded: { + /* istanbul ignore next */ variables() { return { networkId: this.network } }, + /* istanbul ignore next */ query() { return gql` subscription($networkId: String!) { @@ -170,8 +205,8 @@ export default { } ` }, + /* istanbul ignore next */ result() { - /* istanbul ignore next */ this.$apollo.queries.rewards.refetch() } } diff --git a/src/styles/table.css b/src/styles/table.css index 96e06579c3..569ab20276 100644 --- a/src/styles/table.css +++ b/src/styles/table.css @@ -137,10 +137,6 @@ padding: 0; } - .data-table__row__info { - max-width: calc(100vw - 6rem); - } - .data-table th:nth-child(4) { width: 20%; } diff --git a/tests/unit/specs/components/common/TmBalance.spec.js b/tests/unit/specs/components/common/TmBalance.spec.js index 489980c07c..d3ea696e86 100644 --- a/tests/unit/specs/components/common/TmBalance.spec.js +++ b/tests/unit/specs/components/common/TmBalance.spec.js @@ -40,7 +40,21 @@ describe(`TmBalance`, () => { overview: { totalStake: 3210, liquidStake: 1230, - totalRewards: 1000.45 + totalRewards: 1000.45, + rewards: [ + { + amount: 1, + denom: `TOKEN1` + }, + { + amount: 2, + denom: `TOKEN1` + }, + { + amount: 1.5, + denom: `TOKEN1` + } + ] } }) }) @@ -154,6 +168,22 @@ describe(`TmBalance`, () => { ]) }) + it(`should return true if rewards contain multiple denoms`, () => { + wrapper.setData({ + balances: [ + { + amount: 1, + denom: `TOKEN1` + }, + { + amount: 2, + denom: `utoken2` + } + ] + }) + expect(wrapper.vm.isMultiDenomNetwork).toBe(true) + }) + it(`should show How To Get Tokens tutorial`, () => { wrapper.setData({ showTutorial: false @@ -169,4 +199,9 @@ describe(`TmBalance`, () => { wrapper.vm.hideTutorial() expect(wrapper.vm.showTutorial).toBe(false) }) + + it(`should calculate the total rewards amount for each denom when rewards contain multiple denoms`, () => { + const totalDenomRewards = wrapper.vm.calculateTotalRewardsDenom(`TOKEN1`) + expect(totalDenomRewards).toBe(4.5) + }) }) diff --git a/tests/unit/specs/components/common/__snapshots__/TmBalance.spec.js.snap b/tests/unit/specs/components/common/__snapshots__/TmBalance.spec.js.snap index 860e7952c6..966f5cf694 100644 --- a/tests/unit/specs/components/common/__snapshots__/TmBalance.spec.js.snap +++ b/tests/unit/specs/components/common/__snapshots__/TmBalance.spec.js.snap @@ -41,14 +41,18 @@ exports[`TmBalance do not show available atoms when the user has none in the fir class="scroll" >
- +
+ + + +
- - @@ -122,38 +126,48 @@ exports[`TmBalance show the balance header when signed in 1`] = ` class="scroll" >
-

- Available ATOM -

- -

+
+

+ Available ATOM +

+ +

+ + 1,230 - 1,230 - -

-
- -
-

- Total Rewards -

+

+ +
+ +
+
-

+
+

+ Total Rewards +

+ +

+ + +1,000.45 - +1,000.45 - -

+

+
+ + - - diff --git a/tests/unit/specs/components/staking/LiValidator.spec.js b/tests/unit/specs/components/staking/LiValidator.spec.js index 192e756429..b2e6681cc9 100644 --- a/tests/unit/specs/components/staking/LiValidator.spec.js +++ b/tests/unit/specs/components/staking/LiValidator.spec.js @@ -1,4 +1,5 @@ import { shallowMount, createLocalVue } from "@vue/test-utils" +import { toMicroDenom } from "scripts/common" import VueApollo from "vue-apollo" import LiValidator from "src/components/staking/LiValidator" @@ -65,6 +66,12 @@ describe(`LiValidator`, () => { localVue, propsData: { validator, + rewards: [ + { + amount: 1, + denom: `token1` + } + ], index, showOnMobile: "returns" }, @@ -92,4 +99,30 @@ describe(`LiValidator`, () => { }) expect(wrapper.find("avatar-stub").exists()) }) + + it(`should filter the staking denom reward`, () => { + const rewards = [ + { + amount: 1, + denom: `TOKEN1` + }, + { + amount: 2, + denom: `TOKEN2` + }, + { + amount: 3, + denom: `TOKEN3` + } + ] + const self = { + stakingDenom: `TOKEN1`, + rewards: rewards, + toMicroDenom + } + const stakingDenomReward = LiValidator.methods.filterStakingDenomReward.call( + self + ) + expect(stakingDenomReward).toEqual(1) + }) }) diff --git a/tests/unit/specs/components/staking/PageValidator.spec.js b/tests/unit/specs/components/staking/PageValidator.spec.js index 7474614070..bed94d61d7 100644 --- a/tests/unit/specs/components/staking/PageValidator.spec.js +++ b/tests/unit/specs/components/staking/PageValidator.spec.js @@ -1,4 +1,5 @@ import PageValidator from "staking/PageValidator" +import { toMicroDenom } from "scripts/common" import { shallowMount, createLocalVue } from "@vue/test-utils" const validator = { @@ -124,6 +125,31 @@ describe(`PageValidator`, () => { PageValidator.methods.handleIntercom.call(self) expect(self.$store.dispatch).toHaveBeenCalledWith("displayMessenger") }) + it(`should filter the staking denom reward`, () => { + const rewards = [ + { + amount: 1, + denom: `TOKEN1` + }, + { + amount: 2, + denom: `TOKEN2` + }, + { + amount: 3, + denom: `TOKEN3` + } + ] + const self = { + stakingDenom: `TOKEN1`, + rewards: rewards, + toMicroDenom + } + const stakingDenomReward = PageValidator.methods.filterStakingDenomReward.call( + self + ) + expect(stakingDenomReward).toEqual(1) + }) }) describe(`isBlankField method`, () => { diff --git a/tests/unit/specs/components/staking/TableValidators.spec.js b/tests/unit/specs/components/staking/TableValidators.spec.js index 1a20ed4b46..7f94c0f309 100644 --- a/tests/unit/specs/components/staking/TableValidators.spec.js +++ b/tests/unit/specs/components/staking/TableValidators.spec.js @@ -48,10 +48,6 @@ describe(`TableValidators`, () => { expect(wrapper.element).toMatchSnapshot() }) - it(`should add smallName property to validators`, () => { - expect(wrapper.vm.showingValidators[0].smallName).toBe(`cosmos1a`) - }) - it(`should sort the delegates by selected property`, () => { wrapper.vm.sort.property = `operatorAddress` wrapper.vm.sort.order = `desc` diff --git a/tests/unit/specs/components/staking/__snapshots__/TableValidators.spec.js.snap b/tests/unit/specs/components/staking/__snapshots__/TableValidators.spec.js.snap index e8a4989afb..7a5d89bdf4 100644 --- a/tests/unit/specs/components/staking/__snapshots__/TableValidators.spec.js.snap +++ b/tests/unit/specs/components/staking/__snapshots__/TableValidators.spec.js.snap @@ -20,106 +20,121 @@ exports[`TableValidators shows a validator table 1`] = `