1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

Update Connected Sites modal design (#8262)

This commit is contained in:
Whymarrh Whitby 2020-03-31 19:40:02 -02:30 committed by GitHub
parent 2d66e90d07
commit cb7f81bb42
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 353 additions and 590 deletions

View File

@ -1,4 +1,22 @@
{
"connectedSites": {
"message": "Connected Sites"
},
"connectedSitesDescription": {
"message": "$1 is connected to these sites. They can view your account address."
},
"connectManually": {
"message": "Manually connect to current site"
},
"disconnect": {
"message": "Disconnect"
},
"disconnectSite": {
"message": "Disconnect $1?"
},
"disconnectAccountConfirmationDescription": {
"message": "Are you sure you want to disconnect? You may lose site functionality."
},
"migrateSai": {
"message": "A message from Maker: The new Multi-Collateral Dai token has been released. Your old tokens are now called Sai. Please upgrade your Sai tokens to the new Dai."
},
@ -20,16 +38,9 @@
"chartOnlyAvailableEth": {
"message": "Chart only available on Ethereum networks."
},
"connectedSites": {
"message": "Connected Sites"
},
"connectingWithMetaMask": {
"message": "Connecting With MetaMask..."
},
"connectTo": {
"message": "Connect to $1",
"description": "$1 is the name/origin of a site/dapp that the user can connect to metamask"
},
"chooseAnAcount": {
"message": "Choose an account"
},
@ -408,24 +419,12 @@
"details": {
"message": "Details"
},
"disconnectAccount": {
"message": "Disconnect account"
},
"disconnectAll": {
"message": "Disconnect All"
},
"disconnectAllModalDescription": {
"message": "Are you sure? You will be disconnected from all sites on all accounts."
},
"disconnectAccountModalDescription": {
"message": "Are you sure? Your account (\"$1\") will be disconnected from this site."
},
"disconnectAccountQuestion": {
"message": "Disconnect account?"
},
"disconnectFromThisAccount": {
"message": "Disconnect from this account?"
},
"disconnectAllAccountsQuestion": {
"message": "Disconnect all accounts?"
},
@ -435,10 +434,6 @@
"directDepositEtherExplainer": {
"message": "If you already have some Ether, the quickest way to get Ether in your new wallet by direct deposit."
},
"domainLastConnect": {
"message": "Last Connected: $1",
"description": "$1 is the date at which the user was last connected to a given domain"
},
"done": {
"message": "Done"
},
@ -499,10 +494,6 @@
"endOfFlowMessage10": {
"message": "All Done"
},
"extensionId": {
"message": "Extension ID: $1",
"description": "$1 is a string of random letters that are the id of another extension connecting to MetaMask"
},
"externalExtension": {
"message": "External Extension"
},

View File

@ -26,10 +26,6 @@
"connectingWithMetaMask": {
"message": "Connettendo con MetaMask..."
},
"connectTo": {
"message": "Collegati a $1",
"description": "$1 is the name/origin of a site/dapp that the user can connect to metamask"
},
"chooseAnAcount": {
"message": "Scegli un account"
},
@ -408,24 +404,12 @@
"details": {
"message": "Dettagli"
},
"disconnectAccount": {
"message": "Disconnetti account"
},
"disconnectAll": {
"message": "Disconnetti Tutti"
},
"disconnectAllModalDescription": {
"message": "Sei sicuro? Sarai disconnesso da tutti i siti su tutti gli account."
},
"disconnectAccountModalDescription": {
"message": "Sei sicuro? Il tuo account (\"$1\") sarà disconnesso da questo sito."
},
"disconnectAccountQuestion": {
"message": "Disconnettere account?"
},
"disconnectFromThisAccount": {
"message": "Disconnettersi da questo account?"
},
"disconnectAllAccountsQuestion": {
"message": "Disconnettere tutti gli account?"
},
@ -435,10 +419,6 @@
"directDepositEtherExplainer": {
"message": "Se possiedi già degli Ether, questa è la via più veloce per aggiungere Ether al tuo portafoglio con un deposito diretto."
},
"domainLastConnect": {
"message": "Ultima Connessione: $1",
"description": "$1 is the date at which the user was last connected to a given domain"
},
"done": {
"message": "Finito"
},
@ -499,10 +479,6 @@
"endOfFlowMessage10": {
"message": "Tutto Fatto"
},
"extensionId": {
"message": "ID Estensione: $1",
"description": "$1 is a string of random letters that are the id of another extension connecting to MetaMask"
},
"externalExtension": {
"message": "Estensione Esterna"
},

View File

@ -137,16 +137,8 @@ describe('MetaMask', function () {
await driver.findElement(By.xpath(`//h2[contains(text(), 'Connected Sites')]`))
const domains = await driver.findClickableElements(By.css('.connected-sites-list__domain'))
const domains = await driver.findClickableElements(By.css('.connected-sites__domain-name'))
assert.equal(domains.length, 1)
const domainName = await driver.findElement(By.css('.connected-sites-list__domain-name'))
assert.equal(await domainName.getText(), 'E2E Test Dapp')
await domains[0].click()
const permissionDescription = await driver.findElement(By.css('.connected-sites-list__permission-description'))
assert.equal(await permissionDescription.getText(), `View the addresses of the user's chosen accounts.`)
})
it('can get accounts within the dapp', async function () {
@ -158,29 +150,5 @@ describe('MetaMask', function () {
const getAccountsResult = await driver.findElement(By.css('#getAccountsResult'))
assert.equal((await getAccountsResult.getText()).toLowerCase(), publicAddress.toLowerCase())
})
it('can disconnect all accounts', async function () {
await driver.switchToWindow(extension)
await driver.clickElement(By.xpath(`//button[contains(text(), 'Disconnect All')]`))
await driver.clickElement(By.css('.popover-header__close'))
const disconnectModal = await driver.findElement(By.css('span .modal'))
await driver.clickElement(By.css('.disconnect-all-modal .btn-danger'))
await driver.wait(until.stalenessOf(disconnectModal))
await driver.delay(regularDelayMs)
})
it('can no longer get accounts within the dapp', async function () {
await driver.switchToWindow(dapp)
await driver.delay(regularDelayMs)
await driver.clickElement(By.xpath(`//button[contains(text(), 'eth_accounts')]`))
const getAccountsResult = await driver.findElement(By.css('#getAccountsResult'))
assert.equal(await getAccountsResult.getText(), 'Not able to get accounts')
})
})
})

