Taking login and sign pages out + new design for login and signup page (#228)

* make login a seperate page

* make signup a another page

* get previousPath

* setPreviousPath between routes

* new desig for login pagen

* new design for signup page

* added login and exit icons

* added signup and exit icons

* refactor form-container.scss

* deleted extra code
This commit is contained in:
Yining Shi 2016-12-15 18:43:58 -05:00 committed by Cassie Tarakajian
parent 67dad2b86d
commit acad9538cc
16 changed files with 278 additions and 180 deletions

View file

@ -1,6 +1,7 @@
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import DevTools from './components/DevTools'; import DevTools from './components/DevTools';
import { setPreviousPath } from '../IDE/actions/ide';
class App extends React.Component { class App extends React.Component {
constructor(props, context) { constructor(props, context) {
@ -12,6 +13,12 @@ class App extends React.Component {
this.setState({ isMounted: true }); // eslint-disable-line react/no-did-mount-set-state this.setState({ isMounted: true }); // eslint-disable-line react/no-did-mount-set-state
} }
componentWillReceiveProps(nextProps) {
if (nextProps.location !== this.props.location) {
this.props.setPreviousPath(this.props.location.pathname);
}
}
render() { render() {
return ( return (
<div className="app"> <div className="app">
@ -23,7 +30,11 @@ class App extends React.Component {
} }
App.propTypes = { App.propTypes = {
children: PropTypes.object children: PropTypes.object,
location: PropTypes.shape({
pathname: PropTypes.string
}),
setPreviousPath: PropTypes.func.isRequired,
}; };
export default connect()(App); export default connect(() => ({}), { setPreviousPath })(App);

View file

@ -1,63 +0,0 @@
import React from 'react';
import { reduxForm } from 'redux-form';
import { validateAndLoginUser } from '../../User/actions';
import LoginForm from '../components/LoginForm';
// import GithubButton from '../components/GithubButton';
import { Link } from 'react-router';
class LoginView extends React.Component {
componentDidMount() {
this.refs.login.focus();
}
render() {
return (
<div className="login" ref="login" tabIndex="0">
<h1>Login</h1>
<LoginForm {...this.props} />
{/* <h2 className="login__divider">Or</h2>
<GithubButton buttonText="Login with Github" /> */}
<p className="form__navigation-options">
Don't have an account?&nbsp;
<Link className="form__signup-button" to="/signup">Sign Up</Link>
</p>
<p className="form__navigation-options">
Forgot your password?&nbsp;
<Link className="form__reset-password-button" to="/reset-password">Reset your password</Link>
</p>
<Link className="form__cancel-button" to="/">Cancel</Link>
</div>
);
}
}
function mapStateToProps(state, ownProps) {
return {
user: state.user,
previousPath: ownProps.previousPath
};
}
function mapDispatchToProps() {
return {
validateAndLoginUser
};
}
function validate(formProps) {
const errors = {};
if (!formProps.email) {
errors.email = 'Please enter an email';
}
if (!formProps.password) {
errors.password = 'Please enter a password';
}
return errors;
}
export default reduxForm({
form: 'login',
fields: ['email', 'password'],
validate
}, mapStateToProps, mapDispatchToProps)(LoginView);

View file

@ -28,8 +28,6 @@ import SplitPane from 'react-split-pane';
import Overlay from '../../App/components/Overlay'; import Overlay from '../../App/components/Overlay';
import SketchList from '../components/SketchList'; import SketchList from '../components/SketchList';
import About from '../components/About'; import About from '../components/About';
import LoginView from '../components/LoginView';
import SignupView from '../components/SignupView';
import ResetPasswordView from '../components/ResetPasswordView'; import ResetPasswordView from '../components/ResetPasswordView';
import NewPasswordView from '../components/NewPasswordView'; import NewPasswordView from '../components/NewPasswordView';
@ -434,24 +432,6 @@ class IDEView extends React.Component {
); );
} }
})()} })()}
{(() => { // eslint-disable-line
if (this.props.location.pathname === '/login') {
return (
<Overlay>
<LoginView previousPath={this.props.ide.previousPath} />
</Overlay>
);
}
})()}
{(() => { // eslint-disable-line
if (this.props.location.pathname === '/signup') {
return (
<Overlay>
<SignupView previousPath={this.props.ide.previousPath} />
</Overlay>
);
}
})()}
{(() => { // eslint-disable-line {(() => { // eslint-disable-line
if (this.props.location.pathname === '/reset-password') { if (this.props.location.pathname === '/reset-password') {
return ( return (

View file

@ -5,27 +5,29 @@ function LoginForm(props) {
const { fields: { email, password }, handleSubmit, submitting, pristine } = props; const { fields: { email, password }, handleSubmit, submitting, pristine } = props;
return ( return (
<form className="login-form" onSubmit={handleSubmit(props.validateAndLoginUser.bind(this, props.previousPath))}> <form className="login-form" onSubmit={handleSubmit(props.validateAndLoginUser.bind(this, props.previousPath))}>
<p className="login-form__field"> <p className="form__field">
<label htmlFor="email" className="form__label">Email</label>
<input <input
className="login-form__email-input" className="form__input"
aria-label="email" aria-label="email"
type="text" type="text"
placeholder="Email" id="email"
{...domOnlyProps(email)} {...domOnlyProps(email)}
/> />
{email.touched && email.error && <span className="form-error">{email.error}</span>} {email.touched && email.error && <span className="form-error">{email.error}</span>}
</p> </p>
<p className="login-form__field"> <p className="form__field">
<label htmlFor="password" className="form__label">Password</label>
<input <input
className="signup-form__password-input" className="form__input"
aria-label="password" aria-label="password"
type="password" type="password"
placeholder="Password" id="password"
{...domOnlyProps(password)} {...domOnlyProps(password)}
/> />
{password.touched && password.error && <span className="form-error">{password.error}</span>} {password.touched && password.error && <span className="form-error">{password.error}</span>}
</p> </p>
<input type="submit" disabled={submitting || pristine} value="Login" aria-label="login" /> <input type="submit" disabled={submitting || pristine} value="Log In" aria-label="login" />
</form> </form>
); );
} }

View file

@ -5,42 +5,46 @@ function SignupForm(props) {
const { fields: { username, email, password, confirmPassword }, handleSubmit, submitting, invalid, pristine } = props; const { fields: { username, email, password, confirmPassword }, handleSubmit, submitting, invalid, pristine } = props;
return ( return (
<form className="signup-form" onSubmit={handleSubmit(props.signUpUser.bind(this, props.previousPath))}> <form className="signup-form" onSubmit={handleSubmit(props.signUpUser.bind(this, props.previousPath))}>
<p className="signup-form__field"> <p className="form__field">
<label htmlFor="username" className="form__label">User Name</label>
<input <input
className="signup-form__username-input" className="form__input"
aria-label="username" aria-label="username"
type="text" type="text"
placeholder="Username" id="username"
{...domOnlyProps(username)} {...domOnlyProps(username)}
/> />
{username.touched && username.error && <span className="form-error">{username.error}</span>} {username.touched && username.error && <span className="form-error">{username.error}</span>}
</p> </p>
<p className="signup-form__field"> <p className="form__field">
<label htmlFor="email" className="form__label">Email</label>
<input <input
className="signup-form__email-input" className="form__input"
aria-label="email" aria-label="email"
type="text" type="text"
placeholder="Email" id="email"
{...domOnlyProps(email)} {...domOnlyProps(email)}
/> />
{email.touched && email.error && <span className="form-error">{email.error}</span>} {email.touched && email.error && <span className="form-error">{email.error}</span>}
</p> </p>
<p className="signup-form__field"> <p className="form__field">
<label htmlFor="password" className="form__label">Password</label>
<input <input
className="signup-form__password-input" className="form__input"
aria-label="password" aria-label="password"
type="password" type="password"
placeholder="Password" id="password"
{...domOnlyProps(password)} {...domOnlyProps(password)}
/> />
{password.touched && password.error && <span className="form-error">{password.error}</span>} {password.touched && password.error && <span className="form-error">{password.error}</span>}
</p> </p>
<p className="signup-form__field"> <p className="form__field">
<label htmlFor="confirm password" className="form__label">Confirm Password</label>
<input <input
className="signup-form__confirm-password-input" className="form__input"
type="password" type="password"
placeholder="Confirm Password"
aria-label="confirm password" aria-label="confirm password"
id="confirm password"
{...domOnlyProps(confirmPassword)} {...domOnlyProps(confirmPassword)}
/> />
{confirmPassword.touched && confirmPassword.error && <span className="form-error">{confirmPassword.error}</span>} {confirmPassword.touched && confirmPassword.error && <span className="form-error">{confirmPassword.error}</span>}

View file

@ -0,0 +1,89 @@
import React, { PropTypes } from 'react';
import { reduxForm } from 'redux-form';
import { validateAndLoginUser } from '../actions';
import LoginForm from '../components/LoginForm';
// import GithubButton from '../components/GithubButton';
import { Link, browserHistory } from 'react-router';
import InlineSVG from 'react-inlinesvg';
const exitUrl = require('../../../images/exit.svg');
const logoUrl = require('../../../images/p5js-logo.svg');
class LoginView extends React.Component {
constructor(props) {
super(props);
this.closeLoginPage = this.closeLoginPage.bind(this);
this.gotoHomePage = this.gotoHomePage.bind(this);
}
closeLoginPage() {
browserHistory.push(this.props.previousPath);
}
gotoHomePage() {
browserHistory.push('/');
}
render() {
return (
<div className="form-container">
<div className="form-container__header">
<button className="form-container__logo-button" onClick={this.gotoHomePage}>
<InlineSVG src={logoUrl} alt="p5js Logo" />
</button>
<button className="form-container__exit-button" onClick={this.closeLoginPage}>
<InlineSVG src={exitUrl} alt="Close Login Page" />
</button>
</div>
<div className="form-container__content">
<h2 className="form-container__title">Log In</h2>
<LoginForm {...this.props} />
{/* <h2 className="form-container__divider">Or</h2>
<GithubButton buttonText="Login with Github" /> */}
<p className="form__navigation-options">
Don't have an account?&nbsp;
<Link className="form__signup-button" to="/signup">Sign Up</Link>
</p>
<p className="form__navigation-options">
Forgot your password?&nbsp;
<Link className="form__reset-password-button" to="/reset-password">Reset your password</Link>
</p>
</div>
</div>
);
}
}
function mapStateToProps(state) {
return {
user: state.user,
previousPath: state.ide.previousPath
};
}
function mapDispatchToProps() {
return {
validateAndLoginUser
};
}
function validate(formProps) {
const errors = {};
if (!formProps.email) {
errors.email = 'Please enter an email';
}
if (!formProps.password) {
errors.password = 'Please enter a password';
}
return errors;
}
LoginView.propTypes = {
previousPath: PropTypes.string.isRequired
};
export default reduxForm({
form: 'login',
fields: ['email', 'password'],
validate
}, mapStateToProps, mapDispatchToProps)(LoginView);

View file

@ -1,35 +1,57 @@
import React from 'react'; import React, { PropTypes } from 'react';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import * as UserActions from '../../User/actions'; import * as UserActions from '../actions';
import { reduxForm } from 'redux-form'; import { reduxForm } from 'redux-form';
import SignupForm from '../components/SignupForm'; import SignupForm from '../components/SignupForm';
import axios from 'axios'; import axios from 'axios';
import { Link } from 'react-router'; import { Link, browserHistory } from 'react-router';
import InlineSVG from 'react-inlinesvg';
const exitUrl = require('../../../images/exit.svg');
const logoUrl = require('../../../images/p5js-logo.svg');
class SignupView extends React.Component { class SignupView extends React.Component {
componentDidMount() { constructor(props) {
this.refs.signup.focus(); super(props);
this.closeSignupPage = this.closeSignupPage.bind(this);
this.gotoHomePage = this.gotoHomePage.bind(this);
}
closeSignupPage() {
browserHistory.push(this.props.previousPath);
}
gotoHomePage() {
browserHistory.push('/');
} }
render() { render() {
return ( return (
<div className="signup" ref="signup" tabIndex="0"> <div className="form-container">
<h1>Sign Up</h1> <div className="form-container__header">
<button className="form-container__logo-button" onClick={this.gotoHomePage}>
<InlineSVG src={logoUrl} alt="p5js Logo" />
</button>
<button className="form-container__exit-button" onClick={this.closeSignupPage}>
<InlineSVG src={exitUrl} alt="Close Signup Page" />
</button>
</div>
<div className="form-container__content">
<h2 className="form-container__title">Sign Up</h2>
<SignupForm {...this.props} /> <SignupForm {...this.props} />
<p className="form__navigation-options"> <p className="form__navigation-options">
Already have an account?&nbsp; Already have an account?&nbsp;
<Link className="form__login-button" to="/login">Login</Link> <Link className="form__login-button" to="/login">Log In</Link>
</p> </p>
<Link className="form__cancel-button" to="/">Cancel</Link> </div>
</div> </div>
); );
} }
} }
function mapStateToProps(state, ownProps) { function mapStateToProps(state) {
return { return {
user: state.user, user: state.user,
previousPath: ownProps.previousPath previousPath: state.ide.previousPath
}; };
} }
@ -90,6 +112,10 @@ function onSubmitFail(errors) {
console.log(errors); console.log(errors);
} }
SignupView.propTypes = {
previousPath: PropTypes.string.isRequired
};
export default reduxForm({ export default reduxForm({
form: 'signup', form: 'signup',
fields: ['username', 'email', 'password', 'confirmPassword'], fields: ['username', 'email', 'password', 'confirmPassword'],

View file

@ -3,6 +3,8 @@ import React from 'react';
import App from './modules/App/App'; import App from './modules/App/App';
import IDEView from './modules/IDE/pages/IDEView'; import IDEView from './modules/IDE/pages/IDEView';
import FullView from './modules/IDE/pages/FullView'; import FullView from './modules/IDE/pages/FullView';
import LoginView from './modules/User/pages/LoginView';
import SignupView from './modules/User/pages/SignupView';
// import SketchListView from './modules/Sketch/pages/SketchListView'; // import SketchListView from './modules/Sketch/pages/SketchListView';
import { getUser } from './modules/User/actions'; import { getUser } from './modules/User/actions';
@ -14,8 +16,8 @@ const routes = (store) =>
( (
<Route path="/" component={App}> <Route path="/" component={App}>
<IndexRoute component={IDEView} onEnter={checkAuth(store)} /> <IndexRoute component={IDEView} onEnter={checkAuth(store)} />
<Route path="/login" component={IDEView} /> <Route path="/login" component={LoginView} />
<Route path="/signup" component={IDEView} /> <Route path="/signup" component={SignupView} />
<Route path="/reset-password" component={IDEView} /> <Route path="/reset-password" component={IDEView} />
<Route path="/reset-password/:reset_password_token" component={IDEView} /> <Route path="/reset-password/:reset_password_token" component={IDEView} />
<Route path="/projects/:project_id" component={IDEView} /> <Route path="/projects/:project_id" component={IDEView} />

View file

@ -51,12 +51,19 @@
padding: 0; padding: 0;
} }
%none-themify-icon {
background-color: transparent;
border: none;
cursor: pointer;
padding: 0;
}
%button { %button {
@include themify() { @include themify() {
background-color: getThemifyVariable('button-background-color'); background-color: getThemifyVariable('button-background-color');
color: getThemifyVariable('button-color'); color: getThemifyVariable('button-color');
cursor: pointer; cursor: pointer;
border: 1px solid getThemifyVariable('button-border-color'); border: 2px solid getThemifyVariable('button-border-color');
border-radius: 2px; border-radius: 2px;
padding: #{10 / $base-font-size}rem #{30 / $base-font-size}rem; padding: #{10 / $base-font-size}rem #{30 / $base-font-size}rem;
&:enabled:hover { &:enabled:hover {
@ -72,6 +79,27 @@
} }
} }
%forms-button {
background-color: $form-button-background-color;
color: $form-button-color;
cursor: pointer;
border: 2px solid $form-button-color;
border-radius: 2px;
padding: #{8 / $base-font-size}rem #{25 / $base-font-size}rem;
line-height: 1;
margin: #{39 / $base-font-size}rem 0 #{24 / $base-font-size}rem 0;
&:enabled:hover {
border-color: $form-button-background-hover-color;
background-color: $form-button-background-hover-color;
color: $form-button-hover-color;
}
&:enabled:active {
border-color: $form-button-background-active-color;
background-color: $form-button-background-active-color;
color: $form-button-active-color;
}
}
%preferences-button { %preferences-button {
@extend %toolbar-button; @extend %toolbar-button;
@include themify() { @include themify() {

View file

@ -37,7 +37,7 @@ $themes: (
editor-gutter-color: #f4f4f4, editor-gutter-color: #f4f4f4,
file-selected-color: #f4f4f4, file-selected-color: #f4f4f4,
input-text-color: #333, input-text-color: #333,
input-border-color: #979797, input-border-color: #b5b5b5,
), ),
dark: ( dark: (
logo-color: $p5js-pink, logo-color: $p5js-pink,
@ -69,7 +69,7 @@ $themes: (
editor-gutter-color: #363636, editor-gutter-color: #363636,
file-selected-color: #404040, file-selected-color: #404040,
input-text-color: #333, input-text-color: #333,
input-border-color: #979797, input-border-color: #b5b5b5,
), ),
contrast: ( contrast: (
logo-color: $yellow, logo-color: $yellow,
@ -101,7 +101,7 @@ $themes: (
editor-gutter-color: #454545, editor-gutter-color: #454545,
file-selected-color: #404040, file-selected-color: #404040,
input-text-color: #333, input-text-color: #333,
input-border-color: #979797, input-border-color: #b5b5b5,
) )
); );
@ -110,3 +110,13 @@ $console-error-color: #ff5f52;
$toast-background-color: #979797; $toast-background-color: #979797;
$toast-text-color: $white; $toast-text-color: $white;
$form-title-color: rgba(51, 51, 51, 0.87);
$secondary-form-title-color: #b5b5b5;
$form-button-background-color: $white;
$form-button-color: #f10046;
$form-button-background-hover-color: $p5js-pink;
$form-button-background-active-color: #f10046;
$form-button-hover-color: $white;
$form-button-active-color: $white;
$form-navigation-options-color: #999999;

View file

@ -36,7 +36,8 @@ input, button {
input { input {
padding: #{5 / $base-font-size}rem; padding: #{5 / $base-font-size}rem;
border: 1px solid; border: 1px solid ;
border-radius: 2px;
padding: #{10 / $base-font-size}rem; padding: #{10 / $base-font-size}rem;
@include themify() { @include themify() {
color: getThemifyVariable('input-text-color'); color: getThemifyVariable('input-text-color');
@ -46,7 +47,7 @@ input {
input[type="submit"] { input[type="submit"] {
@include themify() { @include themify() {
@extend %button; @extend %forms-button;
} }
} }

View file

@ -0,0 +1,40 @@
.form-container {
text-align: center;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
.form-container__header {
width: 100%;
padding: #{15 / $base-font-size}rem #{34 / $base-font-size}rem;
display: flex;
justify-content: space-between;
}
.form-container__content {
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
}
.form-container__title {
font-weight: normal;
color: $form-title-color;
}
.form-container__divider {
padding: #{20 / $base-font-size}rem 0;
}
.form-container__logo-button {
@extend %none-themify-icon;
}
.form-container__exit-button {
@include themify() {
@extend %icon;
}
}

View file

@ -1,9 +1,10 @@
.form-error { .form-error {
display: block; display: block;
padding-top: #{10 / $base-font-size}rem; padding-top: #{4 / $base-font-size}rem;
color: $console-error-color; color: $p5js-pink;
width: #{300 / $base-font-size}rem; width: #{300 / $base-font-size}rem;
font-size: #{12 / $base-font-size}rem; font-size: #{9 / $base-font-size}rem;
text-align: left;
} }
.form__cancel-button { .form__cancel-button {
@ -12,6 +13,20 @@
} }
.form__navigation-options { .form__navigation-options {
margin-top: #{10 / $base-font-size}rem; margin-top: #{16 / $base-font-size}rem;
font-size: #{12 / $base-font-size}rem; font-size: #{12 / $base-font-size}rem;
color: $form-navigation-options-color;
}
.form__label {
color: $secondary-form-title-color;
font-size: #{12 / $base-font-size}rem;
margin-top: #{25 / $base-font-size}rem;
margin-bottom: #{7 / $base-font-size}rem;
display:block;
}
.form__input {
width: #{360 / $base-font-size}rem;
height: #{40 / $base-font-size}rem;
} }

View file

@ -1,22 +0,0 @@
.login {
@extend %modal;
text-align: center;
display: flex;
flex-direction: column;
justify-content: center;
padding: #{20 / $base-font-size}rem;
align-items: center;
}
.login-form__email-input,
.login-form__password-input {
width: #{300 / $base-font-size}rem;
}
.login-form__field {
margin: #{20 / $base-font-size}rem 0;
}
.login__divider {
padding: #{20 / $base-font-size}rem 0;
}

View file

@ -1,24 +0,0 @@
.signup {
@extend %modal;
text-align: center;
display: flex;
flex-direction: column;
justify-content: center;
padding: #{20 / $base-font-size}rem;
align-items: center;
}
.signup-form__username-input,
.signup-form__email-input,
.signup-form__password-input,
.signup-form__confirm-password-input {
width: #{300 / $base-font-size}rem;
}
.signup-form__field {
margin: #{20 / $base-font-size}rem 0;
}
.signup__divider {
padding: #{20 / $base-font-size}rem 0;
}

View file

@ -17,8 +17,6 @@
@import 'components/nav'; @import 'components/nav';
@import 'components/toolbar'; @import 'components/toolbar';
@import 'components/preferences'; @import 'components/preferences';
@import 'components/signup';
@import 'components/login';
@import 'components/reset-password'; @import 'components/reset-password';
@import 'components/new-password'; @import 'components/new-password';
@import 'components/sketch-list'; @import 'components/sketch-list';
@ -33,6 +31,7 @@
@import 'components/toast'; @import 'components/toast';
@import 'components/timer'; @import 'components/timer';
@import 'components/force-authentication'; @import 'components/force-authentication';
@import 'components/form-container';
@import 'layout/ide'; @import 'layout/ide';
@import 'layout/fullscreen'; @import 'layout/fullscreen';