summaryrefslogtreecommitdiff
path: root/web-ui/src/login
diff options
context:
space:
mode:
Diffstat (limited to 'web-ui/src/login')
-rw-r--r--web-ui/src/login/_login_disclaimer_banner.html9
-rw-r--r--web-ui/src/login/about/welcome.js36
-rw-r--r--web-ui/src/login/about/welcome.scss49
-rw-r--r--web-ui/src/login/about/welcome.spec.js17
-rw-r--r--web-ui/src/login/error/auth_error.js31
-rw-r--r--web-ui/src/login/error/auth_error.scss29
-rw-r--r--web-ui/src/login/error/auth_error.spec.js17
-rw-r--r--web-ui/src/login/error/generic_error.js37
-rw-r--r--web-ui/src/login/error/generic_error.scss59
-rw-r--r--web-ui/src/login/error/generic_error.spec.js17
-rw-r--r--web-ui/src/login/login.css74
-rw-r--r--web-ui/src/login/login.html24
-rw-r--r--web-ui/src/login/login.js38
-rw-r--r--web-ui/src/login/normalize.min.css1
-rw-r--r--web-ui/src/login/opensans.css69
-rw-r--r--web-ui/src/login/page.js61
-rw-r--r--web-ui/src/login/page.scss100
-rw-r--r--web-ui/src/login/page.spec.js53
18 files changed, 721 insertions, 0 deletions
diff --git a/web-ui/src/login/_login_disclaimer_banner.html b/web-ui/src/login/_login_disclaimer_banner.html
new file mode 100644
index 00000000..dfc63030
--- /dev/null
+++ b/web-ui/src/login/_login_disclaimer_banner.html
@@ -0,0 +1,9 @@
+<div>
+ <ul class="accounts">
+ <h2>Some disclaimer</h2>
+ <li>
+ please supply the option --banner with an XML compatible file
+ <div>to override this default message</div>
+ </li>
+ </ul>
+</div>
diff --git a/web-ui/src/login/about/welcome.js b/web-ui/src/login/about/welcome.js
new file mode 100644
index 00000000..93aae8e1
--- /dev/null
+++ b/web-ui/src/login/about/welcome.js
@@ -0,0 +1,36 @@
+/*
+ * 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 { translate } from 'react-i18next';
+
+import './welcome.scss';
+
+export const Welcome = ({ t }) => (
+ <div className='welcome'>
+ <img className='welcome-logo' src='/public/images/welcome.svg' alt={t('login.welcome-image-alt')} />
+ <div>
+ <h3>{t('login.welcome-message')}</h3>
+ </div>
+ </div>
+);
+
+Welcome.propTypes = {
+ t: React.PropTypes.func.isRequired
+};
+
+export default translate('', { wait: true })(Welcome);
diff --git a/web-ui/src/login/about/welcome.scss b/web-ui/src/login/about/welcome.scss
new file mode 100644
index 00000000..1492b154
--- /dev/null
+++ b/web-ui/src/login/about/welcome.scss
@@ -0,0 +1,49 @@
+/*
+ * 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";
+
+.welcome {
+ display: none;
+}
+
+@media only screen and (min-width : 960px) {
+ .welcome {
+ font-size: 0.9em;
+ color: $medium_grey;
+ display: flex;
+ font-weight: 300;
+ flex-direction: column;
+ align-self: flex-end;
+ order: 2;
+ width: 34%;
+ margin-right: 8%;
+ margin-top: -20.5em;
+
+ h3 {
+ font-weight: normal;
+ font-size: 1.2em;
+ margin-top: 0;
+ }
+ }
+
+ .welcome-logo {
+ order: 1;
+ width: 90%;
+ height: 15em;
+ }
+}
diff --git a/web-ui/src/login/about/welcome.spec.js b/web-ui/src/login/about/welcome.spec.js
new file mode 100644
index 00000000..3e190c0c
--- /dev/null
+++ b/web-ui/src/login/about/welcome.spec.js
@@ -0,0 +1,17 @@
+import { shallow } from 'enzyme';
+import expect from 'expect';
+import React from 'react';
+import { Welcome } from 'src/login/about/welcome';
+
+describe('Welcome', () => {
+ let welcome;
+ const mockTranslations = key => key;
+
+ beforeEach(() => {
+ welcome = shallow(<Welcome t={mockTranslations} />);
+ });
+
+ it('renders welcome component', () => {
+ expect(welcome.find('.welcome').length).toEqual(1);
+ });
+});
diff --git a/web-ui/src/login/error/auth_error.js b/web-ui/src/login/error/auth_error.js
new file mode 100644
index 00000000..5dbbc3e7
--- /dev/null
+++ b/web-ui/src/login/error/auth_error.js
@@ -0,0 +1,31 @@
+/*
+ * 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 { translate } from 'react-i18next';
+
+import './auth_error.scss';
+
+export const AuthError = ({ t }) => (
+ <p className='auth-error'>{t('error.auth')}</p>
+);
+
+AuthError.propTypes = {
+ t: React.PropTypes.func.isRequired
+};
+
+export default translate('', { wait: true })(AuthError);
diff --git a/web-ui/src/login/error/auth_error.scss b/web-ui/src/login/error/auth_error.scss
new file mode 100644
index 00000000..f6256be4
--- /dev/null
+++ b/web-ui/src/login/error/auth_error.scss
@@ -0,0 +1,29 @@
+/*
+ * 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";
+
+.auth-error {
+ color: $error;
+ margin: -1em 0 1em 0;
+}
+
+@media only screen and (min-width : 960px) {
+ .auth-error {
+ margin: -2em 0 1em 0;
+ }
+}
diff --git a/web-ui/src/login/error/auth_error.spec.js b/web-ui/src/login/error/auth_error.spec.js
new file mode 100644
index 00000000..55d8920f
--- /dev/null
+++ b/web-ui/src/login/error/auth_error.spec.js
@@ -0,0 +1,17 @@
+import { shallow } from 'enzyme';
+import expect from 'expect';
+import React from 'react';
+import { AuthError } from 'src/login/error/auth_error';
+
+describe('AuthError', () => {
+ let authError;
+ const mockTranslations = key => key;
+
+ beforeEach(() => {
+ authError = shallow(<AuthError t={mockTranslations} />);
+ });
+
+ it('renders error message', () => {
+ expect(authError.find('.auth-error').length).toEqual(1);
+ });
+});
diff --git a/web-ui/src/login/error/generic_error.js b/web-ui/src/login/error/generic_error.js
new file mode 100644
index 00000000..b233d5bb
--- /dev/null
+++ b/web-ui/src/login/error/generic_error.js
@@ -0,0 +1,37 @@
+/*
+ * 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 { translate } from 'react-i18next';
+
+import './generic_error.scss';
+
+export const GenericError = ({ t }) => (
+ <div className='generic-error'>
+ <img className='dead-mail' src='/public/images/dead-mail.svg' alt='' />
+ <div>
+ <h2>{t('error.login.title')}</h2>
+ <p>{t('error.login.message')}</p>
+ </div>
+ </div>
+);
+
+GenericError.propTypes = {
+ t: React.PropTypes.func.isRequired
+};
+
+export default translate('', { wait: true })(GenericError);
diff --git a/web-ui/src/login/error/generic_error.scss b/web-ui/src/login/error/generic_error.scss
new file mode 100644
index 00000000..5a077f32
--- /dev/null
+++ b/web-ui/src/login/error/generic_error.scss
@@ -0,0 +1,59 @@
+/*
+ * 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";
+
+.generic-error {
+ font-size: 0.9em;
+ color: $medium_grey;
+ display: flex;
+ padding: 0 1.5em;
+ font-weight: 300;
+
+ h2 {
+ font-weight: normal;
+ font-size: 1.4em;
+ }
+}
+
+.dead-mail {
+ width: 20%;
+ margin-right: 1.5em;
+ margin-top: 1.7em;
+ align-self: flex-start;
+}
+
+@media only screen and (min-width : 960px) {
+ .generic-error {
+ flex-direction: column;
+ align-self: flex-end;
+ order: 2;
+ width: 34%;
+ margin-right: 8%;
+ margin-top: -19.5em;
+ padding: 0;
+
+ h2 {
+ margin: 0;
+ }
+ }
+
+ .dead-mail {
+ order: 1;
+ width: 30%;
+ }
+}
diff --git a/web-ui/src/login/error/generic_error.spec.js b/web-ui/src/login/error/generic_error.spec.js
new file mode 100644
index 00000000..1ef8349d
--- /dev/null
+++ b/web-ui/src/login/error/generic_error.spec.js
@@ -0,0 +1,17 @@
+import { shallow } from 'enzyme';
+import expect from 'expect';
+import React from 'react';
+import { GenericError } from 'src/login/error/generic_error';
+
+describe('GenericError', () => {
+ let genericError;
+ const mockTranslations = key => key;
+
+ beforeEach(() => {
+ genericError = shallow(<GenericError t={mockTranslations} />);
+ });
+
+ it('renders error message', () => {
+ expect(genericError.find('.generic-error').length).toEqual(1);
+ });
+});
diff --git a/web-ui/src/login/login.css b/web-ui/src/login/login.css
new file mode 100644
index 00000000..9628932f
--- /dev/null
+++ b/web-ui/src/login/login.css
@@ -0,0 +1,74 @@
+/*
+ * 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/>.
+ */
+
+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;
+
+ background-image: url("/public/images/hive-bg.png");
+ background-repeat: repeat;
+}
+
+#root {
+ margin-top: 3%;
+ margin-bottom: 3%;
+}
+
+.disclaimer {
+ 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-content {
+ padding: 1em;
+}
+
+.disclaimer li {
+ margin-top: 1em;
+}
+
+@media only screen and (min-width : 500px) {
+ .disclaimer {
+ width: 60%;
+ }
+}
+
+@media only screen and (min-width : 960px) {
+ body {
+ font-size: 1.2em;
+ }
+
+ .disclaimer {
+ width: 70%;
+ max-width: 700px;
+ padding: 0;
+ font-size: 1em;
+ }
+
+ .disclaimer-content {
+ font-size: 0.7em;
+ }
+}
diff --git a/web-ui/src/login/login.html b/web-ui/src/login/login.html
new file mode 100644
index 00000000..3cebf6f4
--- /dev/null
+++ b/web-ui/src/login/login.html
@@ -0,0 +1,24 @@
+<!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" />
+ <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"/>
+ <title>Pixelated - Login</title>
+ <link rel="stylesheet" type="text/css" href="/public/normalize.min.css" />
+ <link rel="stylesheet" type="text/css" href="/public/login.css" />
+ <link rel="stylesheet" type="text/css" href="/public/opensans.css" />
+ </head>
+ <body>
+ <div class="content">
+ <div id="root"/>
+ <div class="disclaimer">
+ <div class="disclaimer-content">
+ <div t:render="disclaimer"></div>
+ </div>
+ </div>
+ </div>
+ <script type="text/javascript" src="/public/login.js"></script>
+ </body>
+</html>
diff --git a/web-ui/src/login/login.js b/web-ui/src/login/login.js
new file mode 100644
index 00000000..39500b9d
--- /dev/null
+++ b/web-ui/src/login/login.js
@@ -0,0 +1,38 @@
+/*
+ * 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 { render } from 'react-dom';
+import a11y from 'react-a11y';
+
+import { hasQueryParameter } from 'src/common/util';
+import App from 'src/common/app';
+import PageWrapper from './page';
+
+if (process.env.NODE_ENV === 'development') a11y(React);
+
+render(
+ <App
+ child={
+ <PageWrapper
+ authError={hasQueryParameter('auth-error')}
+ error={hasQueryParameter('error')}
+ />
+ }
+ />,
+ document.getElementById('root')
+);
diff --git a/web-ui/src/login/normalize.min.css b/web-ui/src/login/normalize.min.css
new file mode 100644
index 00000000..d3c7f4d5
--- /dev/null
+++ b/web-ui/src/login/normalize.min.css
@@ -0,0 +1 @@
+/*! normalize.css v3.0.1 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0} \ No newline at end of file
diff --git a/web-ui/src/login/opensans.css b/web-ui/src/login/opensans.css
new file mode 100644
index 00000000..8795bdf7
--- /dev/null
+++ b/web-ui/src/login/opensans.css
@@ -0,0 +1,69 @@
+@font-face {
+ font-family: 'Open Sans';
+ font-style: normal;
+ font-weight: 300;
+ 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("/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("/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("/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("/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("/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("/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("/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("/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("/assets/fonts/OpenSans-ExtraboldItalic.woff") format("woff");
+}
diff --git a/web-ui/src/login/page.js b/web-ui/src/login/page.js
new file mode 100644
index 00000000..21acee3f
--- /dev/null
+++ b/web-ui/src/login/page.js
@@ -0,0 +1,61 @@
+/*
+ * 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 { translate } from 'react-i18next';
+import InputField from 'src/common/input_field/input_field';
+import SubmitButton from 'src/common/submit_button/submit_button';
+import AuthError from 'src/login/error/auth_error';
+import GenericError from 'src/login/error/generic_error';
+import Welcome from 'src/login/about/welcome';
+
+import './page.scss';
+
+const errorMessage = (t, authError) => {
+ if (authError) return <AuthError />;
+ return <div />;
+};
+
+const rightPanel = (t, error) => {
+ if (error) return <GenericError />;
+ return <Welcome />;
+};
+
+export const Page = ({ t, authError, error }) => (
+ <div className='login'>
+ <img
+ className={error ? 'logo small-logo' : 'logo'}
+ src='/public/images/logo-orange.svg'
+ alt='Pixelated logo'
+ />
+ {rightPanel(t, error)}
+ <form className='standard' id='login_form' action='/login' method='post' noValidate>
+ {errorMessage(t, authError)}
+ <InputField name='username' label={t('login.email')} autoFocus />
+ <InputField type='password' name='password' label={t('login.password')} />
+ <SubmitButton buttonText={t('login.submit')} />
+ </form>
+ </div>
+);
+
+Page.propTypes = {
+ t: React.PropTypes.func.isRequired,
+ authError: React.PropTypes.bool,
+ error: React.PropTypes.bool
+};
+
+export default translate('', { wait: true })(Page);
diff --git a/web-ui/src/login/page.scss b/web-ui/src/login/page.scss
new file mode 100644
index 00000000..4bc1592c
--- /dev/null
+++ b/web-ui/src/login/page.scss
@@ -0,0 +1,100 @@
+/*
+ * 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";
+
+.login {
+ display: block;
+ width: 90%;
+ margin: auto;
+ max-width: 400px;
+ padding: 2em 0;
+ background-color: $white;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+#login_form {
+ width: 70%;
+ display: flex;
+ align-items: center;
+ flex-direction: column;
+
+ .input-field-group {
+ width: 100%;
+ margin: 0;
+ }
+
+ .submit-button {
+ margin-top: 1.5em;
+ width: 100%;
+ }
+}
+
+.logo {
+ width: 70%;
+ margin-bottom: 2em;
+}
+
+.small-logo {
+ width: 50%;
+ margin-bottom: 1em;
+}
+
+@media only screen and (min-width : 500px) {
+ .login {
+ width: 60%;
+ }
+}
+
+@media only screen and (min-width : 960px) {
+ .content {
+ font-size: 0.9em;
+ }
+
+ .login {
+ display: flex;
+ align-items: flex-start;
+ width: 70%;
+ max-width: 700px;
+ padding: 2.5em 0;
+ height: 22em;
+
+ &:after {
+ content: '';
+ height: 22em;
+ position: absolute;
+ margin-top: 3%;
+ top: 2.5em;
+ left: 50%;
+ border: 1px solid $lighter_gray;
+ transform: scaleX(0.5);
+ }
+ }
+
+ .logo {
+ height: 6em;
+ margin-bottom: 1em;
+ margin-top: 0.5em;
+ }
+
+ .logo, #login_form {
+ width: 34%;
+ margin-left: 8%;
+ }
+}
diff --git a/web-ui/src/login/page.spec.js b/web-ui/src/login/page.spec.js
new file mode 100644
index 00000000..05607ecb
--- /dev/null
+++ b/web-ui/src/login/page.spec.js
@@ -0,0 +1,53 @@
+import { shallow } from 'enzyme';
+import expect from 'expect';
+import React from 'react';
+import { Page } from 'src/login/page';
+import AuthError from 'src/login/error/auth_error';
+import GenericError from 'src/login/error/generic_error';
+import Welcome from 'src/login/about/welcome';
+
+describe('Login', () => {
+ let page;
+ const mockTranslations = key => key;
+
+ it('renders login form', () => {
+ page = shallow(<Page t={mockTranslations} />);
+ expect(page.find('form').props().action).toEqual('/login');
+ });
+
+ it('renders welcome message when no error', () => {
+ page = shallow(<Page t={mockTranslations} />);
+ expect(page.find(Welcome).length).toEqual(1);
+ });
+
+ it('renders auth error message', () => {
+ page = shallow(<Page t={mockTranslations} authError />);
+ expect(page.find(AuthError).length).toEqual(1);
+ });
+
+ it('renders generic error message when error', () => {
+ page = shallow(<Page t={mockTranslations} error />);
+ expect(page.find(GenericError).length).toEqual(1);
+ });
+
+ it('does not render welcome message when error', () => {
+ page = shallow(<Page t={mockTranslations} error />);
+ expect(page.find(Welcome).length).toEqual(0);
+ });
+
+ it('does not render error message', () => {
+ page = shallow(<Page t={mockTranslations} />);
+ expect(page.find(AuthError).length).toEqual(0);
+ expect(page.find(GenericError).length).toEqual(0);
+ });
+
+ it('adds small logo class when error', () => {
+ page = shallow(<Page t={mockTranslations} error />);
+ expect(page.find('.logo').props().className).toEqual('logo small-logo');
+ });
+
+ it('does not add small logo class when no error', () => {
+ page = shallow(<Page t={mockTranslations} />);
+ expect(page.find('.logo').props().className).toEqual('logo');
+ });
+});