diff options
Diffstat (limited to 'web-ui')
9 files changed, 213 insertions, 27 deletions
diff --git a/web-ui/app/locales/en_US/translation.json b/web-ui/app/locales/en_US/translation.json index 22f533c0..22ac9f70 100644 --- a/web-ui/app/locales/en_US/translation.json +++ b/web-ui/app/locales/en_US/translation.json @@ -126,6 +126,9 @@          "paragraph": "Save this message, it is really important.",          "button": "Got it! I'm ready!",          "retry-button": "Hey, I didn't received it" +      }, +      "error": { +        "submit-error": "There was an error. Please try again later."        }      },      "back-to-inbox": "Back to my inbox", diff --git a/web-ui/app/locales/pt_BR/translation.json b/web-ui/app/locales/pt_BR/translation.json index ccc0fe5a..d43487a2 100644 --- a/web-ui/app/locales/pt_BR/translation.json +++ b/web-ui/app/locales/pt_BR/translation.json @@ -103,6 +103,9 @@          "paragraph": "Salve esse e-mail, ele é bem importante.",          "button": "Recebi! Pronto!",          "retry-button": "Ei, eu não recebi" +      }, +      "error": { +        "submit-error": "Ocorreu um erro. Por favor tente novamente mais tarde."        }      },      "back-to-inbox": "Voltar", diff --git a/web-ui/src/backup_account/backup_email/backup_email.js b/web-ui/src/backup_account/backup_email/backup_email.js index 09863950..7e6cb66c 100644 --- a/web-ui/src/backup_account/backup_email/backup_email.js +++ b/web-ui/src/backup_account/backup_email/backup_email.js @@ -40,7 +40,7 @@ export class BackupEmail extends React.Component {        error: !emptyEmail && !validEmail ? t('backup-account.backup-email.error.invalid-email') : '',        submitButtonDisabled: !validEmail || emptyEmail      }); -  } +  };    submitHandler = (event) => {      event.preventDefault(); @@ -54,8 +54,14 @@ export class BackupEmail extends React.Component {        body: JSON.stringify({          csrftoken: [browser.getCookie('XSRF-TOKEN')]        }) -    }).then(() => this.props.onSubmit('success')); -  } +    }).then((response) => { +      if (response.ok) { +        this.props.onSubmit('success'); +      } else { +        this.props.onSubmit('error'); +      } +    }); +  };    render() {      const t = this.props.t; diff --git a/web-ui/src/backup_account/backup_email/backup_email.spec.js b/web-ui/src/backup_account/backup_email/backup_email.spec.js index 48199738..65fad608 100644 --- a/web-ui/src/backup_account/backup_email/backup_email.spec.js +++ b/web-ui/src/backup_account/backup_email/backup_email.spec.js @@ -87,39 +87,61 @@ describe('BackupEmail', () => {    describe('Submit', () => {      let preventDefaultSpy; -    beforeEach((done) => { -      mockOnSubmit = expect.createSpy().andCall(() => done()); +    beforeEach(() => {        preventDefaultSpy = expect.createSpy(); -      expect.spyOn(browser, 'getCookie').andReturn('abc123'); +    }); -      backupEmail = shallow(<BackupEmail t={mockTranslations} onSubmit={mockOnSubmit} />); +    context('on success', () => { +      beforeEach((done) => { +        mockOnSubmit = expect.createSpy().andCall(() => done()); +        expect.spyOn(browser, 'getCookie').andReturn('abc123'); -      fetchMock.post('/backup-account', 204); -      backupEmail.find('form').simulate('submit', { preventDefault: preventDefaultSpy }); -    }); +        fetchMock.post('/backup-account', 204); +        backupEmail = shallow(<BackupEmail t={mockTranslations} onSubmit={mockOnSubmit} />); +        backupEmail.find('form').simulate('submit', { preventDefault: preventDefaultSpy }); +      }); -    it('posts backup email', () => { -      expect(fetchMock.called('/backup-account')).toBe(true, 'Backup account POST was not called'); -    }); +      it('posts backup email', () => { +        expect(fetchMock.called('/backup-account')).toBe(true, 'Backup account POST was not called'); +      }); -    it('sends csrftoken as content', () => { -      expect(fetchMock.lastOptions('/backup-account').body).toContain('"csrftoken":["abc123"]'); -    }); +      it('sends csrftoken as content', () => { +        expect(fetchMock.lastOptions('/backup-account').body).toContain('"csrftoken":["abc123"]'); +      }); -    it('sends content-type header', () => { -      expect(fetchMock.lastOptions('/backup-account').headers['Content-Type']).toEqual('application/json'); -    }); +      it('sends content-type header', () => { +        expect(fetchMock.lastOptions('/backup-account').headers['Content-Type']).toEqual('application/json'); +      }); -    it('sends same origin headers', () => { -      expect(fetchMock.lastOptions('/backup-account').credentials).toEqual('same-origin'); -    }); +      it('sends same origin headers', () => { +        expect(fetchMock.lastOptions('/backup-account').credentials).toEqual('same-origin'); +      }); + +      it('prevents default call to refresh page', () => { +        expect(preventDefaultSpy).toHaveBeenCalled(); +      }); -    it('prevents default call to refresh page', () => { -      expect(preventDefaultSpy).toHaveBeenCalled(); +      it('calls onSubmit from props with success', () => { +        expect(mockOnSubmit).toHaveBeenCalledWith('success'); +      });      }); -    it('calls onSubmit from props when success', () => { -      expect(mockOnSubmit).toHaveBeenCalledWith('success'); +    context('on error', () => { +      beforeEach((done) => { +        mockOnSubmit = expect.createSpy().andCall(() => done()); + +        fetchMock.post('/backup-account', 500); +        backupEmail = shallow(<BackupEmail t={mockTranslations} onSubmit={mockOnSubmit} />); +        backupEmail.find('form').simulate('submit', { preventDefault: preventDefaultSpy }); +      }); + +      it('calls onSubmit from props with error', () => { +        expect(mockOnSubmit).toHaveBeenCalledWith('error'); +      });      });    }); + +  afterEach(() => { +    fetchMock.restore(); +  });  }); diff --git a/web-ui/src/backup_account/page.js b/web-ui/src/backup_account/page.js index 49e4b316..be3bae36 100644 --- a/web-ui/src/backup_account/page.js +++ b/web-ui/src/backup_account/page.js @@ -22,6 +22,7 @@ import Footer from 'src/common/footer/footer';  import Header from 'src/common/header/header';  import BackupEmail from 'src/backup_account/backup_email/backup_email';  import Confirmation from 'src/backup_account/confirmation/confirmation'; +import SnackbarNotification from 'src/common/snackbar_notification/snackbar_notification';  import 'font-awesome/scss/font-awesome.scss';  import './page.scss'; @@ -36,13 +37,20 @@ export class Page extends React.Component {    saveBackupEmail = (status) => {      this.setState({ status }); -  } +  };    mainContent = () => {      if (this.state.status === 'success') return <Confirmation />;      return <BackupEmail onSubmit={this.saveBackupEmail} />;    }; +  showSnackbarOnError = (t) => { +    if (this.state.status === 'error') { +      return <SnackbarNotification message={t('backup-account.error.submit-error')} isError />; +    } +    return undefined; // To satisfy eslint error - consistent-return +  }; +    render() {      const t = this.props.t;      return ( @@ -52,6 +60,7 @@ export class Page extends React.Component {            <section>              {this.mainContent()}            </section> +          {this.showSnackbarOnError(t)}            <Footer />          </div>        </DocumentTitle> diff --git a/web-ui/src/backup_account/page.spec.js b/web-ui/src/backup_account/page.spec.js index bd7bb884..2933a64e 100644 --- a/web-ui/src/backup_account/page.spec.js +++ b/web-ui/src/backup_account/page.spec.js @@ -4,6 +4,7 @@ import React from 'react';  import { Page } from 'src/backup_account/page';  import BackupEmail from 'src/backup_account/backup_email/backup_email';  import Confirmation from 'src/backup_account/confirmation/confirmation'; +import SnackbarNotification from 'src/common/snackbar_notification/snackbar_notification';  describe('BackupAccount', () => {    let page; @@ -41,5 +42,29 @@ describe('BackupAccount', () => {        pageInstance.saveBackupEmail('success');        expect(page.find(Confirmation).length).toEqual(1);      }); + +    context('on submit error', () => { +      beforeEach(() => { +        pageInstance.saveBackupEmail('error'); +      }); + +      it('returns snackbar component on error', () => { +        const snackbar = pageInstance.showSnackbarOnError(pageInstance.props.t); +        expect(snackbar).toEqual(<SnackbarNotification message='backup-account.error.submit-error' isError />); +      }); + +      it('returns nothing when there is no error', () => { +        pageInstance.saveBackupEmail('success'); +        const snackbar = pageInstance.showSnackbarOnError(pageInstance.props.t); +        expect(snackbar).toEqual(undefined); +      }); + +      it('renders snackbar notification on error', () => { +        const snackbar = page.find(SnackbarNotification); +        expect(snackbar).toExist(); +        expect(snackbar.props().message).toEqual('backup-account.error.submit-error'); +        expect(snackbar.props().isError).toEqual(true); +      }); +    });    });  }); diff --git a/web-ui/src/common/snackbar_notification/snackbar_notification.js b/web-ui/src/common/snackbar_notification/snackbar_notification.js new file mode 100644 index 00000000..8a7e5094 --- /dev/null +++ b/web-ui/src/common/snackbar_notification/snackbar_notification.js @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2017 ThoughtWorks, Inc. + * + * Pixelated is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Pixelated is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Pixelated. If not, see <http://www.gnu.org/licenses/>. + */ + +import React from 'react'; +import Snackbar from 'material-ui/Snackbar'; +import { red500, blue500 } from 'material-ui/styles/colors'; + +import './snackbar_notification.scss'; + +const notificationStyle = () => ({ +  top: 0, +  left: 'auto', +  bottom: 'auto', +  alignSelf: 'center', +  transform: 'translate3d(0, 0px, 0)' +}); + +const contentStyle = { +  textAlign: 'center' +}; + +const getStyleByType = (isError) => { +  const style = { height: 'auto' }; +  style.backgroundColor = isError ? red500 : blue500; +  return style; +}; + +const SnackbarNotification = ({ message, isError = false, autoHideDuration = 5000 }) => ( +  <Snackbar +    id='snackbar' +    open +    bodyStyle={getStyleByType(isError)} +    message={message} +    autoHideDuration={autoHideDuration} +    contentStyle={contentStyle} +    style={notificationStyle()} +  /> +); + +SnackbarNotification.propTypes = { +  message: React.PropTypes.string.isRequired, +  isError: React.PropTypes.bool, +  autoHideDuration: React.PropTypes.number +}; + +SnackbarNotification.defaultProps = { +  isError: false, +  autoHideDuration: 5000 +}; + +export default SnackbarNotification; diff --git a/web-ui/src/common/snackbar_notification/snackbar_notification.scss b/web-ui/src/common/snackbar_notification/snackbar_notification.scss new file mode 100644 index 00000000..e37ba4ae --- /dev/null +++ b/web-ui/src/common/snackbar_notification/snackbar_notification.scss @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2017 ThoughtWorks, Inc. + * + * Pixelated is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Pixelated is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Pixelated. If not, see <http://www.gnu.org/licenses/>. + */ + +//TODO: Refer to Issue - https://github.com/callemall/material-ui/issues/3860 +#snackbar > div { +  line-height: 20px !important; +  padding: 24px !important; +} diff --git a/web-ui/src/common/snackbar_notification/snackbar_notification.spec.js b/web-ui/src/common/snackbar_notification/snackbar_notification.spec.js new file mode 100644 index 00000000..24b79e71 --- /dev/null +++ b/web-ui/src/common/snackbar_notification/snackbar_notification.spec.js @@ -0,0 +1,31 @@ +import { shallow } from 'enzyme'; +import expect from 'expect'; +import React from 'react'; +import SnackbarNotification from 'src/common/snackbar_notification/snackbar_notification'; +import Snackbar from 'material-ui/Snackbar'; +import { red500 } from 'material-ui/styles/colors'; + +describe('SnackbarNotification', () => { +  let snackbarNotification; + +  beforeEach(() => { +    snackbarNotification = shallow(<SnackbarNotification message={'Error Message'} isError />); +  }); + +  it('renders snackbar with error message', () => { +    expect(snackbarNotification.find(Snackbar).props().message).toEqual('Error Message'); +  }); + +  it('renders snackbar with open as true', () => { +    expect(snackbarNotification.find(Snackbar).props().open).toEqual(true); +  }); + +  it('renders snackbar with error body style', () => { +    expect(snackbarNotification.find(Snackbar).props().bodyStyle) +      .toEqual({ height: 'auto', backgroundColor: red500 }); +  }); + +  it('renders snackbar with default auto-hide duration', () => { +    expect(snackbarNotification.find(Snackbar).props().autoHideDuration).toEqual(5000); +  }); +});  | 
