diff --git a/src/disco/components/Addon.js b/src/disco/components/Addon.js index 6a822a69753..89891280a57 100644 --- a/src/disco/components/Addon.js +++ b/src/disco/components/Addon.js @@ -6,6 +6,8 @@ import { gettext as _ } from 'core/utils'; import InstallButton from 'disco/containers/InstallButton'; import { validAddonTypes, + validInstallStates, + ERROR, EXTENSION_TYPE, THEME_TYPE, THEME_PREVIEW, @@ -18,7 +20,9 @@ import 'disco/css/Addon.scss'; export default class Addon extends React.Component { static propTypes = { accentcolor: PropTypes.string, + closeErrorAction: PropTypes.func, editorialDescription: PropTypes.string.isRequired, + errorMessage: PropTypes.string, footerURL: PropTypes.string, headerURL: PropTypes.string, heading: PropTypes.string.isRequired, @@ -26,6 +30,7 @@ export default class Addon extends React.Component { name: PropTypes.string.isRequired, slug: PropTypes.string.isRequired, subHeading: PropTypes.string, + status: PropTypes.oneOf(validInstallStates).isRequired, textcolor: PropTypes.string, type: PropTypes.oneOf(validAddonTypes).isRequired, themeAction: PropTypes.func, @@ -41,6 +46,15 @@ export default class Addon extends React.Component { return JSON.stringify({id, name, headerURL, footerURL, textcolor, accentcolor}); } + getError() { + const { status } = this.props; + const errorMessage = this.props.errorMessage || _('An unexpected error occurred'); + return status === ERROR ? (
+

{errorMessage}

+ Close +
) : null; + } + getLogo() { const { id } = this.props; const imageURL = `https://addons-dev-cdn.allizom.org/user-media/addon_icons/0/${id}-64.png?modified=1388632826`; @@ -96,8 +110,9 @@ export default class Addon extends React.Component { return (
{this.getThemeImage()} + {this.getLogo()}
- {this.getLogo()} + {this.getError()}

{heading} {subHeading ? {subHeading} : null}

diff --git a/src/disco/css/Addon.scss b/src/disco/css/Addon.scss index 665804d7722..baad44e250b 100644 --- a/src/disco/css/Addon.scss +++ b/src/disco/css/Addon.scss @@ -4,9 +4,21 @@ $addon-padding: 20px; .addon { + align-items: center; background: #fff; + display: flex; + flex-direction: row; + line-height: 1.5; margin-top: 20px; + &.theme { + display: block; + + .content .copy { + padding: 20px; + } + } + .theme-image { display: block; @@ -24,23 +36,52 @@ $addon-padding: 20px; margin: 0 20px 0 30px; } - .content { + .logo { align-items: center; + align-self: stretch; + display: flex; + background: #fcfcfc; + padding: 0 15px; + + img { + display: block; + height: 64px; + width: 64px; + } + } + + .content { display: flex; + align-items: center; flex-direction: row; - line-height: 1.5; + flex-grow: 1; + position: relative; - .logo { + .error { + align-content: stretch; align-items: center; - align-self: stretch; - background: #fcfcfc; + background: #c33c32 url('../img/warning.svg') no-repeat calc(100% - 40px) 50%; + bottom: 0; + color: #fff; display: flex; - padding: 0 15px; + flex-direction: row; + left: 0; + padding: 15px 20px; + position: absolute; + right: 0; + top: 0; + z-index: 10; - img { + .message { + flex-grow: 1; + } + + .close { + color: #fff; display: block; - height: 64px; - width: 64px; + position: absolute; + left: 20px; + bottom: 15px; } } @@ -53,12 +94,12 @@ $addon-padding: 20px; font-size: 18px; font-weight: medium; margin: 0; + } - .sub-heading { - color: $secondary-font-color; - font-size: 14px; - font-weight: normal; - } + .sub-heading { + color: $secondary-font-color; + font-size: 14px; + font-weight: normal; } .editorial-description { @@ -74,7 +115,4 @@ $addon-padding: 20px; } } - &.theme .content .copy { - padding: 20px; - } } diff --git a/src/disco/img/warning.svg b/src/disco/img/warning.svg new file mode 100644 index 00000000000..d6050ef23b7 --- /dev/null +++ b/src/disco/img/warning.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/tests/client/disco/components/TestAddon.js b/tests/client/disco/components/TestAddon.js index 67ce7134a1f..60e34060bf8 100644 --- a/tests/client/disco/components/TestAddon.js +++ b/tests/client/disco/components/TestAddon.js @@ -9,7 +9,7 @@ import { Provider } from 'react-redux'; import { createStore } from 'redux'; import Addon from 'disco/components/Addon'; -import { THEME_PREVIEW, THEME_RESET_PREVIEW } from 'disco/constants'; +import { ERROR, THEME_PREVIEW, THEME_RESET_PREVIEW } from 'disco/constants'; const result = { @@ -38,6 +38,21 @@ describe('', () => { root = renderAddon(result); }); + it('renders an error overlay', () => { + const data = {...result, status: ERROR, + errorMessage: 'this is an error', closeErrorAction: sinon.stub()}; + root = renderAddon(data); + const error = findDOMNode(root).querySelector('.error'); + assert.equal(error.querySelector('p').textContent, 'this is an error', + 'error message should be present'); + Simulate.click(error.querySelector('.close')); + assert.ok(data.closeErrorAction.called, 'close link action should be called'); + }); + + it('does not normally render an error', () => { + assert.notOk(findDOMNode(root).querySelector('.error')); + }); + it('renders the heading', () => { assert.include(root.refs.heading.textContent, 'test-heading'); });