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

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 styles from './profile.scss';
import profileForm from './profileForm.scss';
import messages from './Profile.messages';
export class Profile extends Component {
export default class Profile extends Component {
static displayName = 'Profile';
static propTypes = {
user: userShape
@ -19,62 +20,67 @@ export class Profile extends Component {
const { user } = this.props;
return (
<div className={styles.container}>
<div>
<Message {...messages.accountPreferencesTitle}>
{(pageTitle) => (
<h2 className={styles.title}>
<h2 className={styles.indexTitle}>
<Helmet title={pageTitle} />
{pageTitle}
</h2>
)}
</Message>
<div className={styles.content}>
<div className={styles.description}>
<Message {...messages.accountDescription} />
<div className={styles.indexContent}>
<div className={styles.descriptionColumn}>
<div className={styles.indexDescription}>
<Message {...messages.accountDescription} />
</div>
</div>
<div className={styles.options}>
<div className={styles.item}>
<h3 className={styles.optionsTitle}>
<Message {...messages.personalData} />
</h3>
<p className={styles.optionsDescription}>
<Message {...messages.preferencesDescription} />
</p>
<div className={styles.formColumn}>
<div className={profileForm.form}>
<div className={styles.item}>
<h3 className={profileForm.title}>
<Message {...messages.personalData} />
</h3>
<p className={profileForm.description}>
<Message {...messages.preferencesDescription} />
</p>
</div>
<ProfileField
label={<Message {...messages.nickname} />}
value={user.username}
warningMessage={<Message {...messages.mojangPriorityWarning} />}
/>
<ProfileField
label={'E-mail'}
value={user.email}
/>
<ProfileField
label={<Message {...messages.password} />}
link="/profile/change-password"
value={<Message {...messages.changedAt} values={{
at: (<Relative value={user.passwordChangedAt * 1000} />)
}} />}
warningMessage={user.shouldChangePassword ? (
<HTMLMessage {...messages.oldHashingAlgoWarning} />
) : ''}
/>
<ProfileField
label={<Message {...messages.twoFactorAuth} />}
value={<Message {...messages.disabled} />}
/>
<ProfileField
label={'UUID'}
value={user.uuid}
readonly
/>
</div>
<ProfileField
label={<Message {...messages.nickname} />}
value={user.username}
warningMessage={<Message {...messages.mojangPriorityWarning} />}
/>
<ProfileField
label={'E-mail'}
value={user.email}
/>
<ProfileField
label={<Message {...messages.password} />}
value={<Message {...messages.changedAt} values={{
at: (<Relative value={user.passwordChangedAt * 1000} />)
}} />}
warningMessage={user.shouldChangePassword ? (
<HTMLMessage {...messages.oldHashingAlgoWarning} />
) : ''}
/>
<ProfileField
label={<Message {...messages.twoFactorAuth} />}
value={<Message {...messages.disabled} />}
/>
<ProfileField
label={'UUID'}
value={user.uuid}
readonly
/>
</div>
</div>
</div>

View File

@ -1,18 +1,21 @@
import React, { Component, PropTypes } from 'react';
import { Link } from 'react-router';
import styles from './profile.scss';
export default class ProfileField extends Component {
static displayName = 'ProfileField';
static propTypes = {
label: React.PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired,
link: PropTypes.string,
value: React.PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired,
warningMessage: React.PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
readonly: PropTypes.bool
};
render() {
const {label, value, warningMessage, readonly} = this.props;
const {label, value, warningMessage, readonly, link = '#'} = this.props;
return (
<div className={styles.paramItem}>
@ -22,9 +25,9 @@ export default class ProfileField extends Component {
{readonly ? '' : (
<div className={styles.paramAction}>
<a href="#">
<Link to={link}>
<span className={styles.paramEditIcon} />
</a>
</Link>
</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/colors.scss';
.container {
margin-top: 55px;
}
.title {
font-family: $font-family-title;
font-size: 30px;
margin-bottom: 12px;
}
.content {
.indexContent {
display: flex;
}
.description {
font-size: 14px;
line-height: 1.4;
color: #9a9a9a;
width: 340px;
padding: 12px 20px 0 0;
box-sizing: border-box;
}
.options {
background: #fff;
.formColumn {
flex-grow: 1;
max-width: 416px;
border-bottom: 10px solid #ddd8ce;
}
.optionsTitle {
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;
}
.descriptionColumn {
width: 340px;
padding: 12px 20px 0 0;
box-sizing: border-box;
}
.optionsDescription {
font-size: 13px;
.indexTitle {
font-family: $font-family-title;
font-size: 30px;
margin-bottom: 12px;
}
.indexDescription {
font-size: 14px;
line-height: 1.4;
color: #9a9a9a;
line-height: 1.25;
margin-top: 25px;
}
.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('darkBlue', $dark_blue);
@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 { Profile } from 'components/profile/Profile';
import ProfilePage from 'pages/profile/ProfilePage';
import Profile from 'components/profile/Profile';
class IndexPage extends Component {
displayName = 'IndexPage';
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 IndexPage from 'pages/index/IndexPage';
import AuthPage from 'pages/auth/AuthPage';
import ProfilePage from 'pages/profile/ProfilePage';
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 Finish from 'components/auth/finish/Finish';
import ProfileChangePassword from 'components/profile/changePassword/ChangePassword';
import authFlow from 'services/authFlow';
export default function routesFactory(store) {
@ -50,6 +53,10 @@ export default function routesFactory(store) {
<Route path="/change-password" components={new ChangePassword()} {...onEnter} />
<Route path="/forgot-password" components={new ForgotPassword()} {...onEnter} />
</Route>
<Route path="profile" component={ProfilePage}>
<Route path="change-password" component={ProfileChangePassword} />
</Route>
</Route>
);
}