View File

@ -1,7 +1,5 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import Button from '../../ui/button'
import IconWithFallBack from '../../ui/icon-with-fallback'
export default class ConnectedSitesList extends Component {
@ -10,148 +8,40 @@ export default class ConnectedSitesList extends Component {
}
static propTypes = {
renderableDomains: PropTypes.arrayOf(PropTypes.shape({
connectedDomains: PropTypes.arrayOf(PropTypes.shape({
name: PropTypes.string,
icon: PropTypes.string,
key: PropTypes.string,
lastConnectedTime: PropTypes.string,
permissionDescriptions: PropTypes.array,
})).isRequired,
domains: PropTypes.object,
showDisconnectAccountModal: PropTypes.func.isRequired,
showDisconnectAllModal: PropTypes.func.isRequired,
tabToConnect: PropTypes.object,
legacyExposeAccounts: PropTypes.func.isRequired,
selectedAddress: PropTypes.string.isRequired,
getOpenMetamaskTabsIds: PropTypes.func.isRequired,
}
state = {
expandedDomain: '',
}
UNSAFE_componentWillMount () {
const { getOpenMetamaskTabsIds } = this.props
getOpenMetamaskTabsIds()
}
handleDomainItemClick (domainKey) {
if (this.state.expandedDomain === domainKey) {
this.setState({ expandedDomain: '' })
} else {
this.setState({ expandedDomain: domainKey })
}
onDisconnectSite: PropTypes.func.isRequired,
}
render () {
const {
renderableDomains,
domains,
showDisconnectAccountModal,
showDisconnectAllModal,
tabToConnect,
legacyExposeAccounts,
selectedAddress,
} = this.props
const { expandedDomain } = this.state
const { connectedDomains, onDisconnectSite } = this.props
const { t } = this.context
return (
<div className="connected-sites-list">
{
renderableDomains.map((domain, domainIndex) => {
const domainIsExpanded = expandedDomain === domain.key
return (
<div
className={classnames('connected-sites-list__domain', {
'connected-sites-list__domain--expanded': domainIsExpanded,
})}
key={`connected-domain-${domainIndex}`}
>
<div className="connected-sites-list__domain-item" onClick={ () => this.handleDomainItemClick(domain.key) }>
<div className="connected-sites-list__domain-item-info-container">
<IconWithFallBack icon={domain.icon} name={domain.name} />
<div className="connected-sites-list__domain-info">
<div className="connected-sites-list__domain-names">
<div className="connected-sites-list__domain-name">
{ domain.extensionId ? t('externalExtension') : domain.name }
</div>
</div>
{ domain.lastConnectedTime
? (
<div className="connected-sites-list__domain-last-connected">
{ t('domainLastConnect', [domain.lastConnectedTime]) }
</div>
)
: null
}
{domainIsExpanded
? (
<div className="connected-sites-list__domain-origin">
{ domain.extensionId ? t('extensionId', [domain.extensionId]) : domain.secondaryName }
</div>
)
: null
}
</div>
</div>
<div className="connected-sites-list__expand-arrow">
{ domainIsExpanded ? <i className="fa fa-chevron-up fa-sm" /> : <i className="fa fa-chevron-down fa-sm" /> }
</div>
</div>
{ domainIsExpanded
? (
<div className="connected-sites-list__permissions">
<div className="connected-sites-list__permission-list">
{
domain.permissionDescriptions.map((description, pdIndex) => {
return (
<div className="connected-sites-list__permission" key={`permissionDescription-${pdIndex}`}>
<i className="fa fa-check-square fa-sm" />
<div className="connected-sites-list__permission-description">
{ description }
</div>
</div>
)
})
}
</div>
<div
className="connected-sites-list__disconnect"
onClick={ () => showDisconnectAccountModal(domain.key, domains[domain.key]) }
>
{ t('disconnectAccount') }
</div>
</div>
)
: null
<main className="connected-sites__content-rows">
{ connectedDomains.map((domain) => (
<div key={domain.key} className="connected-sites__content-row">
<div className="connected-sites__domain-info">
<IconWithFallBack icon={domain.icon} name={domain.name} />
<span className="connected-sites__domain-name" title={domain.extensionId || domain.key}>
{
domain.extensionId
? t('externalExtension')
: domain.key
}
</div>
)
})
}
<div className="connected-sites-list__bottom-buttons">
<div className="connected-sites-list__disconnect-all">
<Button onClick={showDisconnectAllModal} type="danger" >
{ t('disconnectAll') }
</Button>
</span>
</div>
<i
className="fas fa-trash-alt connected-sites__trash"
title={t('disconnect')}
onClick={() => onDisconnectSite(domain.key, domain.name)}
/>
</div>
{ tabToConnect
? (
<div className="connected-sites-list__connect-to">
<Button
onClick={() => legacyExposeAccounts(tabToConnect.origin, selectedAddress)}
type="primary"
>
{ t('connectTo', [tabToConnect.title || tabToConnect.origin]) }
</Button>
</div>
)
: null
}
</div>
</div>
)) }
</main>
)
}
}

View File

@ -1,54 +1,11 @@
import { connect } from 'react-redux'
import ConnectedSitesList from './connected-sites-list.component'
import {
showModal,
legacyExposeAccounts,
getOpenMetamaskTabsIds,
} from '../../../store/actions'
import {
getRenderablePermissionsDomains,
getPermissionsDomains,
getSelectedAddress,
getPermittedAccountsForCurrentTab,
} from '../../../selectors/selectors'
import { getOriginFromUrl } from '../../../helpers/utils/util'
import { getRenderablePermissionsDomains } from '../../../selectors/selectors'
const mapStateToProps = (state) => {
const { openMetaMaskTabs } = state.appState
const { title, url, id } = state.activeTab
const permittedAccounts = getPermittedAccountsForCurrentTab(state)
let tabToConnect
if (url && permittedAccounts.length === 0 && !openMetaMaskTabs[id]) {
tabToConnect = {
title,
origin: getOriginFromUrl(url),
}
}
return {
domains: getPermissionsDomains(state),
renderableDomains: getRenderablePermissionsDomains(state),
tabToConnect,
selectedAddress: getSelectedAddress(state),
connectedDomains: getRenderablePermissionsDomains(state),
}
}
const mapDispatchToProps = (dispatch) => {
return {
showDisconnectAccountModal: (domainKey, domain) => {
dispatch(showModal({ name: 'DISCONNECT_ACCOUNT', domainKey, domain }))
},
showDisconnectAllModal: () => {
dispatch(showModal({ name: 'DISCONNECT_ALL' }))
},
legacyExposeAccounts: (origin, account) => {
dispatch(legacyExposeAccounts(origin, [account]))
},
getOpenMetamaskTabsIds: () => dispatch(getOpenMetamaskTabsIds()),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ConnectedSitesList)
export default connect(mapStateToProps)(ConnectedSitesList)

View File

@ -1,125 +1,37 @@
.connected-sites-list {
font-family: Roboto;
font-style: normal;
font-weight: normal;
&__domain, &__domain--expanded {
border-bottom: 1px solid #c4c4c4;
}
&__domain {
cursor: pointer;
}
&__domain--expanded {
background: #FAFBFC;
cursor: initial;
}
&__domain-item {
.connected-sites {
&__content-rows {
display: flex;
flex-direction: column;
align-items: center;
border-top: 1px solid #D2D8DD;
}
&__content-row {
display: flex;
width: 100%;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 16px;
}
&__domain-item-info-container {
display: flex;
}
&__identicon-container {
position: relative;
display: flex;
justify-content: center;
align-items: center;
height: 32px;
width: 32px;
}
&__identicon-border {
height: 32px;
width: 32px;
border-radius: 50%;
border: 1px solid #F2F3F4;
position: absolute;
background: #FFFFFF;
}
&__identicon {
width: 24px;
height: 24px;
z-index: 1;
&--default {
z-index: 1;
color: black;
}
border-bottom: 1px solid #D2D8DD;
padding: 16px 24px;
}
&__domain-info {
display: flex;
flex-direction: column;
margin-left: 16px;
}
&__domain-names {
display: flex;
flex-direction: row;
align-items: center;
min-width: 0;
font-size: 12px;
}
&__domain-name {
font-size: 18px;
color: #24292E;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-left: 6px;
}
&__domain-origin, &__domain-last-connected {
font-size: 12px;
color: #6A737D;
}
&__domain-last-connected {
margin-top: 2px;
}
&__expand-arrow {
align-self: flex-start;
margin-top: 6px;
}
&__permissions {
padding-left: 16px;
padding-bottom: 24px;
}
&__permission {
display: flex;
align-items: center;
color: #6A737D;
margin-left: 16px;
}
&__permission-description {
margin-left: 18px;
}
&__disconnect {
margin-top: 24px;
color: #D73A49;
&__trash {
cursor: pointer;
}
&__bottom-buttons {
display: flex;
align-items: center;
}
&__disconnect-all {
padding: 10px;
width: 50%;
}
&__connect-to {
padding: 10px;
width: 50%;
}
}
}

View File

@ -1,52 +0,0 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import Modal from '../../modal'
import Button from '../../../ui/button'
export default class DisconnectAccount extends PureComponent {
static propTypes = {
hideModal: PropTypes.func.isRequired,
disconnectAccount: PropTypes.func.isRequired,
accountLabel: PropTypes.string.isRequired,
}
static contextTypes = {
t: PropTypes.func,
}
render () {
const { t } = this.context
const { hideModal, disconnectAccount, accountLabel } = this.props
return (
<Modal
headerText={t('disconnectAccountQuestion')}
onClose={() => hideModal()}
hideFooter
>
<div className="disconnect-account-modal">
<div className="disconnect-account-modal__description">
{ t('disconnectAccountModalDescription', [ accountLabel ]) }
</div>
<Button
type="primary"
onClick={ () => {
disconnectAccount()
hideModal()
}}
>
{ t('disconnectFromThisAccount') }
</Button>
<Button
type="secondary"
onClick={ () => hideModal() }
className="disconnect-account-modal__cancel-button"
>
{ t('cancel') }
</Button>
</div>
</Modal>
)
}
}

View File

@ -1,44 +0,0 @@
import { connect } from 'react-redux'
import { compose } from 'redux'
import withModalProps from '../../../../helpers/higher-order-components/with-modal-props'
import DisconnectAccount from './disconnect-account.component'
import { getCurrentAccountWithSendEtherInfo } from '../../../../selectors/selectors'
import { removePermissionsFor } from '../../../../store/actions'
const mapStateToProps = (state) => {
return {
...(state.appState.modal.modalState.props || {}),
accountLabel: getCurrentAccountWithSendEtherInfo(state).name,
}
}
const mapDispatchToProps = (dispatch) => {
return {
disconnectAccount: (domainKey, domain) => {
const permissionMethodNames = domain.permissions.map((perm) => perm.parentCapability)
dispatch(removePermissionsFor({ [domainKey]: permissionMethodNames }))
},
}
}
const mergeProps = (stateProps, dispatchProps, ownProps) => {
const {
domainKey,
domain,
} = stateProps
const {
disconnectAccount: dispatchDisconnectAccount,
} = dispatchProps
return {
...ownProps,
...stateProps,
...dispatchProps,
disconnectAccount: () => dispatchDisconnectAccount(domainKey, domain),
}
}
export default compose(
withModalProps,
connect(mapStateToProps, mapDispatchToProps, mergeProps)
)(DisconnectAccount)

View File

@ -1 +0,0 @@
export { default } from './disconnect-account.container'

View File

@ -1,25 +0,0 @@
.disconnect-account-modal {
&__description {
color: #24292E;
margin-bottom: 24px;
}
&__cancel-button {
border: none;
margin-top: 12px;
}
}
.disconnect-account-modal-container {
.modal-container__header-text {
@extend %header--18;
}
.modal-container__content {
padding-bottom: 18px;
@media screen and (max-width: 575px) {
padding-bottom: 18px;
}
}
}

View File

@ -12,8 +12,6 @@
@import './edit-approval-permission/index';
@import './disconnect-account/index';
@import './disconnect-all/index';
@import './new-account-modal/index';

View File

@ -29,7 +29,6 @@ import ConfirmDeleteNetwork from './confirm-delete-network'
import AddToAddressBookModal from './add-to-addressbook-modal'
import EditApprovalPermission from './edit-approval-permission'
import NewAccountModal from './new-account-modal'
import DisconnectAccount from './disconnect-account'
import DisconnectAll from './disconnect-all'
const modalContainerBaseStyle = {
@ -168,33 +167,6 @@ const MODALS = {
},
},
DISCONNECT_ACCOUNT: {
contents: <DisconnectAccount />,
mobileModalStyle: {
width: '95%',
top: '10%',
boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px',
transform: 'none',
left: '0',
right: '0',
margin: '0 auto',
borderRadius: '10px',
},
laptopModalStyle: {
width: '375px',
top: '10%',
boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px',
transform: 'none',
left: '0',
right: '0',
margin: '0 auto',
borderRadius: '10px',
},
contentStyle: {
borderRadius: '10px',
},
},
DISCONNECT_ALL: {
contents: <DisconnectAll />,
mobileModalStyle: {

View File

@ -10,6 +10,8 @@
background: #C4C4C4;
}
display: flex;
flex-direction: column;
position: absolute;
width: 328px;
height: 564px;
@ -20,24 +22,48 @@
}
&-header {
&__heading {
display: flex;
padding: 24px;
flex-direction: column;
&__title {
display: flex;
align-items: center;
justify-content: space-between;
@extend %font;
font-weight: bold;
font-size: 18px;
line-height: 25px;
text-align: center;
padding: 20px 0;
border-bottom: 1px solid #DDDEE9;
padding-bottom: 8px;
h2 {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
button {
margin-right: 24px;
}
}
}
&__close {
position: absolute;
top: 13px;
right: 6px;
margin: 10px;
&__subtitle {
@extend %font;
font-weight: normal;
font-size: 14px;
line-height: 20px;
}
&__button {
background: none;
font-size: inherit;
padding: 0;
}
i {
cursor: pointer;
}
}
&-bg {
@ -48,14 +74,10 @@
}
&-content {
padding: 0 5px 0 16px;
margin-top: 5px;
margin-bottom: -10px;
margin-right: 5px;
height: calc(100% - 66px - 10px);
overflow-y: scroll;
position: relative;
display: flex;
flex: 1;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;

View File

@ -1,23 +1,51 @@
import React, { PureComponent } from 'react'
import React, { PureComponent, useContext } from 'react'
import ReactDOM from 'react-dom'
import PropTypes from 'prop-types'
import PopoverHeader from './popover.header.component'
import { I18nContext } from '../../../contexts/i18n'
const Popover = ({ title, children, onClose }) => (
<div className="popover-container">
<div className="popover-bg" onClick={onClose} />
<div className="popover-wrap">
<PopoverHeader title={title} onClose={onClose} />
<div className="popover-content">
{children}
const Popover = ({ title, subtitle, children, onBack, onClose }) => {
const t = useContext(I18nContext)
return (
<div className="popover-container">
<div className="popover-bg" onClick={onClose} />
<div className="popover-wrap">
<header className="popover-header">
<div className="popover-header__title">
<h2 title={title}>
{
onBack
? (
<button
className="fas fa-chevron-left popover-header__button"
title={t('back')}
onClick={onBack}
/>
)
: null
}
{title}
</h2>
<button
className="fas fa-times popover-header__button"
title={t('close')}
onClick={onClose}
/>
</div>
<p className="popover-header__subtitle">{subtitle}</p>
</header>
<div className="popover-content">
{children}
</div>
</div>
</div>
</div>
)
)
}
Popover.propTypes = {
title: PropTypes.string.isRequired,
subtitle: PropTypes.string.isRequired,
children: PropTypes.node.isRequired,
onBack: PropTypes.func,
onClose: PropTypes.func.isRequired,
}

View File

@ -1,22 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import Close from '../icon/close-icon.component'
const PopoverHeader = ({ title, onClose }) => (
<header className="popover-header">
<h2 className="popover-header__heading">{title}</h2>
<button className="popover-header__close" onClick={onClose}>
<Close
size={18}
color="#4A4A4A"
/>
</button>
</header>
)
PopoverHeader.propTypes = {
title: PropTypes.string.isRequired,
onClose: PropTypes.func.isRequired,
}
export default PopoverHeader

View File

@ -10,15 +10,25 @@ const containerStyle = {
position: 'relative',
}
const mainWrapperStyle = {
padding: '0 24px 24px',
}
export default {
title: 'Popover',
}
export const approve = () => (
<div style={containerStyle}>
<Popover title={text('title', 'Approve spend limit')} onClose={action('clicked')}>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Semper eget duis at tellus at urna condimentum. Posuere urna nec tincidunt praesent semper. Arcu dictum varius duis at. A lacus vestibulum sed arcu. Orci porta non pulvinar neque laoreet suspendisse interdum. Pretium fusce id velit ut. Ut consequat semper viverra nam libero justo laoreet sit. In ante metus dictum at tempor commodo ullamcorper a lacus. Posuere morbi leo urna molestie at elementum eu facilisis sed. Libero enim sed faucibus turpis in eu mi bibendum neque. Amet massa vitae tortor condimentum lacinia quis. Pretium viverra suspendisse potenti nullam ac. Pellentesque elit eget gravida cum sociis natoque penatibus. Proin libero nunc consequat interdum varius sit amet. Est ultricies integer quis auctor elit sed vulputate. Ornare arcu odio ut sem nulla pharetra. Eget nullam non nisi est sit. Leo vel fringilla est ullamcorper eget nulla.</p>
<p>Mattis pellentesque id nibh tortor id. Commodo sed egestas egestas fringilla phasellus. Semper eget duis at tellus at urna. Tristique nulla aliquet enim tortor at auctor urna nunc. Pellentesque habitant morbi tristique senectus et netus et. Turpis egestas sed tempus urna et pharetra pharetra massa massa. Mi eget mauris pharetra et ultrices neque ornare aenean. Facilisis volutpat est velit egestas dui id ornare arcu odio. Lacus sed turpis tincidunt id aliquet risus feugiat in. Cras tincidunt lobortis feugiat vivamus. Blandit libero volutpat sed cras ornare arcu. Facilisi morbi tempus iaculis urna id volutpat. Risus viverra adipiscing at in tellus. Leo vel orci porta non pulvinar neque. Malesuada fames ac turpis egestas integer. Euismod nisi porta lorem mollis aliquam.</p>
<Popover
title={text('title', 'Approve spend limit')}
subtitle={text('subtitle', 'This is the new limit')}
onClose={action('clicked')}
>
<main style={mainWrapperStyle}>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Semper eget duis at tellus at urna condimentum. Posuere urna nec tincidunt praesent semper. Arcu dictum varius duis at. A lacus vestibulum sed arcu. Orci porta non pulvinar neque laoreet suspendisse interdum. Pretium fusce id velit ut. Ut consequat semper viverra nam libero justo laoreet sit. In ante metus dictum at tempor commodo ullamcorper a lacus. Posuere morbi leo urna molestie at elementum eu facilisis sed. Libero enim sed faucibus turpis in eu mi bibendum neque. Amet massa vitae tortor condimentum lacinia quis. Pretium viverra suspendisse potenti nullam ac. Pellentesque elit eget gravida cum sociis natoque penatibus. Proin libero nunc consequat interdum varius sit amet. Est ultricies integer quis auctor elit sed vulputate. Ornare arcu odio ut sem nulla pharetra. Eget nullam non nisi est sit. Leo vel fringilla est ullamcorper eget nulla.</p>
<p>Mattis pellentesque id nibh tortor id. Commodo sed egestas egestas fringilla phasellus. Semper eget duis at tellus at urna. Tristique nulla aliquet enim tortor at auctor urna nunc. Pellentesque habitant morbi tristique senectus et netus et. Turpis egestas sed tempus urna et pharetra pharetra massa massa. Mi eget mauris pharetra et ultrices neque ornare aenean. Facilisis volutpat est velit egestas dui id ornare arcu odio. Lacus sed turpis tincidunt id aliquet risus feugiat in. Cras tincidunt lobortis feugiat vivamus. Blandit libero volutpat sed cras ornare arcu. Facilisi morbi tempus iaculis urna id volutpat. Risus viverra adipiscing at in tellus. Leo vel orci porta non pulvinar neque. Malesuada fames ac turpis egestas integer. Euismod nisi porta lorem mollis aliquam.</p>
</main>
</Popover>
</div>
)

View File

@ -1,28 +1,115 @@
import PropTypes from 'prop-types'
import React, { Component } from 'react'
import ConnectedSitesList from '../../components/app/connected-sites-list'
import {
DEFAULT_ROUTE,
} from '../../helpers/constants/routes'
import Popover from '../../components/ui/popover/popover.component'
import { DEFAULT_ROUTE } from '../../helpers/constants/routes'
import Button from '../../components/ui/button'
export default class ConnectSites extends Component {
static propTypes = {
history: PropTypes.object.isRequired,
}
static contextTypes = {
t: PropTypes.func,
}
render () {
const {
history,
} = this.props
static defaultProps = {
tabToConnect: null,
}
static propTypes = {
accountLabel: PropTypes.string.isRequired,
disconnectAccount: PropTypes.func.isRequired,
history: PropTypes.object.isRequired,
tabToConnect: PropTypes.object,
legacyExposeAccount: PropTypes.func.isRequired,
getOpenMetamaskTabsIds: PropTypes.func.isRequired,
}
state = {
sitePendingDisconnect: null,
}
UNSAFE_componentWillMount () {
const { getOpenMetamaskTabsIds } = this.props
getOpenMetamaskTabsIds()
}
setSitePendingDisconnect = (domainKey, domainName) => {
this.setState({
sitePendingDisconnect: {
domainKey,
domainName,
},
})
}
clearSitePendingDisconnect = () => {
this.setState({
sitePendingDisconnect: null,
})
}
disconnect = () => {
const { disconnectAccount } = this.props
const { sitePendingDisconnect } = this.state
disconnectAccount(sitePendingDisconnect.domainKey)
this.clearSitePendingDisconnect()
}
renderConnectedSites () {
const { tabToConnect, legacyExposeAccount } = this.props
const { t } = this.context
return (
<Popover title={this.context.t('connectedSites')} onClose={() => history.push(DEFAULT_ROUTE)}>
<ConnectedSitesList />
</Popover>
<>
<ConnectedSitesList
onDisconnectSite={this.setSitePendingDisconnect}
/>
{ tabToConnect ? (
<footer className="connected-sites__add-site-manually">
<a onClick={legacyExposeAccount}>{ t('connectManually') }</a>
</footer>
) : null }
</>
)
}
renderDisconnectConfirmation () {
const { t } = this.context
return (
<div className="connected-sites__confirmation">
<Button type="secondary" onClick={this.clearSitePendingDisconnect}>
{ t('cancel') }
</Button>
<Button type="primary" onClick={this.disconnect}>
{ t('disconnect') }
</Button>
</div>
)
}
render () {
const { accountLabel, history } = this.props
const { t } = this.context
const { sitePendingDisconnect } = this.state
return (
sitePendingDisconnect
? (
<Popover
title={t('disconnectSite', [sitePendingDisconnect.domainName])}
subtitle={t('disconnectAccountConfirmationDescription')}
onClose={() => history.push(DEFAULT_ROUTE)}
>
{this.renderDisconnectConfirmation()}
</Popover>
)
: (
<Popover
title={t('connectedSites')}
subtitle={t('connectedSitesDescription', [accountLabel])}
onClose={() => history.push(DEFAULT_ROUTE)}
>
{this.renderConnectedSites()}
</Popover>
)
)
}
}

View File

@ -0,0 +1,62 @@
import { connect } from 'react-redux'
import ConnectedSites from './connected-sites.component'
import { getOpenMetamaskTabsIds, legacyExposeAccounts, removePermissionsFor } from '../../store/actions'
import {
getCurrentAccountWithSendEtherInfo,
getPermissionsDomains,
getPermittedAccountsForCurrentTab,
getSelectedAddress,
} from '../../selectors/selectors'
import { getOriginFromUrl } from '../../helpers/utils/util'
const mapStateToProps = (state) => {
const { openMetaMaskTabs } = state.appState
const { title, url, id } = state.activeTab
const permittedAccounts = getPermittedAccountsForCurrentTab(state)
let tabToConnect
if (url && permittedAccounts.length === 0 && !openMetaMaskTabs[id]) {
tabToConnect = {
title,
origin: getOriginFromUrl(url),
}
}
return {
accountLabel: getCurrentAccountWithSendEtherInfo(state).name,
domains: getPermissionsDomains(state),
selectedAddress: getSelectedAddress(state),
tabToConnect,
}
}
const mapDispatchToProps = (dispatch) => {
return {
getOpenMetamaskTabsIds: () => dispatch(getOpenMetamaskTabsIds()),
disconnectAccount: (domainKey, domain) => {
const permissionMethodNames = domain.permissions.map(({ parentCapability }) => parentCapability)
dispatch(removePermissionsFor({
[domainKey]: permissionMethodNames,
}))
},
legacyExposeAccounts: (origin, account) => dispatch(legacyExposeAccounts(origin, [account])),
}
}
const mergeProps = (stateProps, dispatchProps, ownProps) => {
const { domains, selectedAddress, tabToConnect } = stateProps
const {
disconnectAccount,
legacyExposeAccounts: dispatchLegacyExposeAccounts,
} = dispatchProps
return {
...ownProps,
...stateProps,
...dispatchProps,
disconnectAccount: (domainKey) => disconnectAccount(domainKey, domains[domainKey]),
legacyExposeAccount: () => dispatchLegacyExposeAccounts(tabToConnect.origin, selectedAddress),
}
}
export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(ConnectedSites)

View File

@ -1 +1 @@
export { default } from './connected-sites.component'
export { default } from './connected-sites.container'

View File

@ -0,0 +1,32 @@
.connected-sites {
&__confirmation {
display: flex;
flex-direction: row;
justify-content: space-between;
margin-top: 30px;
border-top: 1px solid #D2D8DD;
padding: 16px 24px 16px;
button:first-child {
margin-right: 24px;
}
}
&__add-site-manually {
position: sticky;
bottom: 0;
background: white;
border-top: 1px solid #D2D8DD;
margin-top: -1px;
padding: 16px 24px 24px 24px;
border-radius: 0 0 10px 10px;
font-size: 14px;
line-height: 20px;
z-index: 1;
a, a:hover {
cursor: pointer;
color: #037DD6;
}
}
}

View File

@ -10,7 +10,7 @@ import WalletView from '../../components/app/wallet-view'
import TransactionList from '../../components/app/transaction-list'
import TransactionViewBalance from '../../components/app/transaction-view-balance'
import MenuBar from '../../components/app/menu-bar'
import ConnectedSites from '../connected-sites/connected-sites.component'
import ConnectedSites from '../connected-sites'
import {
RESTORE_VAULT_ROUTE,

View File

@ -10,6 +10,8 @@
@import 'confirm-add-token/index';
@import 'connected-sites/index';
@import 'settings/index';
@import 'first-time-flow/index';