From 26ada8a828ab684c310080a18115a8ef3234aaee Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Mon, 5 Nov 2018 09:37:56 -0330 Subject: Update Connect Request screen design (#5644) * Parameterize NetworkDisplay background colour * Update design for login request screen * Pass siteTitle, siteImage through for calls to ethereum.enable() * Bring the site images closer together --- app/_locales/en/messages.json | 12 +-- app/images/mm-secure.svg | 7 ++ app/images/provider-approval-check.svg | 20 ++++ app/scripts/contentscript.js | 30 ++++++ app/scripts/controllers/provider-approval.js | 40 +++---- test/e2e/beta/metamask-beta-ui.spec.js | 2 +- ui/app/components/index.scss | 2 + ui/app/components/network-display/index.scss | 5 +- .../network-display/network-display.component.js | 17 ++- ui/app/components/pages/home/home.component.js | 4 +- .../provider-approval.component.js | 31 +++--- ui/app/components/provider-page-container/index.js | 3 + .../components/provider-page-container/index.scss | 120 +++++++++++++++++++++ .../provider-page-container-content/index.js | 1 + .../provider-page-container-content.component.js | 71 ++++++++++++ .../provider-page-container-content.container.js | 11 ++ .../provider-page-container-header/index.js | 1 + .../provider-page-container-header.component.js | 12 +++ .../provider-page-container.component.js | 50 +++++++++ 19 files changed, 388 insertions(+), 51 deletions(-) create mode 100644 app/images/mm-secure.svg create mode 100644 app/images/provider-approval-check.svg create mode 100644 ui/app/components/provider-page-container/index.js create mode 100644 ui/app/components/provider-page-container/index.scss create mode 100644 ui/app/components/provider-page-container/provider-page-container-content/index.js create mode 100644 ui/app/components/provider-page-container/provider-page-container-content/provider-page-container-content.component.js create mode 100644 ui/app/components/provider-page-container/provider-page-container-content/provider-page-container-content.container.js create mode 100644 ui/app/components/provider-page-container/provider-page-container-header/index.js create mode 100644 ui/app/components/provider-page-container/provider-page-container-header/provider-page-container-header.component.js create mode 100644 ui/app/components/provider-page-container/provider-page-container.component.js diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index f34a22bd5..95c9efeeb 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -32,14 +32,11 @@ "reject": { "message": "Reject" }, - "providerAPIRequest": { - "message": "Ethereum API Request" - }, - "reviewProviderRequest": { - "message": "Please review this Ethereum API request." + "providerRequest": { + "message": "$1 would like to connect to your account" }, "providerRequestInfo": { - "message": "The domain listed below is requesting access to the Ethereum blockchain and to view your current account. Always double check that you're on the correct site before approving access." + "message": "This site is requesting access to view your current account address. Always make sure you trust the sites you interact with." }, "accept": { "message": "Accept" @@ -212,6 +209,9 @@ "connect": { "message": "Connect" }, + "connectRequest": { + "message": "Connect Request" + }, "connecting": { "message": "Connecting..." }, diff --git a/app/images/mm-secure.svg b/app/images/mm-secure.svg new file mode 100644 index 000000000..1345b75b2 --- /dev/null +++ b/app/images/mm-secure.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/app/images/provider-approval-check.svg b/app/images/provider-approval-check.svg new file mode 100644 index 000000000..c3df71f59 --- /dev/null +++ b/app/images/provider-approval-check.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index fa8b3207f..1cdc85945 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -126,6 +126,8 @@ function listenForProviderRequest () { extension.runtime.sendMessage({ action: 'init-provider-request', origin: source.location.hostname, + siteImage: getSiteIcon(source), + siteTitle: getSiteName(source), }) break case 'ETHEREUM_IS_APPROVED': @@ -285,3 +287,31 @@ function redirectToPhishingWarning () { href: window.location.href, })}` } + +function getSiteName (window) { + const document = window.document + const siteName = document.querySelector('head > meta[property="og:site_name"]') + if (siteName) { + return siteName.content + } + + return document.title +} + +function getSiteIcon (window) { + const document = window.document + + // Use the site's favicon if it exists + const shortcutIcon = document.querySelector('head > link[rel="shortcut icon"]') + if (shortcutIcon) { + return shortcutIcon.href + } + + // Search through available icons in no particular order + const icon = Array.from(document.querySelectorAll('head > link[rel="icon"]')).find((icon) => Boolean(icon.href)) + if (icon) { + return icon.href + } + + return null +} diff --git a/app/scripts/controllers/provider-approval.js b/app/scripts/controllers/provider-approval.js index 728361c79..f2d40e67d 100644 --- a/app/scripts/controllers/provider-approval.js +++ b/app/scripts/controllers/provider-approval.js @@ -24,31 +24,35 @@ class ProviderApprovalController { this.publicConfigStore = publicConfigStore this.store = new ObservableStore() - platform && platform.addMessageListener && platform.addMessageListener(({ action = '', origin }) => { - switch (action) { - case 'init-provider-request': - this._handleProviderRequest(origin) - break - case 'init-is-approved': - this._handleIsApproved(origin) - break - case 'init-is-unlocked': - this._handleIsUnlocked() - break - case 'init-privacy-request': - this._handlePrivacyRequest() - break - } - }) + if (platform && platform.addMessageListener) { + platform.addMessageListener(({ action = '', origin, siteTitle, siteImage }) => { + switch (action) { + case 'init-provider-request': + this._handleProviderRequest(origin, siteTitle, siteImage) + break + case 'init-is-approved': + this._handleIsApproved(origin) + break + case 'init-is-unlocked': + this._handleIsUnlocked() + break + case 'init-privacy-request': + this._handlePrivacyRequest() + break + } + }) + } } /** * Called when a tab requests access to a full Ethereum provider API * * @param {string} origin - Origin of the window requesting full provider access + * @param {string} siteTitle - The title of the document requesting full provider access + * @param {string} siteImage - The icon of the window requesting full provider access */ - _handleProviderRequest (origin) { - this.store.updateState({ providerRequests: [{ origin }] }) + _handleProviderRequest (origin, siteTitle, siteImage) { + this.store.updateState({ providerRequests: [{ origin, siteTitle, siteImage }] }) const isUnlocked = this.keyringController.memStore.getState().isUnlocked if (this.isApproved(origin) && this.caching && isUnlocked) { this.approveProviderRequest(origin) diff --git a/test/e2e/beta/metamask-beta-ui.spec.js b/test/e2e/beta/metamask-beta-ui.spec.js index c8bf5f4ff..5887d0293 100644 --- a/test/e2e/beta/metamask-beta-ui.spec.js +++ b/test/e2e/beta/metamask-beta-ui.spec.js @@ -459,7 +459,7 @@ describe('MetaMask', function () { dapp = windowHandles.find(handle => handle !== extension && handle !== popup) await delay(regularDelayMs) - const approveButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Approve')]`), 10000) + const approveButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Connect')]`)) await approveButton.click() }) diff --git a/ui/app/components/index.scss b/ui/app/components/index.scss index 72de6cb93..e27b0f182 100644 --- a/ui/app/components/index.scss +++ b/ui/app/components/index.scss @@ -32,6 +32,8 @@ @import './pages/index'; +@import './provider-page-container/index'; + @import './selected-account/index'; @import './sender-to-recipient/index'; diff --git a/ui/app/components/network-display/index.scss b/ui/app/components/network-display/index.scss index 2085cff67..e9f2f2057 100644 --- a/ui/app/components/network-display/index.scss +++ b/ui/app/components/network-display/index.scss @@ -3,11 +3,14 @@ display: flex; align-items: center; justify-content: flex-start; - background-color: lighten(rgb(125, 128, 130), 45%); padding: 0 10px; border-radius: 4px; height: 25px; + &--colored { + background-color: lighten(rgb(125, 128, 130), 45%); + } + &--mainnet { background-color: lighten($blue-lagoon, 68%); } diff --git a/ui/app/components/network-display/network-display.component.js b/ui/app/components/network-display/network-display.component.js index 82f9ff9c3..22d617099 100644 --- a/ui/app/components/network-display/network-display.component.js +++ b/ui/app/components/network-display/network-display.component.js @@ -16,7 +16,12 @@ const networkToClassHash = { } export default class NetworkDisplay extends Component { + static defaultProps = { + colored: true, + } + static propTypes = { + colored: PropTypes.bool, network: PropTypes.string, provider: PropTypes.object, } @@ -41,14 +46,16 @@ export default class NetworkDisplay extends Component { } render () { - const { network, provider: { type, nickname } } = this.props + const { colored, network, provider: { type, nickname } } = this.props const networkClass = networkToClassHash[network] return ( -
+
{ networkClass ?
diff --git a/ui/app/components/pages/home/home.component.js b/ui/app/components/pages/home/home.component.js index 7b64ebc4e..b9ec3c258 100644 --- a/ui/app/components/pages/home/home.component.js +++ b/ui/app/components/pages/home/home.component.js @@ -67,7 +67,9 @@ export default class Home extends PureComponent { } if (providerRequests && providerRequests.length > 0) { - return + return ( + + ) } return ( diff --git a/ui/app/components/pages/provider-approval/provider-approval.component.js b/ui/app/components/pages/provider-approval/provider-approval.component.js index 67e8bdd4c..da98bc3fc 100644 --- a/ui/app/components/pages/provider-approval/provider-approval.component.js +++ b/ui/app/components/pages/provider-approval/provider-approval.component.js @@ -1,12 +1,12 @@ -import PageContainerContent from '../../page-container' import PropTypes from 'prop-types' import React, { Component } from 'react' +import ProviderPageContainer from '../../provider-page-container' export default class ProviderApproval extends Component { static propTypes = { - approveProviderRequest: PropTypes.func, - origin: PropTypes.string, - rejectProviderRequest: PropTypes.func, + approveProviderRequest: PropTypes.func.isRequired, + providerRequest: PropTypes.object.isRequired, + rejectProviderRequest: PropTypes.func.isRequired, }; static contextTypes = { @@ -14,22 +14,15 @@ export default class ProviderApproval extends Component { }; render () { - const { approveProviderRequest, origin, rejectProviderRequest } = this.props + const { approveProviderRequest, providerRequest, rejectProviderRequest } = this.props return ( - - {this.context.t('providerRequestInfo')} -
{origin}
-
- )} - submitText={this.context.t('approve')} - cancelText={this.context.t('reject')} - onSubmit={() => { approveProviderRequest(origin) }} - onCancel={() => { rejectProviderRequest(origin) }} - onClose={() => { rejectProviderRequest(origin) }} /> + ) } } diff --git a/ui/app/components/provider-page-container/index.js b/ui/app/components/provider-page-container/index.js new file mode 100644 index 000000000..927c35940 --- /dev/null +++ b/ui/app/components/provider-page-container/index.js @@ -0,0 +1,3 @@ +export {default} from './provider-page-container.component' +export {default as ProviderPageContainerContent} from './provider-page-container-content' +export {default as ProviderPageContainerHeader} from './provider-page-container-header' diff --git a/ui/app/components/provider-page-container/index.scss b/ui/app/components/provider-page-container/index.scss new file mode 100644 index 000000000..a67d7f427 --- /dev/null +++ b/ui/app/components/provider-page-container/index.scss @@ -0,0 +1,120 @@ +.provider-approval-container { + display: flex; + + &__header { + display: flex; + flex-direction: column; + align-items: flex-end; + border-bottom: 1px solid $geyser; + padding: 9px; + } + + &__content { + display: flex; + overflow-y: auto; + flex: 1; + flex-direction: column; + justify-content: space-between; + color: #7C808E; + + h1, h2 { + color: #4A4A4A; + display: flex; + justify-content: center; + text-align: center; + } + + h2 { + font-size: 16px; + line-height: 18px; + padding: 20px; + } + + h1 { + font-size: 22px; + line-height: 26px; + padding: 20px; + } + + p { + padding: 0 40px; + text-align: center; + font-size: 12px; + line-height: 18px; + } + + a, a:hover { + color: $dodger-blue; + } + + .provider-approval-visual { + display: flex; + flex-direction: row; + justify-content: space-evenly; + position: relative; + margin: 0 32px; + + section { + display: flex; + flex-direction: column; + align-items: center; + flex: 1; + } + + h1 { + font-size: 14px; + line-height: 18px; + padding: 8px 0 0; + } + + h2 { + font-size: 10px; + line-height: 14px; + padding: 0; + color: #A2A4AC; + } + + &__check { + width: 40px; + height: 40px; + background: white url("/images/provider-approval-check.svg") no-repeat; + margin-top: 14px; + } + + &__identicon { + width: 64px; + height: 64px; + + &--default { + background-color: lightgray; + width: 64px; + height: 64px; + border-radius: 32px; + display: flex; + align-items: center; + justify-content: center; + font-weight: bold; + } + } + + &:before { + border-top: 2px dashed #CDD1E4; + content: ""; + margin: 0 auto; + position: absolute; + top: 32px; + left: 0; + bottom: 0; + right: 0; + width: 65%; + z-index: -1; + } + } + + .secure-badge { + display: flex; + justify-content: center; + padding: 25px; + } + } +} diff --git a/ui/app/components/provider-page-container/provider-page-container-content/index.js b/ui/app/components/provider-page-container/provider-page-container-content/index.js new file mode 100644 index 000000000..73e491adc --- /dev/null +++ b/ui/app/components/provider-page-container/provider-page-container-content/index.js @@ -0,0 +1 @@ +export {default} from './provider-page-container-content.container' diff --git a/ui/app/components/provider-page-container/provider-page-container-content/provider-page-container-content.component.js b/ui/app/components/provider-page-container/provider-page-container-content/provider-page-container-content.component.js new file mode 100644 index 000000000..20fad5c6d --- /dev/null +++ b/ui/app/components/provider-page-container/provider-page-container-content/provider-page-container-content.component.js @@ -0,0 +1,71 @@ +import PropTypes from 'prop-types' +import React, {PureComponent} from 'react' +import Identicon from '../../identicon' + +export default class ProviderPageContainerContent extends PureComponent { + static propTypes = { + origin: PropTypes.string.isRequired, + selectedIdentity: PropTypes.string.isRequired, + siteImage: PropTypes.string, + siteTitle: PropTypes.string.isRequired, + } + + static contextTypes = { + t: PropTypes.func, + }; + + renderConnectVisual = () => { + const { origin, selectedIdentity, siteImage, siteTitle } = this.props + + return ( +
+
+ {siteImage ? ( + + ) : ( + + {siteTitle.charAt(0).toUpperCase()} + + )} +

{siteTitle}

+

{origin}

+
+ +
+ +

{selectedIdentity.name}

+
+
+ ) + } + + render () { + const { siteTitle } = this.props + const { t } = this.context + + return ( +
+
+

{t('connectRequest')}

+ {this.renderConnectVisual()} +

{t('providerRequest', [siteTitle])}

+

+ {t('providerRequestInfo')} +
+ {t('learnMore')}. +

+
+
+ +
+
+ ) + } +} diff --git a/ui/app/components/provider-page-container/provider-page-container-content/provider-page-container-content.container.js b/ui/app/components/provider-page-container/provider-page-container-content/provider-page-container-content.container.js new file mode 100644 index 000000000..3ea1ce20e --- /dev/null +++ b/ui/app/components/provider-page-container/provider-page-container-content/provider-page-container-content.container.js @@ -0,0 +1,11 @@ +import { connect } from 'react-redux' +import ProviderPageContainerContent from './provider-page-container-content.component' +import { getSelectedIdentity } from '../../../selectors' + +const mapStateToProps = (state) => { + return { + selectedIdentity: getSelectedIdentity(state), + } +} + +export default connect(mapStateToProps)(ProviderPageContainerContent) diff --git a/ui/app/components/provider-page-container/provider-page-container-header/index.js b/ui/app/components/provider-page-container/provider-page-container-header/index.js new file mode 100644 index 000000000..430627d3a --- /dev/null +++ b/ui/app/components/provider-page-container/provider-page-container-header/index.js @@ -0,0 +1 @@ +export {default} from './provider-page-container-header.component' diff --git a/ui/app/components/provider-page-container/provider-page-container-header/provider-page-container-header.component.js b/ui/app/components/provider-page-container/provider-page-container-header/provider-page-container-header.component.js new file mode 100644 index 000000000..41bf6c3dd --- /dev/null +++ b/ui/app/components/provider-page-container/provider-page-container-header/provider-page-container-header.component.js @@ -0,0 +1,12 @@ +import React, {PureComponent} from 'react' +import NetworkDisplay from '../../network-display' + +export default class ProviderPageContainerHeader extends PureComponent { + render () { + return ( +
+ +
+ ) + } +} diff --git a/ui/app/components/provider-page-container/provider-page-container.component.js b/ui/app/components/provider-page-container/provider-page-container.component.js new file mode 100644 index 000000000..902733616 --- /dev/null +++ b/ui/app/components/provider-page-container/provider-page-container.component.js @@ -0,0 +1,50 @@ +import PropTypes from 'prop-types' +import React, {PureComponent} from 'react' +import { ProviderPageContainerContent, ProviderPageContainerHeader } from './' +import { PageContainerFooter } from '../page-container' + +export default class ProviderPageContainer extends PureComponent { + static propTypes = { + approveProviderRequest: PropTypes.func.isRequired, + origin: PropTypes.string.isRequired, + rejectProviderRequest: PropTypes.func.isRequired, + siteImage: PropTypes.string, + siteTitle: PropTypes.string.isRequired, + }; + + static contextTypes = { + t: PropTypes.func, + }; + + onCancel = () => { + const { origin, rejectProviderRequest } = this.props + rejectProviderRequest(origin) + } + + onSubmit = () => { + const { approveProviderRequest, origin } = this.props + approveProviderRequest(origin) + } + + render () { + const {origin, siteImage, siteTitle} = this.props + + return ( +
+ + + this.onCancel()} + cancelText={this.context.t('cancel')} + onSubmit={() => this.onSubmit()} + submitText={this.context.t('connect')} + submitButtonType="confirm" + /> +
+ ) + } +} -- cgit v1.2.3