summaryrefslogtreecommitdiff
path: root/web-ui
diff options
context:
space:
mode:
authorAnike Arni <anikarni@gmail.com>2017-02-21 18:39:06 -0300
committerGitHub <noreply@github.com>2017-02-21 18:39:06 -0300
commit92c6a9dbc39318df48b4b3d5fae1a3888f201343 (patch)
tree548fb92a6ae19d34945c19b2c704cc8c2c97382a /web-ui
parentd5d7c8607138c8f39b55cdaa6ef3231c98d6af8a (diff)
parentdb1db55b806953ff93950b724fc96c8db388bbcf (diff)
Merge pull request #986 from pixelated/login-errors
Translate and make login responsive
Diffstat (limited to 'web-ui')
-rw-r--r--web-ui/app/index.html10
-rw-r--r--web-ui/app/locales/en_US/translation.json10
-rw-r--r--web-ui/app/locales/pt_BR/translation.json10
-rw-r--r--web-ui/src/backup_account/backup_account.html2
-rw-r--r--web-ui/src/common/input_field/input_field.js14
-rw-r--r--web-ui/src/login/app.js36
-rw-r--r--web-ui/src/login/app.scss70
-rw-r--r--web-ui/src/login/login.css143
-rw-r--r--web-ui/src/login/login.html12
-rw-r--r--web-ui/src/login/login.js5
-rw-r--r--web-ui/src/login/opensans.css20
-rw-r--r--web-ui/src/util.js7
-rw-r--r--web-ui/test/unit/login/app.spec.js26
-rw-r--r--web-ui/test/unit/util.spec.js20
14 files changed, 228 insertions, 157 deletions
diff --git a/web-ui/app/index.html b/web-ui/app/index.html
index 72b04c83..ac4189e1 100644
--- a/web-ui/app/index.html
+++ b/web-ui/app/index.html
@@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
-<link rel="icon" type="image/png" href="public/images/Favicon.png">
+<link rel="icon" type="image/png" href="public/images/favicon.png">
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>$account_email - Pixelated Mail</title>
@@ -25,15 +25,15 @@
<path fill="#3E3B38" d="M233.8,678.3h-24v71.2h16.2v-26.5h7.8c14,0,24.3-8,24.3-22.9C258.1,685.6,247.6,678.3,233.8,678.3z
M230.6,710.2h-4.6v-18.8h4.6c6.5,0,12.5,2.2,12.5,9.5C243,708.1,237.1,710.2,230.6,710.2z"/>
<rect x="263.5" y="678.3" fill="#3E3B38" width="16.2" height="71.2"/>
- <polygon fill="#3E3B38" points="350.4,678.3 330.1,678.3 316.9,697.7 303.7,678.3 284.6,678.3 307,711.1 282.6,749.5 302.9,749.5
+ <polygon fill="#3E3B38" points="350.4,678.3 330.1,678.3 316.9,697.7 303.7,678.3 284.6,678.3 307,711.1 282.6,749.5 302.9,749.5
316.9,725.3 331,749.5 352.1,749.5 326.9,711.1 "/>
- <polygon fill="#3E3B38" points="354.7,749.5 395.5,749.5 395.5,735.2 370.9,735.2 370.9,721 394.4,721 394.4,706.6 370.9,706.6
+ <polygon fill="#3E3B38" points="354.7,749.5 395.5,749.5 395.5,735.2 370.9,735.2 370.9,721 394.4,721 394.4,706.6 370.9,706.6
370.9,692.5 395.5,692.5 395.5,678.3 354.7,678.3 "/>
<path fill="#3E3B38" d="M456.1,678.3l-22.9,57h-15.9v-57h-16.2v71.2h26.5h14.3h3.2l5.4-14.3h27l5.4,14.3h17.5l-28.9-71.2H456.1z
M455.7,721l7.8-20.7h0.2l7.8,20.7H455.7z"/>
- <polygon fill="#3E3B38" points="486.4,692.5 503.4,692.5 503.4,749.5 519.6,749.5 519.6,692.5 536.6,692.5 536.6,678.3
+ <polygon fill="#3E3B38" points="486.4,692.5 503.4,692.5 503.4,749.5 519.6,749.5 519.6,692.5 536.6,692.5 536.6,678.3
486.4,678.3 "/>
- <polygon fill="#3E3B38" points="542,749.5 582.8,749.5 582.8,735.2 558.4,735.2 558.4,721 581.9,721 581.9,706.6 558.4,706.6
+ <polygon fill="#3E3B38" points="542,749.5 582.8,749.5 582.8,735.2 558.4,735.2 558.4,721 581.9,721 581.9,706.6 558.4,706.6
558.4,692.5 582.8,692.5 582.8,678.3 542,678.3 "/>
<path fill="#3E3B38" d="M606.5,678.3h-17.9v71.2h17.9c19.7,0,35.9-14.9,35.9-35.6C642.4,693.1,625.9,678.3,606.5,678.3z M607,735
h-2.4v-42.1h2.4c12.1,0,20.3,9.1,20.3,21.1C627.3,725.8,619.1,735,607,735z"/>
diff --git a/web-ui/app/locales/en_US/translation.json b/web-ui/app/locales/en_US/translation.json
index 7252d1d7..7aad4fe8 100644
--- a/web-ui/app/locales/en_US/translation.json
+++ b/web-ui/app/locales/en_US/translation.json
@@ -64,7 +64,8 @@
"error": {
"timeout": "A timeout occurred",
"general": "Problems talking to server",
- "parse": "Got invalid response from server"
+ "parse": "Got invalid response from server",
+ "auth": "Invalid email or password"
},
"tags": {
"inbox": "Inbox",
@@ -84,5 +85,10 @@
"button": "Add Account"
},
"back-to-inbox": "Back to my inbox",
- "footer-text": "Product in development. Feedback and issues to"
+ "footer-text": "Product in development. Feedback and issues to",
+ "login": {
+ "email": "Your email",
+ "password": "Your password",
+ "submit": "Login"
+ }
}
diff --git a/web-ui/app/locales/pt_BR/translation.json b/web-ui/app/locales/pt_BR/translation.json
index 9a4393fa..b38b7535 100644
--- a/web-ui/app/locales/pt_BR/translation.json
+++ b/web-ui/app/locales/pt_BR/translation.json
@@ -64,7 +64,8 @@
"error": {
"timeout": "A operação excedeu o limite de tempo",
"general": "Problemas ao se comunicar com o servidor",
- "parse": "Obteve uma resposta inválida do servidor"
+ "parse": "Obteve uma resposta inválida do servidor",
+ "auth": "E-mail ou senha inválidos"
},
"tags": {
"inbox": "Caixa de Entrada",
@@ -84,5 +85,10 @@
"button": "Adicionar e-mail"
},
"back-to-inbox": "Voltar",
- "footer-text": "Produto em desenvolvimento. Reporte problemas através do"
+ "footer-text": "Produto em desenvolvimento. Reporte problemas através do",
+ "login": {
+ "email": "Seu e-mail",
+ "password": "Sua senha",
+ "submit": "Entrar"
+ }
}
diff --git a/web-ui/src/backup_account/backup_account.html b/web-ui/src/backup_account/backup_account.html
index 55881444..084824f2 100644
--- a/web-ui/src/backup_account/backup_account.html
+++ b/web-ui/src/backup_account/backup_account.html
@@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
- <link rel="icon" type="image/png" href="public/images/Favicon.png" />
+ <link rel="icon" type="image/png" href="public/images/favicon.png" />
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0"/>
diff --git a/web-ui/src/common/input_field/input_field.js b/web-ui/src/common/input_field/input_field.js
index 1378ba74..d4876d9f 100644
--- a/web-ui/src/common/input_field/input_field.js
+++ b/web-ui/src/common/input_field/input_field.js
@@ -19,16 +19,24 @@ import React from 'react';
import './input-field.scss';
-const InputField = ({ label, name }) => (
+const InputField = ({ label, name, type = 'text' }) => (
<div className='input-field-group'>
- <input type='text' name={name} className='input-field' required />
+ <input
+ type={type} name={name} className='input-field'
+ autoFocus='' required
+ />
<label className='input-field-label' htmlFor={name}>{label}</label>
</div>
);
InputField.propTypes = {
label: React.PropTypes.string.isRequired,
- name: React.PropTypes.string.isRequired
+ name: React.PropTypes.string.isRequired,
+ type: React.PropTypes.string
+};
+
+InputField.defaultProps = {
+ type: 'text'
};
export default InputField;
diff --git a/web-ui/src/login/app.js b/web-ui/src/login/app.js
index e6ac3192..07099c60 100644
--- a/web-ui/src/login/app.js
+++ b/web-ui/src/login/app.js
@@ -17,19 +17,31 @@
import React from 'react';
import { translate } from 'react-i18next';
+import InputField from 'src/common/input_field/input_field';
+import SubmitButton from 'src/common/submit_button/submit_button';
-const App = () => (
- <form className='standard' id='login_form' action='/login' method='post'>
- <input
- type='text' name='username' id='email' className='text-field'
- placeholder='username' autoFocus=''
- />
- <input
- type='password' name='password' id='password' className='text-field'
- placeholder='password' autoComplete='off'
- />
- <input type='submit' name='login' value='Login' className='button' />
- </form>
+import './app.scss';
+
+const errorMessage = (t, authError) => {
+ if (authError) return <p className='error'>{t('error.auth')}</p>;
+ return <div />;
+};
+
+export const App = ({ t, authError }) => (
+ <div className='login'>
+ <img className='logo' src='/public/images/logo-orange.svg' alt='Pixelated logo' />
+ {errorMessage(t, authError)}
+ <form className='standard' id='login_form' action='/login' method='post'>
+ <InputField name='username' label={t('login.email')} />
+ <InputField type='password' name='password' label={t('login.password')} />
+ <SubmitButton buttonText={t('login.submit')} />
+ </form>
+ </div>
);
+App.propTypes = {
+ t: React.PropTypes.func.isRequired,
+ authError: React.PropTypes.bool
+};
+
export default translate('', { wait: true })(App);
diff --git a/web-ui/src/login/app.scss b/web-ui/src/login/app.scss
new file mode 100644
index 00000000..f6e6bc11
--- /dev/null
+++ b/web-ui/src/login/app.scss
@@ -0,0 +1,70 @@
+/*
+ * 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 "~scss/base/colors";
+
+.error {
+ color: $error;
+ margin: 10px 0 0 0;
+}
+
+.login {
+ display: block;
+ width: 90%;
+ margin: auto;
+ max-width: 400px;
+ padding: 2em 0;
+ margin-top: 3%;
+ margin-bottom: 3%;
+ background-color: $white;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+#login_form {
+ padding: 20px 0;
+ width: 70%;
+
+ .input-field-group {
+ width: 100%;
+ }
+
+ .submit-button {
+ width: 100%;
+ }
+}
+
+.logo {
+ width: 70%;
+}
+
+@media only screen and (min-width : 500px) {
+ #login_form .input-field-group {
+ margin-top: 1em;
+ }
+
+ .login {
+ width: 60%;
+ }
+}
+
+@media only screen and (min-width : 960px) {
+ .login {
+ width: 40%;
+ }
+}
diff --git a/web-ui/src/login/login.css b/web-ui/src/login/login.css
index 51ab2046..d1206a39 100644
--- a/web-ui/src/login/login.css
+++ b/web-ui/src/login/login.css
@@ -16,130 +16,47 @@
*/
body {
- font-family: "Open Sans", "Microsoft YaHei", "Hiragino Sans GB", "Hiragino Sans GB W3", "微软雅黑", "Helvetica Neue", Arial, sans-serif;
- background-color: #EAEAEA;
- height: 100vh;
- color: #3E3A37;
+ font-family: "Open Sans", "Microsoft YaHei", "Hiragino Sans GB", "Hiragino Sans GB W3", "微软雅黑", "Helvetica Neue", Arial, sans-serif;
+ background-color: #EAEAEA;
+ height: 100vh;
+ color: #3E3A37;
- background-image: url("/public/images/hive-bg.png");
- background-repeat: repeat;
-}
-
-.content {
- height: 100vh;
- width: 100%;
-}
-
-.error {
- color: #D72A25;
- margin: 10px 0 0 0;
-}
-
-.message-panel {
- width: 100%;
- margin: 10px auto;
- z-index: 10000;
- text-align: center;
- }
-
-.message-panel span{
- background: #F7E8AF;
- padding: 5px 60px;
- border: 1px solid #f2db81;
- color: #987b0f;
- box-shadow: 1px 1px 3px #69560b;
-}
-
-.message-panel.message-panel-small span{
- padding: 5px 0px;
- display: inline-block;
- width: 100%;
-}
-
-.login {
- display: block;
- width: 240px;
- margin: auto;
- padding: 45px 40px 35px 40px;
- background-color: #FFF;
- margin-top: 2%;
- margin-bottom: 2%;
-}
-
-form#login_form {
- padding: 10px 0;
+ background-image: url("/public/images/hive-bg.png");
+ background-repeat: repeat;
}
.disclaimer {
- display: block;
- margin-top: 10%;
- width: 50%;
- margin: auto;
- background-color: #2BA6CB;
- color: #FFFFFF;
- font-weight: 300;
- font-size: 0.8rem;
- padding: 1em;
- margin-bottom: 20px;
+ display: block;
+ width: 90%;
+ margin: auto;
+ max-width: 400px;
+ background-color: #2BA6CB;
+ color: #FFFFFF;
+ font-weight: 300;
+ font-size: 0.8rem;
+ margin-bottom: 20px;
}
-.disclaimer li {
- margin-top: 1em;
-}
-
-.logo {
- width: 100%;
- height: auto;
-}
-
-input {
- display: block;
- margin: 10px 0;
- padding-left: 5px;
-}
-
-input.text-field {
- width: 97%;
+.disclaimer-content {
+ padding: 1em;
}
-button, .button, input[type=button] {
- cursor: pointer;
- margin: 0 0 1.25rem;
- border: none;
- position: relative;
- text-decoration: none;
- text-align: center;
- -webkit-appearance: none;
- display: inline-block;
- padding: 0.4rem 1.1rem;
- font-size: 0.9rem;
- background-color: #2ba6cb;
- border-color: #2285a2;
- color: white;
- -webkit-border-radius: 2px;
- -moz-border-radius: 2px;
- -ms-border-radius: 2px;
- -o-border-radius: 2px;
- border-radius: 2px;
-}
-
-button:hover, button:focus, .button:hover, .button:focus, input[type=button]:hover, input[type=button]:focus {
- background-color: #2285a2;
- outline: none;
- color: white;
+.disclaimer li {
+ margin-top: 1em;
}
-ul.accounts {
- margin-bottom: 5%;
-}
+@media only screen and (min-width : 500px) {
+ body {
+ font-size: 1.2em;
+ }
-ul.accounts li {
- display: inline-block;
- list-style: none;
- margin-right: 35px;
- margin-top: 0px;
+ .disclaimer {
+ width: 60%;
+ }
}
-ul.accounts li span {
- font-weight: bold;
+@media only screen and (min-width : 960px) {
+ .disclaimer {
+ width: 40%;
+ }
}
diff --git a/web-ui/src/login/login.html b/web-ui/src/login/login.html
index b8c45180..3cebf6f4 100644
--- a/web-ui/src/login/login.html
+++ b/web-ui/src/login/login.html
@@ -1,7 +1,7 @@
<!DOCTYPE html>
<html xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1">
<head>
- <link rel="icon" type="image/png" href="/public/images/Favicon.png" />
+ <link rel="icon" type="image/png" href="/public/images/favicon.png" />
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0"/>
@@ -12,13 +12,11 @@
</head>
<body>
<div class="content">
- <div class="login">
- <img class="logo" src="/public/images/logo-orange.svg" alt="Pixelated logo"/>
- <p t:render="error_msg" class="error"></p>
- <div id="root"/>
- </div>
+ <div id="root"/>
<div class="disclaimer">
- <div t:render="disclaimer"></div>
+ <div class="disclaimer-content">
+ <div t:render="disclaimer"></div>
+ </div>
</div>
</div>
<script type="text/javascript" src="/public/login.js"></script>
diff --git a/web-ui/src/login/login.js b/web-ui/src/login/login.js
index ddbe1943..74e5a14e 100644
--- a/web-ui/src/login/login.js
+++ b/web-ui/src/login/login.js
@@ -20,14 +20,15 @@ import { render } from 'react-dom';
import a11y from 'react-a11y';
import { I18nextProvider } from 'react-i18next';
-import App from './app';
+import AppWrapper from './app';
import i18n from '../i18n';
+import { hasQueryParameter } from '../util';
if (process.env.NODE_ENV === 'development') a11y(React);
render(
<I18nextProvider i18n={i18n}>
- <App />
+ <AppWrapper authError={hasQueryParameter('auth-error')} />
</I18nextProvider>,
document.getElementById('root')
);
diff --git a/web-ui/src/login/opensans.css b/web-ui/src/login/opensans.css
index a42f346c..8795bdf7 100644
--- a/web-ui/src/login/opensans.css
+++ b/web-ui/src/login/opensans.css
@@ -2,68 +2,68 @@
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
- src: local("Open Sans Light"), local("OpenSans-Light"), url("/fonts/OpenSans-Light.woff") format("woff");
+ src: local("Open Sans Light"), local("OpenSans-Light"), url("/assets/fonts/OpenSans-Light.woff") format("woff");
}
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
- src: local("Open Sans"), local("OpenSans"), url("/fonts/OpenSans.woff") format("woff");
+ src: local("Open Sans"), local("OpenSans"), url("/assets/fonts/OpenSans.woff") format("woff");
}
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
- src: local("Open Sans Semibold"), local("OpenSans-Semibold"), url("/fonts/OpenSans-Semibold.woff") format("woff");
+ src: local("Open Sans Semibold"), local("OpenSans-Semibold"), url("/assets/fonts/OpenSans-Semibold.woff") format("woff");
}
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
- src: local("Open Sans Bold"), local("OpenSans-Bold"), url("/fonts/OpenSans-Bold.woff") format("woff");
+ src: local("Open Sans Bold"), local("OpenSans-Bold"), url("/assets/fonts/OpenSans-Bold.woff") format("woff");
}
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 800;
- src: local("Open Sans Extrabold"), local("OpenSans-Extrabold"), url("/fonts/OpenSans-Extrabold.woff") format("woff");
+ src: local("Open Sans Extrabold"), local("OpenSans-Extrabold"), url("/assets/fonts/OpenSans-Extrabold.woff") format("woff");
}
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 300;
- src: local("Open Sans Light Italic"), local("OpenSansLight-Italic"), url("/fonts/OpenSansLight-Italic.woff") format("woff");
+ src: local("Open Sans Light Italic"), local("OpenSansLight-Italic"), url("/assets/fonts/OpenSansLight-Italic.woff") format("woff");
}
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 400;
- src: local("Open Sans Italic"), local("OpenSans-Italic"), url("/fonts/OpenSans-Italic.woff") format("woff");
+ src: local("Open Sans Italic"), local("OpenSans-Italic"), url("/assets/fonts/OpenSans-Italic.woff") format("woff");
}
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 600;
- src: local("Open Sans Semibold Italic"), local("OpenSans-SemiboldItalic"), url("/fonts/OpenSans-SemiboldItalic.woff ") format("woff");
+ src: local("Open Sans Semibold Italic"), local("OpenSans-SemiboldItalic"), url("/assets/fonts/OpenSans-SemiboldItalic.woff ") format("woff");
}
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 700;
- src: local("Open Sans Bold Italic"), local("OpenSans-BoldItalic"), url("/fonts/OpenSans-BoldItalic.woff") format("woff");
+ src: local("Open Sans Bold Italic"), local("OpenSans-BoldItalic"), url("/assets/fonts/OpenSans-BoldItalic.woff") format("woff");
}
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 800;
- src: local("Open Sans Extrabold Italic"), local("OpenSans-ExtraboldItalic"), url("/fonts/OpenSans-ExtraboldItalic.woff") format("woff");
+ src: local("Open Sans Extrabold Italic"), local("OpenSans-ExtraboldItalic"), url("/assets/fonts/OpenSans-ExtraboldItalic.woff") format("woff");
}
diff --git a/web-ui/src/util.js b/web-ui/src/util.js
new file mode 100644
index 00000000..1b244458
--- /dev/null
+++ b/web-ui/src/util.js
@@ -0,0 +1,7 @@
+export const hasQueryParameter = param => (
+ decodeURIComponent(window.location.search.substring(1)).includes(param)
+);
+
+export default {
+ hasQueryParameter
+};
diff --git a/web-ui/test/unit/login/app.spec.js b/web-ui/test/unit/login/app.spec.js
new file mode 100644
index 00000000..347e2b19
--- /dev/null
+++ b/web-ui/test/unit/login/app.spec.js
@@ -0,0 +1,26 @@
+import { shallow } from 'enzyme';
+import expect from 'expect';
+import React from 'react';
+import { App } from 'src/login/app';
+
+describe('App', () => {
+ let app;
+ const mockTranslations = key => key;
+
+ beforeEach(() => {
+ app = shallow(<App t={mockTranslations} />);
+ });
+
+ it('renders login form', () => {
+ expect(app.find('form').props().action).toEqual('/login');
+ });
+
+ it('renders auth error message', () => {
+ app = shallow(<App t={mockTranslations} authError />);
+ expect(app.find('.error').length).toEqual(1);
+ });
+
+ it('does not render auth error message', () => {
+ expect(app.find('.error').length).toEqual(0);
+ });
+});
diff --git a/web-ui/test/unit/util.spec.js b/web-ui/test/unit/util.spec.js
new file mode 100644
index 00000000..84decf6f
--- /dev/null
+++ b/web-ui/test/unit/util.spec.js
@@ -0,0 +1,20 @@
+import expect from 'expect';
+import Util from 'src/util';
+
+describe('Utils', () => {
+ describe('.hasQueryParameter', () => {
+ global.window = {
+ location: {
+ search: '?auth&lng=pt-BR'
+ }
+ };
+
+ it('checks if param included in query parameters', () => {
+ expect(Util.hasQueryParameter('auth')).toBe(true);
+ });
+
+ it('checks if param not included in query parameters', () => {
+ expect(Util.hasQueryParameter('error')).toBe(false);
+ });
+ });
+});