1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-22 18:00:18 +01:00

Fix account name duplicates (#12867)

* Fix account name duplicates

* Change button disabling logic

* Fix function error

* Requested changes

* Move functions from selectors file into components

* Applying requested changes
This commit is contained in:
filipsekulic 2021-12-16 11:41:55 +01:00 committed by GitHub
parent f946c030b5
commit 62f41600f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 107 additions and 9 deletions

View File

@ -73,6 +73,10 @@
"accountName": {
"message": "Account Name"
},
"accountNameDuplicate": {
"message": "This account name already exists",
"description": "This is an error message shown when the user enters a new account name that matches an existing account name"
},
"accountOptions": {
"message": "Account Options"
},

View File

@ -17,6 +17,7 @@ export default class AccountDetailsModal extends Component {
setAccountLabel: PropTypes.func,
keyrings: PropTypes.array,
rpcPrefs: PropTypes.object,
accounts: PropTypes.array,
};
static contextTypes = {
@ -32,6 +33,7 @@ export default class AccountDetailsModal extends Component {
setAccountLabel,
keyrings,
rpcPrefs,
accounts,
} = this.props;
const { name, address } = selectedIdentity;
@ -39,6 +41,12 @@ export default class AccountDetailsModal extends Component {
return kr.accounts.includes(address);
});
const getAccountsNames = (allAccounts, currentName) => {
return Object.values(allAccounts)
.map((item) => item.name)
.filter((itemName) => itemName !== currentName);
};
let exportPrivateKeyFeatureEnabled = true;
// This feature is disabled for hardware wallets
if (isHardwareKeyring(keyring?.type)) {
@ -51,6 +59,7 @@ export default class AccountDetailsModal extends Component {
className="account-details-modal__name"
defaultValue={name}
onSubmit={(label) => setAccountLabel(address, label)}
accountsNames={getAccountsNames(accounts, name)}
/>
<QrView

View File

@ -4,6 +4,7 @@ import {
getSelectedIdentity,
getRpcPrefsForCurrentProvider,
getCurrentChainId,
getMetaMaskAccountsOrdered,
} from '../../../../selectors';
import AccountDetailsModal from './account-details-modal.component';
@ -13,6 +14,7 @@ const mapStateToProps = (state) => {
selectedIdentity: getSelectedIdentity(state),
keyrings: state.metamask.keyrings,
rpcPrefs: getRpcPrefsForCurrentProvider(state),
accounts: getMetaMaskAccountsOrdered(state),
};
};

View File

@ -30,6 +30,12 @@ describe('Account Details Modal', () => {
name: 'Account 1',
},
},
accounts: {
address: '0xAddress',
lastSelected: 1637764711510,
name: 'Account 1',
balance: '0x543a5fb6caccf599',
},
};
beforeEach(() => {

View File

@ -7,6 +7,11 @@ class EditableLabel extends Component {
onSubmit: PropTypes.func.isRequired,
defaultValue: PropTypes.string,
className: PropTypes.string,
accountsNames: PropTypes.array,
};
static contextTypes = {
t: PropTypes.func,
};
state = {
@ -16,8 +21,9 @@ class EditableLabel extends Component {
handleSubmit() {
const { value } = this.state;
const { accountsNames } = this.props;
if (value === '') {
if (value === '' || accountsNames.includes(value)) {
return;
}
@ -28,6 +34,7 @@ class EditableLabel extends Component {
renderEditing() {
const { value } = this.state;
const { accountsNames } = this.props;
return [
<input
@ -43,7 +50,8 @@ class EditableLabel extends Component {
}}
onChange={(event) => this.setState({ value: event.target.value })}
className={classnames('large-input', 'editable-label__input', {
'editable-label__input--error': value === '',
'editable-label__input--error':
value === '' || accountsNames.includes(value),
})}
autoFocus
/>,
@ -73,13 +81,25 @@ class EditableLabel extends Component {
}
render() {
const { isEditing } = this.state;
const { className } = this.props;
const { isEditing, value } = this.state;
const { className, accountsNames } = this.props;
return (
<div className={classnames('editable-label', className)}>
{isEditing ? this.renderEditing() : this.renderReadonly()}
</div>
<>
<div className={classnames('editable-label', className)}>
{isEditing ? this.renderEditing() : this.renderReadonly()}
</div>
{accountsNames.includes(value) ? (
<div
className={classnames(
'editable-label__error',
'editable-label__error-amount',
)}
>
{this.context.t('accountNameDuplicate')}
</div>
) : null}
</>
);
}
}

View File

@ -34,4 +34,15 @@
cursor: pointer;
color: $dusty-gray;
}
&__error {
@include H7;
left: 8px;
color: $red;
}
&__error-amount {
margin-top: 5px;
}
}

View File

@ -87,6 +87,21 @@
color: $scorpion;
margin-top: 15px;
padding: 0 20px;
&__error {
border: 1px solid $error-3;
}
}
&__error {
@include H7;
left: 8px;
color: $red;
}
&__error-amount {
margin-top: 5px;
}
&__buttons {

View File

@ -1,5 +1,6 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import Button from '../../components/ui/button';
export default class NewAccountCreateForm extends Component {
@ -16,7 +17,13 @@ export default class NewAccountCreateForm extends Component {
render() {
const { newAccountName, defaultAccountName } = this.state;
const { history, createAccount, mostRecentOverviewPage } = this.props;
const {
history,
createAccount,
mostRecentOverviewPage,
accounts,
} = this.props;
const createClick = (_) => {
createAccount(newAccountName || defaultAccountName)
.then(() => {
@ -43,6 +50,14 @@ export default class NewAccountCreateForm extends Component {
});
};
const accountNameExists = (allAccounts, accountName) => {
const accountsNames = allAccounts.map((item) => item.name);
return accountsNames.includes(accountName);
};
const existingAccountName = accountNameExists(accounts, newAccountName);
return (
<div className="new-account-create-form">
<div className="new-account-create-form__input-label">
@ -50,7 +65,9 @@ export default class NewAccountCreateForm extends Component {
</div>
<div>
<input
className="new-account-create-form__input"
className={classnames('new-account-create-form__input', {
'new-account-create-form__input__error': existingAccountName,
})}
value={newAccountName}
placeholder={defaultAccountName}
onChange={(event) =>
@ -58,6 +75,16 @@ export default class NewAccountCreateForm extends Component {
}
autoFocus
/>
{existingAccountName ? (
<div
className={classnames(
' new-account-create-form__error',
' new-account-create-form__error-amount',
)}
>
{this.context.t('accountNameDuplicate')}
</div>
) : null}
<div className="new-account-create-form__buttons">
<Button
type="secondary"
@ -72,6 +99,7 @@ export default class NewAccountCreateForm extends Component {
large
className="new-account-create-form__button"
onClick={createClick}
disabled={existingAccountName}
>
{this.context.t('create')}
</Button>
@ -87,6 +115,7 @@ NewAccountCreateForm.propTypes = {
newAccountNumber: PropTypes.number,
history: PropTypes.object,
mostRecentOverviewPage: PropTypes.string.isRequired,
accounts: PropTypes.array,
};
NewAccountCreateForm.contextTypes = {

View File

@ -1,6 +1,7 @@
import { connect } from 'react-redux';
import * as actions from '../../store/actions';
import { getMostRecentOverviewPage } from '../../ducks/history/history';
import { getMetaMaskAccountsOrdered } from '../../selectors';
import NewAccountCreateForm from './new-account.component';
const mapStateToProps = (state) => {
@ -13,6 +14,7 @@ const mapStateToProps = (state) => {
return {
newAccountNumber,
mostRecentOverviewPage: getMostRecentOverviewPage(state),
accounts: getMetaMaskAccountsOrdered(state),
};
};