diff --git a/assets/images/background.png b/assets/images/background.png
new file mode 100644
index 0000000000..dfa0e3cb83
Binary files /dev/null and b/assets/images/background.png differ
diff --git a/assets/stylesheets/fonts.css b/assets/stylesheets/fonts.css
index 9141e9cbcb..bc4a5c9d06 100644
--- a/assets/stylesheets/fonts.css
+++ b/assets/stylesheets/fonts.css
@@ -27,3 +27,8 @@
font-weight: normal;
font-style: normal;
}
+
+body {
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
diff --git a/frontend/components/App/styles.js b/frontend/components/App/styles.js
index 82d07964ae..3819170964 100644
--- a/frontend/components/App/styles.js
+++ b/frontend/components/App/styles.js
@@ -5,7 +5,7 @@ const { color } = styles;
export default {
footerStyles: {
alignItems: 'center',
- backgroundColor: color.darkGrey,
+ backgroundColor: color.footerBg,
height: '55px',
textAlign: 'center',
position: 'absolute',
diff --git a/frontend/components/LoginRoutes/LoginRoutes.jsx b/frontend/components/LoginRoutes/LoginRoutes.jsx
index d64884d255..acbc93c05b 100644
--- a/frontend/components/LoginRoutes/LoginRoutes.jsx
+++ b/frontend/components/LoginRoutes/LoginRoutes.jsx
@@ -1,23 +1,11 @@
import React, { Component, PropTypes } from 'react';
import componentStyles from './styles';
-import { loadBackground, removeBackground, resizeBackground } from '../../utilities/backgroundImage';
export class LoginRoutes extends Component {
static propTypes = {
children: PropTypes.element,
};
- componentWillMount () {
- const { window } = global;
-
- loadBackground();
- window.onresize = resizeBackground;
- }
-
- componentWillUnmount () {
- removeBackground();
- }
-
render () {
const { containerStyles } = componentStyles;
const { children } = this.props;
@@ -32,4 +20,3 @@ export class LoginRoutes extends Component {
}
export default LoginRoutes;
-
diff --git a/frontend/components/StackedWhiteBoxes/StackedWhiteBoxes.jsx b/frontend/components/StackedWhiteBoxes/StackedWhiteBoxes.jsx
new file mode 100644
index 0000000000..9080a57f2c
--- /dev/null
+++ b/frontend/components/StackedWhiteBoxes/StackedWhiteBoxes.jsx
@@ -0,0 +1,69 @@
+import React, { Component, PropTypes } from 'react';
+import radium from 'radium';
+import { Link } from 'react-router';
+import componentStyles from './styles';
+
+class StackedWhiteBoxes extends Component {
+ static propTypes = {
+ children: PropTypes.element,
+ headerText: PropTypes.string,
+ leadText: PropTypes.string,
+ previousLocation: PropTypes.string,
+ style: PropTypes.object,
+ };
+
+ static defaultProps = {
+ style: {},
+ };
+
+ renderBackButton = () => {
+ const { previousLocation } = this.props;
+ const { exStyles, exWrapperStyles } = componentStyles;
+
+ if (!previousLocation) return false;
+
+ return (
+
+ x
+
+ );
+ }
+
+ renderHeader = () => {
+ const { headerStyles, headerWrapperStyles } = componentStyles;
+ const { headerText, style } = this.props;
+
+ return (
+
+ );
+ }
+
+ render () {
+ const { children, leadText } = this.props;
+ const {
+ boxStyles,
+ containerStyles,
+ smallTabStyles,
+ tabStyles,
+ textStyles,
+ } = componentStyles;
+ const { renderBackButton, renderHeader } = this;
+
+ return (
+
+
+
+
+ {renderBackButton()}
+ {renderHeader()}
+
{leadText}
+ {children}
+
+
+ );
+ }
+}
+
+export default radium(StackedWhiteBoxes);
diff --git a/frontend/components/StackedWhiteBoxes/index.js b/frontend/components/StackedWhiteBoxes/index.js
new file mode 100644
index 0000000000..b04a1032d8
--- /dev/null
+++ b/frontend/components/StackedWhiteBoxes/index.js
@@ -0,0 +1 @@
+export default from './StackedWhiteBoxes';
diff --git a/frontend/components/StackedWhiteBoxes/styles.js b/frontend/components/StackedWhiteBoxes/styles.js
new file mode 100644
index 0000000000..e92bfb2ce7
--- /dev/null
+++ b/frontend/components/StackedWhiteBoxes/styles.js
@@ -0,0 +1,66 @@
+import styles from '../../styles';
+
+const { border, color, font, padding } = styles;
+
+export default {
+ boxStyles: {
+ alignItems: 'center',
+ backgroundColor: color.white,
+ borderTopLeftRadius: border.radius.base,
+ borderTopRightRadius: border.radius.base,
+ boxShadow: border.shadow.blur,
+ boxSizing: 'border-box',
+ display: 'flex',
+ flexDirection: 'column',
+ padding: padding.base,
+ width: '522px',
+ },
+ containerStyles: {
+ alignItems: 'center',
+ display: 'flex',
+ justifyContent: 'center',
+ flexDirection: 'column',
+ },
+ exStyles: {
+ color: color.lightGrey,
+ textDecoration: 'none',
+ },
+ exWrapperStyles: {
+ textAlign: 'right',
+ width: '100%',
+ },
+ headerStyles: {
+ fontFamily: "'Oxygen', sans-serif",
+ fontSize: font.large,
+ fontWeight: '300',
+ color: color.textUltradark,
+ lineHeight: '32px',
+ marginTop: 0,
+ marginBottom: 0,
+ textTransform: 'uppercase',
+ },
+ headerWrapperStyles: {
+ width: '100%',
+ },
+ tabStyles: {
+ backgroundColor: color.white,
+ borderTopLeftRadius: border.radius.base,
+ borderTopRightRadius: border.radius.base,
+ boxShadow: border.shadow.blur,
+ height: '20px',
+ width: '460px',
+ },
+ textStyles: {
+ color: color.purpleGrey,
+ fontSize: font.medium,
+ },
+ smallTabStyles: {
+ backgroundColor: color.white,
+ borderTopLeftRadius: border.radius.base,
+ borderTopRightRadius: border.radius.base,
+ boxShadow: border.shadow.blur,
+ height: '20px',
+ marginTop: padding.base,
+ width: '400px',
+ },
+};
diff --git a/frontend/components/forms/LoginForm/styles.js b/frontend/components/forms/LoginForm/styles.js
index 533b99a6d3..129922ac06 100644
--- a/frontend/components/forms/LoginForm/styles.js
+++ b/frontend/components/forms/LoginForm/styles.js
@@ -19,7 +19,7 @@ export default {
forgotPasswordStyles: {
fontSize: font.medium,
textDecoration: 'none',
- color: color.lightGrey,
+ color: color.accentText,
},
forgotPasswordWrapperStyles: {
marginTop: padding.base,
diff --git a/frontend/components/forms/ResetPasswordForm/ResetPasswordForm.jsx b/frontend/components/forms/ResetPasswordForm/ResetPasswordForm.jsx
new file mode 100644
index 0000000000..96cb017208
--- /dev/null
+++ b/frontend/components/forms/ResetPasswordForm/ResetPasswordForm.jsx
@@ -0,0 +1,144 @@
+import React, { Component, PropTypes } from 'react';
+import componentStyles from './styles';
+import GradientButton from '../../buttons/GradientButton';
+import InputFieldWithIcon from '../fields/InputFieldWithIcon';
+import validatePresence from '../validators/validate_presence';
+import validateEquality from '../validators/validate_equality';
+
+class ResetPasswordForm extends Component {
+ static propTypes = {
+ onSubmit: PropTypes.func,
+ };
+
+ constructor (props) {
+ super(props);
+
+ this.state = {
+ errors: {
+ new_password: null,
+ new_password_confirmation: null,
+ },
+ formData: {
+ new_password: null,
+ new_password_confirmation: null,
+ },
+ };
+ }
+
+ onFormSubmit = (evt) => {
+ evt.preventDefault();
+ const { validate } = this;
+ const { onSubmit } = this.props;
+ const { formData } = this.state;
+
+ if (validate()) {
+ return onSubmit(formData);
+ }
+
+ return false;
+ }
+
+ onInputChange = (inputName) => {
+ return ({ target }) => {
+ const { formData } = this.state;
+ const { value } = target;
+
+ this.setState({
+ errors: {
+ new_password: null,
+ new_password_confirmation: null,
+ },
+ formData: {
+ ...formData,
+ [inputName]: value,
+ },
+ });
+ };
+ }
+
+ validate = () => {
+ const {
+ errors,
+ formData: {
+ new_password: newPassword,
+ new_password_confirmation: newPasswordConfirmation,
+ },
+ } = this.state;
+
+ if (!validatePresence(newPassword)) {
+ this.setState({
+ errors: {
+ ...errors,
+ new_password: 'New Password field must be completed',
+ },
+ });
+
+ return false;
+ }
+
+ if (!validatePresence(newPasswordConfirmation)) {
+ this.setState({
+ errors: {
+ ...errors,
+ new_password_confirmation: 'New Password Confirmation field must be completed',
+ },
+ });
+
+ return false;
+ }
+
+ if (!validateEquality(newPassword, newPasswordConfirmation)) {
+ this.setState({
+ errors: {
+ ...errors,
+ new_password_confirmation: 'Passwords Do Not Match',
+ },
+ });
+
+ return false;
+ }
+
+ return true;
+ }
+
+ render () {
+ const { errors } = this.state;
+ const {
+ formStyles,
+ inputStyles,
+ submitButtonStyles,
+ } = componentStyles;
+ const { onFormSubmit, onInputChange } = this;
+
+ return (
+
+ );
+ }
+}
+
+export default ResetPasswordForm;
diff --git a/frontend/components/forms/ResetPasswordForm/ResetPasswordForm.tests.jsx b/frontend/components/forms/ResetPasswordForm/ResetPasswordForm.tests.jsx
new file mode 100644
index 0000000000..0e1d69b5b9
--- /dev/null
+++ b/frontend/components/forms/ResetPasswordForm/ResetPasswordForm.tests.jsx
@@ -0,0 +1,92 @@
+import React from 'react';
+import expect, { createSpy, restoreSpies } from 'expect';
+import { mount } from 'enzyme';
+import { noop } from 'lodash';
+import ResetPasswordForm from './ResetPasswordForm';
+import { fillInFormInput } from '../../../test/helpers';
+
+describe('ResetPasswordForm - component', () => {
+ const newPassword = 'my new password';
+
+ afterEach(restoreSpies);
+
+ it('updates component state when the new_password field is changed', () => {
+ const form = mount();
+
+ const newPasswordField = form.find({ name: 'new_password' });
+ fillInFormInput(newPasswordField, newPassword);
+
+ const { formData } = form.state();
+ expect(formData).toContain({ new_password: newPassword });
+ });
+
+ it('updates component state when the new_password_confirmation field is changed', () => {
+ const form = mount();
+
+ const newPasswordField = form.find({ name: 'new_password_confirmation' });
+ fillInFormInput(newPasswordField, newPassword);
+
+ const { formData } = form.state();
+ expect(formData).toContain({ new_password_confirmation: newPassword });
+ });
+
+ it('it does not submit the form when the form fields have not been filled out', () => {
+ const submitSpy = createSpy();
+ const form = mount();
+ const submitBtn = form.find('button');
+
+ submitBtn.simulate('submit');
+
+ const { errors } = form.state();
+ expect(errors.new_password).toEqual('New Password field must be completed');
+ expect(submitSpy).toNotHaveBeenCalled();
+ });
+
+ it('it does not submit the form when only the new password field has been filled out', () => {
+ const submitSpy = createSpy();
+ const form = mount();
+ const newPasswordField = form.find({ name: 'new_password' });
+ fillInFormInput(newPasswordField, newPassword);
+ const submitBtn = form.find('button');
+
+ submitBtn.simulate('submit');
+
+ const { errors } = form.state();
+ expect(errors.new_password_confirmation).toEqual('New Password Confirmation field must be completed');
+ expect(submitSpy).toNotHaveBeenCalled();
+ });
+
+ it('submits the form data when the form is submitted', () => {
+ const submitSpy = createSpy();
+ const form = mount();
+ const newPasswordField = form.find({ name: 'new_password' });
+ const newPasswordConfirmationField = form.find({ name: 'new_password_confirmation' });
+ const submitBtn = form.find('button');
+
+ fillInFormInput(newPasswordField, newPassword);
+ fillInFormInput(newPasswordConfirmationField, newPassword);
+ submitBtn.simulate('submit');
+
+ expect(submitSpy).toHaveBeenCalledWith({
+ new_password: newPassword,
+ new_password_confirmation: newPassword,
+ });
+ });
+
+ it('does not submit the form if the new password confirmation does not match', () => {
+ const submitSpy = createSpy();
+ const form = mount();
+ const newPasswordField = form.find({ name: 'new_password' });
+ const newPasswordConfirmationField = form.find({ name: 'new_password_confirmation' });
+ const submitBtn = form.find('button');
+
+ fillInFormInput(newPasswordField, newPassword);
+ fillInFormInput(newPasswordConfirmationField, 'not my new password');
+ submitBtn.simulate('submit');
+
+ expect(submitSpy).toNotHaveBeenCalled();
+ expect(form.state().errors).toInclude({
+ new_password_confirmation: 'Passwords Do Not Match',
+ });
+ });
+});
diff --git a/frontend/components/forms/ResetPasswordForm/index.js b/frontend/components/forms/ResetPasswordForm/index.js
new file mode 100644
index 0000000000..31ada3abee
--- /dev/null
+++ b/frontend/components/forms/ResetPasswordForm/index.js
@@ -0,0 +1 @@
+export default from './ResetPasswordForm';
diff --git a/frontend/components/forms/ResetPasswordForm/styles.js b/frontend/components/forms/ResetPasswordForm/styles.js
new file mode 100644
index 0000000000..2b78f9d5d8
--- /dev/null
+++ b/frontend/components/forms/ResetPasswordForm/styles.js
@@ -0,0 +1,18 @@
+import styles from '../../../styles';
+
+const { border, padding } = styles;
+
+export default {
+ formStyles: {
+ width: '100%',
+ },
+ inputStyles: {
+ width: '100%',
+ },
+ submitButtonStyles: {
+ borderTopLeftRadius: border.radius.base,
+ borderTopRightRadius: border.radius.base,
+ marginTop: padding.base,
+ padding: padding.medium,
+ },
+};
diff --git a/frontend/components/forms/fields/InputFieldWithIcon/styles.js b/frontend/components/forms/fields/InputFieldWithIcon/styles.js
index 2cd45f4f70..5ddf8f47cd 100644
--- a/frontend/components/forms/fields/InputFieldWithIcon/styles.js
+++ b/frontend/components/forms/fields/InputFieldWithIcon/styles.js
@@ -8,7 +8,7 @@ export default {
position: 'relative',
},
errorStyles: {
- color: color.red,
+ color: color.alert,
fontSize: font.small,
textTransform: 'lowercase',
},
@@ -20,7 +20,7 @@ export default {
inputErrorStyles: (error) => {
if (error) {
return {
- borderBottomColor: color.red,
+ borderBottomColor: color.alert,
};
}
@@ -33,8 +33,8 @@ export default {
borderTop: 'none',
borderBottomWidth: '1px',
borderBottomStyle: 'solid',
- borderBottomColor: color.brightPurple,
- color: '#A2A1C8',
+ borderBottomColor: color.brand,
+ color: color.accentText,
width: '378px',
':focus': {
outline: 'none',
@@ -44,7 +44,7 @@ export default {
if (value) {
return {
...baseStyles,
- color: color.grey,
+ color: color.textUltradark,
};
}
@@ -54,7 +54,7 @@ export default {
if (!value) return { visibility: 'hidden', height: '22px' };
return {
- color: color.brightPurple,
+ color: color.brand,
fontSize: font.small,
textTransform: 'lowercase',
};
diff --git a/frontend/components/forms/validators/validate_equality/index.js b/frontend/components/forms/validators/validate_equality/index.js
new file mode 100644
index 0000000000..78ac0a5b7f
--- /dev/null
+++ b/frontend/components/forms/validators/validate_equality/index.js
@@ -0,0 +1,5 @@
+import { isEqual } from 'lodash';
+
+export default (actual, expected) => {
+ return isEqual(actual, expected);
+};
diff --git a/frontend/components/forms/validators/validate_equality/validate_equality.tests.js b/frontend/components/forms/validators/validate_equality/validate_equality.tests.js
new file mode 100644
index 0000000000..40a4a1cfb7
--- /dev/null
+++ b/frontend/components/forms/validators/validate_equality/validate_equality.tests.js
@@ -0,0 +1,22 @@
+import expect from 'expect';
+import validateEquality from './index';
+
+describe('validateEquality - validator', () => {
+ it('returns true for equal inputs', () => {
+ expect(validateEquality('thegnarco', 'thegnarco')).toEqual(true);
+ expect(validateEquality(1, 1)).toEqual(true);
+ expect(validateEquality(1.0, 1)).toEqual(true);
+ expect(validateEquality(['thegnarco'], ['thegnarco'])).toEqual(true);
+ expect(validateEquality({ hello: 'world' }, { hello: 'world' })).toEqual(true);
+ expect(validateEquality({ foo: { bar: 'baz' } }, { foo: { bar: 'baz' } })).toEqual(true);
+ });
+
+ it('returns false for unequal inputs', () => {
+ expect(validateEquality('thegnarco', 'thegnar')).toEqual(false);
+ expect(validateEquality(1, 'thegnar')).toEqual(false);
+ expect(validateEquality(['thegnarco'], [1])).toEqual(false);
+ expect(validateEquality({ hello: 'world' }, { hello: 'foo' })).toEqual(false);
+ expect(validateEquality({ foo: { bar: 'baz' } }, { foo: { bar: 'foo' } })).toEqual(false);
+ });
+});
+
diff --git a/frontend/components/forms/validators/validate_presence/index.js b/frontend/components/forms/validators/validate_presence/index.js
new file mode 100644
index 0000000000..009238e4bb
--- /dev/null
+++ b/frontend/components/forms/validators/validate_presence/index.js
@@ -0,0 +1,3 @@
+export default (actual) => {
+ return !!actual;
+};
diff --git a/frontend/components/forms/validators/validate_presence/validate_presence.tests.js b/frontend/components/forms/validators/validate_presence/validate_presence.tests.js
new file mode 100644
index 0000000000..929bfe5403
--- /dev/null
+++ b/frontend/components/forms/validators/validate_presence/validate_presence.tests.js
@@ -0,0 +1,29 @@
+import expect from 'expect';
+import validatePresence from './index';
+
+const validInputs = [
+ [1, 2, 3],
+ { hello: 'world' },
+ 'hi@thegnar.co',
+];
+
+const invalidInputs = [
+ '',
+ undefined,
+ false,
+ null,
+];
+
+describe('validatePresence - validator', () => {
+ it('returns true for valid inputs', () => {
+ validInputs.forEach(input => {
+ expect(validatePresence(input)).toEqual(true);
+ });
+ });
+
+ it('returns false for invalid inputs', () => {
+ invalidInputs.forEach(input => {
+ expect(validatePresence(input)).toEqual(false);
+ });
+ });
+});
diff --git a/frontend/pages/ForgotPasswordPage/ForgotPasswordPage.jsx b/frontend/pages/ForgotPasswordPage/ForgotPasswordPage.jsx
index b8572eb4e5..dacd7f4170 100644
--- a/frontend/pages/ForgotPasswordPage/ForgotPasswordPage.jsx
+++ b/frontend/pages/ForgotPasswordPage/ForgotPasswordPage.jsx
@@ -8,6 +8,7 @@ import {
} from '../../redux/nodes/components/ForgotPasswordPage/actions';
import ForgotPasswordForm from '../../components/forms/ForgotPasswordForm';
import Icon from '../../components/icons/Icon';
+import StackedWhiteBoxes from '../../components/StackedWhiteBoxes';
export class ForgotPasswordPage extends Component {
static propTypes = {
@@ -71,25 +72,20 @@ export class ForgotPasswordPage extends Component {
}
render () {
- const {
- containerStyles,
- forgotPasswordStyles,
- headerStyles,
- smallWhiteTabStyles,
- textStyles,
- whiteTabStyles,
- } = componentStyles;
+ const leadText = 'If you’ve forgotten your password enter your email below and we will email you a link so that you can reset your password.';
+ const whiteBoxOverrideStyles = {
+ headerWrapper: { textAlign: 'left' },
+ };
return (
-
-
-
-
-
Forgot Password
-
If you’ve forgotten your password enter your email below and we will email you a link so that you can reset your password.
- {this.renderContent()}
-
-
+
+ {this.renderContent()}
+
);
}
}
diff --git a/frontend/pages/ForgotPasswordPage/styles.js b/frontend/pages/ForgotPasswordPage/styles.js
index 68be19b02b..fd81e7cd9f 100644
--- a/frontend/pages/ForgotPasswordPage/styles.js
+++ b/frontend/pages/ForgotPasswordPage/styles.js
@@ -3,12 +3,6 @@ import styles from '../../styles';
const { border, color, font, padding } = styles;
export default {
- containerStyles: {
- alignItems: 'center',
- display: 'flex',
- justifyContent: 'center',
- flexDirection: 'column',
- },
emailSentButtonWrapperStyles: {
backgroundColor: color.successLight,
borderRadius: border.radius.base,
@@ -37,45 +31,4 @@ export default {
emailTextStyles: {
color: color.link,
},
- forgotPasswordStyles: {
- alignItems: 'center',
- backgroundColor: color.white,
- borderTopLeftRadius: border.radius.base,
- borderTopRightRadius: border.radius.base,
- boxShadow: border.shadow.blur,
- boxSizing: 'border-box',
- display: 'flex',
- flexDirection: 'column',
- padding: padding.base,
- width: '522px',
- },
- headerStyles: {
- fontFamily: "'Oxygen', sans-serif",
- fontSize: font.large,
- fontWeight: '300',
- color: color.mediumGrey,
- lineHeight: '32px',
- textTransform: 'uppercase',
- },
- smallWhiteTabStyles: {
- backgroundColor: color.white,
- borderTopLeftRadius: border.radius.base,
- borderTopRightRadius: border.radius.base,
- boxShadow: border.shadow.blur,
- height: '20px',
- marginTop: padding.base,
- width: '400px',
- },
- textStyles: {
- color: color.purpleGrey,
- fontSize: font.medium,
- },
- whiteTabStyles: {
- backgroundColor: color.white,
- borderTopLeftRadius: border.radius.base,
- borderTopRightRadius: border.radius.base,
- boxShadow: border.shadow.blur,
- height: '20px',
- width: '460px',
- },
};
diff --git a/frontend/pages/LoginPage/LoginPage.jsx b/frontend/pages/LoginPage/LoginPage.jsx
index 35eb40fc8f..d68d6cedcf 100644
--- a/frontend/pages/LoginPage/LoginPage.jsx
+++ b/frontend/pages/LoginPage/LoginPage.jsx
@@ -2,7 +2,6 @@ import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { push } from 'react-router-redux';
import componentStyles from './styles';
-import { loadBackground, resizeBackground } from '../../utilities/backgroundImage';
import local from '../../utilities/local';
import LoginForm from '../../components/forms/LoginForm';
import { loginUser } from '../../redux/nodes/auth/actions';
@@ -17,15 +16,11 @@ export class LoginPage extends Component {
componentWillMount () {
const { dispatch } = this.props;
- const { window } = global;
if (local.getItem('auth_token')) {
return dispatch(push('/'));
}
- loadBackground();
- window.onresize = resizeBackground;
-
return false;
}
diff --git a/frontend/pages/LoginPage/LoginPage.tests.jsx b/frontend/pages/LoginPage/LoginPage.tests.jsx
index 71d3b7b7f7..e9d6ee1b9b 100644
--- a/frontend/pages/LoginPage/LoginPage.tests.jsx
+++ b/frontend/pages/LoginPage/LoginPage.tests.jsx
@@ -1,19 +1,10 @@
-import expect, { spyOn, restoreSpies } from 'expect';
+import expect from 'expect';
import { mount } from 'enzyme';
-import { noop } from 'lodash';
-import * as bgImageUtility from '../../utilities/backgroundImage';
import { connectedComponent, reduxMockStore } from '../../test/helpers';
import local from '../../utilities/local';
import LoginPage from './LoginPage';
describe('LoginPage - component', () => {
- beforeEach(() => {
- spyOn(bgImageUtility, 'loadBackground').andReturn(noop);
- spyOn(bgImageUtility, 'resizeBackground').andReturn(noop);
- });
-
- afterEach(restoreSpies);
-
context('when the user is not logged in', () => {
const mockStore = reduxMockStore({ auth: {} });
diff --git a/frontend/pages/LoginSuccessfulPage/styles.js b/frontend/pages/LoginSuccessfulPage/styles.js
index acd49741b3..d128387b58 100644
--- a/frontend/pages/LoginSuccessfulPage/styles.js
+++ b/frontend/pages/LoginSuccessfulPage/styles.js
@@ -4,7 +4,7 @@ const { color, font, padding } = styles;
export default {
loginSuccessStyles: {
- color: color.green,
+ color: color.success,
textTransform: 'uppercase',
fontSize: font.large,
letterSpacing: '2px',
@@ -12,7 +12,7 @@ export default {
},
subtextStyles: {
fontSize: font.medium,
- color: color.lightGrey,
+ color: color.accentText,
},
whiteBoxStyles: {
backgroundColor: color.white,
diff --git a/frontend/pages/ResetPasswordPage/ResetPasswordPage.jsx b/frontend/pages/ResetPasswordPage/ResetPasswordPage.jsx
new file mode 100644
index 0000000000..4555f34ad6
--- /dev/null
+++ b/frontend/pages/ResetPasswordPage/ResetPasswordPage.jsx
@@ -0,0 +1,54 @@
+import React, { Component, PropTypes } from 'react';
+import { connect } from 'react-redux';
+import { noop } from 'lodash';
+import { push } from 'react-router-redux';
+import ResetPasswordForm from '../../components/forms/ResetPasswordForm';
+import StackedWhiteBoxes from '../../components/StackedWhiteBoxes';
+
+export class ResetPasswordPage extends Component {
+ static propTypes = {
+ dispatch: PropTypes.func,
+ token: PropTypes.string,
+ };
+
+ static defaultProps = {
+ dispatch: noop,
+ };
+
+ componentWillMount () {
+ const { dispatch, token } = this.props;
+
+ if (!token) return dispatch(push('/login'));
+
+ return false;
+ }
+
+ onSubmit = (formData) => {
+ console.log('ResetPasswordForm data', formData);
+ return false;
+ }
+
+ render () {
+ const { onSubmit } = this;
+
+ return (
+
+
+
+ );
+ }
+}
+
+const mapStateToProps = (state, ownProps) => {
+ const { query = {} } = ownProps.location || {};
+ const { token } = query;
+
+ return {
+ token,
+ };
+};
+
+export default connect(mapStateToProps)(ResetPasswordPage);
diff --git a/frontend/pages/ResetPasswordPage/ResetPasswordPage.tests.jsx b/frontend/pages/ResetPasswordPage/ResetPasswordPage.tests.jsx
new file mode 100644
index 0000000000..1fbe95eecd
--- /dev/null
+++ b/frontend/pages/ResetPasswordPage/ResetPasswordPage.tests.jsx
@@ -0,0 +1,32 @@
+import React from 'react';
+import expect from 'expect';
+import { mount } from 'enzyme';
+import ConnectedPage, { ResetPasswordPage } from './ResetPasswordPage';
+import testHelpers from '../../test/helpers';
+
+describe('ResetPasswordPage - component', () => {
+ it('renders a ResetPasswordForm', () => {
+ const page = mount();
+
+ expect(page.find('ResetPasswordForm').length).toEqual(1);
+ });
+
+ it('Redirects to the login page when there is no token', () => {
+ const redirectToLoginAction = {
+ type: '@@router/CALL_HISTORY_METHOD',
+ payload: {
+ method: 'push',
+ args: ['/login'],
+ },
+ };
+ const { reduxMockStore, connectedComponent } = testHelpers;
+ const mockStore = reduxMockStore();
+
+ mount(connectedComponent(ConnectedPage, { mockStore }));
+
+ const dispatchedActions = mockStore.getActions();
+
+ expect(dispatchedActions).toInclude(redirectToLoginAction);
+ });
+});
+
diff --git a/frontend/pages/ResetPasswordPage/index.js b/frontend/pages/ResetPasswordPage/index.js
new file mode 100644
index 0000000000..a4aa40b6ae
--- /dev/null
+++ b/frontend/pages/ResetPasswordPage/index.js
@@ -0,0 +1 @@
+export default from './ResetPasswordPage';
diff --git a/frontend/router/index.jsx b/frontend/router/index.jsx
index 31be3a6d79..fcdfb429c9 100644
--- a/frontend/router/index.jsx
+++ b/frontend/router/index.jsx
@@ -9,6 +9,7 @@ import HomePage from '../pages/HomePage';
import LoginPage from '../pages/LoginPage';
import LoginSuccessfulPage from '../pages/LoginSuccessfulPage';
import LoginRoutes from '../components/LoginRoutes';
+import ResetPasswordPage from '../pages/ResetPasswordPage';
import store from '../redux/store';
const history = syncHistoryWithStore(browserHistory, store);
@@ -19,9 +20,10 @@ const routes = (
+
-
+
diff --git a/frontend/styles/color.js b/frontend/styles/color.js
index 2de3ed7b19..8319722080 100644
--- a/frontend/styles/color.js
+++ b/frontend/styles/color.js
@@ -1,19 +1,30 @@
-const grey = '#66696f';
-
export default {
- accentLight: '#EAEDFB',
- brightPurple: '#AE6DDF',
- darkGrey: '#202532',
- green: '#4FD061',
- grey,
- lightGrey: '#B4B4B4',
- link: '#4A90E2',
- logoPurple: '#9651CA',
- mediumGrey: '#6F737F',
- primary: grey,
- purple: '#c38dec',
- purpleGrey: '#858495',
- red: '#FF5850',
- successLight: '#94E39F',
white: '#FFF',
+
+ // Correct Colors
+ brand: '#AE6DDF',
+ brandDark: '#9651CA',
+ brandLight: '#C38DEC',
+ brandUltralight: '#EDD6FF',
+ accentText: '#A8B1CD',
+ accentDark: '#B8C2E3',
+ accentMedium: '#D2DAF4',
+ accentLight: '#EAEDFB',
+ textUltradark: '#66696F',
+ textDark: '#858495',
+ textMedium: '#9CA3AC',
+ textLight: '#B2BBC6',
+ bgMedium: '#F4F6FB',
+ bgLight: '#FCFCFF',
+ link: '#4A90E2',
+ linkLight: '#C0D8F5',
+ alert: '#FF5850',
+ alertLight: '#FFB5B2',
+ success: '#4FD061',
+ successLight: '#94E39F',
+ warning: '#FFAD00',
+ warningLight: '#FFDA8C',
+ footerBg: '#202532',
+ footerAccent: '#84878E',
+ borderMedium: '#D4D8DF',
};
diff --git a/frontend/styles/global.js b/frontend/styles/global.js
index 7b51a9b4df..8cb956cf25 100644
--- a/frontend/styles/global.js
+++ b/frontend/styles/global.js
@@ -15,7 +15,9 @@ export default {
minHeight: '100%',
},
body: {
- color: color.primary,
+ backgroundImage: 'url("/assets/images/background.png")',
+ backgroundSize: 'cover',
+ color: color.textUltradark,
...defaultMargin,
...defaultPadding,
fontFamily: 'Oxygen, sans-serif',
@@ -26,15 +28,4 @@ export default {
'h1, h2, h3': {
lineHeight: 1.2,
},
- '#app': {
- },
- '#bg': {
- position: 'fixed',
- left: 0,
- right: 0,
- top: 0,
- bottom: 0,
- zIndex: '-1',
- opacity: '0.4',
- },
};
diff --git a/frontend/templates/react.tmpl b/frontend/templates/react.tmpl
index 5b8c17e503..b7a7a90639 100644
--- a/frontend/templates/react.tmpl
+++ b/frontend/templates/react.tmpl
@@ -6,7 +6,6 @@
Kolide
-
diff --git a/frontend/utilities/backgroundImage.js b/frontend/utilities/backgroundImage.js
deleted file mode 100644
index 0c71205ab0..0000000000
--- a/frontend/utilities/backgroundImage.js
+++ /dev/null
@@ -1,155 +0,0 @@
-/* eslint-disable no-mixed-operators */
-const { document, window } = global;
-const refreshDuration = 10000;
-const SHAPE_DENSITY = 20;
-
-let numPointsX;
-let numPointsY;
-let points;
-let refreshTimeout;
-let unitHeight;
-let unitWidth;
-
-const randomize = () => {
- const { length: pointsLength } = points;
-
- for (let i = 0; i < pointsLength; i++) {
- const { originX, originY } = points[i];
-
- if (originX !== 0 && originX !== (unitWidth * (numPointsX - 1))) {
- points[i].x = originX + Math.random() * unitWidth - unitWidth / 2;
- }
-
- if (originY !== 0 && originY !== (unitHeight * (numPointsY - 1))) {
- points[i].y = originY + Math.random() * unitHeight - unitHeight / 2;
- }
- }
-};
-
-const refresh = () => {
- randomize();
-
- const svgElement = document.querySelector('#bg svg');
- const childNodes = svgElement.childNodes;
-
- for (let i = 0; i < childNodes.length; i++) {
- const polygon = childNodes[i];
- const animate = polygon.childNodes[0];
- const point1 = points[polygon.point1];
- const point2 = points[polygon.point2];
- const point3 = points[polygon.point3];
-
- if (animate.getAttribute('to')) {
- animate.setAttribute('from', animate.getAttribute('to'));
- }
-
- animate.setAttribute('to', `${point1.x},${point1.y} ${point2.x},${point2.y} ${point3.x},${point3.y}`);
- animate.beginElement();
- }
-
- refreshTimeout = setTimeout(refresh, refreshDuration);
-};
-
-export const loadBackground = () => {
- const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
- const appElement = document.querySelector('#bg');
- const { innerWidth } = window;
- const innerHeight = window.innerHeight;
- const unitSize = (innerWidth + innerHeight) / SHAPE_DENSITY;
- svg.setAttribute('width', innerWidth);
- svg.setAttribute('height', innerHeight);
- appElement.appendChild(svg);
-
- numPointsX = Math.ceil(innerWidth / unitSize) + 1;
- numPointsY = Math.ceil(innerHeight / unitSize) + 1;
- unitWidth = Math.ceil(innerWidth / (numPointsX - 1));
- unitHeight = Math.ceil(innerHeight / (numPointsY - 1));
-
- points = [];
-
- for (let y = 0; y < numPointsY; y++) {
- for (let x = 0; x < numPointsX; x++) {
- const originX = unitWidth * x;
- const originY = unitHeight * y;
-
- points.push({
- x: originX,
- y: originY,
- originX,
- originY,
- });
- }
- }
-
- randomize();
-
- const { length: pointsLength } = points;
-
- for (let i = 0; i < pointsLength; i++) {
- const { originX, originY } = points[i];
-
- if (originX !== unitWidth * (numPointsX - 1) && originY !== unitHeight * (numPointsY - 1)) {
- const { x: topLeftX, y: topLeftY } = points[i];
- const { x: topRightX, y: topRightY } = points[i + 1];
- const { x: bottomLeftX, y: bottomLeftY } = points[i + numPointsX];
- const { x: bottomRightX, y: bottomRightY } = points[i + numPointsX + 1];
-
- const rando = Math.floor(Math.random() * 2);
-
- for (let n = 0; n < 2; n++) {
- const polygon = document.createElementNS(svg.namespaceURI, 'polygon');
-
- if (rando === 0) {
- if (n === 0) {
- polygon.point1 = i;
- polygon.point2 = i + numPointsX;
- polygon.point3 = i + numPointsX + 1;
- polygon.setAttribute('points', `${topLeftX},${topLeftY} ${bottomLeftX},${bottomLeftY} ${bottomRightX},${bottomRightY}`);
- } else if (n === 1) {
- polygon.point1 = i;
- polygon.point2 = i + 1;
- polygon.point3 = i + numPointsX + 1;
- polygon.setAttribute('points', `${topLeftX},${topLeftY} ${topRightX},${topRightY} ${bottomRightX},${bottomRightY}`);
- }
- } else if (rando === 1) {
- if (n === 0) {
- polygon.point1 = i;
- polygon.point2 = i + numPointsX;
- polygon.point3 = i + 1;
- polygon.setAttribute('points', `${topLeftX},${topLeftY} ${bottomLeftX},${bottomLeftY} ${topRightX},${topRightY}`);
- } else if (n === 1) {
- polygon.point1 = i + numPointsX;
- polygon.point2 = i + 1;
- polygon.point3 = i + numPointsX + 1;
- polygon.setAttribute('points', `${bottomLeftX},${bottomLeftY} ${topRightX},${topRightY} ${bottomRightX},${bottomRightY}`);
- }
- }
-
- polygon.setAttribute('fill', `rgba(0, 0, 0, ${Math.random() / 3})`);
-
- const animate = document.createElementNS('http://www.w3.org/2000/svg', 'animate');
-
- animate.setAttribute('fill', 'freeze');
- animate.setAttribute('attributeName', 'points');
- animate.setAttribute('dur', `${refreshDuration}ms`);
- animate.setAttribute('calcMode', 'linear');
- polygon.appendChild(animate);
- svg.appendChild(polygon);
- }
- }
- }
-
- refresh();
-};
-
-export const removeBackground = () => {
- if (document.querySelector('#bg svg')) {
- document.querySelector('#bg svg').remove();
- clearTimeout(refreshTimeout);
- }
-};
-
-export const resizeBackground = () => {
- removeBackground();
- loadBackground();
-};