mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 08:55:24 +00:00
Styling User Registration (#529)
This commit is contained in:
parent
e1db2d4c27
commit
9e6a8eae56
Binary file not shown.
2
assets/images/kolide-logo-condensed.svg
Normal file
2
assets/images/kolide-logo-condensed.svg
Normal file
@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 174 48"><defs><style>.cls-1{fill:#995ecc;}.cls-1,.cls-2,.cls-3,.cls-4,.cls-5{fill-rule:evenodd;}.cls-2{fill:#fff;}.cls-3{fill:#c482f9;}.cls-4{fill:#ae6ddf;}.cls-5{fill:#66696f;}</style></defs><title>Asset 3</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><g id="light-facet-logo"><path class="cls-1" d="M22.72.87.87,22.72A3,3,0,0,0,.1,25.6L3.37,37.8A3,3,0,0,0,5.48,39.9l29.85,8a3,3,0,0,0,2.88-.77l8.93-8.93a3,3,0,0,0,.77-2.88l-8-29.85A3,3,0,0,0,37.8,3.37L25.6.1a3,3,0,0,0-2.88.77"/><polygon class="cls-2" points="25.19 29.34 20.83 29.35 24.39 31.24 24.41 39.05 17.7 39.05 17.67 20.56 13.33 18.25 13.33 15.71 24.37 15.71 24.39 24.07 25.92 24.07 31.21 15.71 39.02 15.71 31.88 27.39 31.86 27.39 39.02 39.05 31.21 39.05 25.19 29.34"/><polygon class="cls-1" points="24.37 15.71 24.37 0.04 31.21 15.71 25.92 24.07 24.38 24.07 24.37 15.71"/><polygon class="cls-3" points="39.02 15.71 39.02 4.04 31.21 15.71 39.02 15.71"/><polygon class="cls-3" points="31.86 27.39 39.02 39.05 48 36.66 31.86 27.39"/><polygon class="cls-4" points="39.02 39.05 31.21 39.05 36.55 47.96 39.02 39.05"/><polygon class="cls-3" points="17.67 20.56 17.7 39.05 4.06 39.05 17.67 20.56"/><polygon class="cls-1" points="17.67 20.56 13.33 18.25 4.06 39.05 17.67 20.56"/><polygon class="cls-4" points="24.37 0.04 24.37 15.71 13.33 15.71 24.37 0.04"/><path class="cls-4" d="M47.9,35.32l-8-29.85A3,3,0,0,0,39,4V15.71L48,36.56a3,3,0,0,0-.06-1.24"/><path class="cls-4" d="M31.21,15.71,39,4a3,3,0,0,0-1.22-.67L25.6.1A3,3,0,0,0,24.37,0Z"/><path class="cls-4" d="M4.06,39l9.27-20.8V15.71L0,24.49A3,3,0,0,0,.1,25.6L3.37,37.8A3,3,0,0,0,4.06,39"/><path class="cls-4" d="M24.41,39H4.07a3,3,0,0,0,1.41.85l29.85,8a3,3,0,0,0,1.23.06Z"/><path class="cls-1" d="M39,39,36.55,48a3,3,0,0,0,1.64-.83l8.93-8.93A3,3,0,0,0,48,36.57Z"/></g><g id="kolide-text"><path class="cls-5" d="M141.58,15.87h-7.9a1.38,1.38,0,0,0-1.58,1.54v20A1.38,1.38,0,0,0,133.67,39h7.9q9.49,0,9.49-4.63V20.49Q151.06,15.86,141.58,15.87Zm4.74,17q0,1.54-4.74,1.54h-3.16a1.38,1.38,0,0,1-1.58-1.54V22.19a1.38,1.38,0,0,1,1.58-1.54h3.16q4.74,0,4.74,1.54Z"/><path class="cls-5" d="M79.89,35.18l-8-7.78,7.9-7.71h0v0a1.34,1.34,0,0,0,0-2.16L78.7,16.43a1.41,1.41,0,0,0-2.23,0l-8.08,7.89a2.86,2.86,0,0,1-2,.8h-.07a1.38,1.38,0,0,1-1.58-1.54V17.41a1.38,1.38,0,0,0-1.58-1.54H61.58A1.38,1.38,0,0,0,60,17.41v20A1.38,1.38,0,0,0,61.58,39h1.58a1.38,1.38,0,0,0,1.58-1.54V31.29a1.38,1.38,0,0,1,1.58-1.54h.1a2.85,2.85,0,0,1,2,.8l6.73,6.55,1.4,1.36a1.43,1.43,0,0,0,2.25,0l1.11-1.09a1.33,1.33,0,0,0,0-2.18Z"/><path class="cls-5" d="M172.42,39h-7.9Q155,39,155,34.37V20.49q0-4.63,9.48-4.62h7.9A1.38,1.38,0,0,1,174,17.41V19a1.38,1.38,0,0,1-1.58,1.54h-7.9q-4.74,0-4.74,1.54v1.54a1.38,1.38,0,0,0,1.58,1.54h3.16a1.38,1.38,0,0,1,1.58,1.54V28.2a1.38,1.38,0,0,1-1.58,1.54h-3.16a1.38,1.38,0,0,0-1.58,1.54v1.54q0,1.54,4.74,1.54h7.9A1.38,1.38,0,0,1,174,35.91v1.54A1.38,1.38,0,0,1,172.42,39Z"/><path class="cls-5" d="M124,39a1.38,1.38,0,0,1-1.58-1.54v-20A1.38,1.38,0,0,1,124,15.87h1.58a1.38,1.38,0,0,1,1.58,1.54v20A1.38,1.38,0,0,1,125.58,39H124Z"/><path class="cls-5" d="M110.28,17.41V32.83q0,1.54,4.74,1.54h3.16a1.38,1.38,0,0,1,1.58,1.54v1.54A1.38,1.38,0,0,1,118.19,39H115q-9.48,0-9.48-4.63v-17a1.38,1.38,0,0,1,1.58-1.54h1.58a1.38,1.38,0,0,1,1.58,1.54Z"/><path class="cls-5" d="M91.79,15.86q-9.48,0-9.48,4.63V34.37q0,4.63,9.48,4.63t9.49-4.63V20.49Q101.28,15.87,91.79,15.86Zm4.74,17q0,1.54-4.74,1.54T87,32.83V22q0-1.39,4.74-1.54,4.74,0,4.74,1.54Z"/></g></g></g></svg>
|
After Width: | Height: | Size: 3.5 KiB |
@ -8,6 +8,7 @@ interface IButtonProps {
|
||||
disabled: boolean;
|
||||
onClick: (evt: React.MouseEvent<HTMLButtonElement>) => boolean;
|
||||
size: string;
|
||||
tabIndex: number;
|
||||
text: string;
|
||||
type: string;
|
||||
variant: string;
|
||||
@ -37,7 +38,7 @@ class Button extends React.Component<IButtonProps, IButtonState> {
|
||||
|
||||
render () {
|
||||
const { handleClick } = this;
|
||||
const { className, disabled, size, text, type, variant } = this.props;
|
||||
const { className, disabled, size, tabIndex, text, type, variant } = this.props;
|
||||
const fullClassName = classnames(`${baseClass}--${variant}`, className, {
|
||||
[baseClass]: variant !== 'unstyled',
|
||||
[`${baseClass}--disabled`]: disabled,
|
||||
@ -49,6 +50,7 @@ class Button extends React.Component<IButtonProps, IButtonState> {
|
||||
className={fullClassName}
|
||||
disabled={disabled}
|
||||
onClick={handleClick}
|
||||
tabIndex={tabIndex}
|
||||
type={type}
|
||||
>
|
||||
{text}
|
||||
|
@ -6,14 +6,15 @@ import Button from 'components/buttons/Button';
|
||||
import InputFieldWithIcon from 'components/forms/fields/InputFieldWithIcon';
|
||||
import helpers from './helpers';
|
||||
|
||||
const formFields = ['name', 'username', 'password', 'password_confirmation', 'email'];
|
||||
const formFields = ['username', 'password', 'password_confirmation', 'email'];
|
||||
const { validate } = helpers;
|
||||
|
||||
class AdminDetails extends Component {
|
||||
static propTypes = {
|
||||
className: PropTypes.string,
|
||||
currentPage: PropTypes.bool,
|
||||
fields: PropTypes.shape({
|
||||
email: formFieldInterface.isRequired,
|
||||
name: formFieldInterface.isRequired,
|
||||
password: formFieldInterface.isRequired,
|
||||
password_confirmation: formFieldInterface.isRequired,
|
||||
username: formFieldInterface.isRequired,
|
||||
@ -22,40 +23,44 @@ class AdminDetails extends Component {
|
||||
};
|
||||
|
||||
render () {
|
||||
const { fields, handleSubmit } = this.props;
|
||||
const { className, currentPage, fields, handleSubmit } = this.props;
|
||||
const tabIndex = currentPage ? 1 : -1;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<InputFieldWithIcon
|
||||
{...fields.name}
|
||||
placeholder="Full Name"
|
||||
/>
|
||||
<InputFieldWithIcon
|
||||
{...fields.username}
|
||||
iconName="username"
|
||||
placeholder="Username"
|
||||
/>
|
||||
<InputFieldWithIcon
|
||||
{...fields.password}
|
||||
iconName="password"
|
||||
placeholder="Password"
|
||||
type="password"
|
||||
/>
|
||||
<InputFieldWithIcon
|
||||
{...fields.password_confirmation}
|
||||
iconName="password"
|
||||
placeholder="Confirm Password"
|
||||
type="password"
|
||||
/>
|
||||
<InputFieldWithIcon
|
||||
{...fields.email}
|
||||
iconName="email"
|
||||
placeholder="Email"
|
||||
/>
|
||||
<div className={className}>
|
||||
<div className="registration-fields">
|
||||
<InputFieldWithIcon
|
||||
{...fields.username}
|
||||
iconName="username"
|
||||
placeholder="Username"
|
||||
tabIndex={tabIndex}
|
||||
/>
|
||||
<InputFieldWithIcon
|
||||
{...fields.password}
|
||||
iconName="password"
|
||||
placeholder="Password"
|
||||
type="password"
|
||||
tabIndex={tabIndex}
|
||||
/>
|
||||
<InputFieldWithIcon
|
||||
{...fields.password_confirmation}
|
||||
iconName="password"
|
||||
placeholder="Confirm Password"
|
||||
type="password"
|
||||
tabIndex={tabIndex}
|
||||
/>
|
||||
<InputFieldWithIcon
|
||||
{...fields.email}
|
||||
iconName="email"
|
||||
placeholder="Email"
|
||||
tabIndex={tabIndex}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
text="Submit"
|
||||
variant="gradient"
|
||||
tabIndex={tabIndex}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -9,24 +9,6 @@ import { fillInFormInput } from 'test/helpers';
|
||||
describe('AdminDetails - form', () => {
|
||||
afterEach(restoreSpies);
|
||||
|
||||
describe('name input', () => {
|
||||
it('renders an input field', () => {
|
||||
const form = mount(<AdminDetails handleSubmit={noop} />);
|
||||
const fullNameField = form.find({ name: 'name' });
|
||||
|
||||
expect(fullNameField.length).toEqual(1);
|
||||
});
|
||||
|
||||
it('updates state when the field changes', () => {
|
||||
const form = mount(<AdminDetails handleSubmit={noop} />);
|
||||
const fullNameField = form.find({ name: 'name' }).find('input');
|
||||
|
||||
fillInFormInput(fullNameField, 'The Gnar Co');
|
||||
|
||||
expect(form.state().formData).toInclude({ name: 'The Gnar Co' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('username input', () => {
|
||||
it('renders an input field', () => {
|
||||
const form = mount(<AdminDetails handleSubmit={noop} />);
|
||||
@ -111,7 +93,6 @@ describe('AdminDetails - form', () => {
|
||||
expect(onSubmitSpy).toNotHaveBeenCalled();
|
||||
expect(form.state().errors).toInclude({
|
||||
email: 'Email must be present',
|
||||
name: 'Full name must be present',
|
||||
password: 'Password must be present',
|
||||
password_confirmation: 'Password confirmation must be present',
|
||||
username: 'Username must be present',
|
||||
@ -152,14 +133,12 @@ describe('AdminDetails - form', () => {
|
||||
const onSubmitSpy = createSpy();
|
||||
const form = mount(<AdminDetails handleSubmit={onSubmitSpy} />);
|
||||
const emailField = form.find({ name: 'email' }).find('input');
|
||||
const fullNameField = form.find({ name: 'name' }).find('input');
|
||||
const passwordConfirmationField = form.find({ name: 'password_confirmation' }).find('input');
|
||||
const passwordField = form.find({ name: 'password' }).find('input');
|
||||
const usernameField = form.find({ name: 'username' }).find('input');
|
||||
const submitBtn = form.find('Button');
|
||||
|
||||
fillInFormInput(emailField, 'hi@gnar.dog');
|
||||
fillInFormInput(fullNameField, 'Gnar Dog');
|
||||
fillInFormInput(passwordField, 'p@ssw0rd');
|
||||
fillInFormInput(passwordConfirmationField, 'p@ssw0rd');
|
||||
fillInFormInput(usernameField, 'gnardog');
|
||||
|
@ -6,7 +6,6 @@ const validate = (formData) => {
|
||||
const errors = {};
|
||||
const {
|
||||
email,
|
||||
name: fullName,
|
||||
password,
|
||||
password_confirmation: passwordConfirmation,
|
||||
username,
|
||||
@ -20,10 +19,6 @@ const validate = (formData) => {
|
||||
errors.email = 'Email must be present';
|
||||
}
|
||||
|
||||
if (!fullName) {
|
||||
errors.name = 'Full name must be present';
|
||||
}
|
||||
|
||||
if (!username) {
|
||||
errors.username = 'Username must be present';
|
||||
}
|
||||
|
@ -1,11 +1,16 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import classnames from 'classnames';
|
||||
|
||||
import Button from 'components/buttons/Button';
|
||||
import formDataInterface from 'interfaces/registration_form_data';
|
||||
import Icon from 'components/Icon';
|
||||
import Checkbox from 'components/forms/fields/Checkbox';
|
||||
|
||||
const baseClass = 'confirm-user-reg';
|
||||
|
||||
class ConfirmationPage extends Component {
|
||||
static propTypes = {
|
||||
className: PropTypes.string,
|
||||
formData: formDataInterface,
|
||||
handleSubmit: PropTypes.func,
|
||||
};
|
||||
@ -20,9 +25,9 @@ class ConfirmationPage extends Component {
|
||||
|
||||
render () {
|
||||
const {
|
||||
className,
|
||||
formData: {
|
||||
email,
|
||||
full_name: fullName,
|
||||
kolide_server_url: kolideWebAddress,
|
||||
org_name: orgName,
|
||||
username,
|
||||
@ -30,38 +35,47 @@ class ConfirmationPage extends Component {
|
||||
} = this.props;
|
||||
const { onSubmit } = this;
|
||||
|
||||
const confirmRegClasses = classnames(className, baseClass);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Icon name="success-check" />
|
||||
<table>
|
||||
<caption>Administrator Configuration</caption>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Full Name:</th>
|
||||
<td>{fullName}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Username:</th>
|
||||
<td>{username}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Email:</th>
|
||||
<td>{email}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Organization:</th>
|
||||
<td>{orgName}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Kolide URL:</th>
|
||||
<td>{kolideWebAddress}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div className={confirmRegClasses}>
|
||||
<div className={`${baseClass}__wrapper`}>
|
||||
<Icon name="success-check" className={`${baseClass}__icon`} />
|
||||
<table className={`${baseClass}__table`}>
|
||||
<caption>Administrator Configuration</caption>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Username:</th>
|
||||
<td>{username}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Email:</th>
|
||||
<td>{email}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Organization:</th>
|
||||
<td>{orgName}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Kolide URL:</th>
|
||||
<td><span className={`${baseClass}__table-url`} title={kolideWebAddress}>{kolideWebAddress}</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div className={`${baseClass}__import`}>
|
||||
<Checkbox name="import-install">
|
||||
<p>I am migrating an existing <strong>osquery</strong> installation.</p>
|
||||
<p>Take me to the <strong>Import Configuration</strong> page.</p>
|
||||
</Checkbox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
onClick={onSubmit}
|
||||
text="Submit"
|
||||
text="Finish"
|
||||
variant="gradient"
|
||||
className={`${baseClass}__submit`}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -9,7 +9,6 @@ describe('ConfirmationPage - form', () => {
|
||||
afterEach(restoreSpies);
|
||||
|
||||
const formData = {
|
||||
full_name: 'Jason Meller',
|
||||
username: 'jmeller',
|
||||
email: 'jason@kolide.co',
|
||||
org_name: 'Kolide',
|
||||
@ -24,7 +23,6 @@ describe('ConfirmationPage - form', () => {
|
||||
/>
|
||||
);
|
||||
|
||||
expect(form.text()).toInclude(formData.full_name);
|
||||
expect(form.text()).toInclude(formData.username);
|
||||
expect(form.text()).toInclude(formData.email);
|
||||
expect(form.text()).toInclude(formData.org_name);
|
||||
|
@ -0,0 +1,92 @@
|
||||
.confirm-user-reg {
|
||||
&__wrapper {
|
||||
padding: 0 35px 25px;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
color: $success;
|
||||
font-size: 120px;
|
||||
display: block;
|
||||
text-align: center;
|
||||
margin: 10px 0 35px;
|
||||
}
|
||||
|
||||
&__submit {
|
||||
bottom: 0;
|
||||
top: auto;
|
||||
position: absolute;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
|
||||
&:active {
|
||||
top: auto;
|
||||
}
|
||||
}
|
||||
|
||||
&__table {
|
||||
width: 100%;
|
||||
|
||||
caption {
|
||||
font-size: 18px;
|
||||
font-weight: $bold;
|
||||
line-height: 0.72;
|
||||
letter-spacing: 0.6px;
|
||||
color: $text-dark;
|
||||
text-transform: uppercase;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
tr {
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
th {
|
||||
font-size: 14px;
|
||||
font-weight: $bold;
|
||||
line-height: 1.71;
|
||||
letter-spacing: 0.5px;
|
||||
color: $text-dark;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
td {
|
||||
font-size: 14px;
|
||||
font-weight: $light;
|
||||
line-height: 1.71;
|
||||
letter-spacing: 0.5px;
|
||||
color: $text-dark;
|
||||
}
|
||||
}
|
||||
|
||||
&__table-url {
|
||||
@include ellipsis(90%);
|
||||
font-family: 'SourceCodePro', $monospace;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
&__import {
|
||||
@include user-select(none);
|
||||
font-size: 14px;
|
||||
font-weight: $light;
|
||||
line-height: 1.71;
|
||||
letter-spacing: 0.5px;
|
||||
color: $text-dark;
|
||||
margin: 30px 0 0;
|
||||
|
||||
label {
|
||||
display: block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
padding-left: 40px;
|
||||
}
|
||||
|
||||
.kolide-checkbox__input {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -11,6 +11,8 @@ const { validate } = helpers;
|
||||
|
||||
class KolideDetails extends Component {
|
||||
static propTypes = {
|
||||
className: PropTypes.string,
|
||||
currentPage: PropTypes.bool,
|
||||
fields: PropTypes.shape({
|
||||
kolide_server_url: formFieldInterface.isRequired,
|
||||
}).isRequired,
|
||||
@ -18,18 +20,24 @@ class KolideDetails extends Component {
|
||||
};
|
||||
|
||||
render () {
|
||||
const { fields, handleSubmit } = this.props;
|
||||
const { className, currentPage, fields, handleSubmit } = this.props;
|
||||
const tabIndex = currentPage ? 1 : -1;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<InputFieldWithIcon
|
||||
{...fields.kolide_server_url}
|
||||
placeholder="Kolide Web Address"
|
||||
/>
|
||||
<div className={className}>
|
||||
<div className="registration-fields">
|
||||
<InputFieldWithIcon
|
||||
{...fields.kolide_server_url}
|
||||
placeholder="Kolide Web Address"
|
||||
tabIndex={tabIndex}
|
||||
hint={['Don’t include ', <code key="hint">/v1</code>, ' or any other path']}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
text="Submit"
|
||||
variant="gradient"
|
||||
tabIndex={tabIndex}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -11,6 +11,8 @@ const { validate } = helpers;
|
||||
|
||||
class OrgDetails extends Component {
|
||||
static propTypes = {
|
||||
className: PropTypes.string,
|
||||
currentPage: PropTypes.bool,
|
||||
fields: PropTypes.shape({
|
||||
org_name: formFieldInterface.isRequired,
|
||||
org_logo_url: formFieldInterface.isRequired,
|
||||
@ -19,22 +21,29 @@ class OrgDetails extends Component {
|
||||
};
|
||||
|
||||
render () {
|
||||
const { fields, handleSubmit } = this.props;
|
||||
const { className, currentPage, fields, handleSubmit } = this.props;
|
||||
const tabIndex = currentPage ? 1 : -1;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<InputFieldWithIcon
|
||||
{...fields.org_name}
|
||||
placeholder="Organization Name"
|
||||
/>
|
||||
<InputFieldWithIcon
|
||||
{...fields.org_logo_url}
|
||||
placeholder="Organization Logo URL (must start with https://)"
|
||||
/>
|
||||
<div className={className}>
|
||||
<div className="registration-fields">
|
||||
<InputFieldWithIcon
|
||||
{...fields.org_name}
|
||||
placeholder="Organization Name"
|
||||
tabIndex={tabIndex}
|
||||
/>
|
||||
<InputFieldWithIcon
|
||||
{...fields.org_logo_url}
|
||||
placeholder="Organization Logo URL"
|
||||
tabIndex={tabIndex}
|
||||
hint="must start with https://"
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
text="Submit"
|
||||
variant="gradient"
|
||||
tabIndex={tabIndex}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import classnames from 'classnames';
|
||||
|
||||
import AdminDetails from 'components/forms/RegistrationForm/AdminDetails';
|
||||
import ConfirmationPage from 'components/forms/RegistrationForm/ConfirmationPage';
|
||||
@ -12,6 +13,8 @@ const PAGE_HEADER_TEXT = {
|
||||
4: 'SUCCESS',
|
||||
};
|
||||
|
||||
const baseClass = 'user-registration';
|
||||
|
||||
class RegistrationForm extends Component {
|
||||
static propTypes = {
|
||||
onNextPage: PropTypes.func,
|
||||
@ -21,8 +24,14 @@ class RegistrationForm extends Component {
|
||||
|
||||
constructor (props) {
|
||||
super(props);
|
||||
const { window } = global;
|
||||
|
||||
this.state = { errors: {}, formData: {} };
|
||||
this.state = {
|
||||
errors: {},
|
||||
formData: {
|
||||
kolide_server_url: window.location.origin,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
onPageFormSubmit = (pageFormData) => {
|
||||
@ -39,19 +48,40 @@ class RegistrationForm extends Component {
|
||||
return onNextPage();
|
||||
}
|
||||
|
||||
onSubmit = () => {
|
||||
onSubmitConfirmation = () => {
|
||||
const { formData } = this.state;
|
||||
const { onSubmit: handleSubmit } = this.props;
|
||||
|
||||
return handleSubmit(formData);
|
||||
}
|
||||
|
||||
isCurrentPage = (num) => {
|
||||
const { page } = this.props;
|
||||
|
||||
if (num === page) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
renderHeader = () => {
|
||||
const { page } = this.props;
|
||||
const headerText = PAGE_HEADER_TEXT[page];
|
||||
|
||||
if (headerText) {
|
||||
return <h2 className={`${baseClass}__title`}>{headerText}</h2>;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
renderDescription = () => {
|
||||
const { page } = this.props;
|
||||
|
||||
if (page === 1) {
|
||||
return (
|
||||
<div>
|
||||
<div className={`${baseClass}__description`}>
|
||||
<p>Additional admins can be designated within the Kolide App</p>
|
||||
<p>Passwords must include 7 characters, at least 1 number (eg. 0-9) and at least 1 symbol (eg. ^&*#)</p>
|
||||
</div>
|
||||
@ -60,7 +90,7 @@ class RegistrationForm extends Component {
|
||||
|
||||
if (page === 2) {
|
||||
return (
|
||||
<div>
|
||||
<div className={`${baseClass}__description`}>
|
||||
<p>Set your Organization's name (eg. Yahoo! Inc)</p>
|
||||
<p>Specify the website URL of your organization (eg. Yahoo.com)</p>
|
||||
</div>
|
||||
@ -69,7 +99,7 @@ class RegistrationForm extends Component {
|
||||
|
||||
if (page === 3) {
|
||||
return (
|
||||
<div>
|
||||
<div className={`${baseClass}__description`}>
|
||||
<p>Define the base URL which osqueryd clients use to connect and register with Kolide.</p>
|
||||
<p>
|
||||
<small>Note: Please ensure the URL you choose is accessible to all endpoints that need to communicate with Kolide. Otherwise, they will not be able to correctly register.</small>
|
||||
@ -81,51 +111,82 @@ class RegistrationForm extends Component {
|
||||
return false;
|
||||
}
|
||||
|
||||
renderHeader = () => {
|
||||
renderContent = () => {
|
||||
const { page } = this.props;
|
||||
const headerText = PAGE_HEADER_TEXT[page];
|
||||
|
||||
if (headerText) {
|
||||
return <h2>{headerText}</h2>;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
renderPageForm = () => {
|
||||
const { formData } = this.state;
|
||||
const { onPageFormSubmit, onSubmit } = this;
|
||||
const { page } = this.props;
|
||||
|
||||
if (page === 1) {
|
||||
return <AdminDetails formData={formData} handleSubmit={onPageFormSubmit} />;
|
||||
}
|
||||
|
||||
if (page === 2) {
|
||||
return <OrgDetails formData={formData} handleSubmit={onPageFormSubmit} />;
|
||||
}
|
||||
|
||||
if (page === 3) {
|
||||
return <KolideDetails formData={formData} handleSubmit={onPageFormSubmit} />;
|
||||
}
|
||||
const {
|
||||
onSubmitConfirmation,
|
||||
renderDescription,
|
||||
renderHeader,
|
||||
} = this;
|
||||
|
||||
if (page === 4) {
|
||||
return <ConfirmationPage formData={formData} handleSubmit={onSubmit} />;
|
||||
return (
|
||||
<div>
|
||||
{renderHeader()}
|
||||
<ConfirmationPage formData={formData} handleSubmit={onSubmitConfirmation} className={`${baseClass}__confirmation`} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
return (
|
||||
<div>
|
||||
{renderHeader()}
|
||||
{renderDescription()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { onSubmit } = this.props;
|
||||
const { renderDescription, renderHeader, renderPageForm } = this;
|
||||
const { onSubmit, page } = this.props;
|
||||
const { formData } = this.state;
|
||||
const { isCurrentPage, onPageFormSubmit, renderContent } = this;
|
||||
|
||||
const containerClass = classnames(`${baseClass}__container`, {
|
||||
[`${baseClass}__container--complete`]: page > 3,
|
||||
});
|
||||
|
||||
const adminDetailsClass = classnames(
|
||||
`${baseClass}__field-wrapper`,
|
||||
`${baseClass}__field-wrapper--admin`
|
||||
);
|
||||
|
||||
const orgDetailsClass = classnames(
|
||||
`${baseClass}__field-wrapper`,
|
||||
`${baseClass}__field-wrapper--org`
|
||||
);
|
||||
|
||||
const kolideDetailsClass = classnames(
|
||||
`${baseClass}__field-wrapper`,
|
||||
`${baseClass}__field-wrapper--kolide`
|
||||
);
|
||||
|
||||
const formSectionClasses = classnames(
|
||||
`${baseClass}__form`,
|
||||
{
|
||||
[`${baseClass}__form--step1-active`]: page === 1,
|
||||
[`${baseClass}__form--step1-complete`]: page > 1,
|
||||
[`${baseClass}__form--step2-active`]: page === 2,
|
||||
[`${baseClass}__form--step2-complete`]: page > 2,
|
||||
[`${baseClass}__form--step3-active`]: page === 3,
|
||||
[`${baseClass}__form--step3-complete`]: page > 3,
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<form onSubmit={onSubmit}>
|
||||
{renderHeader()}
|
||||
{renderDescription()}
|
||||
{renderPageForm()}
|
||||
</form>
|
||||
<div className={baseClass}>
|
||||
<div className={containerClass}>
|
||||
{renderContent()}
|
||||
|
||||
<form onSubmit={onSubmit} className={formSectionClasses}>
|
||||
<AdminDetails formData={formData} handleSubmit={onPageFormSubmit} className={adminDetailsClass} currentPage={isCurrentPage(1)} />
|
||||
|
||||
<OrgDetails formData={formData} handleSubmit={onPageFormSubmit} className={orgDetailsClass} currentPage={isCurrentPage(2)} />
|
||||
|
||||
<KolideDetails formData={formData} handleSubmit={onPageFormSubmit} className={kolideDetailsClass} currentPage={isCurrentPage(3)} />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
196
frontend/components/forms/RegistrationForm/_styles.scss
Normal file
196
frontend/components/forms/RegistrationForm/_styles.scss
Normal file
@ -0,0 +1,196 @@
|
||||
.user-registration {
|
||||
@include display(flex);
|
||||
@include align-content(center);
|
||||
@include justify-content(center);
|
||||
@include flex-grow(1);
|
||||
|
||||
&__container {
|
||||
@include align-self(center);
|
||||
@include size(500px 520px);
|
||||
border-radius: 4px;
|
||||
background-color: $bg-light;
|
||||
box-shadow: 0 0 30px 0 rgba(0, 0, 0, 0.3);
|
||||
box-sizing: border-box;
|
||||
padding: 25px 35px;
|
||||
margin-top: -55px;
|
||||
|
||||
&--complete {
|
||||
padding: 0;
|
||||
|
||||
.user-registration__title {
|
||||
font-size: 24px;
|
||||
font-weight: $bold;
|
||||
line-height: 0.54;
|
||||
letter-spacing: 0.9px;
|
||||
color: $text-dark;
|
||||
padding: 25px 35px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__form {
|
||||
@include transform(translateY(-85px));
|
||||
width: 100%;
|
||||
height: 470px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
padding: 25px 0;
|
||||
|
||||
@include breakpoint(tablet) {
|
||||
@include transform(translateY(-100px));
|
||||
}
|
||||
|
||||
&--step1-complete {
|
||||
.user-registration__field-wrapper--admin {
|
||||
left: -184px;
|
||||
}
|
||||
|
||||
.user-registration__field-wrapper--org {
|
||||
left: 50%;
|
||||
}
|
||||
|
||||
.user-registration__field-wrapper--kolide {
|
||||
left: calc(100% + 184px);
|
||||
}
|
||||
}
|
||||
|
||||
&--step2-complete {
|
||||
.user-registration__field-wrapper--admin {
|
||||
left: calc(-50% - 184px);
|
||||
}
|
||||
|
||||
.user-registration__field-wrapper--org {
|
||||
left: -184px;
|
||||
}
|
||||
|
||||
.user-registration__field-wrapper--kolide {
|
||||
left: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
&--step3-complete {
|
||||
.user-registration__field-wrapper--admin {
|
||||
left: calc(-100% - 184px);
|
||||
}
|
||||
|
||||
.user-registration__field-wrapper--org {
|
||||
left: calc(-50% - 184px);
|
||||
}
|
||||
|
||||
.user-registration__field-wrapper--kolide {
|
||||
left: -184px;
|
||||
}
|
||||
}
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
background-image: linear-gradient(to right, $accent-dark 50%, transparent 50%);
|
||||
background-position: left top;
|
||||
background-repeat: repeat-x;
|
||||
background-size: 17px 2px;
|
||||
position: absolute;
|
||||
top: 100px;
|
||||
left: 50%;
|
||||
width: 50%;
|
||||
height: 2px;
|
||||
content: '';
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&::before {
|
||||
left: auto;
|
||||
right: 50%;
|
||||
}
|
||||
|
||||
&--step1-active {
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&--step3-active,
|
||||
&--step3-complete {
|
||||
&::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__description {
|
||||
font-size: 14px;
|
||||
font-weight: $light;
|
||||
line-height: 1.43;
|
||||
letter-spacing: 0.5px;
|
||||
color: $text-dark;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 18px;
|
||||
font-weight: $bold;
|
||||
line-height: 0.72;
|
||||
letter-spacing: 0.6px;
|
||||
color: $text-dark;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&__field-wrapper {
|
||||
@include transition(left 300ms ease);
|
||||
width: 430px;
|
||||
min-height: 400px;
|
||||
padding-bottom: 75px;
|
||||
border-radius: 4px;
|
||||
background-color: $bg-light;
|
||||
box-shadow: 0 10px 30px 0 rgba(0, 0, 0, 0.3);
|
||||
margin: 0 auto;
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
margin-left: -215px;
|
||||
z-index: 2;
|
||||
|
||||
&--admin {
|
||||
left: 50%;
|
||||
}
|
||||
|
||||
&--org {
|
||||
left: calc(100% + 184px);
|
||||
}
|
||||
|
||||
&--kolide {
|
||||
top: 45px;
|
||||
left: calc(150% + 184px);
|
||||
}
|
||||
|
||||
input {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.button {
|
||||
bottom: 0;
|
||||
top: auto;
|
||||
position: absolute;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
|
||||
&:active {
|
||||
top: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.registration-fields {
|
||||
padding: 0 35px 25px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
&__confirmation {
|
||||
background-color: $bg-light;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
width: 500px;
|
||||
height: 500px;
|
||||
}
|
||||
}
|
33
frontend/components/forms/fields/Checkbox/Checkbox.jsx
Normal file
33
frontend/components/forms/fields/Checkbox/Checkbox.jsx
Normal file
@ -0,0 +1,33 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import { noop } from 'lodash';
|
||||
|
||||
const baseClass = 'kolide-checkbox';
|
||||
|
||||
class InputField extends Component {
|
||||
static propTypes = {
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
onChange: noop,
|
||||
};
|
||||
|
||||
render () {
|
||||
const { children, className, name, onChange } = this.props;
|
||||
const checkBoxClass = classnames(baseClass, className);
|
||||
|
||||
return (
|
||||
<label htmlFor={name} className={checkBoxClass}>
|
||||
<input type="checkbox" name={name} id={name} className={`${checkBoxClass}__input`} onChange={onChange} />
|
||||
<span className={`${checkBoxClass}__tick`} />
|
||||
{children}
|
||||
</label>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default InputField;
|
11
frontend/components/forms/fields/Checkbox/Checkbox.tests.jsx
Normal file
11
frontend/components/forms/fields/Checkbox/Checkbox.tests.jsx
Normal file
@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
import expect from 'expect';
|
||||
import { mount } from 'enzyme';
|
||||
|
||||
import Checkbox from './Checkbox';
|
||||
|
||||
describe('Checkbox - component', () => {
|
||||
it('renders', () => {
|
||||
expect(mount(<Checkbox />)).toExist();
|
||||
});
|
||||
});
|
45
frontend/components/forms/fields/Checkbox/_styles.scss
Normal file
45
frontend/components/forms/fields/Checkbox/_styles.scss
Normal file
@ -0,0 +1,45 @@
|
||||
.kolide-checkbox {
|
||||
&__input {
|
||||
visibility: hidden;
|
||||
margin: 0;
|
||||
|
||||
&:checked + .kolide-checkbox__tick {
|
||||
&::after {
|
||||
background-color: $brand;
|
||||
border: solid 2px $brand;
|
||||
}
|
||||
|
||||
&::before {
|
||||
@include transform(rotate(45deg));
|
||||
@include position(absolute, 50% null null 50%);
|
||||
box-sizing: border-box;
|
||||
display: table;
|
||||
width: 7px;
|
||||
height: 13px;
|
||||
margin: -8px 0 0 -4px;
|
||||
border: 2px solid $white;
|
||||
border-top: 0;
|
||||
border-left: 0;
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__tick {
|
||||
@include size(20px);
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
|
||||
&::after {
|
||||
@include transition(border 75ms ease-in-out, background 75ms ease-in-out);
|
||||
@include size(20px);
|
||||
border-radius: 2px;
|
||||
border: solid 2px $border-medium;
|
||||
content: '';
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
background-color: $white;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
1
frontend/components/forms/fields/Checkbox/index.js
Normal file
1
frontend/components/forms/fields/Checkbox/index.js
Normal file
@ -0,0 +1 @@
|
||||
export default from './Checkbox';
|
@ -10,10 +10,12 @@ class InputFieldWithIcon extends InputField {
|
||||
static propTypes = {
|
||||
autofocus: PropTypes.bool,
|
||||
error: PropTypes.string,
|
||||
hint: PropTypes.oneOfType([PropTypes.array, PropTypes.string]),
|
||||
iconName: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
placeholder: PropTypes.string,
|
||||
tabIndex: PropTypes.number,
|
||||
type: PropTypes.string,
|
||||
className: PropTypes.string,
|
||||
};
|
||||
@ -33,9 +35,19 @@ class InputFieldWithIcon extends InputField {
|
||||
return <div className={labelClasses}>{placeholder}</div>;
|
||||
}
|
||||
|
||||
renderHint = () => {
|
||||
const { hint } = this.props;
|
||||
|
||||
if (hint) {
|
||||
return <span className={`${baseClass}__hint`}>{hint}</span>;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
render () {
|
||||
const { className, error, iconName, name, placeholder, type, value } = this.props;
|
||||
const { onInputChange } = this;
|
||||
const { className, error, iconName, name, placeholder, tabIndex, type, value } = this.props;
|
||||
const { onInputChange, renderHint } = this;
|
||||
|
||||
const inputClasses = classnames(
|
||||
`${baseClass}__input`,
|
||||
@ -60,10 +72,12 @@ class InputFieldWithIcon extends InputField {
|
||||
className={inputClasses}
|
||||
placeholder={placeholder}
|
||||
ref={(r) => { this.input = r; }}
|
||||
tabIndex={tabIndex}
|
||||
type={type}
|
||||
value={value}
|
||||
/>
|
||||
{iconName && <Icon name={iconName} className={iconClasses} />}
|
||||
{renderHint()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
position: absolute;
|
||||
right: 6px;
|
||||
top: 28px;
|
||||
font-size: 20px;
|
||||
font-size: 14px;
|
||||
color: $accent-text;
|
||||
|
||||
&--active {
|
||||
@ -27,13 +27,18 @@
|
||||
font-size: 20px;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-color: $brand-ultralight;
|
||||
color: $accent-text;
|
||||
padding-right: 30px;
|
||||
opacity: 1;
|
||||
text-indent: 1px;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
font-family: 'Oxygen', $helvetica;
|
||||
color: $text-dark;
|
||||
|
||||
@include placeholder {
|
||||
color: $accent-text;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
@ -63,4 +68,19 @@
|
||||
font-size: $small;
|
||||
text-transform: lowercase;
|
||||
}
|
||||
|
||||
&__hint {
|
||||
font-size: 14px;
|
||||
font-weight: $normal;
|
||||
line-height: 1.57;
|
||||
letter-spacing: 1px;
|
||||
color: $accent-text;
|
||||
|
||||
code {
|
||||
color: $brand-light;
|
||||
background-color: $accent-light;
|
||||
padding: 2px;
|
||||
font-family: 'SourceCodePro', $monospace;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
font-size: $xsmall;
|
||||
display: none;
|
||||
|
||||
@include breakpoint(tablet) {
|
||||
@include breakpoint(ltdesktop) {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
@ -20,7 +20,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@include breakpoint(tablet) {
|
||||
@include breakpoint(ltdesktop) {
|
||||
padding-top: $pad-most;
|
||||
}
|
||||
}
|
||||
|
@ -181,7 +181,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@include breakpoint(tablet) {
|
||||
@include breakpoint(ltdesktop) {
|
||||
.Select-menu-outer {
|
||||
.Select-menu {
|
||||
min-width: 665px;
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { PropTypes } from 'react';
|
||||
|
||||
export default PropTypes.shape({
|
||||
full_name: PropTypes.string,
|
||||
username: PropTypes.string,
|
||||
password: PropTypes.string,
|
||||
password_confirmation: PropTypes.string,
|
||||
|
@ -24,21 +24,26 @@ class Breadcrumbs extends Component {
|
||||
render () {
|
||||
const { onClick } = this;
|
||||
const { page } = this.props;
|
||||
const page1ClassName = classnames('button--unstyled', 'page-1-btn', {
|
||||
'is-active': page >= 1,
|
||||
const baseClass = 'registration-breadcrumbs';
|
||||
const pageBaseClass = `${baseClass}__page`;
|
||||
const page1ClassName = classnames(pageBaseClass, `${pageBaseClass}--1`, 'button--unstyled', {
|
||||
[`${pageBaseClass}--active`]: page === 1,
|
||||
[`${pageBaseClass}--complete`]: page > 1,
|
||||
});
|
||||
const page2ClassName = classnames('button--unstyled', 'page-2-btn', {
|
||||
'is-active': page >= 2,
|
||||
const page2ClassName = classnames(pageBaseClass, `${pageBaseClass}--2`, 'button--unstyled', {
|
||||
[`${pageBaseClass}--active`]: page === 2,
|
||||
[`${pageBaseClass}--complete`]: page > 2,
|
||||
});
|
||||
const page3ClassName = classnames('button--unstyled', 'page-3-btn', {
|
||||
'is-active': page >= 3,
|
||||
const page3ClassName = classnames(pageBaseClass, `${pageBaseClass}--3`, 'button--unstyled', {
|
||||
[`${pageBaseClass}--active`]: page === 3,
|
||||
[`${pageBaseClass}--complete`]: page > 3,
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button className={page1ClassName} onClick={onClick(1)}>Page 1</button>
|
||||
<button className={page2ClassName} onClick={onClick(2)}>Page 2</button>
|
||||
<button className={page3ClassName} onClick={onClick(3)}>Page 3</button>
|
||||
<div className={baseClass}>
|
||||
<button className={page1ClassName} onClick={onClick(1)}>Setup User</button>
|
||||
<button className={page2ClassName} onClick={onClick(2)}>Setup Organization</button>
|
||||
<button className={page3ClassName} onClick={onClick(3)}>Set Kolide URL</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -14,21 +14,21 @@ describe('Breadcrumbs - component', () => {
|
||||
|
||||
it('renders page 1 Button as active when the page prop is 1', () => {
|
||||
const component = mount(<Breadcrumbs page={1} />);
|
||||
const page1Btn = component.find('button.page-1-btn');
|
||||
const page2Btn = component.find('button.page-2-btn');
|
||||
const page3Btn = component.find('button.page-3-btn');
|
||||
const page1Btn = component.find('button.registration-breadcrumbs__page--1');
|
||||
const page2Btn = component.find('button.registration-breadcrumbs__page--2');
|
||||
const page3Btn = component.find('button.registration-breadcrumbs__page--3');
|
||||
|
||||
expect(page1Btn.prop('className')).toInclude('is-active');
|
||||
expect(page2Btn.prop('className')).toNotInclude('is-active');
|
||||
expect(page3Btn.prop('className')).toNotInclude('is-active');
|
||||
expect(page1Btn.prop('className')).toInclude('registration-breadcrumbs__page--active');
|
||||
expect(page2Btn.prop('className')).toNotInclude('registration-breadcrumbs__page--active');
|
||||
expect(page3Btn.prop('className')).toNotInclude('registration-breadcrumbs__page--active');
|
||||
});
|
||||
|
||||
it('calls the onClick prop with the page # when clicked', () => {
|
||||
const onClickSpy = createSpy();
|
||||
const component = mount(<Breadcrumbs page={1} onClick={onClickSpy} />);
|
||||
const page1Btn = component.find('button.page-1-btn');
|
||||
const page2Btn = component.find('button.page-2-btn');
|
||||
const page3Btn = component.find('button.page-3-btn');
|
||||
const page1Btn = component.find('button.registration-breadcrumbs__page--1');
|
||||
const page2Btn = component.find('button.registration-breadcrumbs__page--2');
|
||||
const page3Btn = component.find('button.registration-breadcrumbs__page--3');
|
||||
|
||||
page1Btn.simulate('click');
|
||||
|
||||
|
146
frontend/pages/RegistrationPage/Breadcrumbs/_styles.scss
Normal file
146
frontend/pages/RegistrationPage/Breadcrumbs/_styles.scss
Normal file
@ -0,0 +1,146 @@
|
||||
.registration-breadcrumbs {
|
||||
@include display(flex);
|
||||
@include justify-content(space-between);
|
||||
@include align-content(center);
|
||||
width: 665px;
|
||||
height: 125px;
|
||||
margin: 0 auto;
|
||||
|
||||
@include breakpoint(tablet) {
|
||||
height: 75px;
|
||||
}
|
||||
|
||||
&__page {
|
||||
text-align: center;
|
||||
width: 145px;
|
||||
font-size: 14px;
|
||||
font-weight: $normal;
|
||||
line-height: 1.53;
|
||||
letter-spacing: 0.5px;
|
||||
color: $text-medium;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 235px;
|
||||
height: 2px;
|
||||
background-image: linear-gradient(to right, $accent-dark 50%, transparent 50%);
|
||||
background-position: left top;
|
||||
background-repeat: repeat-x;
|
||||
background-size: 17px 2px;
|
||||
bottom: 43px;
|
||||
left: 84px;
|
||||
|
||||
@include breakpoint(tablet) {
|
||||
bottom: 23px;
|
||||
}
|
||||
}
|
||||
|
||||
&::after {
|
||||
@extend %kolidecon;
|
||||
@include size(24px);
|
||||
background-color: $white;
|
||||
display: block;
|
||||
border-radius: 50%;
|
||||
content: '';
|
||||
font-size: 28px;
|
||||
margin: 14px auto 0;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
cursor: pointer;
|
||||
|
||||
@include breakpoint(tablet) {
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&--active {
|
||||
font-weight: $bold;
|
||||
color: $text-dark;
|
||||
}
|
||||
|
||||
&--1 {
|
||||
&::after {
|
||||
border: solid 2px $success;
|
||||
}
|
||||
|
||||
&.registration-breadcrumbs__page--active {
|
||||
&::after {
|
||||
background-color: $success;
|
||||
}
|
||||
}
|
||||
|
||||
&.registration-breadcrumbs__page--complete {
|
||||
&::before {
|
||||
background-image: linear-gradient(to right, $success 0%, $link 100%);
|
||||
background-size: auto;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
&::after {
|
||||
@include size(28px);
|
||||
content: '\f035';
|
||||
color: $success;
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&--2 {
|
||||
&::after {
|
||||
border: solid 2px $link;
|
||||
}
|
||||
|
||||
&.registration-breadcrumbs__page--active {
|
||||
&::after {
|
||||
background-color: $link;
|
||||
}
|
||||
}
|
||||
|
||||
&.registration-breadcrumbs__page--complete {
|
||||
&::before {
|
||||
background-image: linear-gradient(to right, $link 0%, $brand-light 100%);
|
||||
background-size: auto;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
&::after {
|
||||
@include size(28px);
|
||||
content: '\f035';
|
||||
color: $link;
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&--3 {
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&::after {
|
||||
border: solid 2px $brand-light;
|
||||
}
|
||||
|
||||
&.registration-breadcrumbs__page--active {
|
||||
&::after {
|
||||
background-color: $brand-light;
|
||||
}
|
||||
}
|
||||
|
||||
&.registration-breadcrumbs__page--complete {
|
||||
&::after {
|
||||
@include size(28px);
|
||||
content: '\f035';
|
||||
color: $brand-light;
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -10,6 +10,8 @@ import { setup } from 'redux/nodes/auth/actions';
|
||||
import { showBackgroundImage } from 'redux/nodes/app/actions';
|
||||
import userInterface from 'interfaces/user';
|
||||
|
||||
import kolideLogo from '../../../assets/images/kolide-logo-condensed.svg';
|
||||
|
||||
export class RegistrationPage extends Component {
|
||||
static propTypes = {
|
||||
currentUser: userInterface,
|
||||
@ -70,6 +72,11 @@ export class RegistrationPage extends Component {
|
||||
}
|
||||
|
||||
onSetPage = (page) => {
|
||||
const { page: currentPage } = this.state;
|
||||
if (page >= currentPage) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.setState({ page });
|
||||
|
||||
return false;
|
||||
@ -85,7 +92,12 @@ export class RegistrationPage extends Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="registration-page">
|
||||
<img
|
||||
alt="Kolide"
|
||||
src={kolideLogo}
|
||||
className="registration-page__logo"
|
||||
/>
|
||||
<Breadcrumbs onClick={onSetPage} page={page} />
|
||||
<RegistrationForm page={page} onNextPage={onNextPage} onSubmit={onRegistrationFormSubmit} />
|
||||
</div>
|
||||
|
@ -84,6 +84,7 @@ describe('RegistrationPage - component', () => {
|
||||
describe('#onSetPage', () => {
|
||||
it('sets state to the page number', () => {
|
||||
const page = mount(<RegistrationPage />);
|
||||
page.setState({ page: 3 });
|
||||
page.node.onSetPage(3);
|
||||
|
||||
expect(page.state()).toInclude({ page: 3 });
|
||||
|
19
frontend/pages/RegistrationPage/_styles.scss
Normal file
19
frontend/pages/RegistrationPage/_styles.scss
Normal file
@ -0,0 +1,19 @@
|
||||
.registration-page {
|
||||
@include display(flex);
|
||||
@include justify-content(center);
|
||||
@include flex-direction(column);
|
||||
min-height: calc(100vh - #{$footer-height});
|
||||
position: relative;
|
||||
|
||||
&__logo {
|
||||
@include position(absolute, 15px null null 15px);
|
||||
width: 200px;
|
||||
|
||||
@include breakpoint(tablet) {
|
||||
width: 125px;
|
||||
position: static;
|
||||
display: block;
|
||||
margin: 15px 0 0 15px;
|
||||
}
|
||||
}
|
||||
}
|
@ -26,7 +26,7 @@
|
||||
border: solid 1px $accent-dark;
|
||||
box-shadow: inset 0 0 8px 0 rgba($black, 0.12);
|
||||
box-sizing: border-box;
|
||||
font-family: Source-codePro, Oxygen;
|
||||
font-family: SourceCodePro, Oxygen;
|
||||
font-size: $medium;
|
||||
height: 60px;
|
||||
letter-spacing: 1.2px;
|
||||
@ -100,6 +100,6 @@
|
||||
|
||||
code {
|
||||
color: $brand;
|
||||
font-family: Source-codePro, Oxygen;
|
||||
font-family: SourceCodePro, Oxygen;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
html {
|
||||
position: relative;
|
||||
min-height: 100%;
|
||||
min-width: $min-width;
|
||||
// Because iOS hates us we must fight to the death!
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
// End Apple War
|
||||
@ -13,6 +14,7 @@ body {
|
||||
font-family: 'Oxygen', sans-serif;
|
||||
font-size: $base;
|
||||
line-height: 1.6;
|
||||
min-width: $min-width;
|
||||
}
|
||||
|
||||
h1,
|
||||
@ -37,6 +39,7 @@ h3 {
|
||||
}
|
||||
|
||||
input,
|
||||
textarea {
|
||||
textarea,
|
||||
button {
|
||||
font-family: 'Oxygen', sans-serif;
|
||||
}
|
||||
|
@ -112,18 +112,6 @@
|
||||
content: '\f029';
|
||||
}
|
||||
|
||||
.kolidecon-username:before {
|
||||
content: '\f02a';
|
||||
}
|
||||
|
||||
.kolidecon-password:before {
|
||||
content: '\f02b';
|
||||
}
|
||||
|
||||
.kolidecon-email:before {
|
||||
content: '\f02c';
|
||||
}
|
||||
|
||||
.kolidecon-query:before {
|
||||
content: '\f02d';
|
||||
}
|
||||
@ -288,6 +276,18 @@
|
||||
content: '\f03d';
|
||||
}
|
||||
|
||||
.kolidecon-username:before {
|
||||
content: '\f02a';
|
||||
}
|
||||
|
||||
.kolidecon-password:before {
|
||||
content: '\f02b';
|
||||
}
|
||||
|
||||
.kolidecon-email:before {
|
||||
content: '\f02c';
|
||||
}
|
||||
|
||||
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
|
@ -1,10 +1,15 @@
|
||||
$min-width: 768px;
|
||||
$medium-width: 1280px;
|
||||
$medium-width: 1024px;
|
||||
$desktop-width: 1280px;
|
||||
$max-width: 2560px;
|
||||
|
||||
@mixin breakpoint($size: desktop) {
|
||||
@if ($size == tablet) {
|
||||
@media (min-width: $min-width) and (max-width: $medium-width - 1) {
|
||||
@media (max-width: $medium-width) {
|
||||
@content;
|
||||
}
|
||||
} @else if ($size == ltdesktop) {
|
||||
@media (min-width: $min-width) and (max-width: $desktop-width - 1) {
|
||||
@content;
|
||||
}
|
||||
} @else {
|
||||
|
@ -2,6 +2,7 @@
|
||||
<html data-uuid="{{ .UUID }}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" type="text/css" href="/assets/bundle.css">
|
||||
<title>Kolide</title>
|
||||
</head>
|
||||
|
Loading…
Reference in New Issue
Block a user