mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Choose accounts refactor (#13039)
* added wrapper around account list to prevent storybook from collapsing the list * updated translation files * added snap-connect page * refactored account list out of the choose account component * fixed width * removed unnecessary scss from choose-account component, fixed props in choose account story * removed snaps-connect page, added comments to ChooseAccount * updated choose account subtitle text, updated styling for title & subtitle, removed redundant account list story * updated component name, updated paths * fixed linter errors * added comments * removed unused message * removed selectAccounts key from all locales * updated class name for account list header, updated allAreSelected function to use length checks * Revert "removed unused message" This reverts commit 32771bc83c08f120825ef75f0741f3034e7dbecb. * Revert "removed selectAccounts key from all locales" This reverts commit ccfa4a860f9a75693d893d7c404384e719de297e. * updated locale messages to use selectAccounts key * removed stray import * updated scss * updated translation key * removed chooseAccounts key from en locale * removed optional chaining * changes * updated subjectMetadata * updated subject types * update useOriginMetadata function to include unknown subject type * updated permission connect header props, removed host and added subjectType to targetSubjectMetadata * added subjectType to targetSubjectMetadata * removed console.log * changed prop name to iconUrl
This commit is contained in:
parent
8cc185a8b1
commit
f946c030b5
@ -2443,7 +2443,7 @@
|
|||||||
"message": "Select a higher gas fee to accelerate the processing of your transaction.*"
|
"message": "Select a higher gas fee to accelerate the processing of your transaction.*"
|
||||||
},
|
},
|
||||||
"selectAccounts": {
|
"selectAccounts": {
|
||||||
"message": "Select account(s)"
|
"message": "Select the account(s) to use on this site"
|
||||||
},
|
},
|
||||||
"selectAll": {
|
"selectAll": {
|
||||||
"message": "Select all"
|
"message": "Select all"
|
||||||
|
@ -38,7 +38,7 @@ function sendMetadataHandler(
|
|||||||
end,
|
end,
|
||||||
{ addSubjectMetadata, subjectType },
|
{ addSubjectMetadata, subjectType },
|
||||||
) {
|
) {
|
||||||
const { params } = req;
|
const { origin, params } = req;
|
||||||
if (params && typeof params === 'object' && !Array.isArray(params)) {
|
if (params && typeof params === 'object' && !Array.isArray(params)) {
|
||||||
const { icon = null, name = null, ...remainingParams } = params;
|
const { icon = null, name = null, ...remainingParams } = params;
|
||||||
|
|
||||||
@ -47,6 +47,7 @@ function sendMetadataHandler(
|
|||||||
iconUrl: icon,
|
iconUrl: icon,
|
||||||
name,
|
name,
|
||||||
subjectType,
|
subjectType,
|
||||||
|
origin,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return end(ethErrors.rpc.invalidParams({ data: params }));
|
return end(ethErrors.rpc.invalidParams({ data: params }));
|
||||||
|
@ -2725,7 +2725,6 @@ export default class MetamaskController extends EventEmitter {
|
|||||||
// Miscellaneous
|
// Miscellaneous
|
||||||
addSubjectMetadata: this.subjectMetadataController.addSubjectMetadata.bind(
|
addSubjectMetadata: this.subjectMetadataController.addSubjectMetadata.bind(
|
||||||
this.subjectMetadataController,
|
this.subjectMetadataController,
|
||||||
origin,
|
|
||||||
),
|
),
|
||||||
getProviderState: this.getProviderState.bind(this),
|
getProviderState: this.getProviderState.bind(this),
|
||||||
getUnlockPromise: this.appStateController.getUnlockPromise.bind(
|
getUnlockPromise: this.appStateController.getUnlockPromise.bind(
|
||||||
|
@ -50,9 +50,10 @@ export const MESSAGE_TYPE = {
|
|||||||
* third parties and itself (e.g. when the background communicated with the UI).
|
* third parties and itself (e.g. when the background communicated with the UI).
|
||||||
*/
|
*/
|
||||||
export const SUBJECT_TYPES = {
|
export const SUBJECT_TYPES = {
|
||||||
WEBSITE: 'website',
|
|
||||||
EXTENSION: 'extension',
|
EXTENSION: 'extension',
|
||||||
INTERNAL: 'internal',
|
INTERNAL: 'internal',
|
||||||
|
UNKNOWN: 'unknown',
|
||||||
|
WEBSITE: 'website',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const POLLING_TOKEN_ENVIRONMENT_TYPES = {
|
export const POLLING_TOKEN_ENVIRONMENT_TYPES = {
|
||||||
|
@ -100,7 +100,7 @@ export default class PermissionPageContainerContent extends PureComponent {
|
|||||||
<div className="permission-approval-container__content">
|
<div className="permission-approval-container__content">
|
||||||
<div className="permission-approval-container__content-container">
|
<div className="permission-approval-container__content-container">
|
||||||
<PermissionsConnectHeader
|
<PermissionsConnectHeader
|
||||||
icon={subjectMetadata.iconUrl}
|
iconUrl={subjectMetadata.iconUrl}
|
||||||
iconName={subjectMetadata.name}
|
iconName={subjectMetadata.name}
|
||||||
headerTitle={title}
|
headerTitle={title}
|
||||||
headerText={
|
headerText={
|
||||||
|
@ -17,13 +17,14 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
color: $Black-100;
|
color: $Black-100;
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__subtitle {
|
&__subtitle {
|
||||||
@include H6;
|
@include H6;
|
||||||
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: $Grey-500;
|
color: $Black-100;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__subtitle {
|
&__subtitle {
|
||||||
|
@ -4,7 +4,7 @@ import SiteOrigin from '../../ui/site-origin/site-origin';
|
|||||||
|
|
||||||
export default class PermissionsConnectHeader extends Component {
|
export default class PermissionsConnectHeader extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
icon: PropTypes.string,
|
iconUrl: PropTypes.string,
|
||||||
iconName: PropTypes.string.isRequired,
|
iconName: PropTypes.string.isRequired,
|
||||||
siteOrigin: PropTypes.string.isRequired,
|
siteOrigin: PropTypes.string.isRequired,
|
||||||
headerTitle: PropTypes.node,
|
headerTitle: PropTypes.node,
|
||||||
@ -12,17 +12,17 @@ export default class PermissionsConnectHeader extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
icon: null,
|
iconUrl: null,
|
||||||
headerTitle: '',
|
headerTitle: '',
|
||||||
headerText: '',
|
headerText: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
renderHeaderIcon() {
|
renderHeaderIcon() {
|
||||||
const { icon, iconName, siteOrigin } = this.props;
|
const { iconUrl, iconName, siteOrigin } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="permissions-connect-header__icon">
|
<div className="permissions-connect-header__icon">
|
||||||
<SiteOrigin siteOrigin={siteOrigin} iconSrc={icon} name={iconName} />
|
<SiteOrigin siteOrigin={siteOrigin} iconSrc={iconUrl} name={iconName} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
175
ui/components/ui/account-list/account-list.js
Normal file
175
ui/components/ui/account-list/account-list.js
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||||
|
import CheckBox, { CHECKED, INDETERMINATE, UNCHECKED } from '../check-box';
|
||||||
|
import Identicon from '../identicon';
|
||||||
|
import UserPreferencedCurrencyDisplay from '../../app/user-preferenced-currency-display';
|
||||||
|
import { PRIMARY } from '../../../helpers/constants/common';
|
||||||
|
import Tooltip from '../tooltip';
|
||||||
|
|
||||||
|
const AccountList = ({
|
||||||
|
selectNewAccountViaModal,
|
||||||
|
accounts,
|
||||||
|
addressLastConnectedMap,
|
||||||
|
selectedAccounts,
|
||||||
|
nativeCurrency,
|
||||||
|
allAreSelected,
|
||||||
|
deselectAll,
|
||||||
|
selectAll,
|
||||||
|
handleAccountClick,
|
||||||
|
}) => {
|
||||||
|
const t = useI18nContext();
|
||||||
|
|
||||||
|
const Header = () => {
|
||||||
|
let checked;
|
||||||
|
if (allAreSelected()) {
|
||||||
|
checked = CHECKED;
|
||||||
|
} else if (selectedAccounts.size === 0) {
|
||||||
|
checked = UNCHECKED;
|
||||||
|
} else {
|
||||||
|
checked = INDETERMINATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classnames({
|
||||||
|
'choose-account-list__header--one-item': accounts.length === 1,
|
||||||
|
'choose-account-list__header--multiple-items': accounts.length > 1,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{accounts.length > 1 ? (
|
||||||
|
<div className="choose-account-list__select-all">
|
||||||
|
<CheckBox
|
||||||
|
className="choose-account-list__header-check-box"
|
||||||
|
checked={checked}
|
||||||
|
onClick={() => (allAreSelected() ? deselectAll() : selectAll())}
|
||||||
|
/>
|
||||||
|
<div className="choose-account-list__text-grey">
|
||||||
|
{t('selectAll')}
|
||||||
|
</div>
|
||||||
|
<Tooltip
|
||||||
|
position="bottom"
|
||||||
|
html={
|
||||||
|
<div style={{ width: 200, padding: 4 }}>
|
||||||
|
{t('selectingAllWillAllow')}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<i className="fa fa-info-circle" />
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
<div
|
||||||
|
className="choose-account-list__text-blue"
|
||||||
|
onClick={() => selectNewAccountViaModal(handleAccountClick)}
|
||||||
|
>
|
||||||
|
{t('newAccount')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const List = () => {
|
||||||
|
return (
|
||||||
|
<div className="choose-account-list__wrapper">
|
||||||
|
<div className="choose-account-list__list">
|
||||||
|
{accounts.map((account, index) => {
|
||||||
|
const { address, addressLabel, balance } = account;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={`choose-account-list-${index}`}
|
||||||
|
onClick={() => handleAccountClick(address)}
|
||||||
|
className="choose-account-list__account"
|
||||||
|
>
|
||||||
|
<div className="choose-account-list__account-info-wrapper">
|
||||||
|
<CheckBox
|
||||||
|
className="choose-account-list__list-check-box"
|
||||||
|
checked={selectedAccounts.has(address)}
|
||||||
|
/>
|
||||||
|
<Identicon diameter={34} address={address} />
|
||||||
|
<div className="choose-account-list__account__info">
|
||||||
|
<div className="choose-account-list__account__label">
|
||||||
|
{addressLabel}
|
||||||
|
</div>
|
||||||
|
<UserPreferencedCurrencyDisplay
|
||||||
|
className="choose-account-list__account__balance"
|
||||||
|
type={PRIMARY}
|
||||||
|
value={balance}
|
||||||
|
style={{ color: '#6A737D' }}
|
||||||
|
suffix={nativeCurrency}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{addressLastConnectedMap[address] ? (
|
||||||
|
<Tooltip
|
||||||
|
title={`${t('lastConnected')} ${
|
||||||
|
addressLastConnectedMap[address]
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<i className="fa fa-info-circle" />
|
||||||
|
</Tooltip>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="choose-account-list">
|
||||||
|
<Header />
|
||||||
|
<List />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
AccountList.propTypes = {
|
||||||
|
/**
|
||||||
|
* Array of user account objects
|
||||||
|
*/
|
||||||
|
accounts: PropTypes.arrayOf(
|
||||||
|
PropTypes.shape({
|
||||||
|
address: PropTypes.string,
|
||||||
|
addressLabel: PropTypes.string,
|
||||||
|
lastConnectedDate: PropTypes.string,
|
||||||
|
balance: PropTypes.string,
|
||||||
|
}),
|
||||||
|
).isRequired,
|
||||||
|
/**
|
||||||
|
* Function to select a new account via modal
|
||||||
|
*/
|
||||||
|
selectNewAccountViaModal: PropTypes.func.isRequired,
|
||||||
|
/**
|
||||||
|
* A map of the last connected addresses
|
||||||
|
*/
|
||||||
|
addressLastConnectedMap: PropTypes.object,
|
||||||
|
/**
|
||||||
|
* Native currency of current chain
|
||||||
|
*/
|
||||||
|
nativeCurrency: PropTypes.string.isRequired,
|
||||||
|
/**
|
||||||
|
* Currently selected accounts
|
||||||
|
*/
|
||||||
|
selectedAccounts: PropTypes.object.isRequired,
|
||||||
|
/**
|
||||||
|
* Function to check if all accounts are selected
|
||||||
|
*/
|
||||||
|
allAreSelected: PropTypes.func.isRequired,
|
||||||
|
/**
|
||||||
|
* Function to deselect all accounts
|
||||||
|
*/
|
||||||
|
deselectAll: PropTypes.func.isRequired,
|
||||||
|
/**
|
||||||
|
* Function to select all accounts
|
||||||
|
*/
|
||||||
|
selectAll: PropTypes.func.isRequired,
|
||||||
|
/**
|
||||||
|
* Function to handle account click
|
||||||
|
*/
|
||||||
|
handleAccountClick: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AccountList;
|
1
ui/components/ui/account-list/index.js
Normal file
1
ui/components/ui/account-list/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './account-list';
|
140
ui/components/ui/account-list/index.scss
Normal file
140
ui/components/ui/account-list/index.scss
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
.choose-account-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&__header--one-item,
|
||||||
|
&__header--multiple-items {
|
||||||
|
display: flex;
|
||||||
|
flex: 0;
|
||||||
|
margin-top: 36px;
|
||||||
|
width: 100%;
|
||||||
|
padding-inline-start: 15px;
|
||||||
|
padding-inline-end: 17px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__header--one-item {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__header--multiple-items {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__select-all {
|
||||||
|
display: flex;
|
||||||
|
margin-inline-start: 16px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__header-check-box {
|
||||||
|
margin-right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__wrapper {
|
||||||
|
width: 92%;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__list {
|
||||||
|
flex: 2 1 0;
|
||||||
|
width: 92%;
|
||||||
|
max-height: max-content;
|
||||||
|
border: 1px solid #d0d5da;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-top: 8px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__account {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px;
|
||||||
|
border-bottom: 1px solid #d2d8dd;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
&:last-of-type {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $Grey-000;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-inline-start: 16px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
@include H6;
|
||||||
|
|
||||||
|
color: $Black-100;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__balance {
|
||||||
|
@include H7;
|
||||||
|
|
||||||
|
color: $Grey-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__last-connected {
|
||||||
|
@include H8;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
color: $primary-blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__account-info-wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__list-check-box {
|
||||||
|
margin-inline-end: 16px;
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa-info-circle {
|
||||||
|
color: $Grey-200;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-inline-start: 8px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa-info-circle:hover {
|
||||||
|
color: $Grey-300;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__text,
|
||||||
|
&__text-blue,
|
||||||
|
&__text-grey {
|
||||||
|
@include H6;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__text-blue {
|
||||||
|
color: $primary-blue;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__text-grey {
|
||||||
|
color: $Grey-500;
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
/** Please import your files in alphabetical order **/
|
/** Please import your files in alphabetical order **/
|
||||||
@import 'account-mismatch-warning/index';
|
@import 'account-mismatch-warning/index';
|
||||||
|
@import 'account-list/index';
|
||||||
@import 'actionable-message/index';
|
@import 'actionable-message/index';
|
||||||
@import 'alert-circle-icon/index';
|
@import 'alert-circle-icon/index';
|
||||||
@import 'alert/index';
|
@import 'alert/index';
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { getSubjectMetadata } from '../selectors';
|
import { getSubjectMetadata } from '../selectors';
|
||||||
|
import { SUBJECT_TYPES } from '../../shared/constants/app';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} OriginMetadata
|
* @typedef {Object} OriginMetadata
|
||||||
@ -27,6 +28,7 @@ export function useOriginMetadata(origin) {
|
|||||||
host: url.host,
|
host: url.host,
|
||||||
hostname: url.hostname,
|
hostname: url.hostname,
|
||||||
origin,
|
origin,
|
||||||
|
subjectType: SUBJECT_TYPES.UNKNOWN,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (subjectMetadata?.[origin]) {
|
if (subjectMetadata?.[origin]) {
|
||||||
|
@ -1,242 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import classnames from 'classnames';
|
|
||||||
import Identicon from '../../../components/ui/identicon';
|
|
||||||
import Button from '../../../components/ui/button';
|
|
||||||
import CheckBox, {
|
|
||||||
CHECKED,
|
|
||||||
INDETERMINATE,
|
|
||||||
UNCHECKED,
|
|
||||||
} from '../../../components/ui/check-box';
|
|
||||||
import Tooltip from '../../../components/ui/tooltip';
|
|
||||||
import { PRIMARY } from '../../../helpers/constants/common';
|
|
||||||
import UserPreferencedCurrencyDisplay from '../../../components/app/user-preferenced-currency-display';
|
|
||||||
import PermissionsConnectHeader from '../../../components/app/permissions-connect-header';
|
|
||||||
import PermissionsConnectFooter from '../../../components/app/permissions-connect-footer';
|
|
||||||
|
|
||||||
export default class ChooseAccount extends Component {
|
|
||||||
static propTypes = {
|
|
||||||
accounts: PropTypes.arrayOf(
|
|
||||||
PropTypes.shape({
|
|
||||||
address: PropTypes.string,
|
|
||||||
addressLabel: PropTypes.string,
|
|
||||||
lastConnectedDate: PropTypes.string,
|
|
||||||
balance: PropTypes.string,
|
|
||||||
}),
|
|
||||||
).isRequired,
|
|
||||||
selectAccounts: PropTypes.func.isRequired,
|
|
||||||
selectNewAccountViaModal: PropTypes.func.isRequired,
|
|
||||||
nativeCurrency: PropTypes.string.isRequired,
|
|
||||||
addressLastConnectedMap: PropTypes.object,
|
|
||||||
cancelPermissionsRequest: PropTypes.func.isRequired,
|
|
||||||
permissionsRequestId: PropTypes.string.isRequired,
|
|
||||||
selectedAccountAddresses: PropTypes.object.isRequired,
|
|
||||||
targetSubjectMetadata: PropTypes.shape({
|
|
||||||
extensionId: PropTypes.string,
|
|
||||||
iconUrl: PropTypes.string,
|
|
||||||
name: PropTypes.string.isRequired,
|
|
||||||
origin: PropTypes.string.isRequired,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
state = {
|
|
||||||
selectedAccounts: this.props.selectedAccountAddresses,
|
|
||||||
};
|
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
addressLastConnectedMap: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
static contextTypes = {
|
|
||||||
t: PropTypes.func,
|
|
||||||
};
|
|
||||||
|
|
||||||
handleAccountClick(address) {
|
|
||||||
const { selectedAccounts } = this.state;
|
|
||||||
|
|
||||||
const newSelectedAccounts = new Set(selectedAccounts);
|
|
||||||
|
|
||||||
if (newSelectedAccounts.has(address)) {
|
|
||||||
newSelectedAccounts.delete(address);
|
|
||||||
} else {
|
|
||||||
newSelectedAccounts.add(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ selectedAccounts: newSelectedAccounts });
|
|
||||||
}
|
|
||||||
|
|
||||||
selectAll() {
|
|
||||||
const { accounts } = this.props;
|
|
||||||
|
|
||||||
const newSelectedAccounts = new Set(
|
|
||||||
accounts.map((account) => account.address),
|
|
||||||
);
|
|
||||||
|
|
||||||
this.setState({ selectedAccounts: newSelectedAccounts });
|
|
||||||
}
|
|
||||||
|
|
||||||
deselectAll() {
|
|
||||||
this.setState({ selectedAccounts: new Set() });
|
|
||||||
}
|
|
||||||
|
|
||||||
allAreSelected() {
|
|
||||||
const { accounts } = this.props;
|
|
||||||
const { selectedAccounts } = this.state;
|
|
||||||
|
|
||||||
return accounts.every(({ address }) => selectedAccounts.has(address));
|
|
||||||
}
|
|
||||||
|
|
||||||
renderAccountsList = () => {
|
|
||||||
const { accounts, nativeCurrency, addressLastConnectedMap } = this.props;
|
|
||||||
const { selectedAccounts } = this.state;
|
|
||||||
return (
|
|
||||||
<div className="permissions-connect-choose-account__accounts-list">
|
|
||||||
{accounts.map((account, index) => {
|
|
||||||
const { address, addressLabel, balance } = account;
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={`permissions-connect-choose-account-${index}`}
|
|
||||||
onClick={() => this.handleAccountClick(address)}
|
|
||||||
className="permissions-connect-choose-account__account"
|
|
||||||
>
|
|
||||||
<div className="permissions-connect-choose-account__account-info-wrapper">
|
|
||||||
<CheckBox
|
|
||||||
className="permissions-connect-choose-account__list-check-box"
|
|
||||||
checked={selectedAccounts.has(address)}
|
|
||||||
/>
|
|
||||||
<Identicon diameter={34} address={address} />
|
|
||||||
<div className="permissions-connect-choose-account__account__info">
|
|
||||||
<div className="permissions-connect-choose-account__account__label">
|
|
||||||
{addressLabel}
|
|
||||||
</div>
|
|
||||||
<UserPreferencedCurrencyDisplay
|
|
||||||
className="permissions-connect-choose-account__account__balance"
|
|
||||||
type={PRIMARY}
|
|
||||||
value={balance}
|
|
||||||
style={{ color: '#6A737D' }}
|
|
||||||
suffix={nativeCurrency}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{addressLastConnectedMap[address] ? (
|
|
||||||
<Tooltip
|
|
||||||
title={`${this.context.t('lastConnected')} ${
|
|
||||||
addressLastConnectedMap[address]
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<i className="fa fa-info-circle" />
|
|
||||||
</Tooltip>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
renderAccountsListHeader() {
|
|
||||||
const { t } = this.context;
|
|
||||||
const { selectNewAccountViaModal, accounts } = this.props;
|
|
||||||
const { selectedAccounts } = this.state;
|
|
||||||
|
|
||||||
let checked;
|
|
||||||
if (this.allAreSelected()) {
|
|
||||||
checked = CHECKED;
|
|
||||||
} else if (selectedAccounts.size === 0) {
|
|
||||||
checked = UNCHECKED;
|
|
||||||
} else {
|
|
||||||
checked = INDETERMINATE;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={classnames({
|
|
||||||
'permissions-connect-choose-account__accounts-list-header--one-item':
|
|
||||||
accounts.length === 1,
|
|
||||||
'permissions-connect-choose-account__accounts-list-header--two-items':
|
|
||||||
accounts.length > 1,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{accounts.length > 1 ? (
|
|
||||||
<div className="permissions-connect-choose-account__select-all">
|
|
||||||
<CheckBox
|
|
||||||
className="permissions-connect-choose-account__header-check-box"
|
|
||||||
checked={checked}
|
|
||||||
onClick={() =>
|
|
||||||
this.allAreSelected() ? this.deselectAll() : this.selectAll()
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<div className="permissions-connect-choose-account__text-grey">
|
|
||||||
{this.context.t('selectAll')}
|
|
||||||
</div>
|
|
||||||
<Tooltip
|
|
||||||
position="bottom"
|
|
||||||
html={
|
|
||||||
<div style={{ width: 200, padding: 4 }}>
|
|
||||||
{t('selectingAllWillAllow')}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<i className="fa fa-info-circle" />
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
<div
|
|
||||||
className="permissions-connect-choose-account__text-blue"
|
|
||||||
onClick={() =>
|
|
||||||
selectNewAccountViaModal(this.handleAccountClick.bind(this))
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{this.context.t('newAccount')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
selectAccounts,
|
|
||||||
permissionsRequestId,
|
|
||||||
cancelPermissionsRequest,
|
|
||||||
targetSubjectMetadata,
|
|
||||||
accounts,
|
|
||||||
} = this.props;
|
|
||||||
const { selectedAccounts } = this.state;
|
|
||||||
const { t } = this.context;
|
|
||||||
return (
|
|
||||||
<div className="permissions-connect-choose-account">
|
|
||||||
<PermissionsConnectHeader
|
|
||||||
icon={targetSubjectMetadata.iconUrl}
|
|
||||||
iconName={targetSubjectMetadata.name}
|
|
||||||
headerTitle={t('connectWithMetaMask')}
|
|
||||||
headerText={
|
|
||||||
accounts.length > 0
|
|
||||||
? t('selectAccounts')
|
|
||||||
: t('connectAccountOrCreate')
|
|
||||||
}
|
|
||||||
siteOrigin={targetSubjectMetadata.origin}
|
|
||||||
/>
|
|
||||||
{this.renderAccountsListHeader()}
|
|
||||||
{this.renderAccountsList()}
|
|
||||||
<div className="permissions-connect-choose-account__footer-container">
|
|
||||||
<PermissionsConnectFooter />
|
|
||||||
<div className="permissions-connect-choose-account__bottom-buttons">
|
|
||||||
<Button
|
|
||||||
onClick={() => cancelPermissionsRequest(permissionsRequestId)}
|
|
||||||
type="secondary"
|
|
||||||
>
|
|
||||||
{t('cancel')}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={() => selectAccounts(selectedAccounts)}
|
|
||||||
type="primary"
|
|
||||||
disabled={selectedAccounts.size === 0}
|
|
||||||
>
|
|
||||||
{t('next')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
148
ui/pages/permissions-connect/choose-account/choose-account.js
Normal file
148
ui/pages/permissions-connect/choose-account/choose-account.js
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||||
|
import Button from '../../../components/ui/button';
|
||||||
|
import PermissionsConnectHeader from '../../../components/app/permissions-connect-header';
|
||||||
|
import PermissionsConnectFooter from '../../../components/app/permissions-connect-footer';
|
||||||
|
import AccountList from '../../../components/ui/account-list';
|
||||||
|
|
||||||
|
const ChooseAccount = ({
|
||||||
|
selectedAccountAddresses,
|
||||||
|
addressLastConnectedMap = {},
|
||||||
|
accounts,
|
||||||
|
selectAccounts,
|
||||||
|
selectNewAccountViaModal,
|
||||||
|
cancelPermissionsRequest,
|
||||||
|
permissionsRequestId,
|
||||||
|
targetSubjectMetadata,
|
||||||
|
nativeCurrency,
|
||||||
|
}) => {
|
||||||
|
const [selectedAccounts, setSelectedAccounts] = useState(
|
||||||
|
selectedAccountAddresses,
|
||||||
|
);
|
||||||
|
const t = useI18nContext();
|
||||||
|
|
||||||
|
const handleAccountClick = (address) => {
|
||||||
|
const newSelectedAccounts = new Set(selectedAccounts);
|
||||||
|
if (newSelectedAccounts.has(address)) {
|
||||||
|
newSelectedAccounts.delete(address);
|
||||||
|
} else {
|
||||||
|
newSelectedAccounts.add(address);
|
||||||
|
}
|
||||||
|
setSelectedAccounts(newSelectedAccounts);
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectAll = () => {
|
||||||
|
const newSelectedAccounts = new Set(
|
||||||
|
accounts.map((account) => account.address),
|
||||||
|
);
|
||||||
|
setSelectedAccounts(newSelectedAccounts);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deselectAll = () => {
|
||||||
|
setSelectedAccounts(new Set());
|
||||||
|
};
|
||||||
|
|
||||||
|
const allAreSelected = () => {
|
||||||
|
return accounts.length === selectedAccounts.size;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="permissions-connect-choose-account">
|
||||||
|
<PermissionsConnectHeader
|
||||||
|
iconUrl={targetSubjectMetadata?.iconUrl}
|
||||||
|
iconName={targetSubjectMetadata?.name}
|
||||||
|
headerTitle={t('connectWithMetaMask')}
|
||||||
|
headerText={
|
||||||
|
accounts.length > 0
|
||||||
|
? t('selectAccounts')
|
||||||
|
: t('connectAccountOrCreate')
|
||||||
|
}
|
||||||
|
siteOrigin={targetSubjectMetadata?.origin}
|
||||||
|
/>
|
||||||
|
<AccountList
|
||||||
|
accounts={accounts}
|
||||||
|
selectNewAccountViaModal={selectNewAccountViaModal}
|
||||||
|
addressLastConnectedMap={addressLastConnectedMap}
|
||||||
|
nativeCurrency={nativeCurrency}
|
||||||
|
selectedAccounts={selectedAccounts}
|
||||||
|
allAreSelected={allAreSelected}
|
||||||
|
deselectAll={deselectAll}
|
||||||
|
selectAll={selectAll}
|
||||||
|
handleAccountClick={handleAccountClick}
|
||||||
|
/>
|
||||||
|
<div className="permissions-connect-choose-account__footer-container">
|
||||||
|
<PermissionsConnectFooter />
|
||||||
|
<div className="permissions-connect-choose-account__bottom-buttons">
|
||||||
|
<Button
|
||||||
|
onClick={() => cancelPermissionsRequest(permissionsRequestId)}
|
||||||
|
type="secondary"
|
||||||
|
>
|
||||||
|
{t('cancel')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => selectAccounts(selectedAccounts)}
|
||||||
|
type="primary"
|
||||||
|
disabled={selectedAccounts.size === 0}
|
||||||
|
>
|
||||||
|
{t('next')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ChooseAccount.propTypes = {
|
||||||
|
/**
|
||||||
|
* Array of user account objects
|
||||||
|
*/
|
||||||
|
accounts: PropTypes.arrayOf(
|
||||||
|
PropTypes.shape({
|
||||||
|
address: PropTypes.string,
|
||||||
|
addressLabel: PropTypes.string,
|
||||||
|
lastConnectedDate: PropTypes.string,
|
||||||
|
balance: PropTypes.string,
|
||||||
|
}),
|
||||||
|
).isRequired,
|
||||||
|
/**
|
||||||
|
* Function to select an account
|
||||||
|
*/
|
||||||
|
selectAccounts: PropTypes.func.isRequired,
|
||||||
|
/**
|
||||||
|
* Function to select a new account via modal
|
||||||
|
*/
|
||||||
|
selectNewAccountViaModal: PropTypes.func.isRequired,
|
||||||
|
/**
|
||||||
|
* Native currency of current chain
|
||||||
|
*/
|
||||||
|
nativeCurrency: PropTypes.string.isRequired,
|
||||||
|
/**
|
||||||
|
* A map of the last connected addresses
|
||||||
|
*/
|
||||||
|
addressLastConnectedMap: PropTypes.object,
|
||||||
|
/**
|
||||||
|
* Function to cancel permission request
|
||||||
|
*/
|
||||||
|
cancelPermissionsRequest: PropTypes.func.isRequired,
|
||||||
|
/**
|
||||||
|
* Permission request Id
|
||||||
|
*/
|
||||||
|
permissionsRequestId: PropTypes.string.isRequired,
|
||||||
|
/**
|
||||||
|
* Currently selected account addresses
|
||||||
|
*/
|
||||||
|
selectedAccountAddresses: PropTypes.object.isRequired,
|
||||||
|
/**
|
||||||
|
* Domain data used to display site-origin pill
|
||||||
|
*/
|
||||||
|
targetSubjectMetadata: PropTypes.shape({
|
||||||
|
extensionId: PropTypes.string,
|
||||||
|
iconUrl: PropTypes.string,
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
origin: PropTypes.string.isRequired,
|
||||||
|
subjectType: PropTypes.string,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChooseAccount;
|
@ -1 +1 @@
|
|||||||
export { default } from './choose-account.component';
|
export { default } from './choose-account';
|
||||||
|
@ -7,143 +7,14 @@
|
|||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
.fa-info-circle {
|
|
||||||
color: $Grey-200;
|
|
||||||
cursor: pointer;
|
|
||||||
margin-left: 8px;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fa-info-circle:hover {
|
|
||||||
color: $Grey-300;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (min-width: $break-large) {
|
@media screen and (min-width: $break-large) {
|
||||||
width: 426px;
|
width: 426px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
&__title {
|
&__title {
|
||||||
@include H4;
|
@include H4;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__text,
|
|
||||||
&__text-blue,
|
|
||||||
&__text-grey {
|
|
||||||
@include H6;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__text-blue {
|
|
||||||
color: $primary-blue;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__text-grey {
|
|
||||||
color: $Grey-500;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__accounts-list {
|
|
||||||
flex: 2 1 0;
|
|
||||||
width: 92%;
|
|
||||||
max-height: max-content;
|
|
||||||
border: 1px solid #d0d5da;
|
|
||||||
box-sizing: border-box;
|
|
||||||
border-radius: 8px;
|
|
||||||
margin-top: 8px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__accounts-list-header--one-item,
|
|
||||||
&__accounts-list-header--two-items {
|
|
||||||
display: flex;
|
|
||||||
flex: 0;
|
|
||||||
margin-top: 36px;
|
|
||||||
width: 100%;
|
|
||||||
padding-left: 15px;
|
|
||||||
padding-right: 17px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__accounts-list-header--one-item {
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__accounts-list-header--two-items {
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__account-info-wrapper {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: center;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__list-check-box {
|
|
||||||
margin-right: 16px;
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__header-check-box {
|
|
||||||
margin-right: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__select-all {
|
|
||||||
display: flex;
|
|
||||||
margin-left: 16px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__account {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 16px;
|
|
||||||
border-bottom: 1px solid #d2d8dd;
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
&:last-of-type {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $Grey-000;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__info {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
margin-left: 16px;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__label {
|
|
||||||
@include H6;
|
|
||||||
|
|
||||||
color: $Black-100;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__balance {
|
|
||||||
@include H7;
|
|
||||||
|
|
||||||
color: $Grey-500;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__last-connected {
|
|
||||||
@include H8;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-end;
|
|
||||||
color: $primary-blue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__cancel {
|
&__cancel {
|
||||||
color: $Red-400;
|
color: $Red-400;
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,7 @@ export default class PermissionConnect extends Component {
|
|||||||
iconUrl: PropTypes.string,
|
iconUrl: PropTypes.string,
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
origin: PropTypes.string.isRequired,
|
origin: PropTypes.string.isRequired,
|
||||||
|
subjectType: PropTypes.string,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ import {
|
|||||||
CONNECT_ROUTE,
|
CONNECT_ROUTE,
|
||||||
CONNECT_CONFIRM_PERMISSIONS_ROUTE,
|
CONNECT_CONFIRM_PERMISSIONS_ROUTE,
|
||||||
} from '../../helpers/constants/routes';
|
} from '../../helpers/constants/routes';
|
||||||
|
import { SUBJECT_TYPES } from '../../../shared/constants/app';
|
||||||
import PermissionApproval from './permissions-connect.component';
|
import PermissionApproval from './permissions-connect.component';
|
||||||
|
|
||||||
const mapStateToProps = (state, ownProps) => {
|
const mapStateToProps = (state, ownProps) => {
|
||||||
@ -46,7 +47,7 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
let targetSubjectMetadata = null;
|
let targetSubjectMetadata = null;
|
||||||
if (origin) {
|
if (origin) {
|
||||||
if (subjectMetadata[origin]) {
|
if (subjectMetadata[origin]) {
|
||||||
targetSubjectMetadata = { ...subjectMetadata[origin], origin };
|
targetSubjectMetadata = subjectMetadata[origin];
|
||||||
} else {
|
} else {
|
||||||
const targetUrl = new URL(origin);
|
const targetUrl = new URL(origin);
|
||||||
targetSubjectMetadata = {
|
targetSubjectMetadata = {
|
||||||
@ -54,6 +55,7 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
origin,
|
origin,
|
||||||
iconUrl: null,
|
iconUrl: null,
|
||||||
extensionId: null,
|
extensionId: null,
|
||||||
|
subjectType: SUBJECT_TYPES.UNKNOWN,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user