diff --git a/i18n/locales/en/common.json b/i18n/locales/en/common.json index b73567e62f..d829655d17 100644 --- a/i18n/locales/en/common.json +++ b/i18n/locales/en/common.json @@ -16,6 +16,7 @@ "Account Balance": "Account Balance", "Account Info": "Account Info", "Account Name": "Account Name", + "Account bookmarked": "Account bookmarked", "Account initialization": "Account initialization", "Accounts on Ledger": "Accounts on Ledger", "Action Denied by User": "Action Denied by User", @@ -40,7 +41,6 @@ "Amount": "Amount", "Amount (LSK)": "Amount (LSK)", "Amount of LSK earned by a delegate from forging blocks.": "Amount of LSK earned by a delegate from forging blocks.", - "Amount of transaction": "Amount of transaction", "Amount transfered": "Amount transfered", "An error occoured while rendering this page": "An error occoured while rendering this page", "An error occurred while creating the transaction.": "An error occurred while creating the transaction.", @@ -61,6 +61,7 @@ "Blockchain Application Registration": "Blockchain Application Registration", "Blocks": "Blocks", "Blocks forged": "Blocks forged", + "Bookmark account": "Bookmark account", "Bookmarks": "Bookmarks", "Browse through our tutorials, check out the FAQ or connect with our knowledgeable community.": "Browse through our tutorials, check out the FAQ or connect with our knowledgeable community.", "By voting you decide who is trusted to verify transactions and maintain the Lisk network, whilst collecting the rewards for doing so.": "By voting you decide who is trusted to verify transactions and maintain the Lisk network, whilst collecting the rewards for doing so.", @@ -83,6 +84,7 @@ "Confirm": "Confirm", "Confirm (Fee: 1 LSK)": "Confirm (Fee: 1 LSK)", "Confirm (Fee: {{fee}} LSK)": "Confirm (Fee: {{fee}} LSK)", + "Confirm on Ledger": "Confirm on Ledger", "Confirm to register your second passphrase on the blockchain.": "Confirm to register your second passphrase on the blockchain.", "Confirm transaction on Ledger Nano S": "Confirm transaction on Ledger Nano S", "Confirm transfer": "Confirm transfer", @@ -157,7 +159,7 @@ "Entered passphrase does not belong to the active account": "Entered passphrase does not belong to the active account", "Error": "Error", "Error on Ledger Connection. Be sure your device is connected properly": "Error on Ledger Connection. Be sure your device is connected properly", - "Every transaction needs to be confirmed and forged into Lisks blockchain network. \n Such operations require hardware resources and because of that we ask for a small fee for processing those.": "Every transaction needs to be confirmed and forged into Lisks blockchain network. \n Such operations require hardware resources and because of that we ask for a small fee for processing those.", + "Every transaction needs to be confirmed and forged into Lisks blockchain network. \n Such operations require hardware resources and because of that there is a small fee for processing those.": "Every transaction needs to be confirmed and forged into Lisks blockchain network. \n Such operations require hardware resources and because of that there is a small fee for processing those.", "Explain Blockchain Like I'm 5": "Explain Blockchain Like I'm 5", "Explore as a Guest": "Explore as a Guest", "Explore the network": "Explore the network", @@ -255,6 +257,7 @@ "Lisk.Chat": "Lisk.Chat", "Loading accounts": "Loading accounts", "Local": "Local", + "Log Out": "Log Out", "Login Type not recognized.": "Login Type not recognized.", "Logout": "Logout", "Look at your Ledger for confirmation": "Look at your Ledger for confirmation", @@ -275,6 +278,7 @@ "Minimize": "Minimize", "Multisignature Creation": "Multisignature Creation", "My Account": "My Account", + "My Wallet": "My Wallet", "Name": "Name", "Name is already taken!": "Name is already taken!", "Name is available": "Name is available", @@ -301,6 +305,7 @@ "On your dashboard": "On your dashboard", "Onboarding whenever you need": "Onboarding whenever you need", "Oops! Wrong passphrase": "Oops! Wrong passphrase", + "Oops, looks like something went wrong. Please try again.": "Oops, looks like something went wrong. Please try again.", "Open the Lisk App": "Open the Lisk App", "Open the Lisk App with your Ledger Nano S": "Open the Lisk App with your Ledger Nano S", "Out": "Out", @@ -392,7 +397,7 @@ "Send to Address": "Send to Address", "Send to address": "Send to address", "Send to this address": "Send to this address", - "Send {{value}} LSK": "Send {{value}} LSK", + "Send {{amount}} LSK": "Send {{amount}} LSK", "Sender": "Sender", "Session timeout": "Session timeout", "Session timeout in": "Session timeout in", @@ -462,10 +467,12 @@ "Transaction": "Transaction", "Transaction Fee": "Transaction Fee", "Transaction ID": "Transaction ID", + "Transaction aborted on device": "Transaction aborted on device", "Transaction failed": "Transaction failed", "Transaction fee": "Transaction fee", "Transaction is being processed and will be confirmed. It may take up to 15 minutes to be secured in the blockchain.": "Transaction is being processed and will be confirmed. It may take up to 15 minutes to be secured in the blockchain.", "Transaction not found": "Transaction not found", + "Transaction submitted": "Transaction submitted", "Transaction summary": "Transaction summary", "Transactions can’t be reversed": "Transactions can’t be reversed", "Transfer Transaction": "Transfer Transaction", @@ -536,6 +543,7 @@ "You can use ": "You can use ", "You can use your passphrase to sign a message. ": "You can use your passphrase to sign a message. ", "You have already registered as a delegate.": "You have already registered as a delegate.", + "You have cancelled the transaction on your hardware wallet.": "You have cancelled the transaction on your hardware wallet.", "You have cancelled the transaction on your hardware wallet. You can either continue or retry.": "You have cancelled the transaction on your hardware wallet. You can either continue or retry.", "You have cancelled voting on your hardware wallet.": "You have cancelled voting on your hardware wallet.", "You must keep it safe as it is the only way to access your wallet and cannot be recovered if lost.": "You must keep it safe as it is the only way to access your wallet and cannot be recovered if lost.", @@ -544,6 +552,7 @@ "You will be signed out in a minute due to no network activity. You can turn off Auto-Logout in the settings.": "You will be signed out in a minute due to no network activity. You can turn off Auto-Logout in the settings.", "You will need it to use your Lisk ID, like sending and voting. You are responsible for keeping your second passphrase safe. No one can restore it, not even Lisk.": "You will need it to use your Lisk ID, like sending and voting. You are responsible for keeping your second passphrase safe. No one can restore it, not even Lisk.", "You will send a small amount of {{fee}} LSK to yourself and therefore initialize your ID.": "You will send a small amount of {{fee}} LSK to yourself and therefore initialize your ID.", + "You'll find it in your Wallet and it will be confirmed in a matter of minutes.": "You'll find it in your Wallet and it will be confirmed in a matter of minutes.", "You've received {{value}} LSK.": "You've received {{value}} LSK.", "Your Lisk ID is how you recognize and interact with your unique Lisk account, think of it as your email.": "Your Lisk ID is how you recognize and interact with your unique Lisk account, think of it as your email.", "Your account has been created!": "Your account has been created!", @@ -587,10 +596,10 @@ "{{count}} delegate(s) selected to unvote_plural": "{{count}} delegates selected to unvote", "{{count}} delegate(s) selected to vote": "{{count}} delegate selected to vote", "{{count}} delegate(s) selected to vote_plural": "{{count}} delegates selected to vote", + "{{length}} extra bytes": "{{length}} extra bytes", "{{length}} extra characters": "{{length}} extra characters", "{{length}} out of {{maxLength}} characters left": "{{length}} out of {{maxLength}} characters left", + "{{length}} out of {{total}} bytes left": "{{length}} out of {{total}} bytes left", "{{length}} out of {{total}} characters left": "{{length}} out of {{total}} characters left", - "{{paragraph}}": "{{paragraph}}", - "{{title}}": "{{title}}", "{{title}} has been added to your Dashboard.": "{{title}} has been added to your Dashboard." } diff --git a/src/actions/search.js b/src/actions/search.js index 03925fd715..a9108b75b4 100644 --- a/src/actions/search.js +++ b/src/actions/search.js @@ -74,7 +74,8 @@ const searchVotes = ({ address, offset, limit }) => if (!liskAPIClient) return; dispatch(loadingStarted(actionTypes.searchVotes)); const votes = await getVotes(liskAPIClient, { address, offset, limit }) - .then(res => res.data.votes); + .then(res => res.data.votes || []) + .catch(() => dispatch(loadingFinished(actionTypes.searchVotes))); dispatch({ type: actionTypes.searchVotes, @@ -129,14 +130,14 @@ export const searchAccount = ({ address }) => const accountData = { ...response, }; - if (accountData.publicKey) { + if (accountData.delegate && accountData.delegate.username) { dispatch(searchDelegate({ publicKey: accountData.publicKey, address })); dispatch(searchVoters({ address, publicKey: accountData.publicKey })); } dispatch({ data: accountData, type: actionTypes.searchAccount }); dispatch(updateWallet(response, getState().peers)); + dispatch(searchVotes({ address, offset: 0, limit: 101 })); }); - dispatch(searchVotes({ address, offset: 0, limit: 101 })); } }; diff --git a/src/actions/transactions.js b/src/actions/transactions.js index e3a96f987a..2f70a29f17 100644 --- a/src/actions/transactions.js +++ b/src/actions/transactions.js @@ -13,6 +13,7 @@ import Fees from '../constants/fees'; import transactionTypes from '../constants/transactionTypes'; import { toRawLsk } from '../utils/lsk'; import { sendWithLedger } from '../utils/api/ledger'; +import { loginType } from '../constants/hwConstants'; export const cleanTransactions = () => ({ type: actionTypes.cleanTransactions, @@ -243,10 +244,10 @@ export const transactionsUpdated = ({ const handleSentError = ({ error, account, dispatch }) => { let text; switch (account.loginType) { - case 0: + case loginType.normal: text = error && error.message ? `${error.message}.` : i18next.t('An error occurred while creating the transaction.'); break; - case 1: + case loginType.ledger: text = i18next.t('You have cancelled the transaction on your hardware wallet. You can either continue or retry.'); break; default: @@ -268,11 +269,11 @@ export const sent = ({ const liskAPIClient = getState().peers.liskAPIClient; const timeOffset = getTimeOffset(getState()); switch (account.loginType) { - case 0: + case loginType.normal: // eslint-disable-next-line [error, callResult] = await to(send(liskAPIClient, recipientId, toRawLsk(amount), passphrase, secondPassphrase, data, timeOffset)); break; - case 1: + case loginType.ledger: // eslint-disable-next-line [error, callResult] = await to(sendWithLedger(liskAPIClient, account, recipientId, toRawLsk(amount), secondPassphrase, data, timeOffset)); break; diff --git a/src/components/bookmarkV2/bookmark.css b/src/components/bookmarkV2/bookmark.css index 6df6fa9219..ac7c9a004e 100644 --- a/src/components/bookmarkV2/bookmark.css +++ b/src/components/bookmarkV2/bookmark.css @@ -12,6 +12,16 @@ font-size: var(--paragraph-font-size-l); height: 36px; line-height: 1.83; + + & ~ .bookmarkContainer { + opacity: 0 !important; + visibility: hidden !important; + } + + &:focus ~ .bookmarkContainer { + opacity: 1 !important; + visibility: visible !important; + } } } @@ -39,6 +49,7 @@ z-index: 3; max-height: 176px; overflow: hidden; + transition: visibility var(--animation-speed-fast) linear, opacity var(--animation-speed-fast) linear; & > div:first-child { overflow: auto; diff --git a/src/components/bookmarkV2/bookmark.test.js b/src/components/bookmarkV2/bookmark.test.js index ddfba3641e..5787c535c0 100644 --- a/src/components/bookmarkV2/bookmark.test.js +++ b/src/components/bookmarkV2/bookmark.test.js @@ -59,7 +59,7 @@ describe('BookmarkV2', () => { expect(wrapper).toContainMatchingElement('.recipient'); expect(wrapper).toContainMatchingElement('InputV2.input'); expect(wrapper).toContainMatchingElement('SpinnerV2.spinner'); - expect(wrapper).not.toContainMatchingElement('.bookmark-list'); + expect(wrapper).toContainMatchingElement('.bookmark-list'); }); it('should validate bookmark', () => { diff --git a/src/components/bookmarkV2/index.js b/src/components/bookmarkV2/index.js index d6b6ef5778..c7051dd2ce 100644 --- a/src/components/bookmarkV2/index.js +++ b/src/components/bookmarkV2/index.js @@ -19,6 +19,7 @@ class Bookmark extends React.Component { this.loaderTimeout = null; this.listContainerRef = null; + this.input = null; this.onHandleKeyPress = this.onHandleKeyPress.bind(this); this.getFilterList = this.getFilterList.bind(this); @@ -27,11 +28,14 @@ class Bookmark extends React.Component { this.onChange = this.onChange.bind(this); this.onSelectedAccount = this.onSelectedAccount.bind(this); this.resetListIndex = this.resetListIndex.bind(this); + this.handleUpdateIndex = this.handleUpdateIndex.bind(this); } getFilterList() { const { followedAccounts, recipient } = this.props; + if (recipient.value === '') return followedAccounts; + return followedAccounts .filter(account => account.title.toLowerCase().includes(recipient.value.toLowerCase()) || @@ -71,6 +75,10 @@ class Bookmark extends React.Component { } } + handleUpdateIndex(dropdownIndex) { + this.setState({ dropdownIndex }); + } + onKeyPressEnter() { const { dropdownIndex } = this.state; const account = this.getFilterList()[dropdownIndex]; @@ -102,7 +110,7 @@ class Bookmark extends React.Component { this.setState({ isLoading: true }); this.loaderTimeout = setTimeout(() => { // istanbul ignore else - if (this.getFilterList().length === 0) this.setState({ isLoading: false }); + if (this.getFilterList().length >= 0) this.setState({ isLoading: false }); this.props.validateBookmark(); }, 300); @@ -115,7 +123,6 @@ class Bookmark extends React.Component { const { recipient, placeholder, - showSuggestions, } = this.props; const { dropdownIndex } = this.state; const showAccountVisual = recipient.address.length && !recipient.error; @@ -147,28 +154,25 @@ class Bookmark extends React.Component { className={`${styles.status} ${!this.state.isLoading && recipient.value ? styles.show : styles.hide}`} src={ recipient.error ? svg.alert_icon : svg.ok_icon} /> - { - showSuggestions && recipient.value !== '' - ?
-
{ this.listContainerRef = node; }}> - -
-
- : null - } +
+
{ this.listContainerRef = node; }}> + +
+
{recipient.feedback} diff --git a/src/components/dashboard/dashboard.css b/src/components/dashboard/dashboard.css index b716db30d0..f3bee01da7 100644 --- a/src/components/dashboard/dashboard.css +++ b/src/components/dashboard/dashboard.css @@ -55,6 +55,13 @@ } } +.initLink { + margin-left: 25px; + color: var(--color-link-active); + font-size: var(--paragraph-font-size-l); + text-decoration: none; +} + .following { height: auto; padding: 0; diff --git a/src/components/dashboard/index.js b/src/components/dashboard/index.js index d6b89ecc79..72c40c74e6 100644 --- a/src/components/dashboard/index.js +++ b/src/components/dashboard/index.js @@ -2,7 +2,7 @@ import { connect } from 'react-redux'; import { Link } from 'react-router-dom'; import { translate } from 'react-i18next'; -import React from 'react'; +import React, { Fragment } from 'react'; import throttle from 'lodash.throttle'; import grid from 'flexboxgrid/dist/flexboxgrid.css'; import BoxV2 from '../boxV2'; @@ -14,6 +14,8 @@ import FollowedAccounts from '../followedAccounts/index'; import QuickTips from '../quickTips'; import NewsFeed from '../newsFeed'; import removeDuplicateTransactions from '../../utils/transactions'; +import Piwik from '../../utils/piwik'; +import links from '../../constants/externalLinks'; import { fromRawLsk } from '../../utils/lsk'; import breakpoints from './../../constants/breakpoints'; import fees from './../../constants/fees'; @@ -89,9 +91,21 @@ class Dashboard extends React.Component { className={`${grid['col-xs-12']} initialize-banner`} title={t('Initialize Lisk ID')} footer={( - - {t('Create First Transaction')} - )}> + + + {t('Create First Transaction')} + + Piwik.trackingEvent('AccountInit', 'link', 'Initialize my lisk account')} + rel='noopener noreferrer' + > + {this.props.t('Learn more about Lisk ID initialization')} + + )}>

{t('It is recommended that you initialize your Lisk ID.')}

{t('The easiest way to do this is to send LSK to yourself by clicking this button.')}

{t('It will cost you only the usual {{fee}} LSK transaction fee.', { fee: fromRawLsk(fees.send) })}

diff --git a/src/components/followAccount/followAccount.js b/src/components/followAccount/followAccount.js index 2fc13f8e4e..6fcca592bc 100644 --- a/src/components/followAccount/followAccount.js +++ b/src/components/followAccount/followAccount.js @@ -4,7 +4,7 @@ import { translate } from 'react-i18next'; import { getIndexOfFollowedAccount } from '../../utils/followedAccounts'; import SpinnerV2 from '../spinnerV2/spinnerV2'; import svg from '../../utils/svgIcons'; -import { FontIcon } from '../fontIcon'; +// import { FontIcon } from '../fontIcon'; import { InputV2 } from '../toolbox/inputsV2'; import { PrimaryButtonV2, DangerButtonV2 } from '../toolbox/buttons/button'; import styles from './followAccount.css'; @@ -191,16 +191,17 @@ class FollowAccount extends React.Component { {fields.accountName.feedback}
- */} {isFollowing ? {t('Unfollow')} : {t('Confirm')} diff --git a/src/components/sendV2/form/form.css b/src/components/sendV2/form/form.css index 31286645c1..3a23a76a3b 100644 --- a/src/components/sendV2/form/form.css +++ b/src/components/sendV2/form/form.css @@ -10,7 +10,7 @@ max-width: 443px; height: auto; background-color: var(--color-white); - box-shadow: var(--box-shadow-standar2); + box-shadow: var(--box-shadow-graph-selector); border-radius: var(--border-radius-box); } diff --git a/src/components/sendV2/form/form.js b/src/components/sendV2/form/form.js index 6b21680c6b..4bcbc159dd 100644 --- a/src/components/sendV2/form/form.js +++ b/src/components/sendV2/form/form.js @@ -20,7 +20,8 @@ class Form extends React.Component { super(props); this.state = { - isLoading: false, + isAmountLoading: false, + isReferenceLoading: false, fields: { recipient: { address: '', @@ -32,6 +33,7 @@ class Form extends React.Component { title: '', value: '', showSuggestions: false, + following: false, }, amount: { error: false, @@ -57,31 +59,39 @@ class Form extends React.Component { this.onSelectedAccount = this.onSelectedAccount.bind(this); this.validateAmountAndReference = this.validateAmountAndReference.bind(this); this.validateBookmark = this.validateBookmark.bind(this); + this.checkIfBoormakedAccount = this.checkIfBoormakedAccount.bind(this); } componentDidMount() { this.ifDataFromPrevState(); this.ifDataFromUrl(); + this.checkIfBoormakedAccount(); } ifDataFromPrevState() { const { prevState } = this.props; if (prevState.fields && Object.entries(prevState.fields).length > 0) { - this.setState({ fields: { ...prevState.fields } }); + this.setState({ + fields: { + ...this.state.fields, + ...prevState.fields, + }, + }); } } ifDataFromUrl() { const { fields = {} } = this.props; - if (Object.entries(fields).length > 0) { + if (fields.recipient.address !== '' || fields.amount.value !== '' || fields.reference.value !== '') { this.setState({ fields: { ...this.state.fields, recipient: { ...this.state.fields.recipient, address: fields.recipient.address, + value: fields.recipient.address, }, amount: { ...this.state.fields.amount, @@ -96,6 +106,15 @@ class Form extends React.Component { } } + checkIfBoormakedAccount() { + const { followedAccounts, fields } = this.props; + const account = followedAccounts.length + ? followedAccounts.find(acc => acc.address === fields.recipient.address) + : false; + + if (account) this.onSelectedAccount(account); + } + onInputChange({ target }) { this.setState({ fields: { @@ -117,12 +136,27 @@ class Form extends React.Component { if (followedAccounts.length && recipient.value !== '') { isAccountValid = followedAccounts - .find(account => account.title.toLowerCase() === recipient.value.toLowerCase()) || false; + .find(account => (account.title.toLowerCase() === recipient.value.toLowerCase()) || + account.address.toLowerCase() === recipient.value.toLowerCase()) || false; isAddressValid = regex.address.test(recipient.value); } else { isAddressValid = recipient.value.match(regex.address); } + // istanbul ignore else + if (!isAccountValid && !isAddressValid && recipient.value) { + recipient = { + ...this.state.recipient, + address: '', + balance: '', + error: true, + feedback: 'Provide a correct wallet address or a name of a bookmarked account', + selected: false, + title: '', + showSuggestions: true, + }; + } + // istanbul ignore else if (isAddressValid) { recipient = { @@ -132,6 +166,7 @@ class Form extends React.Component { error: false, feedback: '', showSuggestions: false, + following: false, }; } @@ -146,20 +181,7 @@ class Form extends React.Component { error: false, feedback: '', showSuggestions: false, - }; - } - - // istanbul ignore else - if (!isAccountValid && !isAddressValid && recipient.value) { - recipient = { - ...this.state.recipient, - address: '', - balance: '', - error: true, - feedback: 'Provide a correct wallet address or a name of a followed account', - selected: false, - title: '', - showSuggestions: true, + following: true, }; } @@ -173,7 +195,7 @@ class Form extends React.Component { feedback: '', selected: false, title: '', - showSuggestions: false, + showSuggestions: true, }; } @@ -200,6 +222,7 @@ class Form extends React.Component { error: '', feedback: '', showSuggestions: false, + following: true, }, }, }); @@ -212,7 +235,7 @@ class Form extends React.Component { validateAmountField(value) { if (/([^\d.])/g.test(value)) return this.props.t('Provide a correct amount of LSK'); if (/(\.)(.*\1){1}/g.test(value) || /\.$/.test(value)) return this.props.t('Invalid amount'); - if (value > this.getMaxAmount()) return this.props.t('Provided amount is higher than your current balance.'); + if (parseFloat(this.getMaxAmount()) < value) return this.props.t('Provided amount is higher than your current balance.'); return false; } @@ -235,9 +258,9 @@ class Form extends React.Component { const byteCount = encodeURI(value).split(/%..|./).length - 1; error = byteCount > messageMaxLength; feedback = error - ? t('{{length}} extra characters', { length: byteCount - messageMaxLength }) - : t('{{length}} out of {{total}} characters left', { - length: messageMaxLength - value.length, + ? t('{{length}} extra bytes', { length: byteCount - messageMaxLength }) + : t('{{length}} out of {{total}} bytes left', { + length: messageMaxLength - byteCount, total: messageMaxLength, }); } @@ -246,6 +269,7 @@ class Form extends React.Component { fields: { ...prevState.fields, [name]: { + ...prevState.fields[name], error: !!error, value, feedback, @@ -257,10 +281,11 @@ class Form extends React.Component { onAmountOrReferenceChange({ target }) { clearTimeout(this.loaderTimeout); - this.setState({ isLoading: true }); + if (target.name === 'amount') this.setState({ isAmountLoading: true }); + if (target.name === 'reference') this.setState({ isReferenceLoading: true }); this.loaderTimeout = setTimeout(() => { - this.setState({ isLoading: false }); + this.setState({ isAmountLoading: false, isReferenceLoading: false }); this.validateAmountAndReference(target.name, target.value); }, 300); @@ -291,7 +316,7 @@ class Form extends React.Component {
- +
diff --git a/src/components/sendV2/summary/summary.test.js b/src/components/sendV2/summary/summary.test.js index ca091b3e3b..d9ba56b81d 100644 --- a/src/components/sendV2/summary/summary.test.js +++ b/src/components/sendV2/summary/summary.test.js @@ -49,8 +49,11 @@ describe('Summary', () => { const props = { t: v => v, account: { + address: accounts['second passphrase account'].address, secondPublicKey: accounts['second passphrase account'].secondPublicKey, }, + failedTransactions: '', + pendingTransactions: [], fields: { recipient: { address: '123123L', @@ -61,6 +64,8 @@ describe('Summary', () => { reference: { value: 1, }, + isLoading: false, + isHardwareWalletConnected: false, }, prevState: { fields: {}, @@ -68,6 +73,8 @@ describe('Summary', () => { prevStep: jest.fn(), nextStep: jest.fn(), sent: jest.fn(), + isLoading: false, + isHardwareWalletConnected: false, }; beforeEach(() => { @@ -98,4 +105,29 @@ describe('Summary', () => { wrapper.update(); expect(props.nextStep).toBeCalled(); }); + + it('should goind to next page if everything is successfull with Hardware Wallet', () => { + const newProps = { ...props }; + newProps.account = { + ...props.account, + hwInfo: { + deviceId: '123123sdf', + }, + }; + wrapper = mount(, options); + wrapper.setProps({ + ...props, + pendingTransactions: [{ + senderId: accounts['second passphrase account'].address, + recipientId: '123123L', + amount: 1, + }], + fields: { + ...props.fields, + isLoading: true, + }, + }); + wrapper.update(); + expect(props.nextStep).toBeCalled(); + }); }); diff --git a/src/components/sendV2/transactionStatus/index.js b/src/components/sendV2/transactionStatus/index.js index a109e4bda9..b4c460dbff 100644 --- a/src/components/sendV2/transactionStatus/index.js +++ b/src/components/sendV2/transactionStatus/index.js @@ -3,15 +3,19 @@ import { connect } from 'react-redux'; import { translate } from 'react-i18next'; import TransactionStatus from './transactionStatus'; import actionTypes from '../../../constants/actions'; +import { searchAccount } from '../../../actions/search'; const mapStateToProps = state => ({ failedTransactions: state.transactions.failed, + followedAccounts: state.followedAccounts ? state.followedAccounts.accounts : [], + delegates: state.search.delegates || {}, }); const mapDispatchToProps = { transactionFailedClear: () => ({ type: actionTypes.transactionFailedClear, }), + searchAccount, }; export default connect(mapStateToProps, mapDispatchToProps)(translate()(TransactionStatus)); diff --git a/src/components/sendV2/transactionStatus/statusMessages.js b/src/components/sendV2/transactionStatus/statusMessages.js new file mode 100644 index 0000000000..f1a27ec761 --- /dev/null +++ b/src/components/sendV2/transactionStatus/statusMessages.js @@ -0,0 +1,28 @@ +/* istanbul ignore file */ +import svg from '../../../utils/svgIcons'; + +const statusMessages = t => ({ + success: { + headerIcon: svg.transactionSuccess, + bodyText: { + title: t('Transaction submitted'), + paragraph: t("You'll find it in your Wallet and it will be confirmed in a matter of minutes."), + }, + }, + error: { + headerIcon: svg.transactionError, + bodyText: { + title: t('Transaction failed'), + paragraph: t('Oops, looks like something went wrong. Please try again.'), + }, + }, + hw: { + headerIcon: svg.transactionError, + bodyText: { + title: t('Transaction aborted on device'), + paragraph: t('You have cancelled the transaction on your hardware wallet.'), + }, + }, +}); + +export default statusMessages; diff --git a/src/components/sendV2/transactionStatus/transactionStatus.css b/src/components/sendV2/transactionStatus/transactionStatus.css index dee06e4f18..eda4d5b99c 100644 --- a/src/components/sendV2/transactionStatus/transactionStatus.css +++ b/src/components/sendV2/transactionStatus/transactionStatus.css @@ -55,6 +55,37 @@ height: auto; padding: 0 16px; flex-direction: column; + + & .btn { + width: 227px; + } + + & .followBtn { + position: relative; + } + + & .followDropdown { + left: calc(50% - 184px); + top: calc(100% + 10px); + width: 368px; + padding: 0; + } + + & .followingButton { + display: flex; + + &::after { + border: 4px solid transparent; + border-top-color: currentColor; + content: ''; + margin-left: 8px; + margin-top: 4px; + } + } + + & :global(.dropdown-arrow) { + right: calc(50% - 18px); + } } .errorReport { diff --git a/src/components/sendV2/transactionStatus/transactionStatus.js b/src/components/sendV2/transactionStatus/transactionStatus.js index 3bec6a2854..f385c7b4a5 100644 --- a/src/components/sendV2/transactionStatus/transactionStatus.js +++ b/src/components/sendV2/transactionStatus/transactionStatus.js @@ -1,15 +1,36 @@ import React from 'react'; -import { PrimaryButtonV2 } from '../../toolbox/buttons/button'; +import { PrimaryButtonV2, SecondaryButtonV2 } from '../../toolbox/buttons/button'; import Piwik from '../../../utils/piwik'; -import svg from '../../../utils/svgIcons'; +import statusMessage from './statusMessages'; +import DropdownV2 from '../../toolbox/dropdownV2/dropdownV2'; +import FollowAccount from '../../followAccount'; +import { getIndexOfFollowedAccount } from '../../../utils/followedAccounts'; import styles from './transactionStatus.css'; class TransactionStatus extends React.Component { constructor(props) { super(props); + this.state = { + isFollowAccountDropdown: false, + }; + + this.followContainerRef = {}; + this.backToWallet = this.backToWallet.bind(this); this.onErrorReport = this.onErrorReport.bind(this); + this.onPrevStep = this.onPrevStep.bind(this); + this.onFollowingDropdownToggle = this.onFollowingDropdownToggle.bind(this); + this.handleClickOutsideDropdown = this.handleClickOutsideDropdown.bind(this); + } + + componentDidMount() { + this.props.searchAccount({ address: this.props.fields.recipient.address }); + } + + /* istanbul ignore next */ + componentWillUnmount() { + document.removeEventListener('click', this.handleClickOutsideDropdown); } backToWallet() { @@ -19,6 +40,21 @@ class TransactionStatus extends React.Component { this.props.finalCallback(); } + onFollowingDropdownToggle() { + if (this.state.isFollowAccountDropdown) { + document.removeEventListener('click', this.handleClickOutsideDropdown); + } else { + document.addEventListener('click', this.handleClickOutsideDropdown); + } + + this.setState(prevState => ({ isFollowAccountDropdown: !prevState.isFollowAccountDropdown })); + } + + handleClickOutsideDropdown(e) { + if (this.followContainerRef.contains(e.target)) return; + this.onFollowingDropdownToggle(); + } + // eslint-disable-next-line class-methods-use-this onErrorReport() { const recipient = 'hubdev@lisk.io'; @@ -26,26 +62,35 @@ class TransactionStatus extends React.Component { return `mailto:${recipient}?&subject=${subject}`; } + onPrevStep() { + this.props.transactionFailedClear(); + this.props.prevStep({ fields: { ...this.props.fields } }); + } + + // eslint-disable-next-line complexity render() { - const isTransactionSuccess = this.props.failedTransactions === undefined; - - let transactionStatus = { - headerIcon: svg.transactionSuccess, - bodyText: { - title: 'Transaction submitted', - paragraph: 'You will find it in My Transactions in a matter of minutes', - }, - }; + const hwTransactionError = this.props.fields.isHardwareWalletConnected && this.props.fields.hwTransactionStatus === 'error'; + const messages = statusMessage(this.props.t); + let transactionStatus = this.props.failedTransactions === undefined + ? messages.success + : messages.error; + + const isFollowing = getIndexOfFollowedAccount( + this.props.followedAccounts, + { address: this.props.fields.recipient.address }, + ) !== -1; + + const followBtnLabel = isFollowing + ? this.props.t('Account bookmarked') + : this.props.t('Bookmark account'); + + const delegate = Object.entries(this.props.delegates).length + ? this.props.delegates[this.props.fields.recipient.address] + : {}; // istanbul ignore else - if (!isTransactionSuccess) { - transactionStatus = { - headerIcon: svg.transactionError, - bodyText: { - title: 'Transaction failed', - paragraph: 'Oops, looks like something went wrong. Please try again.', - }, - }; + if (this.props.fields.isHardwareWalletConnected) { + transactionStatus = hwTransactionError ? messages.hw : messages.success; } return ( @@ -54,13 +99,41 @@ class TransactionStatus extends React.Component {
-

{this.props.t('{{title}}', { title: transactionStatus.bodyText.title })}

-

{this.props.t('{{paragraph}}', { paragraph: transactionStatus.bodyText.paragraph })}

+

{transactionStatus.bodyText.title}

+

{transactionStatus.bodyText.paragraph}