mirror of
https://github.com/elyby/accounts-frontend.git
synced 2024-10-01 15:57:46 +05:30
В первом приближении заинтегрировался с беком
This commit is contained in:
parent
19eec8f7a4
commit
a94ddaf131
@ -1,48 +1,69 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
|
|
||||||
import { FormattedMessage as Message } from 'react-intl';
|
import { FormattedMessage as Message } from 'react-intl';
|
||||||
import Helmet from 'react-helmet';
|
import Helmet from 'react-helmet';
|
||||||
|
|
||||||
import buttons from 'components/ui/buttons.scss';
|
import buttons from 'components/ui/buttons.scss';
|
||||||
import { Panel, PanelBody, PanelFooter } from 'components/ui/Panel';
|
|
||||||
import { Input } from 'components/ui/Form';
|
import { Input } from 'components/ui/Form';
|
||||||
|
|
||||||
|
import BaseAuthBody from './BaseAuthBody';
|
||||||
import styles from './activation.scss';
|
import styles from './activation.scss';
|
||||||
import {helpLinks as helpLinksStyles} from './helpLinks.scss';
|
|
||||||
import messages from './Activation.messages';
|
import messages from './Activation.messages';
|
||||||
|
|
||||||
export default function Activation() {
|
class Body extends BaseAuthBody {
|
||||||
var Title = () => ( // TODO: separate component for PageTitle
|
static propTypes = {
|
||||||
<Message {...messages.accountActivationTitle}>
|
...BaseAuthBody.propTypes,
|
||||||
{(msg) => <span>{msg}<Helmet title={msg} /></span>}
|
activate: PropTypes.func.isRequired,
|
||||||
</Message>
|
auth: PropTypes.shape({
|
||||||
);
|
error: PropTypes.string,
|
||||||
Title.goBack = '/register';
|
login: PropTypes.shape({
|
||||||
|
login: PropTypes.stirng
|
||||||
|
})
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
render() {
|
||||||
Title,
|
return (
|
||||||
Body: () => (
|
|
||||||
<div>
|
<div>
|
||||||
|
{this.renderErrors()}
|
||||||
|
|
||||||
<div className={styles.description}>
|
<div className={styles.description}>
|
||||||
<div className={styles.descriptionImage} />
|
<div className={styles.descriptionImage} />
|
||||||
|
|
||||||
<div className={styles.descriptionText}>
|
<div className={styles.descriptionText}>
|
||||||
<Message {...messages.activationMailWasSent} values={{
|
<Message {...messages.activationMailWasSent} values={{
|
||||||
email: (<b>erickskrauch@yandex.ru</b>)
|
email: (<b>{this.props.user.email}</b>)
|
||||||
}} />
|
}} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.formRow}>
|
<div className={styles.formRow}>
|
||||||
<Input color="blue" className={styles.activationCodeInput} placeholder={messages.enterTheCode} />
|
<Input {...this.bindField('key')}
|
||||||
|
color="blue"
|
||||||
|
className={styles.activationCodeInput}
|
||||||
|
autoFocus
|
||||||
|
required
|
||||||
|
placeholder={messages.enterTheCode}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
),
|
);
|
||||||
Footer: (props) => (
|
}
|
||||||
<button className={buttons.blue} onClick={(event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
props.history.push('/oauth/permissions');
|
onFormSubmit() {
|
||||||
}}>
|
this.props.activate(this.serialize());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Activation() {
|
||||||
|
return {
|
||||||
|
Title: () => ( // TODO: separate component for PageTitle
|
||||||
|
<Message {...messages.accountActivationTitle}>
|
||||||
|
{(msg) => <span>{msg}<Helmet title={msg} /></span>}
|
||||||
|
</Message>
|
||||||
|
),
|
||||||
|
Body,
|
||||||
|
Footer: () => (
|
||||||
|
<button className={buttons.blue}>
|
||||||
<Message {...messages.confirmEmail} />
|
<Message {...messages.confirmEmail} />
|
||||||
</button>
|
</button>
|
||||||
),
|
),
|
||||||
@ -53,45 +74,3 @@ export default function Activation() {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export class _Activation extends Component {
|
|
||||||
displayName = 'Activation';
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Message {...messages.accountActivationTitle}>
|
|
||||||
{(msg) => <Helmet title={msg} />}
|
|
||||||
</Message>
|
|
||||||
|
|
||||||
<Panel icon="arrowLeft" title={<Message {...messages.accountActivationTitle} />}>
|
|
||||||
<PanelBody>
|
|
||||||
<div className={styles.description}>
|
|
||||||
<div className={styles.descriptionImage} />
|
|
||||||
|
|
||||||
<div className={styles.descriptionText}>
|
|
||||||
<Message {...messages.activationMailWasSent} values={{
|
|
||||||
email: (<b>erickskrauch@yandex.ru</b>)
|
|
||||||
}} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={styles.formRow}>
|
|
||||||
<Input color="blue" className={styles.activationCodeInput} placeholder={messages.enterTheCode} />
|
|
||||||
</div>
|
|
||||||
</PanelBody>
|
|
||||||
<PanelFooter>
|
|
||||||
<button className={buttons.blue}>
|
|
||||||
<Message {...messages.confirmEmail} />
|
|
||||||
</button>
|
|
||||||
</PanelFooter>
|
|
||||||
</Panel>
|
|
||||||
<div className={helpLinksStyles}>
|
|
||||||
<a href="#">
|
|
||||||
<Message {...messages.didNotReceivedEmail} />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
76
src/components/auth/AuthError.jsx
Normal file
76
src/components/auth/AuthError.jsx
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
|
||||||
|
import { FormattedMessage as Message } from 'react-intl';
|
||||||
|
import { PanelBodyHeader } from 'components/ui/Panel';
|
||||||
|
|
||||||
|
import messages from './AuthError.messages';
|
||||||
|
|
||||||
|
export default class AuthError extends Component {
|
||||||
|
static displayName = 'AuthError';
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
error: PropTypes.string.isRequired,
|
||||||
|
onClose: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let { error } = this.props;
|
||||||
|
|
||||||
|
if (this.errorsMap[error]) {
|
||||||
|
error = this.errorsMap[error]();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PanelBodyHeader type="error" onClose={this.props.onClose}>
|
||||||
|
{error}
|
||||||
|
</PanelBodyHeader>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
errorsMap = {
|
||||||
|
'error.login_required': () => <Message {...messages.loginRequired} />,
|
||||||
|
'error.login_not_exist': () => <Message {...messages.loginNotExist} />,
|
||||||
|
'error.password_required': () => <Message {...messages.passwordRequired} />,
|
||||||
|
|
||||||
|
'error.password_incorrect': () => (
|
||||||
|
<span>
|
||||||
|
<Message {...messages.invalidPassword} />
|
||||||
|
<br/>
|
||||||
|
<Message {...messages.suggestResetPassword} values={{
|
||||||
|
link: (
|
||||||
|
<a href="#">
|
||||||
|
<Message {...messages.forgotYourPassword} />
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
}} />
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
|
||||||
|
'error.username_required': () => <Message {...messages.usernameRequired} />,
|
||||||
|
'error.email_required': () => <Message {...messages.emailRequired} />,
|
||||||
|
'error.email_invalid': () => <Message {...messages.emailInvalid} />,
|
||||||
|
|
||||||
|
'error.email_not_available': () => (
|
||||||
|
<span>
|
||||||
|
<Message {...messages.emailNotAvailable} />
|
||||||
|
<br/>
|
||||||
|
<Message {...messages.suggestResetPassword} values={{
|
||||||
|
link: (
|
||||||
|
<a href="#">
|
||||||
|
<Message {...messages.forgotYourPassword} />
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
}} />
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
|
||||||
|
'error.rePassword_required': () => <Message {...messages.rePasswordRequired} />,
|
||||||
|
'error.password_too_short': () => <Message {...messages.passwordTooShort} />,
|
||||||
|
'error.rePassword_does_not_match': () => <Message {...messages.passwordsDoesNotMatch} />,
|
||||||
|
'error.rulesAgreement_required': () => <Message {...messages.rulesAgreementRequired} />,
|
||||||
|
'error.you_must_accept_rules': () => this.errorsMap['error.rulesAgreement_required'](),
|
||||||
|
'error.key_required': () => <Message {...messages.keyRequired} />,
|
||||||
|
'error.key_is_required': () => this.errorsMap['error.key_required'](),
|
||||||
|
'error.key_not_exists': () => <Message {...messages.keyNotExists} />
|
||||||
|
};
|
||||||
|
}
|
83
src/components/auth/AuthError.messages.js
Normal file
83
src/components/auth/AuthError.messages.js
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import { defineMessages } from 'react-intl';
|
||||||
|
|
||||||
|
export default defineMessages({
|
||||||
|
invalidPassword: {
|
||||||
|
id: 'invalidPassword',
|
||||||
|
defaultMessage: 'You entered wrong account password.'
|
||||||
|
},
|
||||||
|
|
||||||
|
suggestResetPassword: {
|
||||||
|
id: 'suggestResetPassword',
|
||||||
|
defaultMessage: 'Are you have {link}?'
|
||||||
|
},
|
||||||
|
|
||||||
|
forgotYourPassword: {
|
||||||
|
id: 'forgotYourPassword',
|
||||||
|
defaultMessage: 'forgot your password'
|
||||||
|
},
|
||||||
|
|
||||||
|
loginRequired: {
|
||||||
|
id: 'loginRequired',
|
||||||
|
defaultMessage: 'Please enter email or username'
|
||||||
|
},
|
||||||
|
|
||||||
|
loginNotExist: {
|
||||||
|
id: 'loginNotExist',
|
||||||
|
defaultMessage: 'Sorry, Ely doesn\'t recognise your login.'
|
||||||
|
},
|
||||||
|
|
||||||
|
passwordRequired: {
|
||||||
|
id: 'passwordRequired',
|
||||||
|
defaultMessage: 'Please enter password'
|
||||||
|
},
|
||||||
|
|
||||||
|
usernameRequired: {
|
||||||
|
id: 'usernameRequired',
|
||||||
|
defaultMessage: 'Username is required'
|
||||||
|
},
|
||||||
|
|
||||||
|
emailRequired: {
|
||||||
|
id: 'emailRequired',
|
||||||
|
defaultMessage: 'Email is required'
|
||||||
|
},
|
||||||
|
|
||||||
|
emailInvalid: {
|
||||||
|
id: 'emailInvalid',
|
||||||
|
defaultMessage: 'Email is invalid'
|
||||||
|
},
|
||||||
|
|
||||||
|
emailNotAvailable: {
|
||||||
|
id: 'emailNotAvailable',
|
||||||
|
defaultMessage: 'This email is already registered.'
|
||||||
|
},
|
||||||
|
|
||||||
|
rePasswordRequired: {
|
||||||
|
id: 'rePasswordRequired',
|
||||||
|
defaultMessage: 'Please retype your password'
|
||||||
|
},
|
||||||
|
|
||||||
|
passwordTooShort: {
|
||||||
|
id: 'passwordTooShort',
|
||||||
|
defaultMessage: 'Your password is too short'
|
||||||
|
},
|
||||||
|
|
||||||
|
passwordsDoesNotMatch: {
|
||||||
|
id: 'passwordsDoesNotMatch',
|
||||||
|
defaultMessage: 'The passwords does not match'
|
||||||
|
},
|
||||||
|
|
||||||
|
rulesAgreementRequired: {
|
||||||
|
id: 'rulesAgreementRequired',
|
||||||
|
defaultMessage: 'You must accept rules in order to create an account'
|
||||||
|
},
|
||||||
|
|
||||||
|
keyRequired: {
|
||||||
|
id: 'keyRequired',
|
||||||
|
defaultMessage: 'Please, enter an activation key'
|
||||||
|
},
|
||||||
|
|
||||||
|
keyNotExists: {
|
||||||
|
id: 'keyNotExists',
|
||||||
|
defaultMessage: 'The key is incorrect'
|
||||||
|
}
|
||||||
|
});
|
43
src/components/auth/BaseAuthBody.jsx
Normal file
43
src/components/auth/BaseAuthBody.jsx
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* Helps with form fields binding, form serialization and errors rendering
|
||||||
|
*/
|
||||||
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
|
||||||
|
import AuthError from './AuthError';
|
||||||
|
|
||||||
|
export default class BaseAuthBody extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
clearErrors: PropTypes.func.isRequired,
|
||||||
|
auth: PropTypes.shape({
|
||||||
|
error: PropTypes.string
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
renderErrors() {
|
||||||
|
return this.props.auth.error
|
||||||
|
? <AuthError error={this.props.auth.error} onClose={this.onClearErrors} />
|
||||||
|
: ''
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
onClearErrors = this.props.clearErrors;
|
||||||
|
|
||||||
|
form = {};
|
||||||
|
|
||||||
|
bindField(name) {
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
ref: (el) => {
|
||||||
|
this.form[name] = el;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
serialize() {
|
||||||
|
return Object.keys(this.form).reduce((acc, key) => {
|
||||||
|
acc[key] = this.form[key].getValue();
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
}
|
@ -1,40 +1,57 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { routeActions } from 'react-router-redux';
|
|
||||||
|
|
||||||
import { FormattedMessage as Message } from 'react-intl';
|
import { FormattedMessage as Message } from 'react-intl';
|
||||||
import Helmet from 'react-helmet';
|
import Helmet from 'react-helmet';
|
||||||
|
|
||||||
import buttons from 'components/ui/buttons.scss';
|
import buttons from 'components/ui/buttons.scss';
|
||||||
import { Panel, PanelBody, PanelFooter } from 'components/ui/Panel';
|
|
||||||
import { Input } from 'components/ui/Form';
|
import { Input } from 'components/ui/Form';
|
||||||
|
|
||||||
|
import BaseAuthBody from './BaseAuthBody';
|
||||||
import messages from './Login.messages';
|
import messages from './Login.messages';
|
||||||
import {helpLinks as helpLinksStyles} from './helpLinks.scss';
|
|
||||||
import passwordMessages from './Password.messages';
|
import passwordMessages from './Password.messages';
|
||||||
|
|
||||||
export default function Login() {
|
class Body extends BaseAuthBody {
|
||||||
var context = {
|
static propTypes = {
|
||||||
onSubmit(event) {
|
...BaseAuthBody.propTypes,
|
||||||
event.preventDefault();
|
login: PropTypes.func.isRequired,
|
||||||
|
auth: PropTypes.shape({
|
||||||
this.props.push('/password');
|
error: PropTypes.string,
|
||||||
}
|
login: PropTypes.shape({
|
||||||
|
login: PropTypes.stirng
|
||||||
|
})
|
||||||
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{this.renderErrors()}
|
||||||
|
|
||||||
|
<Input {...this.bindField('login')}
|
||||||
|
icon="envelope"
|
||||||
|
autoFocus
|
||||||
|
required
|
||||||
|
placeholder={messages.emailOrUsername}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onFormSubmit() {
|
||||||
|
this.props.login(this.serialize());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Login() {
|
||||||
return {
|
return {
|
||||||
Title: () => ( // TODO: separate component for PageTitle
|
Title: () => ( // TODO: separate component for PageTitle
|
||||||
<Message {...messages.loginTitle}>
|
<Message {...messages.loginTitle}>
|
||||||
{(msg) => <span>{msg}<Helmet title={msg} /></span>}
|
{(msg) => <span>{msg}<Helmet title={msg} /></span>}
|
||||||
</Message>
|
</Message>
|
||||||
),
|
),
|
||||||
Body: () => <Input icon="envelope" type="email" placeholder={messages.emailOrUsername} />,
|
Body,
|
||||||
Footer: (props) => (
|
Footer: () => (
|
||||||
<button className={buttons.green} onClick={(event) => {
|
<button className={buttons.green} type="submit">
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
props.history.push('/password');
|
|
||||||
}}>
|
|
||||||
<Message {...messages.next} />
|
<Message {...messages.next} />
|
||||||
</button>
|
</button>
|
||||||
),
|
),
|
||||||
@ -45,44 +62,3 @@ export default function Login() {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
class _Login extends Component {
|
|
||||||
displayName = 'Login';
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Message {...messages.loginTitle}>
|
|
||||||
{(msg) => <Helmet title={msg} />}
|
|
||||||
</Message>
|
|
||||||
|
|
||||||
<Panel title={<Message {...messages.loginTitle} />}>
|
|
||||||
<PanelBody>
|
|
||||||
<Input icon="envelope" type="email" placeholder={messages.emailOrUsername} />
|
|
||||||
</PanelBody>
|
|
||||||
<PanelFooter>
|
|
||||||
<button className={buttons.green} onClick={this.onSubmit}>
|
|
||||||
<Message {...messages.next} />
|
|
||||||
</button>
|
|
||||||
</PanelFooter>
|
|
||||||
</Panel>
|
|
||||||
<div className={helpLinksStyles}>
|
|
||||||
<a href="#">
|
|
||||||
<Message {...passwordMessages.forgotPassword} />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
onSubmit = (event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
this.props.push('/password');
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// export connect(null, {
|
|
||||||
// push: routeActions.push
|
|
||||||
// })(Login);
|
|
||||||
|
25
src/components/auth/Logout.jsx
Normal file
25
src/components/auth/Logout.jsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import { logout } from 'components/auth/actions';
|
||||||
|
|
||||||
|
class Logout extends Component {
|
||||||
|
static displayName = 'Logout';
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
logout: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
this.props.logout();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return <span />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(null, {
|
||||||
|
logout
|
||||||
|
})(Logout);
|
@ -1,48 +1,73 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { routeActions } from 'react-router-redux';
|
||||||
import { TransitionMotion, spring } from 'react-motion';
|
import { TransitionMotion, spring } from 'react-motion';
|
||||||
import ReactHeight from 'react-height';
|
import ReactHeight from 'react-height';
|
||||||
|
|
||||||
import { Panel, PanelBody, PanelFooter, PanelHeader } from 'components/ui/Panel';
|
import { Panel, PanelBody, PanelFooter, PanelHeader } from 'components/ui/Panel';
|
||||||
|
import { Form } from 'components/ui/Form';
|
||||||
import {helpLinks as helpLinksStyles} from 'components/auth/helpLinks.scss';
|
import {helpLinks as helpLinksStyles} from 'components/auth/helpLinks.scss';
|
||||||
import panelStyles from 'components/ui/panel.scss';
|
import panelStyles from 'components/ui/panel.scss';
|
||||||
import icons from 'components/ui/icons.scss';
|
import icons from 'components/ui/icons.scss';
|
||||||
|
|
||||||
|
import * as actions from './actions';
|
||||||
|
|
||||||
const opacitySpringConfig = [300, 20];
|
const opacitySpringConfig = [300, 20];
|
||||||
const transformSpringConfig = [500, 50];
|
const transformSpringConfig = [500, 50];
|
||||||
const changeContextSpringConfig = [500, 20];
|
const changeContextSpringConfig = [500, 20];
|
||||||
|
|
||||||
export default class PanelTransition extends Component {
|
class PanelTransition extends Component {
|
||||||
|
static displayName = 'PanelTransition';
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
auth: PropTypes.shape({
|
||||||
|
error: PropTypes.string,
|
||||||
|
login: PropTypes.shape({
|
||||||
|
login: PropTypes.string,
|
||||||
|
password: PropTypes.string
|
||||||
|
})
|
||||||
|
}).isRequired,
|
||||||
|
goBack: React.PropTypes.func.isRequired,
|
||||||
|
setError: React.PropTypes.func.isRequired,
|
||||||
|
clearErrors: React.PropTypes.func.isRequired,
|
||||||
|
path: PropTypes.string.isRequired,
|
||||||
|
Title: PropTypes.element.isRequired,
|
||||||
|
Body: PropTypes.element.isRequired,
|
||||||
|
Footer: PropTypes.element.isRequired,
|
||||||
|
Links: PropTypes.element.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
height: {},
|
height: {},
|
||||||
contextHeight: 0
|
contextHeight: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
var previousRoute = this.props.location;
|
var nextPath = nextProps.path;
|
||||||
|
var previousPath = this.props.path;
|
||||||
|
|
||||||
var next = nextProps.path;
|
if (nextPath !== previousPath) {
|
||||||
var prev = previousRoute && previousRoute.pathname;
|
var direction = this.getDirection(nextPath, previousPath);
|
||||||
|
var forceHeight = direction === 'Y' && nextPath !== previousPath ? 1 : 0;
|
||||||
|
|
||||||
var direction = this.getDirection(next, next, prev);
|
this.props.clearErrors();
|
||||||
var forceHeight = direction === 'Y' && next !== prev ? 1 : 0;
|
this.setState({
|
||||||
|
direction,
|
||||||
|
forceHeight,
|
||||||
|
previousPath
|
||||||
|
});
|
||||||
|
|
||||||
this.setState({
|
if (forceHeight) {
|
||||||
direction,
|
setTimeout(() => {
|
||||||
forceHeight,
|
this.setState({forceHeight: 0});
|
||||||
previousRoute
|
}, 100);
|
||||||
});
|
}
|
||||||
|
|
||||||
if (forceHeight) {
|
|
||||||
setTimeout(() => {
|
|
||||||
this.setState({forceHeight: 0});
|
|
||||||
}, 100);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
var {previousRoute, height, contextHeight, forceHeight} = this.state;
|
const {height, canAnimateHeight, contextHeight, forceHeight} = this.state;
|
||||||
|
|
||||||
const {path, Title, Body, Footer, Links} = this.props;
|
const {path, Title, Body, Footer, Links} = this.props;
|
||||||
|
|
||||||
@ -54,7 +79,7 @@ export default class PanelTransition extends Component {
|
|||||||
Body,
|
Body,
|
||||||
Footer,
|
Footer,
|
||||||
Links,
|
Links,
|
||||||
hasBackButton: previousRoute && previousRoute.pathname === Title.type.goBack,
|
hasBackButton: Title.type.goBack,
|
||||||
transformSpring: spring(0, transformSpringConfig),
|
transformSpring: spring(0, transformSpringConfig),
|
||||||
opacitySpring: spring(1, opacitySpringConfig)
|
opacitySpring: spring(1, opacitySpringConfig)
|
||||||
},
|
},
|
||||||
@ -67,24 +92,28 @@ export default class PanelTransition extends Component {
|
|||||||
willLeave={this.willLeave}
|
willLeave={this.willLeave}
|
||||||
>
|
>
|
||||||
{(items) => {
|
{(items) => {
|
||||||
var keys = Object.keys(items).filter((key) => key !== 'common');
|
const keys = Object.keys(items).filter((key) => key !== 'common');
|
||||||
|
|
||||||
|
const contentHeight = {
|
||||||
|
overflow: 'hidden',
|
||||||
|
height: forceHeight ? items.common.switchContextHeightSpring : 'auto'
|
||||||
|
};
|
||||||
|
|
||||||
|
const bodyHeight = {
|
||||||
|
position: 'relative',
|
||||||
|
height: `${canAnimateHeight ? items.common.heightSpring : height[path]}px`
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<Form id={path} onSubmit={this.onFormSubmit} onInvalid={this.onFormInvalid}>
|
||||||
<Panel>
|
<Panel>
|
||||||
<PanelHeader>
|
<PanelHeader>
|
||||||
{keys.map((key) => this.getHeader(key, items[key]))}
|
{keys.map((key) => this.getHeader(key, items[key]))}
|
||||||
</PanelHeader>
|
</PanelHeader>
|
||||||
<div style={{
|
<div style={contentHeight}>
|
||||||
overflow: 'hidden',
|
<ReactHeight onHeightReady={this.onUpdateContextHeight}>
|
||||||
height: forceHeight ? items.common.switchContextHeightSpring : 'auto'
|
|
||||||
}}>
|
|
||||||
<ReactHeight onHeightReady={this.updateContextHeight}>
|
|
||||||
<PanelBody>
|
<PanelBody>
|
||||||
<div style={{
|
<div style={bodyHeight}>
|
||||||
position: 'relative',
|
|
||||||
height: `${previousRoute ? items.common.heightSpring : height[path]}px`
|
|
||||||
}}>
|
|
||||||
{keys.map((key) => this.getBody(key, items[key]))}
|
{keys.map((key) => this.getBody(key, items[key]))}
|
||||||
</div>
|
</div>
|
||||||
</PanelBody>
|
</PanelBody>
|
||||||
@ -97,21 +126,24 @@ export default class PanelTransition extends Component {
|
|||||||
<div className={helpLinksStyles}>
|
<div className={helpLinksStyles}>
|
||||||
{keys.map((key) => this.getLinks(key, items[key]))}
|
{keys.map((key) => this.getLinks(key, items[key]))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Form>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
</TransitionMotion>
|
</TransitionMotion>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
willEnter = (key, styles) => {
|
onFormSubmit = () => {
|
||||||
return this.getTransitionStyles(key, styles);
|
this.body.onFormSubmit();
|
||||||
};
|
};
|
||||||
|
|
||||||
willLeave = (key, styles) => {
|
onFormInvalid = (errorMessage) => {
|
||||||
return this.getTransitionStyles(key, styles, {isLeave: true});
|
this.props.setError(errorMessage);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
willEnter = (key, styles) => this.getTransitionStyles(key, styles);
|
||||||
|
willLeave = (key, styles) => this.getTransitionStyles(key, styles, {isLeave: true});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} key
|
* @param {string} key
|
||||||
* @param {Object} styles
|
* @param {Object} styles
|
||||||
@ -140,8 +172,11 @@ export default class PanelTransition extends Component {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
updateHeight = (height) => {
|
onUpdateHeight = (height) => {
|
||||||
|
const canAnimateHeight = Object.keys(this.state.height).length > 1 || this.state.height[[this.props.path]];
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
|
canAnimateHeight,
|
||||||
height: {
|
height: {
|
||||||
...this.state.height,
|
...this.state.height,
|
||||||
[this.props.path]: height
|
[this.props.path]: height
|
||||||
@ -149,7 +184,7 @@ export default class PanelTransition extends Component {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
updateContextHeight = (height) => {
|
onUpdateContextHeight = (height) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
contextHeight: height
|
contextHeight: height
|
||||||
});
|
});
|
||||||
@ -158,10 +193,11 @@ export default class PanelTransition extends Component {
|
|||||||
onGoBack = (event) => {
|
onGoBack = (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
this.props.history.goBack();
|
this.body.onGoBack && this.body.onGoBack();
|
||||||
|
this.props.goBack();
|
||||||
};
|
};
|
||||||
|
|
||||||
getDirection(key, next, prev) {
|
getDirection(next, prev) {
|
||||||
var not = (path) => prev !== path && next !== path;
|
var not = (path) => prev !== path && next !== path;
|
||||||
|
|
||||||
var map = {
|
var map = {
|
||||||
@ -172,7 +208,7 @@ export default class PanelTransition extends Component {
|
|||||||
'/oauth/permissions': 'Y'
|
'/oauth/permissions': 'Y'
|
||||||
};
|
};
|
||||||
|
|
||||||
return map[key];
|
return map[next];
|
||||||
}
|
}
|
||||||
|
|
||||||
getHeader(key, props) {
|
getHeader(key, props) {
|
||||||
@ -192,7 +228,7 @@ export default class PanelTransition extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
var backButton = (
|
var backButton = (
|
||||||
<button style={sideScrollStyle} onClick={this.onGoBack} className={panelStyles.headerControl}>
|
<button style={sideScrollStyle} type="button" onClick={this.onGoBack} className={panelStyles.headerControl}>
|
||||||
<span className={icons.arrowLeft} />
|
<span className={icons.arrowLeft} />
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
@ -201,7 +237,7 @@ export default class PanelTransition extends Component {
|
|||||||
<div key={`header${key}`} style={style}>
|
<div key={`header${key}`} style={style}>
|
||||||
{hasBackButton ? backButton : null}
|
{hasBackButton ? backButton : null}
|
||||||
<div style={scrollStyle}>
|
<div style={scrollStyle}>
|
||||||
{Title}
|
{React.cloneElement(Title, this.props)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -228,8 +264,13 @@ export default class PanelTransition extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactHeight key={`body${key}`} style={style} onHeightReady={this.updateHeight}>
|
<ReactHeight key={`body${key}`} style={style} onHeightReady={this.onUpdateHeight}>
|
||||||
{Body}
|
{React.cloneElement(Body, {
|
||||||
|
...this.props,
|
||||||
|
ref: (body) => {
|
||||||
|
this.body = body;
|
||||||
|
}
|
||||||
|
})}
|
||||||
</ReactHeight>
|
</ReactHeight>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -241,7 +282,7 @@ export default class PanelTransition extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={`footer${key}`} style={style}>
|
<div key={`footer${key}`} style={style}>
|
||||||
{Footer}
|
{React.cloneElement(Footer, this.props)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -253,7 +294,7 @@ export default class PanelTransition extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={`links${key}`} style={style}>
|
<div key={`links${key}`} style={style}>
|
||||||
{Links}
|
{React.cloneElement(Links, this.props)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -293,3 +334,16 @@ export default class PanelTransition extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default connect((state) => ({
|
||||||
|
user: state.user,
|
||||||
|
auth: state.auth,
|
||||||
|
path: state.routing.location.pathname
|
||||||
|
}), {
|
||||||
|
goBack: routeActions.goBack,
|
||||||
|
login: actions.login,
|
||||||
|
logout: actions.logout,
|
||||||
|
register: actions.register,
|
||||||
|
activate: actions.activate,
|
||||||
|
clearErrors: actions.clearErrors,
|
||||||
|
setError: actions.setError
|
||||||
|
})(PanelTransition);
|
||||||
|
@ -1,61 +1,86 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
|
|
||||||
import { FormattedMessage as Message } from 'react-intl';
|
import { FormattedMessage as Message } from 'react-intl';
|
||||||
import Helmet from 'react-helmet';
|
import Helmet from 'react-helmet';
|
||||||
|
|
||||||
import buttons from 'components/ui/buttons.scss';
|
import buttons from 'components/ui/buttons.scss';
|
||||||
import icons from 'components/ui/icons.scss';
|
import icons from 'components/ui/icons.scss';
|
||||||
import { Panel, PanelBody, PanelFooter, PanelBodyHeader } from 'components/ui/Panel';
|
|
||||||
import { Input, Checkbox } from 'components/ui/Form';
|
import { Input, Checkbox } from 'components/ui/Form';
|
||||||
|
|
||||||
|
import BaseAuthBody from './BaseAuthBody';
|
||||||
import styles from './password.scss';
|
import styles from './password.scss';
|
||||||
import {helpLinks as helpLinksStyles} from './helpLinks.scss';
|
|
||||||
import messages from './Password.messages';
|
import messages from './Password.messages';
|
||||||
|
|
||||||
|
class Body extends BaseAuthBody {
|
||||||
|
static propTypes = {
|
||||||
|
...BaseAuthBody.propTypes,
|
||||||
|
login: PropTypes.func.isRequired,
|
||||||
|
logout: PropTypes.func.isRequired,
|
||||||
|
auth: PropTypes.shape({
|
||||||
|
error: PropTypes.string,
|
||||||
|
login: PropTypes.shape({
|
||||||
|
login: PropTypes.stirng,
|
||||||
|
password: PropTypes.stirng
|
||||||
|
})
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {user} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{this.renderErrors()}
|
||||||
|
|
||||||
|
<div className={styles.miniProfile}>
|
||||||
|
<div className={styles.avatar}>
|
||||||
|
{user.avatar
|
||||||
|
? <img src={user.avatar} />
|
||||||
|
: <span className={icons.user} />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div className={styles.email}>
|
||||||
|
{user.email || user.username}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Input {...this.bindField('password')}
|
||||||
|
icon="key"
|
||||||
|
type="password"
|
||||||
|
autoFocus
|
||||||
|
required
|
||||||
|
placeholder={messages.accountPassword}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Checkbox {...this.bindField('rememberMe')} label={<Message {...messages.rememberMe} />} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onFormSubmit() {
|
||||||
|
this.props.login({
|
||||||
|
...this.serialize(),
|
||||||
|
login: this.props.user.email || this.props.user.username
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onGoBack() {
|
||||||
|
this.props.logout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default function Password() {
|
export default function Password() {
|
||||||
var Title = () => ( // TODO: separate component for PageTitle
|
var Title = () => ( // TODO: separate component for PageTitle
|
||||||
<Message {...messages.passwordTitle}>
|
<Message {...messages.passwordTitle}>
|
||||||
{(msg) => <span>{msg}<Helmet title={msg} /></span>}
|
{(msg) => <span>{msg}<Helmet title={msg} /></span>}
|
||||||
</Message>
|
</Message>
|
||||||
);
|
);
|
||||||
Title.goBack = '/login';
|
Title.goBack = true;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
Title,
|
Title,
|
||||||
Body: () => (
|
Body,
|
||||||
<div>
|
Footer: () => (
|
||||||
<PanelBodyHeader type="error">
|
<button className={buttons.green} type="submit">
|
||||||
<Message {...messages.invalidPassword} />
|
|
||||||
<br/>
|
|
||||||
<Message {...messages.suggestResetPassword} values={{
|
|
||||||
link: (
|
|
||||||
<a href="#">
|
|
||||||
<Message {...messages.forgotYourPassword} />
|
|
||||||
</a>
|
|
||||||
)
|
|
||||||
}} />
|
|
||||||
</PanelBodyHeader>
|
|
||||||
<div className={styles.miniProfile}>
|
|
||||||
<div className={styles.avatar}>
|
|
||||||
{/*<img src="//lorempixel.com/g/90/90" />*/}
|
|
||||||
<span className={icons.user} />
|
|
||||||
</div>
|
|
||||||
<div className={styles.email}>
|
|
||||||
{/* На деле тут может быть и ник, в зависимости от того, что введут в 1 вьюху */}
|
|
||||||
erickskrauch@yandex.ru
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Input icon="key" type="password" placeholder={messages.accountPassword} />
|
|
||||||
|
|
||||||
<Checkbox label={<Message {...messages.rememberMe} />} />
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
Footer: (props) => (
|
|
||||||
<button className={buttons.green} onClick={(event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
props.history.push('/oauth/permissions');
|
|
||||||
}}>
|
|
||||||
<Message {...messages.signInButton} />
|
<Message {...messages.signInButton} />
|
||||||
</button>
|
</button>
|
||||||
),
|
),
|
||||||
@ -66,56 +91,3 @@ export default function Password() {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export class _Password extends Component {
|
|
||||||
displayName = 'Password';
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Message {...messages.passwordTitle}>
|
|
||||||
{(msg) => <Helmet title={msg} />}
|
|
||||||
</Message>
|
|
||||||
|
|
||||||
<Panel icon="arrowLeft" title={<Message {...messages.passwordTitle} />}>
|
|
||||||
<PanelBody>
|
|
||||||
<PanelBodyHeader type="error">
|
|
||||||
<Message {...messages.invalidPassword} />
|
|
||||||
<br/>
|
|
||||||
<Message {...messages.suggestResetPassword} values={{
|
|
||||||
link: (
|
|
||||||
<a href="#">
|
|
||||||
<Message {...messages.forgotYourPassword} />
|
|
||||||
</a>
|
|
||||||
)
|
|
||||||
}} />
|
|
||||||
</PanelBodyHeader>
|
|
||||||
<div className={styles.miniProfile}>
|
|
||||||
<div className={styles.avatar}>
|
|
||||||
{/*<img src="//lorempixel.com/g/90/90" />*/}
|
|
||||||
<span className={icons.user} />
|
|
||||||
</div>
|
|
||||||
<div className={styles.email}>
|
|
||||||
{/* На деле тут может быть и ник, в зависимости от того, что введут в 1 вьюху */}
|
|
||||||
erickskrauch@yandex.ru
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Input icon="key" type="password" placeholder={messages.accountPassword} />
|
|
||||||
|
|
||||||
<Checkbox label={<Message {...messages.rememberMe} />} />
|
|
||||||
</PanelBody>
|
|
||||||
<PanelFooter>
|
|
||||||
<button className={buttons.green}>
|
|
||||||
<Message {...messages.signInButton} />
|
|
||||||
</button>
|
|
||||||
</PanelFooter>
|
|
||||||
</Panel>
|
|
||||||
<div className={helpLinksStyles}>
|
|
||||||
<a href="#">
|
|
||||||
<Message {...messages.forgotPassword} />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,25 +1,33 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
|
|
||||||
import { FormattedMessage as Message } from 'react-intl';
|
import { FormattedMessage as Message } from 'react-intl';
|
||||||
import Helmet from 'react-helmet';
|
import Helmet from 'react-helmet';
|
||||||
|
|
||||||
import buttons from 'components/ui/buttons.scss';
|
import buttons from 'components/ui/buttons.scss';
|
||||||
import icons from 'components/ui/icons.scss';
|
import icons from 'components/ui/icons.scss';
|
||||||
import { Panel, PanelBody, PanelFooter, PanelBodyHeader } from 'components/ui/Panel';
|
import { PanelBodyHeader } from 'components/ui/Panel';
|
||||||
|
|
||||||
|
import BaseAuthBody from './BaseAuthBody';
|
||||||
import styles from './permissions.scss';
|
import styles from './permissions.scss';
|
||||||
import {helpLinks as helpLinksStyles} from './helpLinks.scss';
|
|
||||||
import messages from './Permissions.messages';
|
import messages from './Permissions.messages';
|
||||||
|
|
||||||
export default function Permissions() {
|
class Body extends BaseAuthBody {
|
||||||
return {
|
static propTypes = {
|
||||||
Title: () => ( // TODO: separate component for PageTitle
|
...BaseAuthBody.propTypes,
|
||||||
<Message {...messages.permissionsTitle}>
|
login: PropTypes.func.isRequired,
|
||||||
{(msg) => <span>{msg}<Helmet title={msg} /></span>}
|
auth: PropTypes.shape({
|
||||||
</Message>
|
error: PropTypes.string,
|
||||||
),
|
login: PropTypes.shape({
|
||||||
Body: () => (
|
login: PropTypes.stirng
|
||||||
|
})
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
{this.renderErrors()}
|
||||||
|
|
||||||
<PanelBodyHeader>
|
<PanelBodyHeader>
|
||||||
<div className={styles.authInfo}>
|
<div className={styles.authInfo}>
|
||||||
<div className={styles.authInfoAvatar}>
|
<div className={styles.authInfoAvatar}>
|
||||||
@ -30,7 +38,7 @@ export default function Permissions() {
|
|||||||
<Message {...messages.youAuthorizedAs} />
|
<Message {...messages.youAuthorizedAs} />
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.authInfoEmail}>
|
<div className={styles.authInfoEmail}>
|
||||||
erickskrauch@yandex.ru
|
{'erickskrauch@yandex.ru'}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</PanelBodyHeader>
|
</PanelBodyHeader>
|
||||||
@ -47,9 +55,24 @@ export default function Permissions() {
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onFormSubmit() {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Permissions() {
|
||||||
|
return {
|
||||||
|
Title: () => ( // TODO: separate component for PageTitle
|
||||||
|
<Message {...messages.permissionsTitle}>
|
||||||
|
{(msg) => <span>{msg}<Helmet title={msg} /></span>}
|
||||||
|
</Message>
|
||||||
),
|
),
|
||||||
|
Body,
|
||||||
Footer: () => (
|
Footer: () => (
|
||||||
<button className={buttons.green}>
|
<button className={buttons.orange} autoFocus>
|
||||||
<Message {...messages.approve} />
|
<Message {...messages.approve} />
|
||||||
</button>
|
</button>
|
||||||
),
|
),
|
||||||
@ -60,57 +83,3 @@ export default function Permissions() {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export class _Permissions extends Component {
|
|
||||||
displayName = 'Permissions';
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Message {...messages.permissionsTitle}>
|
|
||||||
{(msg) => <Helmet title={msg} />}
|
|
||||||
</Message>
|
|
||||||
|
|
||||||
<Panel title={<Message {...messages.permissionsTitle} />}>
|
|
||||||
<PanelBody>
|
|
||||||
<PanelBodyHeader>
|
|
||||||
<div className={styles.authInfo}>
|
|
||||||
<div className={styles.authInfoAvatar}>
|
|
||||||
{/*<img src="//lorempixel.com/g/90/90" />*/}
|
|
||||||
<span className={icons.user} />
|
|
||||||
</div>
|
|
||||||
<div className={styles.authInfoTitle}>
|
|
||||||
<Message {...messages.youAuthorizedAs} />
|
|
||||||
</div>
|
|
||||||
<div className={styles.authInfoEmail}>
|
|
||||||
erickskrauch@yandex.ru
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</PanelBodyHeader>
|
|
||||||
<div className={styles.permissionsContainer}>
|
|
||||||
<div className={styles.permissionsTitle}>
|
|
||||||
<Message {...messages.theAppNeedsAccess} />
|
|
||||||
</div>
|
|
||||||
<ul className={styles.permissionsList}>
|
|
||||||
<li>Authorization for Minecraft servers</li>
|
|
||||||
<li>Manage your skins directory and additional rows for multiline</li>
|
|
||||||
<li>Change the active skin</li>
|
|
||||||
<li>View your E-mail address</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</PanelBody>
|
|
||||||
<PanelFooter>
|
|
||||||
<button className={buttons.green}>
|
|
||||||
<Message {...messages.approve} />
|
|
||||||
</button>
|
|
||||||
</PanelFooter>
|
|
||||||
</Panel>
|
|
||||||
<div className={helpLinksStyles}>
|
|
||||||
<a href="#">
|
|
||||||
<Message {...messages.decline} />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,16 +1,93 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
|
|
||||||
import { FormattedMessage as Message } from 'react-intl';
|
import { FormattedMessage as Message } from 'react-intl';
|
||||||
import Helmet from 'react-helmet';
|
import Helmet from 'react-helmet';
|
||||||
|
|
||||||
import buttons from 'components/ui/buttons.scss';
|
import buttons from 'components/ui/buttons.scss';
|
||||||
import { Panel, PanelBody, PanelFooter } from 'components/ui/Panel';
|
|
||||||
import { Input, Checkbox } from 'components/ui/Form';
|
import { Input, Checkbox } from 'components/ui/Form';
|
||||||
|
|
||||||
import {helpLinks as helpLinksStyles} from './helpLinks.scss';
|
import BaseAuthBody from './BaseAuthBody';
|
||||||
import messages from './Register.messages';
|
import messages from './Register.messages';
|
||||||
import activationMessages from './Activation.messages';
|
import activationMessages from './Activation.messages';
|
||||||
|
|
||||||
|
// TODO: password and username can be validate for length and sameness
|
||||||
|
|
||||||
|
class Body extends BaseAuthBody {
|
||||||
|
static propTypes = {
|
||||||
|
...BaseAuthBody.propTypes,
|
||||||
|
register: PropTypes.func.isRequired,
|
||||||
|
auth: PropTypes.shape({
|
||||||
|
error: PropTypes.string,
|
||||||
|
register: PropTypes.shape({
|
||||||
|
email: PropTypes.string,
|
||||||
|
username: PropTypes.stirng,
|
||||||
|
password: PropTypes.stirng,
|
||||||
|
rePassword: PropTypes.stirng,
|
||||||
|
rulesAgreement: PropTypes.boolean
|
||||||
|
})
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{this.renderErrors()}
|
||||||
|
|
||||||
|
<Input {...this.bindField('username')}
|
||||||
|
icon="user"
|
||||||
|
color="blue"
|
||||||
|
type="text"
|
||||||
|
autoFocus
|
||||||
|
required
|
||||||
|
placeholder={messages.yourNickname}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Input {...this.bindField('email')}
|
||||||
|
icon="envelope"
|
||||||
|
color="blue"
|
||||||
|
type="email"
|
||||||
|
required
|
||||||
|
placeholder={messages.yourEmail}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Input {...this.bindField('password')}
|
||||||
|
icon="key"
|
||||||
|
color="blue"
|
||||||
|
type="password"
|
||||||
|
required
|
||||||
|
placeholder={messages.accountPassword}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Input {...this.bindField('rePassword')}
|
||||||
|
icon="key"
|
||||||
|
color="blue"
|
||||||
|
type="password"
|
||||||
|
required
|
||||||
|
placeholder={messages.repeatPassword}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Checkbox {...this.bindField('rulesAgreement')}
|
||||||
|
color="blue"
|
||||||
|
required
|
||||||
|
label={
|
||||||
|
<Message {...messages.acceptRules} values={{
|
||||||
|
link: (
|
||||||
|
<a href="#">
|
||||||
|
<Message {...messages.termsOfService} />
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
}} />
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onFormSubmit() {
|
||||||
|
this.props.register(this.serialize());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default function Register() {
|
export default function Register() {
|
||||||
return {
|
return {
|
||||||
Title: () => ( // TODO: separate component for PageTitle
|
Title: () => ( // TODO: separate component for PageTitle
|
||||||
@ -18,30 +95,9 @@ export default function Register() {
|
|||||||
{(msg) => <span>{msg}<Helmet title={msg} /></span>}
|
{(msg) => <span>{msg}<Helmet title={msg} /></span>}
|
||||||
</Message>
|
</Message>
|
||||||
),
|
),
|
||||||
Body: () => (
|
Body,
|
||||||
<div>
|
Footer: () => (
|
||||||
<Input icon="user" color="blue" type="text" placeholder={messages.yourNickname} />
|
<button className={buttons.blue} type="submit">
|
||||||
<Input icon="envelope" color="blue" type="email" placeholder={messages.yourEmail} />
|
|
||||||
<Input icon="key" color="blue" type="password" placeholder={messages.accountPassword} />
|
|
||||||
<Input icon="key" color="blue" type="password" placeholder={messages.repeatPassword} />
|
|
||||||
|
|
||||||
<Checkbox color="blue" label={
|
|
||||||
<Message {...messages.acceptRules} values={{
|
|
||||||
link: (
|
|
||||||
<a href="#">
|
|
||||||
<Message {...messages.termsOfService} />
|
|
||||||
</a>
|
|
||||||
)
|
|
||||||
}} />
|
|
||||||
} />
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
Footer: (props) => (
|
|
||||||
<button className={buttons.blue} onClick={(event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
props.history.push('/activation');
|
|
||||||
}}>
|
|
||||||
<Message {...messages.signUpButton} />
|
<Message {...messages.signUpButton} />
|
||||||
</button>
|
</button>
|
||||||
),
|
),
|
||||||
@ -52,46 +108,3 @@ export default function Register() {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export class _Register extends Component {
|
|
||||||
displayName = 'Register';
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Message {...messages.registerTitle}>
|
|
||||||
{(msg) => <Helmet title={msg} />}
|
|
||||||
</Message>
|
|
||||||
|
|
||||||
<Panel title={<Message {...messages.registerTitle} />}>
|
|
||||||
<PanelBody>
|
|
||||||
<Input icon="user" color="blue" type="text" placeholder={messages.yourNickname} />
|
|
||||||
<Input icon="envelope" color="blue" type="email" placeholder={messages.yourEmail} />
|
|
||||||
<Input icon="key" color="blue" type="password" placeholder={messages.accountPassword} />
|
|
||||||
<Input icon="key" color="blue" type="password" placeholder={messages.repeatPassword} />
|
|
||||||
|
|
||||||
<Checkbox color="blue" label={
|
|
||||||
<Message {...messages.acceptRules} values={{
|
|
||||||
link: (
|
|
||||||
<a href="#">
|
|
||||||
<Message {...messages.termsOfService} />
|
|
||||||
</a>
|
|
||||||
)
|
|
||||||
}} />
|
|
||||||
} />
|
|
||||||
</PanelBody>
|
|
||||||
<PanelFooter>
|
|
||||||
<button className={buttons.blue}>
|
|
||||||
<Message {...messages.signUpButton} />
|
|
||||||
</button>
|
|
||||||
</PanelFooter>
|
|
||||||
</Panel>
|
|
||||||
<div className={helpLinksStyles}>
|
|
||||||
<a href="#">
|
|
||||||
<Message {...activationMessages.didNotReceivedEmail} />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
104
src/components/auth/actions.js
Normal file
104
src/components/auth/actions.js
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
import { routeActions } from 'react-router-redux';
|
||||||
|
|
||||||
|
import { updateUser, logout as logoutUser } from 'components/user/actions';
|
||||||
|
import request from 'services/request';
|
||||||
|
|
||||||
|
export function login({login = '', password = '', rememberMe = false}) {
|
||||||
|
const PASSWORD_REQUIRED = 'error.password_required';
|
||||||
|
const LOGIN_REQUIRED = 'error.login_required';
|
||||||
|
|
||||||
|
return (dispatch) =>
|
||||||
|
request.post(
|
||||||
|
'/api/authentication/login',
|
||||||
|
{login, password, rememberMe}
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
dispatch(updateUser({
|
||||||
|
isGuest: false
|
||||||
|
}));
|
||||||
|
|
||||||
|
dispatch(redirectToGoal());
|
||||||
|
})
|
||||||
|
.catch((resp) => {
|
||||||
|
if (resp.errors.password === PASSWORD_REQUIRED) {
|
||||||
|
dispatch(updateUser({
|
||||||
|
username: login,
|
||||||
|
email: login
|
||||||
|
}));
|
||||||
|
dispatch(routeActions.push('/password'));
|
||||||
|
} else {
|
||||||
|
if (resp.errors.login === LOGIN_REQUIRED && password) {
|
||||||
|
dispatch(logout());
|
||||||
|
}
|
||||||
|
const errorMessage = resp.errors[Object.keys(resp.errors)[0]];
|
||||||
|
dispatch(setError(errorMessage));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function register({
|
||||||
|
email = '',
|
||||||
|
username = '',
|
||||||
|
password = '',
|
||||||
|
rePassword = '',
|
||||||
|
rulesAgreement = false
|
||||||
|
}) {
|
||||||
|
return (dispatch) =>
|
||||||
|
request.post(
|
||||||
|
'/api/signup/register',
|
||||||
|
{email, username, password, rePassword, rulesAgreement}
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
dispatch(routeActions.push('/activation'));
|
||||||
|
})
|
||||||
|
.catch((resp) => {
|
||||||
|
const errorMessage = resp.errors[Object.keys(resp.errors)[0]];
|
||||||
|
dispatch(setError(errorMessage));
|
||||||
|
})
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function activate({key = ''}) {
|
||||||
|
return (dispatch) =>
|
||||||
|
request.post(
|
||||||
|
'/api/signup/confirm',
|
||||||
|
{key}
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
dispatch(updateUser({
|
||||||
|
isActive: true
|
||||||
|
}));
|
||||||
|
|
||||||
|
dispatch(redirectToGoal());
|
||||||
|
})
|
||||||
|
.catch((resp) => {
|
||||||
|
const errorMessage = resp.errors[Object.keys(resp.errors)[0]];
|
||||||
|
dispatch(setError(errorMessage));
|
||||||
|
})
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
function redirectToGoal() {
|
||||||
|
return routeActions.push('/oauth/permissions');
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ERROR = 'error';
|
||||||
|
export function setError(error) {
|
||||||
|
return {
|
||||||
|
type: ERROR,
|
||||||
|
payload: error,
|
||||||
|
error: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearErrors() {
|
||||||
|
return setError(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function logout() {
|
||||||
|
return (dispatch) => {
|
||||||
|
dispatch(logoutUser());
|
||||||
|
dispatch(routeActions.push('/login'));
|
||||||
|
};
|
||||||
|
}
|
23
src/components/auth/reducer.js
Normal file
23
src/components/auth/reducer.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { combineReducers } from 'redux';
|
||||||
|
|
||||||
|
import { ERROR } from './actions';
|
||||||
|
|
||||||
|
export default combineReducers({
|
||||||
|
error
|
||||||
|
});
|
||||||
|
|
||||||
|
function error(
|
||||||
|
state = null,
|
||||||
|
{type, payload = null, error = false}
|
||||||
|
) {
|
||||||
|
switch (type) {
|
||||||
|
case ERROR:
|
||||||
|
if (!error) {
|
||||||
|
throw new Error('Expected payload with error');
|
||||||
|
}
|
||||||
|
return payload;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
@ -1,60 +1,166 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import {injectIntl, intlShape} from 'react-intl';
|
import { intlShape } from 'react-intl';
|
||||||
|
|
||||||
import icons from './icons.scss';
|
import icons from './icons.scss';
|
||||||
import styles from './form.scss';
|
import styles from './form.scss';
|
||||||
|
|
||||||
function Input(props) {
|
export class Input extends Component {
|
||||||
var { icon, color = 'green' } = props;
|
static displayName = 'Input';
|
||||||
|
|
||||||
props = {
|
static propTypes = {
|
||||||
type: 'text',
|
placeholder: PropTypes.shape({
|
||||||
...props
|
id: PropTypes.string
|
||||||
|
}),
|
||||||
|
icon: PropTypes.string,
|
||||||
|
color: PropTypes.oneOf(['green', 'blue', 'red'])
|
||||||
};
|
};
|
||||||
|
|
||||||
if (props.placeholder && props.placeholder.id) {
|
static contextTypes = {
|
||||||
props.placeholder = props.intl.formatMessage(props.placeholder);
|
intl: intlShape.isRequired
|
||||||
}
|
};
|
||||||
|
|
||||||
var baseClass = styles.formRow;
|
render() {
|
||||||
if (icon) {
|
let { icon, color = 'green' } = this.props;
|
||||||
baseClass = styles.formIconRow;
|
|
||||||
icon = (
|
const props = {
|
||||||
<div className={classNames(styles.formFieldIcon, icons[icon])} />
|
type: 'text',
|
||||||
|
...this.props
|
||||||
|
};
|
||||||
|
|
||||||
|
if (props.placeholder && props.placeholder.id) {
|
||||||
|
props.placeholder = this.context.intl.formatMessage(props.placeholder);
|
||||||
|
}
|
||||||
|
|
||||||
|
let baseClass = styles.formRow;
|
||||||
|
if (icon) {
|
||||||
|
baseClass = styles.formIconRow;
|
||||||
|
icon = (
|
||||||
|
<div className={classNames(styles.formFieldIcon, icons[icon])} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={baseClass}>
|
||||||
|
<input ref={this.setEl} className={styles[`${color}TextField`]} {...props} />
|
||||||
|
{icon}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
setEl = (el) => {
|
||||||
<div className={baseClass}>
|
this.el = el;
|
||||||
<input className={styles[`${color}TextField`]} {...props} />
|
};
|
||||||
{icon}
|
|
||||||
</div>
|
getValue() {
|
||||||
);
|
return this.el.value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Input.displayName = 'Input';
|
export class Checkbox extends Component {
|
||||||
Input.propTypes = {
|
static displayName = 'Checkbox';
|
||||||
intl: intlShape.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
const IntlInput = injectIntl(Input);
|
static propTypes = {
|
||||||
|
color: PropTypes.oneOf(['green', 'blue', 'red'])
|
||||||
|
};
|
||||||
|
|
||||||
export {IntlInput as Input};
|
render() {
|
||||||
|
const { label, color = 'green' } = this.props;
|
||||||
|
|
||||||
export function Checkbox(props) {
|
return (
|
||||||
var { label, color = 'green' } = props;
|
<div className={styles[`${color}CheckboxRow`]}>
|
||||||
|
<label className={styles.checkboxContainer}>
|
||||||
|
<input ref={this.setEl} className={styles.checkboxInput} type="checkbox" {...this.props} />
|
||||||
|
<div className={styles.checkbox} />
|
||||||
|
{label}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
setEl = (el) => {
|
||||||
<div className={styles[`${color}CheckboxRow`]}>
|
this.el = el;
|
||||||
<label className={styles.checkboxContainer}>
|
};
|
||||||
<input className={styles.checkboxInput} type="checkbox" />
|
|
||||||
<div className={styles.checkbox} />
|
getValue() {
|
||||||
{label}
|
return this.el.checked ? 1 : 0;
|
||||||
</label>
|
}
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Checkbox.displayName = 'Checkbox';
|
export class Form extends Component {
|
||||||
|
static displayName = 'Form';
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
id: PropTypes.string, // and id, that uniquely identifies form contents
|
||||||
|
onSubmit: PropTypes.func,
|
||||||
|
onInvalid: PropTypes.func,
|
||||||
|
children: PropTypes.oneOfType([
|
||||||
|
PropTypes.arrayOf(PropTypes.node),
|
||||||
|
PropTypes.node
|
||||||
|
])
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
id: 'default',
|
||||||
|
onSubmit() {},
|
||||||
|
onInvalid() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
isTouched: false
|
||||||
|
};
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
if (nextProps.id !== this.props.id) {
|
||||||
|
this.setState({
|
||||||
|
isTouched: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<form
|
||||||
|
className={classNames(
|
||||||
|
styles.form,
|
||||||
|
{
|
||||||
|
[styles.formTouched]: this.state.isTouched
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
onSubmit={this.onFormSubmit}
|
||||||
|
noValidate
|
||||||
|
>
|
||||||
|
{this.props.children}
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onFormSubmit = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (!this.state.isTouched) {
|
||||||
|
this.setState({
|
||||||
|
isTouched: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const form = event.currentTarget;
|
||||||
|
|
||||||
|
if (form.checkValidity()) {
|
||||||
|
this.props.onSubmit();
|
||||||
|
} else {
|
||||||
|
const firstError = form.querySelectorAll(':invalid')[0];
|
||||||
|
firstError.focus();
|
||||||
|
|
||||||
|
let errorMessage = firstError.validationMessage;
|
||||||
|
if (firstError.validity.valueMissing) {
|
||||||
|
errorMessage = `error.${firstError.name}_required`;
|
||||||
|
} else if (firstError.validity.typeMismatch) {
|
||||||
|
errorMessage = `error.${firstError.name}_invalid`;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.onInvalid(errorMessage);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import React from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import styles from './panel.scss';
|
import styles from './panel.scss';
|
||||||
import icons from './icons.scss';
|
import icons from './icons.scss';
|
||||||
@ -56,21 +58,41 @@ export function PanelFooter(props) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PanelBodyHeader(props) {
|
export class PanelBodyHeader extends Component {
|
||||||
var { type = 'default' } = props;
|
static displayName = 'PanelBodyHeader';
|
||||||
|
|
||||||
var close;
|
static propTypes = {
|
||||||
|
type: PropTypes.oneOf(['default', 'error']),
|
||||||
|
onClose: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
if (type === 'error') {
|
render() {
|
||||||
close = (
|
const {type = 'default', children} = this.props;
|
||||||
<span className={styles.close} />
|
|
||||||
|
let close;
|
||||||
|
if (type === 'error') {
|
||||||
|
close = (
|
||||||
|
<span className={styles.close} onClick={this.onClose} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const className = classNames(styles[`${type}BodyHeader`], {
|
||||||
|
[styles.isClosed]: this.state && this.state.isClosed
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={className} {...this.props}>
|
||||||
|
{close}
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
onClose = (event) => {
|
||||||
<div className={styles[`${type}BodyHeader`]} {...props}>
|
event.preventDefault();
|
||||||
{close}
|
|
||||||
{props.children}
|
this.setState({isClosed: true});
|
||||||
</div>
|
|
||||||
);
|
this.props.onClose();
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -58,3 +58,4 @@
|
|||||||
|
|
||||||
@include button-theme('blue', $blue);
|
@include button-theme('blue', $blue);
|
||||||
@include button-theme('green', $green);
|
@include button-theme('green', $green);
|
||||||
|
@include button-theme('orange', $orange);
|
||||||
|
@ -66,8 +66,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border-color: #aaa;
|
&,
|
||||||
|
|
||||||
~ .formFieldIcon {
|
~ .formFieldIcon {
|
||||||
border-color: #aaa;
|
border-color: #aaa;
|
||||||
}
|
}
|
||||||
@ -95,12 +94,14 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
border: 2px solid lighter($black);
|
border: 2px solid lighter($black);
|
||||||
color: #444;
|
color: #444;
|
||||||
|
cursor: default;
|
||||||
|
|
||||||
@include form-transition();
|
@include form-transition();
|
||||||
}
|
}
|
||||||
|
|
||||||
@include input-theme('green', $green);
|
@include input-theme('green', $green);
|
||||||
@include input-theme('blue', $blue);
|
@include input-theme('blue', $blue);
|
||||||
|
@include input-theme('red', $red);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -190,3 +191,44 @@
|
|||||||
|
|
||||||
@include checkbox-theme('green', $green);
|
@include checkbox-theme('green', $green);
|
||||||
@include checkbox-theme('blue', $blue);
|
@include checkbox-theme('blue', $blue);
|
||||||
|
@include checkbox-theme('red', $red);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Form validation
|
||||||
|
*/
|
||||||
|
|
||||||
|
.formTouched .textField:invalid {
|
||||||
|
box-shadow: none;
|
||||||
|
|
||||||
|
&,
|
||||||
|
~ .formFieldIcon {
|
||||||
|
border-color: #3e2727;
|
||||||
|
}
|
||||||
|
|
||||||
|
~ .formFieldIcon {
|
||||||
|
color: #3e2727;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
&,
|
||||||
|
~ .formFieldIcon {
|
||||||
|
border-color: $red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
border-color: $red;
|
||||||
|
|
||||||
|
~ .formFieldIcon {
|
||||||
|
background: $red;
|
||||||
|
border-color: $red;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.formTouched .checkboxInput:invalid {
|
||||||
|
~ .checkbox {
|
||||||
|
border-color: $red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -74,9 +74,20 @@ $bodyTopBottomPadding: 15px;
|
|||||||
|
|
||||||
.bodyHeader {
|
.bodyHeader {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
margin: (-$bodyTopBottomPadding) (-$bodyLeftRightPadding);
|
margin: (-$bodyTopBottomPadding) (-$bodyLeftRightPadding);
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
|
max-height: 200px;
|
||||||
|
|
||||||
|
transition: 0.4s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isClosed {
|
||||||
|
max-height: 0;
|
||||||
|
opacity: 0;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.errorBodyHeader {
|
.errorBodyHeader {
|
||||||
|
58
src/components/user/User.js
Normal file
58
src/components/user/User.js
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import { PropTypes } from 'react';
|
||||||
|
|
||||||
|
const KEY_USER = 'user';
|
||||||
|
|
||||||
|
export default class User {
|
||||||
|
/**
|
||||||
|
* @param {Object|string|undefined} data plain object or jwt token or empty to load from storage
|
||||||
|
*
|
||||||
|
* @return {User}
|
||||||
|
*/
|
||||||
|
constructor(data) {
|
||||||
|
if (!data) {
|
||||||
|
return this.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: strict value types validation
|
||||||
|
|
||||||
|
const defaults = {
|
||||||
|
id: null,
|
||||||
|
token: '',
|
||||||
|
username: '',
|
||||||
|
email: '',
|
||||||
|
avatar: '',
|
||||||
|
isGuest: true,
|
||||||
|
isActive: false
|
||||||
|
};
|
||||||
|
|
||||||
|
const user = Object.keys(defaults).reduce((user, key) => {
|
||||||
|
if (data.hasOwnProperty(key)) {
|
||||||
|
user[key] = data[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}, defaults);
|
||||||
|
|
||||||
|
localStorage.setItem(KEY_USER, JSON.stringify(user));
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
load() {
|
||||||
|
try {
|
||||||
|
return new User(JSON.parse(localStorage.getItem(KEY_USER)));
|
||||||
|
} catch (error) {
|
||||||
|
return new User({isGuest: true});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const userShape = PropTypes.shape({
|
||||||
|
id: PropTypes.number,
|
||||||
|
token: PropTypes.string,
|
||||||
|
username: PropTypes.string,
|
||||||
|
email: PropTypes.string,
|
||||||
|
avatar: PropTypes.string,
|
||||||
|
isGuest: PropTypes.bool.isRequired,
|
||||||
|
isActive: PropTypes.bool.isRequired
|
||||||
|
});
|
23
src/components/user/actions.js
Normal file
23
src/components/user/actions.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
export const UPDATE = 'USER_UPDATE';
|
||||||
|
/**
|
||||||
|
* @param {string|Object} payload jwt token or user object
|
||||||
|
* @return {Object} action definition
|
||||||
|
*/
|
||||||
|
export function updateUser(payload) {
|
||||||
|
return {
|
||||||
|
type: UPDATE,
|
||||||
|
payload
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SET = 'USER_SET';
|
||||||
|
export function setUser(payload) {
|
||||||
|
return {
|
||||||
|
type: SET,
|
||||||
|
payload
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function logout() {
|
||||||
|
return setUser({isGuest: true});
|
||||||
|
}
|
25
src/components/user/reducer.js
Normal file
25
src/components/user/reducer.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { UPDATE, SET } from './actions';
|
||||||
|
|
||||||
|
import User from './User';
|
||||||
|
|
||||||
|
export default function user(
|
||||||
|
state = new User(),
|
||||||
|
{type, payload = null}
|
||||||
|
) {
|
||||||
|
switch (type) {
|
||||||
|
case UPDATE:
|
||||||
|
if (!payload) {
|
||||||
|
throw new Error('payload is required for user reducer');
|
||||||
|
}
|
||||||
|
|
||||||
|
return new User({
|
||||||
|
...state,
|
||||||
|
...payload
|
||||||
|
});
|
||||||
|
case SET:
|
||||||
|
return new User(payload || {});
|
||||||
|
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
@ -8,16 +8,31 @@ import buttons from 'components/ui/buttons.scss';
|
|||||||
import messages from './Userbar.messages.js';
|
import messages from './Userbar.messages.js';
|
||||||
import styles from './userbar.scss';
|
import styles from './userbar.scss';
|
||||||
|
|
||||||
|
import { userShape } from 'components/user/User';
|
||||||
|
|
||||||
export default class Userbar extends Component {
|
export default class Userbar extends Component {
|
||||||
|
static displayName = 'Userbar';
|
||||||
|
static propTypes = {
|
||||||
|
user: userShape
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { user } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.userbar}>
|
<div className={styles.userbar}>
|
||||||
<Link to="/register" className={buttons.blue}>
|
{user.isGuest
|
||||||
<Message {...messages.register} />
|
? (
|
||||||
</Link>
|
<Link to="/register" className={buttons.blue}>
|
||||||
<Link to="/oauth/permissions" className={buttons.blue}>
|
<Message {...messages.register} />
|
||||||
Test oAuth
|
</Link>
|
||||||
</Link>
|
)
|
||||||
|
: (
|
||||||
|
<Link to="/logout" className={buttons.blue}>
|
||||||
|
<Message {...messages.logout} />
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -4,5 +4,10 @@ export default defineMessages({
|
|||||||
register: {
|
register: {
|
||||||
id: 'register',
|
id: 'register',
|
||||||
defaultMessage: 'Join'
|
defaultMessage: 'Join'
|
||||||
|
},
|
||||||
|
|
||||||
|
logout: {
|
||||||
|
id: 'logout',
|
||||||
|
defaultMessage: 'Logout'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -18,7 +18,7 @@ import { syncHistory, routeReducer } from 'react-router-redux';
|
|||||||
import { IntlProvider } from 'react-intl';
|
import { IntlProvider } from 'react-intl';
|
||||||
|
|
||||||
import reducers from 'reducers';
|
import reducers from 'reducers';
|
||||||
import routes from 'routes';
|
import routesFactory from 'routes';
|
||||||
|
|
||||||
import 'index.scss';
|
import 'index.scss';
|
||||||
|
|
||||||
@ -51,7 +51,7 @@ ReactDOM.render(
|
|||||||
<IntlProvider locale="en" messages={{}}>
|
<IntlProvider locale="en" messages={{}}>
|
||||||
<ReduxProvider store={store}>
|
<ReduxProvider store={store}>
|
||||||
<Router history={browserHistory}>
|
<Router history={browserHistory}>
|
||||||
{routes}
|
{routesFactory(store)}
|
||||||
</Router>
|
</Router>
|
||||||
</ReduxProvider>
|
</ReduxProvider>
|
||||||
</IntlProvider>,
|
</IntlProvider>,
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
import AppInfo from 'components/auth/AppInfo';
|
import AppInfo from 'components/auth/AppInfo';
|
||||||
import PanelTransition from 'components/auth/PanelTransition';
|
import PanelTransition from 'components/auth/PanelTransition';
|
||||||
|
|
||||||
import styles from './auth.scss';
|
import styles from './auth.scss';
|
||||||
|
|
||||||
class AuthPage extends Component {
|
export default class AuthPage extends Component {
|
||||||
static displayName = 'AuthPage';
|
static displayName = 'AuthPage';
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
@ -39,7 +38,3 @@ class AuthPage extends Component {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect((state) => ({
|
|
||||||
path: state.routing.location.pathname
|
|
||||||
}))(AuthPage);
|
|
||||||
|
@ -8,7 +8,7 @@ $sidebar-width: 320px;
|
|||||||
right: 0;
|
right: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 50px;
|
top: 50px;
|
||||||
z-index: 1;
|
z-index: 10;
|
||||||
|
|
||||||
background: $black;
|
background: $black;
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import React from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
|
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import { Link } from 'react-router';
|
import { Link } from 'react-router';
|
||||||
|
|
||||||
import Userbar from 'components/userbar/Userbar';
|
import Userbar from 'components/userbar/Userbar';
|
||||||
|
|
||||||
import styles from './root.scss';
|
import styles from './root.scss';
|
||||||
|
|
||||||
export default function RootPage(props) {
|
function RootPage(props) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.root}>
|
<div className={styles.root}>
|
||||||
<div className={styles.header}>
|
<div className={styles.header}>
|
||||||
@ -15,7 +16,7 @@ export default function RootPage(props) {
|
|||||||
Ely.by
|
Ely.by
|
||||||
</Link>
|
</Link>
|
||||||
<div className={styles.userbar}>
|
<div className={styles.userbar}>
|
||||||
<Userbar />
|
<Userbar {...props} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -25,3 +26,12 @@ export default function RootPage(props) {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RootPage.displayName = 'RootPage';
|
||||||
|
RootPage.propTypes = {
|
||||||
|
children: PropTypes.element
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect((state) => ({
|
||||||
|
user: state.user
|
||||||
|
}))(RootPage);
|
||||||
|
@ -1,2 +1,7 @@
|
|||||||
|
import auth from 'components/auth/reducer';
|
||||||
|
import user from 'components/user/reducer';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
auth,
|
||||||
|
user
|
||||||
};
|
};
|
||||||
|
@ -10,29 +10,46 @@ import Login from 'components/auth/Login';
|
|||||||
import Permissions from 'components/auth/Permissions';
|
import Permissions from 'components/auth/Permissions';
|
||||||
import Activation from 'components/auth/Activation';
|
import Activation from 'components/auth/Activation';
|
||||||
import Password from 'components/auth/Password';
|
import Password from 'components/auth/Password';
|
||||||
|
import Logout from 'components/auth/Logout';
|
||||||
|
|
||||||
function requireAuth(nextState, replace) {
|
export default function routesFactory(store) {
|
||||||
// if (!auth.loggedIn()) {
|
function checkAuth(nextState, replace) {
|
||||||
replace({
|
const state = store.getState();
|
||||||
pathname: '/login',
|
|
||||||
state: {
|
let forcePath;
|
||||||
nextPathname: nextState.location.pathname
|
if (!state.user.isGuest) {
|
||||||
|
if (!state.user.isActive) {
|
||||||
|
forcePath = '/activation';
|
||||||
|
} else {
|
||||||
|
forcePath = '/oauth/permissions';
|
||||||
}
|
}
|
||||||
});
|
} else {
|
||||||
// }
|
if (state.user.email || state.user.username) {
|
||||||
|
forcePath = '/password';
|
||||||
|
} else {
|
||||||
|
forcePath = '/login';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (forcePath && state.routing.location.pathname !== forcePath) {
|
||||||
|
replace({pathname: forcePath});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Route path="/" component={RootPage}>
|
||||||
|
<IndexRoute component={IndexPage} onEnter={checkAuth} />
|
||||||
|
|
||||||
|
<Route path="auth" component={AuthPage}>
|
||||||
|
<Route path="/login" components={new Login()} onEnter={checkAuth} />
|
||||||
|
<Route path="/password" components={new Password()} onEnter={checkAuth} />
|
||||||
|
<Route path="/register" components={new Register()} />
|
||||||
|
<Route path="/activation" components={new Activation()} />
|
||||||
|
<Route path="/oauth/permissions" components={new Permissions()} onEnter={checkAuth} />
|
||||||
|
<Route path="/oauth/:id" component={Permissions} />
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
<Route path="logout" component={Logout} />
|
||||||
|
</Route>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default (
|
|
||||||
<Route path="/" component={RootPage}>
|
|
||||||
<IndexRoute component={IndexPage} onEnter={requireAuth} />
|
|
||||||
|
|
||||||
<Route path="auth" component={AuthPage}>
|
|
||||||
<Route path="/login" components={new Login()} />
|
|
||||||
<Route path="/password" components={new Password()} />
|
|
||||||
<Route path="/register" components={new Register()} />
|
|
||||||
<Route path="/activation" components={new Activation()} />
|
|
||||||
<Route path="/oauth/permissions" components={new Permissions()} />
|
|
||||||
<Route path="/oauth/:id" component={Permissions} />
|
|
||||||
</Route>
|
|
||||||
</Route>
|
|
||||||
);
|
|
||||||
|
27
src/services/request.js
Normal file
27
src/services/request.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
function serialize(data) {
|
||||||
|
return Object.keys(data)
|
||||||
|
.map(
|
||||||
|
(keyName) =>
|
||||||
|
[keyName, data[keyName]]
|
||||||
|
.map(encodeURIComponent)
|
||||||
|
.join('=')
|
||||||
|
)
|
||||||
|
.join('&')
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
post(url, data) {
|
||||||
|
return fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
|
||||||
|
},
|
||||||
|
body: serialize(data)
|
||||||
|
})
|
||||||
|
.then((resp) => resp.json())
|
||||||
|
.then((resp) => Promise[resp.success ? 'resolve' : 'reject'](resp))
|
||||||
|
;
|
||||||
|
}
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user