Наверстал форму смены пароля и чуток отрефакторил профиль

This commit is contained in:
SleepWalker 2016-04-17 12:35:04 +03:00
parent 107a2bdd70
commit e5552ee5f8
11 changed files with 279 additions and 96 deletions

View File

@ -7,9 +7,10 @@ import { userShape } from 'components/user/User';
import ProfileField from './ProfileField'; import ProfileField from './ProfileField';
import styles from './profile.scss'; import styles from './profile.scss';
import profileForm from './profileForm.scss';
import messages from './Profile.messages'; import messages from './Profile.messages';
export class Profile extends Component { export default class Profile extends Component {
static displayName = 'Profile'; static displayName = 'Profile';
static propTypes = { static propTypes = {
user: userShape user: userShape
@ -19,27 +20,30 @@ export class Profile extends Component {
const { user } = this.props; const { user } = this.props;
return ( return (
<div className={styles.container}> <div>
<Message {...messages.accountPreferencesTitle}> <Message {...messages.accountPreferencesTitle}>
{(pageTitle) => ( {(pageTitle) => (
<h2 className={styles.title}> <h2 className={styles.indexTitle}>
<Helmet title={pageTitle} /> <Helmet title={pageTitle} />
{pageTitle} {pageTitle}
</h2> </h2>
)} )}
</Message> </Message>
<div className={styles.content}> <div className={styles.indexContent}>
<div className={styles.description}> <div className={styles.descriptionColumn}>
<div className={styles.indexDescription}>
<Message {...messages.accountDescription} /> <Message {...messages.accountDescription} />
</div> </div>
</div>
<div className={styles.options}> <div className={styles.formColumn}>
<div className={profileForm.form}>
<div className={styles.item}> <div className={styles.item}>
<h3 className={styles.optionsTitle}> <h3 className={profileForm.title}>
<Message {...messages.personalData} /> <Message {...messages.personalData} />
</h3> </h3>
<p className={styles.optionsDescription}> <p className={profileForm.description}>
<Message {...messages.preferencesDescription} /> <Message {...messages.preferencesDescription} />
</p> </p>
</div> </div>
@ -57,6 +61,7 @@ export class Profile extends Component {
<ProfileField <ProfileField
label={<Message {...messages.password} />} label={<Message {...messages.password} />}
link="/profile/change-password"
value={<Message {...messages.changedAt} values={{ value={<Message {...messages.changedAt} values={{
at: (<Relative value={user.passwordChangedAt * 1000} />) at: (<Relative value={user.passwordChangedAt * 1000} />)
}} />} }} />}
@ -78,6 +83,7 @@ export class Profile extends Component {
</div> </div>
</div> </div>
</div> </div>
</div>
); );
} }
} }

View File

@ -1,18 +1,21 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { Link } from 'react-router';
import styles from './profile.scss'; import styles from './profile.scss';
export default class ProfileField extends Component { export default class ProfileField extends Component {
static displayName = 'ProfileField'; static displayName = 'ProfileField';
static propTypes = { static propTypes = {
label: React.PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired, label: React.PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired,
link: PropTypes.string,
value: React.PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired, value: React.PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired,
warningMessage: React.PropTypes.oneOfType([PropTypes.string, PropTypes.element]), warningMessage: React.PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
readonly: PropTypes.bool readonly: PropTypes.bool
}; };
render() { render() {
const {label, value, warningMessage, readonly} = this.props; const {label, value, warningMessage, readonly, link = '#'} = this.props;
return ( return (
<div className={styles.paramItem}> <div className={styles.paramItem}>
@ -22,9 +25,9 @@ export default class ProfileField extends Component {
{readonly ? '' : ( {readonly ? '' : (
<div className={styles.paramAction}> <div className={styles.paramAction}>
<a href="#"> <Link to={link}>
<span className={styles.paramEditIcon} /> <span className={styles.paramEditIcon} />
</a> </Link>
</div> </div>
)} )}
</div> </div>

View File

@ -0,0 +1,65 @@
import React, { Component } from 'react';
import { FormattedMessage as Message } from 'react-intl';
import { Link } from 'react-router';
import Helmet from 'react-helmet';
import classNames from 'classnames';
import { LabeledInput } from 'components/ui/Form';
import buttons from 'components/ui/buttons.scss';
import styles from 'components/profile/profileForm.scss';
import messages from './ChangePassword.messages';
export default class ChangePassword extends Component {
displayName = 'ChangePassword';
render() {
return (
<div className={styles.contentWithBackButton}>
<Link className={styles.backButton} to="/" />
<div className={styles.form}>
<div className={styles.formBody}>
<Message {...messages.changePasswordTitle}>
{(pageTitle) => (
<h3 className={styles.title}>
<Helmet title={pageTitle} />
{pageTitle}
</h3>
)}
</Message>
<div className={styles.formRow}>
<p className={styles.description}>
<Message {...messages.changePasswordDescription} />
<br/>
<b>
<Message {...messages.achievementLossWarning} />
</b>
</p>
</div>
<div className={styles.formRow}>
<LabeledInput skin="light" label={messages.newPasswordLabel} />
</div>
<div className={styles.formRow}>
<p className={styles.description}>
<Message {...messages.passwordRequirements} />
</p>
</div>
<div className={styles.formRow}>
<LabeledInput skin="light" label={messages.repeatNewPasswordLabel} />
</div>
</div>
<button className={classNames(buttons.green, buttons.block)}>
<Message {...messages.changePasswordButton} />
</button>
</div>
</div>
);
}
}

View File

@ -0,0 +1,39 @@
import { defineMessages } from 'react-intl';
export default defineMessages({
changePasswordTitle: {
id: 'changePasswordTitle',
defaultMessage: 'Change password'
// defaultMessage: 'Смена пароля'
},
changePasswordDescription: {
id: 'changePasswordDescription',
defaultMessage: 'Please take a password, that will be different from your passwords on the other sites and will not be the same you are using to enter Minecraft game servers you are playing.'
// defaultMessage: 'Придумайте пароль, который будет отличаться от ваших паролей на других сайтах и не будет совпадаеть с тем паролем, который вы используете для входа на различные игровые сервера Minecraft.'
},
achievementLossWarning: {
id: 'achievementLossWarning',
defaultMessage: 'Are you cherish your game achievements, right?'
// defaultMessage: 'Вы ведь дорожите своими игровыми достижениями?'
},
passwordRequirements: {
id: 'passwordRequirements',
defaultMessage: 'Password must contain at least 8 characters. It can be any symbols — do not limit yourself, create an unpredictable password!'
// defaultMessage: 'Пароль должен содержать не менее 8 символов. Это могут быть любым символы — не ограничивайте себя, придумайте непредсказуемый пароль!'
},
changePasswordButton: {
id: 'changePasswordButton',
defaultMessage: 'Change password'
// defaultMessage: 'Сменить пароль'
},
newPasswordLabel: {
id: 'newPasswordLabel',
defaultMessage: 'New password:'
// defaultMessage: 'Новый пароль:'
},
repeatNewPasswordLabel: {
id: 'repeatNewPasswordLabel',
defaultMessage: 'Repeat the password:'
// defaultMessage: 'Повторите указанный пароль:'
}
});

View File

@ -1,62 +1,32 @@
@import '~components/ui/fonts.scss'; @import '~components/ui/fonts.scss';
@import '~components/ui/colors.scss'; @import '~components/ui/colors.scss';
.container { .indexContent {
margin-top: 55px;
}
.title {
font-family: $font-family-title;
font-size: 30px;
margin-bottom: 12px;
}
.content {
display: flex; display: flex;
} }
.description { .formColumn {
font-size: 14px;
line-height: 1.4;
color: #9a9a9a;
width: 340px;
padding: 12px 20px 0 0;
box-sizing: border-box;
}
.options {
background: #fff;
flex-grow: 1; flex-grow: 1;
max-width: 416px; max-width: 416px;
border-bottom: 10px solid #ddd8ce; border-bottom: 10px solid #ddd8ce;
} }
.optionsTitle { .descriptionColumn {
position: relative; width: 340px;
font-size: 24px; padding: 12px 20px 0 0;
box-sizing: border-box;
}
.indexTitle {
font-family: $font-family-title; font-family: $font-family-title;
padding-bottom: 9px; font-size: 30px;
margin-bottom: 12px;
&:after {
content: '';
display: block;
position: absolute;
left: 0;
bottom: 0;
height: 3px;
width: 86px;
background: $green;
}
} }
.optionsDescription { .indexDescription {
font-size: 13px; font-size: 14px;
line-height: 1.4;
color: #9a9a9a; color: #9a9a9a;
line-height: 1.25;
margin-top: 25px;
} }
.item { .item {

View File

@ -0,0 +1,65 @@
@import '~components/ui/fonts.scss';
@import '~components/ui/colors.scss';
.contentWithBackButton {
position: relative;
padding-left: 60px;
width: 400px;
margin: 0 auto;
}
.backButton {
composes: arrow from 'components/ui/icons.scss';
position: absolute;
left: 0;
top: 15px;
width: 25px;
height: 25px;
padding: 15px;
transform: rotate(90deg);
color: #ccc;
font-size: 25px;
}
.form {
background: #fff;
overflow: hidden; // disable margin collapsing
}
.formBody {
margin: 30px;
}
.formRow {
margin: 25px 0;
}
.title {
position: relative;
font-size: 24px;
font-family: $font-family-title;
padding-bottom: 9px;
&:after {
content: '';
display: block;
position: absolute;
left: 0;
bottom: 0;
height: 3px;
width: 86px;
background: $green;
}
}
.description {
font-size: 13px;
color: #9a9a9a;
line-height: 1.3;
margin-top: 25px;
}

View File

@ -71,3 +71,8 @@
@include button-theme('orange', $orange); @include button-theme('orange', $orange);
@include button-theme('darkBlue', $dark_blue); @include button-theme('darkBlue', $dark_blue);
@include button-theme('lightViolet', $light_violet); @include button-theme('lightViolet', $light_violet);
.block {
display: block;
width: 100%;
}

View File

@ -2,13 +2,18 @@ import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Profile } from 'components/profile/Profile'; import ProfilePage from 'pages/profile/ProfilePage';
import Profile from 'components/profile/Profile';
class IndexPage extends Component { class IndexPage extends Component {
displayName = 'IndexPage'; displayName = 'IndexPage';
render() { render() {
return (<Profile {...this.props} />); return (
<ProfilePage>
<Profile {...this.props} />
</ProfilePage>
);
} }
} }

View File

@ -0,0 +1,15 @@
import React, { Component } from 'react';
import styles from './profile.scss';
export default class ProfilePage extends Component {
displayName = 'ProfilePage';
render() {
return (
<div className={styles.container}>
{this.props.children}
</div>
);
}
}

View File

@ -0,0 +1,3 @@
.container {
margin-top: 55px;
}

View File

@ -4,6 +4,7 @@ import { Route, IndexRoute } from 'react-router';
import RootPage from 'pages/root/RootPage'; import RootPage from 'pages/root/RootPage';
import IndexPage from 'pages/index/IndexPage'; import IndexPage from 'pages/index/IndexPage';
import AuthPage from 'pages/auth/AuthPage'; import AuthPage from 'pages/auth/AuthPage';
import ProfilePage from 'pages/profile/ProfilePage';
import { authenticate } from 'components/user/actions'; import { authenticate } from 'components/user/actions';
@ -18,6 +19,8 @@ import ChangePassword from 'components/auth/changePassword/ChangePassword';
import ForgotPassword from 'components/auth/forgotPassword/ForgotPassword'; import ForgotPassword from 'components/auth/forgotPassword/ForgotPassword';
import Finish from 'components/auth/finish/Finish'; import Finish from 'components/auth/finish/Finish';
import ProfileChangePassword from 'components/profile/changePassword/ChangePassword';
import authFlow from 'services/authFlow'; import authFlow from 'services/authFlow';
export default function routesFactory(store) { export default function routesFactory(store) {
@ -50,6 +53,10 @@ export default function routesFactory(store) {
<Route path="/change-password" components={new ChangePassword()} {...onEnter} /> <Route path="/change-password" components={new ChangePassword()} {...onEnter} />
<Route path="/forgot-password" components={new ForgotPassword()} {...onEnter} /> <Route path="/forgot-password" components={new ForgotPassword()} {...onEnter} />
</Route> </Route>
<Route path="profile" component={ProfilePage}>
<Route path="change-password" component={ProfileChangePassword} />
</Route>
</Route> </Route>
); );
} }