diff --git a/CHANGELOG.md b/CHANGELOG.md index 2734ab54d1..2111f3ac95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,12 +5,14 @@ Changelog ### Chores +- Updated header section styles of delegation rewards screen ([PR 2509](https://github.com/input-output-hk/daedalus/pull/2509)) - Reduced the clock drift tolerance to 4,5 seconds ([PR 2510](https://github.com/input-output-hk/daedalus/pull/2510)) - Updates Catalyst Fund4 dates ([PR 2495](https://github.com/input-output-hk/daedalus/pull/2495)) - Updated `cardano-wallet` to revision `7df30796` ([PR 2495](https://github.com/input-output-hk/daedalus/pull/2495)) ### Fixes +- Fixed fee calculation edge cases in wallet send form ([PR 2501](https://github.com/input-output-hk/daedalus/pull/2501)) - Handle empty strings in transaction metadata correctly ([PR 2503](https://github.com/input-output-hk/daedalus/pull/2503)) ## 4.0.4 diff --git a/source/renderer/app/components/staking/layouts/StakingWithNavigation.js b/source/renderer/app/components/staking/layouts/StakingWithNavigation.js index 96a6625005..63af7658bd 100644 --- a/source/renderer/app/components/staking/layouts/StakingWithNavigation.js +++ b/source/renderer/app/components/staking/layouts/StakingWithNavigation.js @@ -1,6 +1,7 @@ // @flow import React, { Component, createRef } from 'react'; import type { ElementRef, Node } from 'react'; +import classnames from 'classnames'; import { observer } from 'mobx-react'; import StakingNavigation from '../navigation/StakingNavigation'; import styles from './StakingWithNavigation.scss'; @@ -13,35 +14,18 @@ type Props = { isIncentivizedTestnet: boolean, }; -type State = { - scrollTop: number, -}; - type ContextValue = { - scrollTop: number, scrollElementRef: ?ElementRef<*>, }; export const StakingPageScrollContext = React.createContext({ - scrollTop: 0, scrollElementRef: null, }); @observer -export default class StakingWithNavigation extends Component { +export default class StakingWithNavigation extends Component { stakingPageRef = createRef<*>(); - constructor(props: Props) { - super(props); - this.state = { - scrollTop: 0, - }; - } - - handleScroll = (evt: SyntheticEvent) => { - this.setState({ scrollTop: evt.currentTarget.scrollTop }); - }; - render() { const { children, @@ -50,13 +34,13 @@ export default class StakingWithNavigation extends Component { isActiveNavItem, isIncentivizedTestnet, } = this.props; - const { scrollTop } = this.state; + const componentStyles = classnames([styles.component, styles[activeItem]]); return ( -
+
{
{ this.stakingPageRef.current = ref; }} diff --git a/source/renderer/app/components/staking/layouts/StakingWithNavigation.scss b/source/renderer/app/components/staking/layouts/StakingWithNavigation.scss index 60472a7a59..ef49105794 100644 --- a/source/renderer/app/components/staking/layouts/StakingWithNavigation.scss +++ b/source/renderer/app/components/staking/layouts/StakingWithNavigation.scss @@ -2,6 +2,12 @@ display: flex; flex-direction: column; height: 100%; + + &.rewards { + .page { + overflow-y: hidden; + } + } } .navigation { diff --git a/source/renderer/app/components/staking/rewards/StakingRewards.js b/source/renderer/app/components/staking/rewards/StakingRewards.js index c8f18f686f..d1b9afb749 100644 --- a/source/renderer/app/components/staking/rewards/StakingRewards.js +++ b/source/renderer/app/components/staking/rewards/StakingRewards.js @@ -94,6 +94,7 @@ type Props = { type State = { rewardsOrder: string, rewardsSortBy: string, + contentScrollTop: number, }; @observer @@ -111,6 +112,7 @@ export default class StakingRewards extends Component { this.state = { rewardsOrder: REWARD_ORDERS.DESCENDING, rewardsSortBy: REWARD_FIELDS.DATE, + contentScrollTop: 0, }; } @@ -167,9 +169,13 @@ export default class StakingRewards extends Component { }); }; + handleContentScroll = (evt: SyntheticEvent) => { + this.setState({ contentScrollTop: evt.currentTarget.scrollTop }); + }; + render() { const { rewards, isLoading, onLearnMoreClick } = this.props; - const { rewardsOrder, rewardsSortBy } = this.state; + const { rewardsOrder, rewardsSortBy, contentScrollTop } = this.state; const { intl } = this.context; const noRewards = !isLoading && ((rewards && !rewards.length) || !rewards); const showRewards = rewards && rewards.length > 0 && !isLoading; @@ -192,10 +198,14 @@ export default class StakingRewards extends Component { title: intl.formatMessage(messages.tableHeaderReward), }, ]; + const headerWrapperClasses = classNames([ + styles.headerWrapper, + contentScrollTop > 10 ? styles.headerWrapperWithShadow : null, + ]); return (
-
+
{intl.formatMessage(messages.title)}
@@ -205,101 +215,109 @@ export default class StakingRewards extends Component {
)}
+
+ + {noRewards && ( +
+ {intl.formatMessage(messages.noRewards)} +
+ )} - - {noRewards && ( -
- {intl.formatMessage(messages.noRewards)} -
- )} + {sortedRewards && ( + + + + {map(availableTableHeaders, (tableHeader) => { + const isSorted = tableHeader.name === rewardsSortBy; + const sortIconClasses = classNames([ + styles.sortIcon, + isSorted ? styles.sorted : null, + isSorted && rewardsOrder === 'asc' + ? styles.ascending + : null, + ]); - {sortedRewards && ( -
- - - {map(availableTableHeaders, (tableHeader) => { - const isSorted = tableHeader.name === rewardsSortBy; - const sortIconClasses = classNames([ - styles.sortIcon, - isSorted ? styles.sorted : null, - isSorted && rewardsOrder === 'asc' - ? styles.ascending - : null, - ]); + return ( + + ); + })} + + + + {map(sortedRewards, (reward, key) => { + const rewardDate = get(reward, REWARD_FIELDS.DATE, ''); + const rewardPoolTicker = get( + reward, + [REWARD_FIELDS.POOL, REWARD_FIELDS.TICKER], + '' + ); + const rewardPoolName = get( + reward, + [REWARD_FIELDS.POOL, REWARD_FIELDS.NAME], + '' + ); + const rewardWallet = get( + reward, + REWARD_FIELDS.WALLET_NAME, + '' + ); + const isRestoring = get(reward, REWARD_FIELDS.IS_RESTORING); + const rewardAmount = get( + reward, + REWARD_FIELDS.REWARD + ).toFormat(DECIMAL_PLACES_IN_ADA); return ( - + + + + + + ); })} - - - - {map(sortedRewards, (reward, key) => { - const rewardDate = get(reward, REWARD_FIELDS.DATE, ''); - const rewardPoolTicker = get( - reward, - [REWARD_FIELDS.POOL, REWARD_FIELDS.TICKER], - '' - ); - const rewardPoolName = get( - reward, - [REWARD_FIELDS.POOL, REWARD_FIELDS.NAME], - '' - ); - const rewardWallet = get( - reward, - REWARD_FIELDS.WALLET_NAME, - '' - ); - const isRestoring = get(reward, REWARD_FIELDS.IS_RESTORING); - const rewardAmount = get( - reward, - REWARD_FIELDS.REWARD - ).toFormat(DECIMAL_PLACES_IN_ADA); - - return ( - - - - - - - ); - })} - -
+ this.handleRewardsSort(tableHeader.name) + } + > + {tableHeader.title} + +
this.handleRewardsSort(tableHeader.name)} - > - {tableHeader.title} - -
{rewardDate} +

+ + [{rewardPoolTicker}] + {' '} + {rewardPoolName} +

+
{rewardWallet}{isRestoring ? '-' : `${rewardAmount} ADA`}
{rewardDate} -

- - [{rewardPoolTicker}] - {' '} - {rewardPoolName} -

-
{rewardWallet}{isRestoring ? '-' : `${rewardAmount} ADA`}
- )} + + + )} - {isLoading && ( -
- + {isLoading && ( +
+ +
+ )} + +
+
*
+
+ +
- )} - - -
-
*
-
- -
diff --git a/source/renderer/app/components/staking/rewards/StakingRewards.scss b/source/renderer/app/components/staking/rewards/StakingRewards.scss index 33672fb041..467f622a6f 100644 --- a/source/renderer/app/components/staking/rewards/StakingRewards.scss +++ b/source/renderer/app/components/staking/rewards/StakingRewards.scss @@ -1,11 +1,18 @@ @import '../../../themes/mixins/link'; + .component { - padding: 20px 0 20px 20px; + height: 100%; + padding: 0 0 20px 20px; .headerWrapper { + align-items: center; display: flex; + justify-content: space-between; line-height: 1.38; - margin: 0 20px 10px; + margin: 0 0 0 -20px; + padding: 10px 40px; + position: relative; + transition: box-shadow 0.25s ease-out; & > .title { color: var(--theme-staking-font-color-lighter); @@ -25,6 +32,16 @@ } } + .headerWrapperWithShadow { + box-shadow: 0 2.5px 10px 0 rgba(0, 0, 0, 0.25); + } + + .contentWrapper { + height: calc(100% - 53px); + overflow-x: hidden; + overflow-y: scroll; + } + .noRewardsLabel { color: var(--theme-staking-font-color-lighter); flex-grow: 1; diff --git a/source/renderer/app/components/staking/rewards/StakingRewardsForIncentivizedTestnet.js b/source/renderer/app/components/staking/rewards/StakingRewardsForIncentivizedTestnet.js index f1a2efcaf5..cc5d490cd6 100644 --- a/source/renderer/app/components/staking/rewards/StakingRewardsForIncentivizedTestnet.js +++ b/source/renderer/app/components/staking/rewards/StakingRewardsForIncentivizedTestnet.js @@ -10,7 +10,6 @@ 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 { StakingPageScrollContext } from '../layouts/StakingWithNavigation'; import { bigNumberComparator, stringComparator, @@ -114,6 +113,7 @@ type Props = { type State = { rewardsOrder: string, rewardsSortBy: string, + contentScrollTop: number, }; @observer @@ -135,6 +135,7 @@ export default class StakingRewardsForIncentivizedTestnet extends Component< this.state = { rewardsOrder: REWARD_ORDERS.DESCENDING, rewardsSortBy: REWARD_FIELDS.WALLET_NAME, + contentScrollTop: 0, }; } @@ -231,6 +232,10 @@ export default class StakingRewardsForIncentivizedTestnet extends Component< ); }; + handleContentScroll = (evt: SyntheticEvent) => { + this.setState({ contentScrollTop: evt.currentTarget.scrollTop }); + }; + render() { const { rewards, @@ -239,7 +244,7 @@ export default class StakingRewardsForIncentivizedTestnet extends Component< onCopyAddress, onOpenExternalLink, } = this.props; - const { rewardsOrder, rewardsSortBy } = this.state; + const { rewardsOrder, rewardsSortBy, contentScrollTop } = this.state; const { intl } = this.context; const noRewards = !isLoading && ((rewards && !rewards.length) || !rewards); const showRewards = rewards && rewards.length > 0 && !isLoading; @@ -272,181 +277,171 @@ export default class StakingRewardsForIncentivizedTestnet extends Component< ); - const exportCsvButtonClasses = (ctx) => - classNames([ - 'flat', - styles.actionButton, - ctx.scrollTop > 10 ? styles.actionButtonFaded : null, - ]); - + const exportCsvButtonClasses = classNames(['flat', styles.actionButton]); const explorerButtonClasses = classNames([ 'flat', styles.actionExplorerLink, ]); + const headerWrapperClasses = classNames([ + styles.headerWrapper, + contentScrollTop > 10 ? styles.headerWrapperWithShadow : null, + ]); return ( - - {(context) => ( -
-
-
- {intl.formatMessage(messages.title)} +
+
+
+ {intl.formatMessage(messages.title)} +
+ {!noRewards && ( +
+
+ + {noRewards && ( +
+ {intl.formatMessage(messages.noRewards)}
- {!noRewards && ( -
- - {noRewards && ( -
- {intl.formatMessage(messages.noRewards)} -
- )} + )} - {sortedRewards.length > 0 && ( - - - - {map(availableTableHeaders, (tableHeader) => { - const isSorted = tableHeader.name === rewardsSortBy; - const sortIconClasses = classNames([ - styles.sortIcon, - isSorted ? styles.sorted : null, - isSorted && rewardsOrder === 'asc' - ? styles.ascending - : null, - ]); - - return ( - - ); - })} - - - - {map(sortedRewards, (reward, key) => { - const rewardWallet = get( - reward, - REWARD_FIELDS.WALLET_NAME - ); - const isRestoring = get( - reward, - REWARD_FIELDS.IS_RESTORING - ); - const syncingProgress = get( - reward, - REWARD_FIELDS.SYNCING_PROGRESS - ); - const rewardAmount = get( - reward, - REWARD_FIELDS.REWARD - ).toFormat(DECIMAL_PLACES_IN_ADA); - const rewardsAddress = get( - reward, - REWARD_FIELDS.REWARDS_ADDRESS - ); + {sortedRewards.length > 0 && ( +
- this.handleRewardsSort(tableHeader.name) - } - > - {tableHeader.title} - -
+ + + {map(availableTableHeaders, (tableHeader) => { + const isSorted = tableHeader.name === rewardsSortBy; + const sortIconClasses = classNames([ + styles.sortIcon, + isSorted ? styles.sorted : null, + isSorted && rewardsOrder === 'asc' + ? styles.ascending + : null, + ]); return ( - - - - - + ); })} - -
- {rewardWallet} - - {rewardsAddress && ( -
- onCopyAddress(rewardsAddress)} - > -
- - {rewardsAddress} - - - - -
-
- {IS_EXPLORER_LINK_BUTTON_ENABLED && ( - - onOpenExternalLink(rewardsAddress) - } - skin={ButtonSkin} - label={intl.formatMessage( - messages.actionViewInExplorer - )} - linkProps={{ - className: styles.externalLink, - hasIconBefore: false, - hasIconAfter: true, - }} - /> - )} -
- )} -
- {isRestoring ? '-' : rewardAmount} - {isRestoring && ( -
- - - -
- )} -
+ this.handleRewardsSort(tableHeader.name) + } + > + {tableHeader.title} + +
- )} + + + + {map(sortedRewards, (reward, key) => { + const rewardWallet = get(reward, REWARD_FIELDS.WALLET_NAME); + const isRestoring = get(reward, REWARD_FIELDS.IS_RESTORING); + const syncingProgress = get( + reward, + REWARD_FIELDS.SYNCING_PROGRESS + ); + const rewardAmount = get( + reward, + REWARD_FIELDS.REWARD + ).toFormat(DECIMAL_PLACES_IN_ADA); + const rewardsAddress = get( + reward, + REWARD_FIELDS.REWARDS_ADDRESS + ); - {isLoading && ( -
- -
- )} -
+ return ( + + {rewardWallet} + + {rewardsAddress && ( +
+ onCopyAddress(rewardsAddress)} + > +
+ + {rewardsAddress} + + + + +
+
+ {IS_EXPLORER_LINK_BUTTON_ENABLED && ( + + onOpenExternalLink(rewardsAddress) + } + skin={ButtonSkin} + label={intl.formatMessage( + messages.actionViewInExplorer + )} + linkProps={{ + className: styles.externalLink, + hasIconBefore: false, + hasIconAfter: true, + }} + /> + )} +
+ )} + + + {isRestoring ? '-' : rewardAmount} + {isRestoring && ( +
+ + + +
+ )} + + + ); + })} + + + )} -
-
- + {isLoading && ( +
+
+ )} + +
+
+
- )} - +
+
); } diff --git a/source/renderer/app/components/staking/rewards/StakingRewardsForIncentivizedTestnet.scss b/source/renderer/app/components/staking/rewards/StakingRewardsForIncentivizedTestnet.scss index e5edec080c..09e8a9a981 100644 --- a/source/renderer/app/components/staking/rewards/StakingRewardsForIncentivizedTestnet.scss +++ b/source/renderer/app/components/staking/rewards/StakingRewardsForIncentivizedTestnet.scss @@ -2,13 +2,18 @@ @import '../../../themes/mixins/loading-spinner'; .component { - padding: 20px 0 20px 20px; + height: 100%; + padding: 0 0 20px 20px; .headerWrapper { - align-items: flex-end; + align-items: center; display: flex; + justify-content: space-between; line-height: 1.38; - margin: 0 20px 10px; + margin: 0 0 0 -20px; + padding: 10px 40px; + position: relative; + transition: box-shadow 0.25s ease-out; & > .title { color: var(--theme-staking-font-color-lighter); @@ -22,19 +27,8 @@ display: flex; height: auto; padding: 7px 12px; - position: fixed; - right: 20px; - top: 144px; width: auto; - &.actionButtonFaded { - opacity: 0.5; - - &:hover { - opacity: 1; - } - } - .actionLabel { line-height: 1.36; margin-right: 12px; @@ -52,6 +46,21 @@ } } + .headerWrapperWithShadow { + box-shadow: 0 2.5px 10px 0 rgba(0, 0, 0, 0.25); + } + + .contentWrapper { + height: calc(100% - 53px); + overflow-x: hidden; + overflow-y: scroll; + + &::-webkit-scrollbar-track { + margin-bottom: -20px; + margin-top: -20px; + } + } + .noRewardsLabel { color: var(--theme-staking-font-color-lighter); flex-grow: 1; diff --git a/source/renderer/app/components/wallet/WalletSendForm.js b/source/renderer/app/components/wallet/WalletSendForm.js index 6964fd5b90..87a65d51bf 100755 --- a/source/renderer/app/components/wallet/WalletSendForm.js +++ b/source/renderer/app/components/wallet/WalletSendForm.js @@ -410,7 +410,10 @@ export default class WalletSendForm extends Component { calculateTransactionFee = async () => { const { form } = this; - const hasEmptyAssetFields = this.selectedAssetsAmounts.includes('0'); + const emptyAssetFieldValue = '0'; + const hasEmptyAssetFields = this.selectedAssetsAmounts.includes( + emptyAssetFieldValue + ); if (!form.isValid || hasEmptyAssetFields) { form.showErrors(true); return false; @@ -454,7 +457,8 @@ export default class WalletSendForm extends Component { this.isLatestTransactionFeeRequest( this.state.feeCalculationRequestQue, prevFeeCalculationRequestQue - ) + ) && + !this.selectedAssetsAmounts.includes(emptyAssetFieldValue) ) { this._isCalculatingTransactionFee = false; this.setState({ diff --git a/source/renderer/app/i18n/locales/defaultMessages.json b/source/renderer/app/i18n/locales/defaultMessages.json index 25147eb790..e378773073 100644 --- a/source/renderer/app/i18n/locales/defaultMessages.json +++ b/source/renderer/app/i18n/locales/defaultMessages.json @@ -5558,13 +5558,13 @@ "description": "Title \"Earned delegation rewards\" label on the staking rewards page.", "end": { "column": 3, - "line": 34 + "line": 33 }, "file": "source/renderer/app/components/staking/rewards/StakingRewardsForIncentivizedTestnet.js", "id": "staking.rewards.title", "start": { "column": 9, - "line": 29 + "line": 28 } }, { @@ -5572,13 +5572,13 @@ "description": "Filename prefix for the \"Export CSV\" on the staking rewards page.", "end": { "column": 3, - "line": 40 + "line": 39 }, "file": "source/renderer/app/components/staking/rewards/StakingRewardsForIncentivizedTestnet.js", "id": "staking.rewards.csvFilenamePrefix", "start": { "column": 21, - "line": 35 + "line": 34 } }, { @@ -5586,13 +5586,13 @@ "description": "Label for the \"Export CSV\" button on the staking rewards page.", "end": { "column": 3, - "line": 46 + "line": 45 }, "file": "source/renderer/app/components/staking/rewards/StakingRewardsForIncentivizedTestnet.js", "id": "staking.rewards.exportButtonLabel", "start": { "column": 21, - "line": 41 + "line": 40 } }, { @@ -5600,13 +5600,13 @@ "description": "\"No rewards\" rewards label on staking rewards page.", "end": { "column": 3, - "line": 51 + "line": 50 }, "file": "source/renderer/app/components/staking/rewards/StakingRewardsForIncentivizedTestnet.js", "id": "staking.rewards.no.rewards", "start": { "column": 13, - "line": 47 + "line": 46 } }, { @@ -5614,13 +5614,13 @@ "description": "Table header \"Wallet\" label on staking rewards page", "end": { "column": 3, - "line": 56 + "line": 55 }, "file": "source/renderer/app/components/staking/rewards/StakingRewardsForIncentivizedTestnet.js", "id": "staking.rewards.tableHeader.wallet", "start": { "column": 21, - "line": 52 + "line": 51 } }, { @@ -5628,13 +5628,13 @@ "description": "Table header \"Reward\" label on staking rewards page", "end": { "column": 3, - "line": 61 + "line": 60 }, "file": "source/renderer/app/components/staking/rewards/StakingRewardsForIncentivizedTestnet.js", "id": "staking.rewards.tableHeader.reward", "start": { "column": 21, - "line": 57 + "line": 56 } }, { @@ -5642,13 +5642,13 @@ "description": "Table header \"Rewards address\" label on staking rewards page", "end": { "column": 3, - "line": 66 + "line": 65 }, "file": "source/renderer/app/components/staking/rewards/StakingRewardsForIncentivizedTestnet.js", "id": "staking.rewards.tableHeader.rewardsAddress", "start": { "column": 29, - "line": 62 + "line": 61 } }, { @@ -5656,13 +5656,13 @@ "description": "Table header \"Date\" label in exported csv file", "end": { "column": 3, - "line": 71 + "line": 70 }, "file": "source/renderer/app/components/staking/rewards/StakingRewardsForIncentivizedTestnet.js", "id": "staking.rewards.tableHeader.date", "start": { "column": 19, - "line": 67 + "line": 66 } }, { @@ -5670,13 +5670,13 @@ "description": "Rewards description text on staking rewards page", "end": { "column": 3, - "line": 77 + "line": 76 }, "file": "source/renderer/app/components/staking/rewards/StakingRewardsForIncentivizedTestnet.js", "id": "staking.rewards.note", "start": { "column": 8, - "line": 72 + "line": 71 } }, { @@ -5684,13 +5684,13 @@ "description": "unknown stake pool label on staking rewards page.", "end": { "column": 3, - "line": 82 + "line": 81 }, "file": "source/renderer/app/components/staking/rewards/StakingRewardsForIncentivizedTestnet.js", "id": "staking.delegationCenter.syncingTooltipLabel", "start": { "column": 23, - "line": 78 + "line": 77 } }, { @@ -5698,13 +5698,13 @@ "description": "View in explorer button label on staking rewards page.", "end": { "column": 3, - "line": 87 + "line": 86 }, "file": "source/renderer/app/components/staking/rewards/StakingRewardsForIncentivizedTestnet.js", "id": "staking.rewards.actionViewInExplorer", "start": { "column": 24, - "line": 83 + "line": 82 } } ], diff --git a/source/renderer/app/utils/formatters.js b/source/renderer/app/utils/formatters.js index 556c6dcad9..22b68ee92a 100644 --- a/source/renderer/app/utils/formatters.js +++ b/source/renderer/app/utils/formatters.js @@ -95,7 +95,11 @@ export const shortNumber = (value: number | BigNumber): string => { return formattedAmount; }; -export const formattedAmountToNaturalUnits = (amount: string): string => { +export const formattedAmountToNaturalUnits = (amount: ?string): string => { + if (!amount) { + return '0'; + } + const cleanedAmount = amount .replace(/\./g, '') // removes all the dot separators .replace(/,/g, '') // removes all the comma separators