Forgot password (#162)
* GradientButton components * Style guide updates * Display errors and override styles for InputFieldWithIcon * Envelope Icon * Login page form submission (#157) * API client utility * moves test helpers to the test directory * Utility to namespace local storage keys * LoginSuccessfulPage component * Check icon * adds auth to redux state * successful form submission * Allow tests to load dummy SVG static images & test fixes * ForgotPassword Page, Form & route * Email validator
This commit is contained in:
@@ -48,7 +48,7 @@ test-js:
|
||||
test: lint test-go test-js
|
||||
|
||||
generate: .prefix
|
||||
go-bindata -pkg=server -o=server/bindata.go frontend/templates/ assets/ assets/images/
|
||||
go-bindata -pkg=server -o=server/bindata.go frontend/templates/ assets/...
|
||||
$(shell npm bin)/webpack --progress --colors --bail
|
||||
|
||||
generate-dev: .prefix
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
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;
|
||||
|
||||
return (
|
||||
<div style={containerStyles}>
|
||||
<img alt="Kolide text logo" src="/assets/images/kolide-logo-text.svg" />
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default LoginRoutes;
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export default from './LoginRoutes';
|
||||
@@ -0,0 +1,9 @@
|
||||
export default {
|
||||
containerStyles: {
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
paddingTop: '80px',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import radium from 'radium';
|
||||
import componentStyles from './styles';
|
||||
|
||||
class GradientButton extends Component {
|
||||
static propTypes = {
|
||||
disabled: PropTypes.bool,
|
||||
onClick: PropTypes.func,
|
||||
style: PropTypes.object,
|
||||
text: PropTypes.string,
|
||||
type: PropTypes.string,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
style: {},
|
||||
};
|
||||
|
||||
render () {
|
||||
const { disabled, onClick, style, text, type } = this.props;
|
||||
|
||||
return (
|
||||
<button
|
||||
disabled={disabled}
|
||||
onClick={onClick}
|
||||
style={[componentStyles(disabled), style]}
|
||||
type={type}
|
||||
>
|
||||
{text}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default radium(GradientButton);
|
||||
@@ -0,0 +1 @@
|
||||
export default from './GradientButton';
|
||||
@@ -0,0 +1,32 @@
|
||||
import styles from '../../../styles';
|
||||
|
||||
const { border, color, font, padding } = styles;
|
||||
|
||||
export default (disabled) => {
|
||||
const cursor = disabled ? 'not-allowed' : 'pointer';
|
||||
|
||||
return {
|
||||
backgroundImage: 'linear-gradient(134deg, #7166D9 0%, #C86DD7 100%)',
|
||||
border: 'none',
|
||||
cursor,
|
||||
borderBottomLeftRadius: border.radius.base,
|
||||
borderBottomRightRadius: border.radius.base,
|
||||
boxSizing: 'border-box',
|
||||
color: color.white,
|
||||
fontSize: font.large,
|
||||
letterSpacing: '4px',
|
||||
padding: padding.base,
|
||||
fontWeight: '300',
|
||||
textTransform: 'uppercase',
|
||||
width: '100%',
|
||||
boxShadow: '0 3px 0 #734893',
|
||||
position: 'relative',
|
||||
':active': {
|
||||
top: '2px',
|
||||
boxShadow: '0 1px 0 #734893, 0 -2px 0 #D1D9E9',
|
||||
},
|
||||
':focus': {
|
||||
outline: 'none',
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,90 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import radium from 'radium';
|
||||
import componentStyles from './styles';
|
||||
import GradientButton from '../../buttons/GradientButton';
|
||||
import InputFieldWithIcon from '../fields/InputFieldWithIcon';
|
||||
import validEmail from '../validators/valid_email';
|
||||
|
||||
class ForgotPasswordForm extends Component {
|
||||
static propTypes = {
|
||||
onSubmit: PropTypes.func,
|
||||
};
|
||||
|
||||
constructor (props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
formData: {
|
||||
email: null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
onInputFieldChange = (evt) => {
|
||||
const { value } = evt.target;
|
||||
|
||||
this.setState({
|
||||
error: null,
|
||||
formData: {
|
||||
email: value,
|
||||
},
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
onFormSubmit = (evt) => {
|
||||
evt.preventDefault();
|
||||
|
||||
const { formData } = this.state;
|
||||
const { onSubmit } = this.props;
|
||||
|
||||
if (this.validate()) {
|
||||
return onSubmit(formData);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
validate = () => {
|
||||
const { formData: { email } } = this.state;
|
||||
|
||||
if (validEmail(email)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
error: `${email} is not a valid email`,
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
render () {
|
||||
const { error, formData: { email } } = this.state;
|
||||
const { formStyles, inputStyles, submitButtonStyles } = componentStyles;
|
||||
const { onFormSubmit, onInputFieldChange } = this;
|
||||
const disabled = !email;
|
||||
|
||||
return (
|
||||
<form onSubmit={onFormSubmit} style={formStyles}>
|
||||
<InputFieldWithIcon
|
||||
error={error}
|
||||
iconName="envelope"
|
||||
name="email"
|
||||
onChange={onInputFieldChange}
|
||||
placeholder="Email Address"
|
||||
style={inputStyles}
|
||||
/>
|
||||
<GradientButton
|
||||
disabled={disabled}
|
||||
type="submit"
|
||||
style={submitButtonStyles}
|
||||
text="Reset Password"
|
||||
/>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default radium(ForgotPasswordForm);
|
||||
@@ -0,0 +1,67 @@
|
||||
import React from 'react';
|
||||
import expect, { createSpy, restoreSpies } from 'expect';
|
||||
import { mount } from 'enzyme';
|
||||
import { noop } from 'lodash';
|
||||
import ForgotPasswordForm from './ForgotPasswordForm';
|
||||
import { fillInFormInput } from '../../../test/helpers';
|
||||
|
||||
const email = 'hi@thegnar.co';
|
||||
|
||||
describe('ForgotPasswordForm - component', () => {
|
||||
afterEach(restoreSpies);
|
||||
|
||||
it('renders an InputFieldWithIcon components', () => {
|
||||
const form = mount(<ForgotPasswordForm onSubmit={noop} />);
|
||||
|
||||
expect(form.find('InputFieldWithIcon').length).toEqual(1);
|
||||
});
|
||||
|
||||
it('updates component state when the email field is changed', () => {
|
||||
const form = mount(<ForgotPasswordForm onSubmit={noop} />);
|
||||
|
||||
const emailField = form.find({ name: 'email' });
|
||||
fillInFormInput(emailField, email);
|
||||
|
||||
const { formData } = form.state();
|
||||
expect(formData).toContain({ email });
|
||||
});
|
||||
|
||||
it('it does not submit the form when the form fields have not been filled out', () => {
|
||||
const submitSpy = createSpy();
|
||||
const form = mount(<ForgotPasswordForm onSubmit={submitSpy} />);
|
||||
const submitBtn = form.find('button');
|
||||
|
||||
submitBtn.simulate('submit');
|
||||
|
||||
expect(submitBtn.prop('disabled')).toEqual(true);
|
||||
expect(submitSpy).toNotHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('submits the form data when the form is submitted', () => {
|
||||
const submitSpy = createSpy();
|
||||
const form = mount(<ForgotPasswordForm onSubmit={submitSpy} />);
|
||||
const emailField = form.find({ name: 'email' });
|
||||
const submitBtn = form.find('button');
|
||||
|
||||
fillInFormInput(emailField, email);
|
||||
submitBtn.simulate('submit');
|
||||
|
||||
expect(submitSpy).toHaveBeenCalledWith({ email });
|
||||
});
|
||||
|
||||
it('does not submit the form if the email is not valid', () => {
|
||||
const submitSpy = createSpy();
|
||||
const form = mount(<ForgotPasswordForm onSubmit={submitSpy} />);
|
||||
const emailField = form.find({ name: 'email' });
|
||||
const submitBtn = form.find('button');
|
||||
|
||||
fillInFormInput(emailField, 'invalid-email');
|
||||
submitBtn.simulate('submit');
|
||||
|
||||
expect(submitSpy).toNotHaveBeenCalled();
|
||||
expect(form.state()).toInclude({
|
||||
error: 'invalid-email is not a valid email',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export default from './ForgotPasswordForm';
|
||||
@@ -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,
|
||||
},
|
||||
};
|
||||
@@ -1,8 +1,11 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { Link } from 'react-router';
|
||||
import radium from 'radium';
|
||||
import componentStyles from './styles';
|
||||
import avatar from './avatar.svg';
|
||||
import componentStyles from './styles';
|
||||
import GradientButton from '../../buttons/GradientButton';
|
||||
import InputFieldWithIcon from '../fields/InputFieldWithIcon';
|
||||
import paths from '../../../router/paths';
|
||||
|
||||
class LoginForm extends Component {
|
||||
static propTypes = {
|
||||
@@ -54,7 +57,13 @@ class LoginForm extends Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { containerStyles, submitButtonStyles, formStyles } = componentStyles;
|
||||
const {
|
||||
containerStyles,
|
||||
forgotPasswordStyles,
|
||||
forgotPasswordWrapperStyles,
|
||||
formStyles,
|
||||
submitButtonStyles,
|
||||
} = componentStyles;
|
||||
const { onInputChange, onFormSubmit } = this;
|
||||
const canSubmit = this.canSubmit();
|
||||
|
||||
@@ -75,13 +84,17 @@ class LoginForm extends Component {
|
||||
placeholder="Password"
|
||||
type="password"
|
||||
/>
|
||||
<div style={forgotPasswordWrapperStyles}>
|
||||
<Link style={forgotPasswordStyles} to={paths.FORGOT_PASSWORD}>Forgot Password?</Link>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
style={submitButtonStyles(canSubmit)}
|
||||
<GradientButton
|
||||
disabled={!canSubmit}
|
||||
onClick={onFormSubmit}
|
||||
style={submitButtonStyles}
|
||||
text="Login"
|
||||
type="submit"
|
||||
>
|
||||
Login
|
||||
</button>
|
||||
/>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,35 +16,20 @@ export default {
|
||||
width: FORM_WIDTH,
|
||||
minHeight: '350px',
|
||||
},
|
||||
forgotPasswordStyles: {
|
||||
fontSize: font.medium,
|
||||
textDecoration: 'none',
|
||||
color: color.lightGrey,
|
||||
},
|
||||
forgotPasswordWrapperStyles: {
|
||||
marginTop: padding.base,
|
||||
textAlign: 'right',
|
||||
width: '378px',
|
||||
},
|
||||
formStyles: {
|
||||
boxShadow: '0 5px 30px 0 rgba(0,0,0,0.30)',
|
||||
},
|
||||
submitButtonStyles: (canSubmit) => {
|
||||
const cursor = canSubmit ? 'pointer' : 'not-allowed';
|
||||
|
||||
return {
|
||||
backgroundImage: 'linear-gradient(134deg, #7166D9 0%, #C86DD7 100%)',
|
||||
border: 'none',
|
||||
cursor,
|
||||
borderBottomLeftRadius: border.radius.base,
|
||||
borderBottomRightRadius: border.radius.base,
|
||||
boxSizing: 'border-box',
|
||||
color: color.white,
|
||||
fontSize: font.large,
|
||||
letterSpacing: '4px',
|
||||
padding: padding.base,
|
||||
fontWeight: '300',
|
||||
textTransform: 'uppercase',
|
||||
width: FORM_WIDTH,
|
||||
boxShadow: '0 3px 0 #734893',
|
||||
position: 'relative',
|
||||
':active': {
|
||||
top: '2px',
|
||||
boxShadow: '0 1px 0 #734893, 0 -2px 0 #D1D9E9',
|
||||
},
|
||||
':focus': {
|
||||
outline: 'none',
|
||||
},
|
||||
};
|
||||
submitButtonStyles: {
|
||||
width: FORM_WIDTH,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -5,14 +5,17 @@ import componentStyles from './styles';
|
||||
|
||||
class InputFieldWithIcon extends Component {
|
||||
static propTypes = {
|
||||
error: PropTypes.string,
|
||||
iconName: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
placeholder: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
type: PropTypes.string,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
style: {},
|
||||
type: 'text',
|
||||
};
|
||||
|
||||
@@ -22,31 +25,55 @@ class InputFieldWithIcon extends Component {
|
||||
}
|
||||
|
||||
onInputChange = (evt) => {
|
||||
evt.preventDefault();
|
||||
|
||||
const { value } = evt.target;
|
||||
const { onChange } = this.props;
|
||||
|
||||
this.setState({ value });
|
||||
return onChange(evt);
|
||||
};
|
||||
}
|
||||
|
||||
iconVariant = () => {
|
||||
const { error } = this.props;
|
||||
const { value } = this.state;
|
||||
|
||||
if (error) return 'error';
|
||||
|
||||
if (value) return 'colored';
|
||||
|
||||
return 'default';
|
||||
}
|
||||
|
||||
renderHeading = () => {
|
||||
const { error, placeholder } = this.props;
|
||||
const { value } = this.state;
|
||||
const { errorStyles, placeholderStyles } = componentStyles;
|
||||
|
||||
if (error) {
|
||||
return <div style={errorStyles}>{error}</div>;
|
||||
}
|
||||
|
||||
return <div style={placeholderStyles(value)}>{placeholder}</div>;
|
||||
}
|
||||
|
||||
render () {
|
||||
const { iconName, name, placeholder, type } = this.props;
|
||||
const { containerStyles, iconStyles, inputStyles, placeholderStyles } = componentStyles;
|
||||
const { error, iconName, name, placeholder, style, type } = this.props;
|
||||
const { containerStyles, iconStyles, inputErrorStyles, inputStyles } = componentStyles;
|
||||
const { value } = this.state;
|
||||
const { onInputChange } = this;
|
||||
const iconVariant = value ? 'colored' : 'default';
|
||||
const { iconVariant, onInputChange } = this;
|
||||
|
||||
return (
|
||||
<div style={containerStyles}>
|
||||
<div style={placeholderStyles(value)}>{placeholder}</div>
|
||||
{this.renderHeading()}
|
||||
<input
|
||||
name={name}
|
||||
onChange={onInputChange}
|
||||
placeholder={placeholder}
|
||||
style={inputStyles(value)}
|
||||
style={[inputStyles(value), inputErrorStyles(error), style]}
|
||||
type={type}
|
||||
/>
|
||||
<Icon name={iconName} style={iconStyles} variant={iconVariant} />
|
||||
<Icon name={iconName} style={iconStyles} variant={iconVariant()} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,11 +7,25 @@ export default {
|
||||
marginTop: padding.base,
|
||||
position: 'relative',
|
||||
},
|
||||
errorStyles: {
|
||||
color: color.red,
|
||||
fontSize: font.small,
|
||||
textTransform: 'lowercase',
|
||||
},
|
||||
iconStyles: {
|
||||
position: 'absolute',
|
||||
right: '6px',
|
||||
top: '29px',
|
||||
},
|
||||
inputErrorStyles: (error) => {
|
||||
if (error) {
|
||||
return {
|
||||
borderBottomColor: color.red,
|
||||
};
|
||||
}
|
||||
|
||||
return {};
|
||||
},
|
||||
inputStyles: (value) => {
|
||||
const baseStyles = {
|
||||
borderLeft: 'none',
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
const EMAIL_REGEX = /\S+@\S+\.\S+/;
|
||||
|
||||
export default (email) => {
|
||||
if (EMAIL_REGEX.test(email)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
import expect from 'expect';
|
||||
import validEmail from './index';
|
||||
|
||||
describe('valid_email - validators', () => {
|
||||
const validEmails = [
|
||||
'hi@thegnar.co',
|
||||
'hi@gnar.dog',
|
||||
'kolide@gmail.com',
|
||||
];
|
||||
const invalidEmails = [
|
||||
'www.thegnar.co',
|
||||
'bill@shakespeare',
|
||||
];
|
||||
|
||||
it('returns true for valid emails', () => {
|
||||
validEmails.forEach((email) => {
|
||||
expect(validEmail(email)).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('returns false for invalid emails', () => {
|
||||
invalidEmails.forEach((email) => {
|
||||
expect(validEmail(email)).toEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import radium from 'radium';
|
||||
import Check from './svg/Check';
|
||||
import Envelope from './svg/Envelope';
|
||||
import KolideLoginBackground from './svg/KolideLoginBackground';
|
||||
import Lock from './svg/Lock';
|
||||
import User from './svg/User';
|
||||
@@ -15,6 +16,7 @@ class Icon extends Component {
|
||||
|
||||
static iconNames = {
|
||||
check: Check,
|
||||
envelope: Envelope,
|
||||
kolideLoginBackground: KolideLoginBackground,
|
||||
lock: Lock,
|
||||
user: User,
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import React, { Component } from 'react';
|
||||
import base, { basePropTypes } from '../base';
|
||||
|
||||
class Envelope extends Component {
|
||||
static propTypes = {
|
||||
...basePropTypes,
|
||||
};
|
||||
|
||||
static variants = {
|
||||
default: {
|
||||
fill: '#EAEEFB',
|
||||
stroke: '#B9C2E4',
|
||||
},
|
||||
colored: {
|
||||
fill: '#EED6FF',
|
||||
stroke: '#C48DED',
|
||||
},
|
||||
error: {
|
||||
fill: '#FFB5B2',
|
||||
stroke: '#FF5850',
|
||||
},
|
||||
};
|
||||
|
||||
render () {
|
||||
const { alt, style, variant } = this.props;
|
||||
const color = Envelope.variants[variant];
|
||||
|
||||
return (
|
||||
<svg
|
||||
width="11px"
|
||||
height="9px"
|
||||
viewBox="0 0 11 09"
|
||||
alt={alt}
|
||||
style={style}
|
||||
>
|
||||
<g>
|
||||
<defs>
|
||||
<rect id="path-1" x="0" y="0" width="11" height="8" />
|
||||
<mask id="mask-2" maskContentUnits="userSpaceOnUse" maskUnits="objectBoundingBox" x="0" y="0" width="11" height="8" fill="white">
|
||||
<use xlinkHref="#path-1" />
|
||||
</mask>
|
||||
</defs>
|
||||
<g stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
|
||||
<g transform="translate(-714.000000, -594.000000)" stroke={color.stroke}>
|
||||
<g transform="translate(241.000000, 370.000000)">
|
||||
<g transform="translate(473.000000, 225.000000)">
|
||||
<use mask="url(#mask-2)" strokeWidth="2" fill={color.fill} xlinkHref="#path-1" />
|
||||
<polyline points="0.533320551 0.599345964 5.52216436 3.65714135 10.5638681 0.475880292" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default base(Envelope);
|
||||
@@ -0,0 +1 @@
|
||||
export default from './Envelope.svg.jsx';
|
||||
@@ -0,0 +1,34 @@
|
||||
import React, { Component } from 'react';
|
||||
import componentStyles from './styles';
|
||||
import ForgotPasswordForm from '../../components/forms/ForgotPasswordForm';
|
||||
|
||||
class ForgotPasswordPage extends Component {
|
||||
onSubmit = (formData) => {
|
||||
console.log('ForgotPasswordPage formData', formData);
|
||||
}
|
||||
|
||||
render () {
|
||||
const {
|
||||
containerStyles,
|
||||
forgotPasswordStyles,
|
||||
headerStyles,
|
||||
smallWhiteTabStyles,
|
||||
textStyles,
|
||||
whiteTabStyles,
|
||||
} = componentStyles;
|
||||
|
||||
return (
|
||||
<div style={containerStyles}>
|
||||
<div style={smallWhiteTabStyles} />
|
||||
<div style={whiteTabStyles} />
|
||||
<div style={forgotPasswordStyles}>
|
||||
<p style={headerStyles}>Forgot Password</p>
|
||||
<p style={textStyles}>If you’ve forgotten your password enter your email below and we will email you a link so that you can reset your password.</p>
|
||||
<ForgotPasswordForm onSubmit={this.onSubmit} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ForgotPasswordPage;
|
||||
@@ -0,0 +1 @@
|
||||
export default from './ForgotPasswordPage';
|
||||
@@ -0,0 +1,53 @@
|
||||
import styles from '../../styles';
|
||||
|
||||
const { border, color, font, padding } = styles;
|
||||
|
||||
export default {
|
||||
containerStyles: {
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
flexDirection: 'column',
|
||||
},
|
||||
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',
|
||||
},
|
||||
};
|
||||
@@ -38,16 +38,13 @@ export class LoginPage extends Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { containerStyles, formWrapperStyles, whiteTabStyles } = componentStyles;
|
||||
const { formWrapperStyles, whiteTabStyles } = componentStyles;
|
||||
const { onSubmit } = this;
|
||||
|
||||
return (
|
||||
<div style={containerStyles}>
|
||||
<div style={formWrapperStyles}>
|
||||
<img alt="Kolide text logo" src="/assets/images/kolide-logo-text.svg" />
|
||||
<div style={whiteTabStyles} />
|
||||
<LoginForm onSubmit={onSubmit} />
|
||||
</div>
|
||||
<div style={formWrapperStyles}>
|
||||
<div style={whiteTabStyles} />
|
||||
<LoginForm onSubmit={onSubmit} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,12 +3,6 @@ import styles from '../../styles';
|
||||
const { color, padding } = styles;
|
||||
|
||||
export default {
|
||||
containerStyles: {
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
paddingTop: '80px',
|
||||
},
|
||||
formWrapperStyles: {
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
|
||||
@@ -4,9 +4,11 @@ import { Provider } from 'react-redux';
|
||||
import radium from 'radium';
|
||||
import { syncHistoryWithStore } from 'react-router-redux';
|
||||
import App from '../components/App';
|
||||
import ForgotPasswordPage from '../pages/ForgotPasswordPage';
|
||||
import HomePage from '../pages/HomePage';
|
||||
import LoginPage from '../pages/LoginPage';
|
||||
import LoginSuccessfulPage from '../pages/LoginSuccessfulPage';
|
||||
import LoginRoutes from '../components/LoginRoutes';
|
||||
import store from '../redux/store';
|
||||
|
||||
const history = syncHistoryWithStore(browserHistory, store);
|
||||
@@ -16,8 +18,11 @@ const routes = (
|
||||
<Router history={history}>
|
||||
<Route path="/" component={radium(App)}>
|
||||
<IndexRoute component={radium(HomePage)} />
|
||||
<Route path="login" component={radium(LoginPage)} />
|
||||
<Route path="login_successful" component={radium(LoginSuccessfulPage)} />
|
||||
<Route component={radium(LoginRoutes)}>
|
||||
<Route path="login" component={radium(LoginPage)} />
|
||||
<Route path="login_successful" component={radium(LoginSuccessfulPage)} />
|
||||
<Route path="forgot_password" component={radium(ForgotPasswordPage)} />
|
||||
</Route>
|
||||
</Route>
|
||||
</Router>
|
||||
</Provider>
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export default {
|
||||
FORGOT_PASSWORD: '/forgot_password',
|
||||
HOME: '/',
|
||||
LOGIN: '/login',
|
||||
};
|
||||
@@ -2,4 +2,7 @@ export default {
|
||||
radius: {
|
||||
base: '4px',
|
||||
},
|
||||
shadow: {
|
||||
blur: '0 0 30px 0 rgba(0,0,0,0.30)',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -7,7 +7,10 @@ export default {
|
||||
grey,
|
||||
lightGrey: '#B4B4B4',
|
||||
logoPurple: '#9651CA',
|
||||
mediumGrey: '#6F737F',
|
||||
primary: grey,
|
||||
purple: '#c38dec',
|
||||
purpleGrey: '#858495',
|
||||
red: '#FF5850',
|
||||
white: '#FFF',
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import { pxToRem } from './helpers';
|
||||
|
||||
export default {
|
||||
auto: 'auto',
|
||||
medium: pxToRem(16),
|
||||
base: pxToRem(18),
|
||||
half: pxToRem(9),
|
||||
none: 0,
|
||||
|
||||
Reference in New Issue
Block a user