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 ( +
+

{headerText}

+
+ ); + } + + 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(); -};