diff --git a/src/disco/components/Addon.js b/src/disco/components/Addon.js
index 928bad7c0ba..de78c980066 100644
--- a/src/disco/components/Addon.js
+++ b/src/disco/components/Addon.js
@@ -29,6 +29,7 @@ import {
THEME_PREVIEW,
THEME_RESET_PREVIEW,
THEME_TYPE,
+ UNINSTALLING,
UNINSTALLED,
UNINSTALL_CATEGORY,
} from 'disco/constants';
@@ -56,6 +57,7 @@ export class Addon extends React.Component {
iconUrl: PropTypes.string,
id: PropTypes.string.isRequired,
installURL: PropTypes.string,
+ needsRestart: PropTypes.bool.isRequired,
previewURL: PropTypes.string,
name: PropTypes.string.isRequired,
setCurrentStatus: PropTypes.func.isRequired,
@@ -68,6 +70,7 @@ export class Addon extends React.Component {
static defaultProps = {
// Defaults themeAction to the imported func.
themeAction,
+ needsRestart: false,
}
componentDidMount() {
@@ -84,12 +87,18 @@ export class Addon extends React.Component {
}
getError() {
- return this.props.status === ERROR ? (
+ return this.props.status === ERROR ? (
{this.errorMessage()}
Close
) : null;
}
+ getRestart() {
+ return this.props.needsRestart ? (
+
{this.restartMessage()}
+
) : null;
+ }
+
getLogo() {
const { iconUrl } = this.props;
if (this.props.type === EXTENSION_TYPE) {
@@ -140,6 +149,16 @@ export class Addon extends React.Component {
}
}
+ restartMessage() {
+ const { status, i18n } = this.props;
+ switch (status) {
+ case UNINSTALLING:
+ return i18n.gettext('This add-on will be uninstalled after you restart Firefox.');
+ default:
+ return i18n.gettext('Please restart Firefox to use this add-on.');
+ }
+ }
+
closeError = (e) => {
e.preventDefault();
this.setCurrentStatus();
@@ -174,6 +193,7 @@ export class Addon extends React.Component {
{this.getLogo()}
{this.getError()}
+ {this.getRestart()}
.error {
- align-content: stretch;
- align-items: center;
- background: #c33c32 url('../img/warning.svg') no-repeat calc(100% - 40px) 50%;
- bottom: 0;
- color: #fff;
- display: flex;
- flex-direction: row;
- left: 0;
- padding: 15px 20px;
- position: absolute;
- right: 0;
- top: 0;
- z-index: 10;
-
- .message {
- flex-grow: 1;
- }
-
- .close {
- bottom: 10px;
- color: #fff;
- display: block;
- left: 15px;
- padding: 5px;
- position: absolute;
- }
- }
.copy {
padding: 30px 20px;
@@ -120,4 +92,40 @@ $addon-padding: 20px;
}
}
+ .notification {
+ align-content: stretch;
+ align-items: center;
+ bottom: 0;
+ color: #fff;
+ display: flex;
+ flex-direction: row;
+ left: 0;
+ padding: 15px 20px;
+ position: absolute;
+ right: 0;
+ top: 0;
+
+ &.error {
+ background: #c33c32 url('../img/warning.svg') no-repeat calc(100% - 40px) 50%;
+ z-index: 20;
+ }
+
+ &.restart {
+ background: #70CF5B url('../img/restart.svg') no-repeat calc(100% - 40px) 50%;
+ z-index: 10;
+ }
+
+ .message {
+ flex-grow: 1;
+ }
+
+ .close {
+ bottom: 10px;
+ color: #fff;
+ display: block;
+ left: 15px;
+ padding: 5px;
+ position: absolute;
+ }
+ }
}
diff --git a/src/disco/img/restart.svg b/src/disco/img/restart.svg
new file mode 100644
index 00000000000..9f19e162c3a
--- /dev/null
+++ b/src/disco/img/restart.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/disco/reducers/installations.js b/src/disco/reducers/installations.js
index 308e00d3ed5..6a12664ae3c 100644
--- a/src/disco/reducers/installations.js
+++ b/src/disco/reducers/installations.js
@@ -40,6 +40,7 @@ export default function installations(state = {}, { type, payload }) {
url: payload.url,
downloadProgress: 0,
status: normalizeStatus(payload.status),
+ needsRestart: payload.needsRestart || false,
};
} else if (type === START_DOWNLOAD) {
addon.status = DOWNLOADING;
diff --git a/tests/client/disco/components/TestAddon.js b/tests/client/disco/components/TestAddon.js
index bf7da780f3a..dc2f3cff563 100644
--- a/tests/client/disco/components/TestAddon.js
+++ b/tests/client/disco/components/TestAddon.js
@@ -28,6 +28,7 @@ import {
THEME_RESET_PREVIEW,
THEME_TYPE,
UNINSTALLED,
+ UNINSTALLING,
UNINSTALL_CATEGORY,
} from 'disco/constants';
import { getFakeAddonManagerWrapper, getFakeI18nInst } from 'tests/client/helpers';
@@ -63,7 +64,7 @@ describe('', () => {
it('renders a default error overlay', () => {
const data = {...result, status: ERROR, setCurrentStatus: sinon.stub()};
root = renderAddon(data);
- const error = findDOMNode(root).querySelector('.error');
+ const error = findDOMNode(root).querySelector('.notification.error');
assert.equal(
error.querySelector('p').textContent,
'An unexpected error occurred.',
@@ -77,7 +78,7 @@ describe('', () => {
...result, status: ERROR, error: INSTALL_FAILED, setCurrentStatus: sinon.stub(),
};
root = renderAddon(data);
- const error = findDOMNode(root).querySelector('.error');
+ const error = findDOMNode(root).querySelector('.notification.error');
assert.equal(
error.querySelector('p').textContent,
'Installation failed. Please try again.',
@@ -91,7 +92,7 @@ describe('', () => {
...result, status: ERROR, error: DOWNLOAD_FAILED, setCurrentStatus: sinon.stub(),
};
root = renderAddon(data);
- const error = findDOMNode(root).querySelector('.error');
+ const error = findDOMNode(root).querySelector('.notification.error');
assert.equal(
error.querySelector('p').textContent,
'Download failed. Please check your connection.',
@@ -101,7 +102,31 @@ describe('', () => {
});
it('does not normally render an error', () => {
- assert.notOk(findDOMNode(root).querySelector('.error'));
+ assert.notOk(findDOMNode(root).querySelector('.notification.error'));
+ });
+
+ it('renders a default restart notification', () => {
+ const data = { ...result, needsRestart: true };
+ root = renderAddon(data);
+ const restart = findDOMNode(root).querySelector('.notification.restart');
+ assert.equal(
+ restart.querySelector('p').textContent,
+ 'Please restart Firefox to use this add-on.',
+ 'restart message should be present');
+ });
+
+ it('renders a uninstallation restart notification', () => {
+ const data = { ...result, needsRestart: true, status: UNINSTALLING };
+ root = renderAddon(data);
+ const restart = findDOMNode(root).querySelector('.notification.restart');
+ assert.equal(
+ restart.querySelector('p').textContent,
+ 'This add-on will be uninstalled after you restart Firefox.',
+ 'restart uninstallation message should be present');
+ });
+
+ it('does not normally render a restart notification', () => {
+ assert.notOk(findDOMNode(root).querySelector('.notification.restart'));
});
it('renders the heading', () => {
diff --git a/tests/client/disco/reducers/test_installations.js b/tests/client/disco/reducers/test_installations.js
index caf4619ad8b..6d1ef97a481 100644
--- a/tests/client/disco/reducers/test_installations.js
+++ b/tests/client/disco/reducers/test_installations.js
@@ -42,6 +42,29 @@ describe('installations reducer', () => {
url: 'https://cdn.amo/download/my-addon.xpi',
downloadProgress: 0,
status: UNINSTALLED,
+ needsRestart: false,
+ },
+ });
+ });
+
+ it('passes down needsRestart=true', () => {
+ assert.deepEqual(
+ installations(undefined, {
+ type: INSTALL_STATE,
+ payload: {
+ guid: 'my-addon@me.com',
+ url: 'https://cdn.amo/download/my-addon.xpi',
+ status: UNINSTALLING,
+ needsRestart: true,
+ },
+ }),
+ {
+ 'my-addon@me.com': {
+ guid: 'my-addon@me.com',
+ url: 'https://cdn.amo/download/my-addon.xpi',
+ downloadProgress: 0,
+ status: UNINSTALLING,
+ needsRestart: true,
},
});
});
@@ -61,6 +84,7 @@ describe('installations reducer', () => {
url: undefined,
downloadProgress: 0,
status: INSTALLED,
+ needsRestart: false,
},
});
});
@@ -80,6 +104,7 @@ describe('installations reducer', () => {
url: undefined,
downloadProgress: 0,
status: UNINSTALLED,
+ needsRestart: false,
},
});
});
@@ -100,6 +125,7 @@ describe('installations reducer', () => {
url: 'https://cdn.amo/download/an-addon.xpi',
downloadProgress: 0,
status: INSTALLED,
+ needsRestart: false,
},
});
});